C51单片机学习-DAY3

下面这版已经整理成 CSDN Markdown 技术博客格式,可以直接复制粘贴发布。我已经把项目背景统一改成了"常规小项目 / 小型控制项目",没有出现你要求避开的词。

51单片机C语言重新入门:第三天学习C51中的sfr、sbit、寄存器和位操作

前言

这是我重新学习 51 单片机 C 语言的第三天笔记。

前两天主要学习了:

text 复制代码
第一天:C51 程序结构、main()、while(1)、LED 闪烁
第二天:数据类型、变量、bit、unsigned char、unsigned int

第三天开始进入 51 单片机 C 语言中非常核心的一部分:

text 复制代码
sfr
sbit
寄存器
端口
位操作

这部分内容非常重要,因为从今天开始,代码就不只是普通变量运算,而是开始真正控制单片机内部硬件。

本篇文章主要记录以下内容:

  • 什么是寄存器;
  • 什么是 sfr
  • 什么是 sbit
  • P3P3.7P3^7 的区别;
  • 如何控制单个 IO;
  • 如何操作整个端口;
  • 二进制和十六进制的关系;
  • |=&= ~^= 等位操作;
  • GPIO 模式配置;
  • LED 输出和按键输入综合示例。

一、第三天学习目标

第三天的目标是:

text 复制代码
理解什么是寄存器
理解 sfr 是什么
理解 sbit 是什么
知道 P0、P1、P2、P3 这些端口是怎么来的
知道如何控制某一个 IO 口
掌握 |=、&=、~、^ 这些位操作
能看懂 P3M0、P3M1 这类 IO 配置语句
能自己写出 LED、按键、IO 模式配置代码

今天的核心可以总结为一句话:

单片机 C 语言控制硬件,本质上就是通过寄存器控制单片机内部模块。


二、什么是寄存器?

在单片机中,寄存器可以理解为:

单片机内部的控制开关和状态记录表。

单片机内部有很多硬件模块,例如:

text 复制代码
IO 端口
定时器
串口
ADC
PWM
中断
时钟
低功耗模块

这些模块要想正常工作,就需要先进行配置。

配置的方法不是用鼠标点击,而是通过写寄存器。

例如:

c 复制代码
P3 = 0xFF;

这句话就是往 P3 端口寄存器里面写入 0xFF

通俗理解就是:

text 复制代码
你往寄存器里写什么值,单片机硬件就按照这个值去工作。

三、寄存器的通俗理解

可以把单片机想象成一个设备控制柜。

控制柜上有很多开关:

text 复制代码
开关1:控制 P3.0
开关2:控制 P3.1
开关3:控制 P3.2
......
开关8:控制 P3.7

这些开关合在一起,就是一个 8 位寄存器。

例如 P3 端口寄存器:

text 复制代码
P3.7  P3.6  P3.5  P3.4  P3.3  P3.2  P3.1  P3.0

每一位都可以是:

text 复制代码
0:低电平
1:高电平

所以:

c 复制代码
P3 = 0xFF;

相当于:

text 复制代码
P3.7 = 1
P3.6 = 1
P3.5 = 1
P3.4 = 1
P3.3 = 1
P3.2 = 1
P3.1 = 1
P3.0 = 1

也就是 P3 所有引脚都输出高电平。

再比如:

c 复制代码
P3 = 0x00;

相当于:

text 复制代码
P3.7 ~ P3.0 全部输出低电平

四、什么是sfr?

1. sfr的含义

sfr 是 C51 里的关键字,全称是:

text 复制代码
Special Function Register

中文可以理解为:

text 复制代码
特殊功能寄存器

它的作用是:

给单片机内部某个特殊功能寄存器起一个名字。

例如:

c 复制代码
sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;

这几句话的意思是:

text 复制代码
P0 这个寄存器的地址是 0x80
P1 这个寄存器的地址是 0x90
P2 这个寄存器的地址是 0xA0
P3 这个寄存器的地址是 0xB0

也就是说,当代码中写:

c 复制代码
P3 = 0xFF;

编译器就知道你要操作的是地址 0xB0 对应的那个特殊功能寄存器。


2. sfr的通俗理解

可以这样理解:

text 复制代码
寄存器本来只有地址,比如 0xB0。
地址不好记,所以用 sfr 给它起个名字 P3。
以后写 P3,就等于操作 0xB0 这个寄存器。

就像一个人的身份证号很难记,但是名字比较容易记。

text 复制代码
0xB0 是寄存器的"地址"
P3 是寄存器的"名字"

3. 平时需要自己写 sfr P3 = 0xB0 吗?

大多数情况下不需要。

因为使用的单片机头文件里一般已经写好了。

例如程序开头经常会写:

c 复制代码
#include "STC8G.H"

这个头文件里面通常已经定义了很多寄存器,例如:

c 复制代码
sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;

所以在程序中可以直接写:

c 复制代码
P3 = 0xFF;

而不用自己重新定义 P3。

但是,虽然平时不经常手写 sfr,也必须理解它的含义。否则后面看头文件、看寄存器代码时会比较吃力。


五、什么是sbit?

1. sbit的含义

sbit 是 C51 里的关键字,用来定义某个寄存器中的某一位。

例如:

c 复制代码
sbit LED = P3^7;

意思是:

text 复制代码
把 P3.7 这个引脚命名为 LED。

之后就可以写:

c 复制代码
LED = 0;
LED = 1;

实际上就是控制 P3.7 输出低电平或高电平。


2. sbit的通俗理解

如果说 sfr 是给一个 8 位寄存器起名字,那么 sbit 就是给这个寄存器里面的某一位起名字。

例如 P3 是一整个端口:

text 复制代码
P3.7  P3.6  P3.5  P3.4  P3.3  P3.2  P3.1  P3.0

如果只想控制 P3.7,就可以写:

c 复制代码
sbit LED = P3^7;

这样以后:

c 复制代码
LED = 0;

就相当于:

text 复制代码
P3.7 = 0

六、sfr和sbit的区别

可以这样理解:

关键字 作用 操作对象 示例
sfr 定义一个特殊功能寄存器 8 位寄存器 sfr P3 = 0xB0;
sbit 定义某个寄存器的一位 1 个 bit sbit LED = P3^7;

通俗说:

text 复制代码
sfr 管一整组,比如 P3 整个端口。
sbit 管其中一位,比如 P3.7 这一个引脚。

例如:

c 复制代码
P3 = 0xFF;

这是操作整个 P3 端口。

而:

c 复制代码
LED = 1;

这是只操作 P3.7 这一位。


七、P3、P3.7、P3^7分别是什么?

这是初学者很容易混淆的地方。


1. P3

c 复制代码
P3

表示整个 P3 端口寄存器。

它有 8 位:

text 复制代码
P3.7 P3.6 P3.5 P3.4 P3.3 P3.2 P3.1 P3.0

所以:

c 复制代码
P3 = 0xFF;

表示控制整个 P3 口。


2. P3.7

text 复制代码
P3.7

这是平时描述硬件引脚时的写法,意思是:

text 复制代码
P3 端口的第 7 位

但是在 C51 代码里不能直接写:

c 复制代码
P3.7 = 1;

这是错误的 C 语法。


3. P3^7

在 C51 的 sbit 定义中,要写成:

c 复制代码
sbit LED = P3^7;

这里的 P3^7 表示:

text 复制代码
P3 端口的第 7 位,也就是 P3.7。

注意:

sbit LED = P3^7; 这种语句中,^ 表示位编号。

但是在普通 C 表达式中,^ 是按位异或运算。

例如:

c 复制代码
P3 = P3 ^ 0x80;

这里的 ^ 就是异或,不是"P3 的第 7 位"。

这个地方一定要区分清楚。


八、用sbit控制单个IO

假设 LED 接在 P3.7:

c 复制代码
sbit LED = P3^7;

那么:

c 复制代码
LED = 0;

表示:

text 复制代码
P3.7 输出低电平
c 复制代码
LED = 1;

表示:

text 复制代码
P3.7 输出高电平

完整例子:

c 复制代码
#include "STC8G.H"

sbit LED = P3^7;

void main(void)
{
    while(1)
    {
        LED = 0;
        LED = 1;
    }
}

这段代码会让 P3.7 不断在低电平和高电平之间变化。

不过因为变化太快,肉眼看不到明显闪烁,所以通常需要加延时函数。


九、用sfr操作整个端口

除了用 sbit 控制一个引脚,也可以直接操作整个端口。

例如:

c 复制代码
P3 = 0x00;

表示:

text 复制代码
P3.0 ~ P3.7 全部输出低电平
c 复制代码
P3 = 0xFF;

表示:

text 复制代码
P3.0 ~ P3.7 全部输出高电平

如果写:

c 复制代码
P3 = 0x80;

需要先把 0x80 转成二进制:

text 复制代码
0x80 = 1000 0000

所以:

text 复制代码
P3.7 = 1
P3.6 = 0
P3.5 = 0
P3.4 = 0
P3.3 = 0
P3.2 = 0
P3.1 = 0
P3.0 = 0

也就是说,只有 P3.7 为高电平,其余都是低电平。


十、二进制和十六进制必须熟悉

单片机程序中经常能看到这些数:

text 复制代码
0x01
0x02
0x04
0x08
0x10
0x20
0x40
0x80

它们分别对应 8 个 bit:

十六进制 二进制 对应位
0x01 0000 0001 bit0
0x02 0000 0010 bit1
0x04 0000 0100 bit2
0x08 0000 1000 bit3
0x10 0001 0000 bit4
0x20 0010 0000 bit5
0x40 0100 0000 bit6
0x80 1000 0000 bit7

这个表非常重要。

以后看到:

c 复制代码
P3M0 |= 0x80;

就应该知道:

text 复制代码
这是在操作 bit7。

看到:

c 复制代码
P3M0 |= 0x04;

就应该知道:

text 复制代码
这是在操作 bit2。

十一、什么是位操作?

位操作就是:

对一个 8 位数据中的某一位或几位进行操作。

比如 P3 是 8 位:

text 复制代码
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

如果只想修改 P3.7,而不影响 P3.0 ~ P3.6,就不能直接写:

c 复制代码
P3 = 0x80;

因为这会把其他位全部改成 0。

所以需要使用位操作。

常见位操作有:

c 复制代码
|=     置 1
&=     清 0
^=     取反
~      按位取反

十二、置1:使用 |=

1. 基本写法

如果想把某一位置 1,可以用:

c 复制代码
寄存器 |= 掩码;

例如:

c 复制代码
P3 |= 0x80;

意思是:

text 复制代码
把 P3.7 置 1,其他位保持不变。

2. 为什么可以保持其他位不变?

假设 P3 当前值是:

text 复制代码
P3 = 0101 0011

执行:

c 复制代码
P3 |= 0x80;

其中:

text 复制代码
0x80 = 1000 0000

按位或运算:

text 复制代码
  0101 0011
| 1000 0000
------------
  1101 0011

可以看到:

text 复制代码
bit7 被置 1
其他位保持原来的值

所以:

c 复制代码
P3 |= 0x80;

就是只把 P3.7 置 1。


3. 常见例子

c 复制代码
P3 |= 0x01;   // P3.0 置 1
P3 |= 0x02;   // P3.1 置 1
P3 |= 0x04;   // P3.2 置 1
P3 |= 0x08;   // P3.3 置 1
P3 |= 0x10;   // P3.4 置 1
P3 |= 0x20;   // P3.5 置 1
P3 |= 0x40;   // P3.6 置 1
P3 |= 0x80;   // P3.7 置 1

十三、清0:使用 &= ~

1. 基本写法

如果想把某一位清 0,可以用:

c 复制代码
寄存器 &= ~掩码;

例如要把 P3.7 清 0:

c 复制代码
P3 &= ~0x80;

其中:

text 复制代码
0x80  = 1000 0000
~0x80 = 0111 1111

2. 为什么可以清bit7?

假设 P3 当前值是:

text 复制代码
P3 = 1101 0011

执行:

c 复制代码
P3 &= ~0x80;

~0x80 的结果可以理解为:

text 复制代码
~0x80 = 0111 1111

按位与运算:

text 复制代码
  1101 0011
& 0111 1111
------------
  0101 0011

可以看到:

text 复制代码
bit7 被清 0
其他位保持不变

所以:

c 复制代码
P3 &= ~0x80;

就是只把 P3.7 清 0。


3. 另一种写法

清 bit7 也可以写成:

c 复制代码
P3 &= 0x7F;

因为:

text 复制代码
0x7F = 0111 1111

所以:

c 复制代码
P3 &= 0x7F;

和:

c 复制代码
P3 &= ~0x80;

效果一样。

不过更推荐:

c 复制代码
P3 &= ~0x80;

因为它更容易看出目标是清 bit7。


十四、取反:使用 ^=

1. 基本写法

如果想让某一位取反,可以用:

c 复制代码
寄存器 ^= 掩码;

例如:

c 复制代码
P3 ^= 0x80;

意思是:

text 复制代码
P3.7 取反。
如果原来是 1,就变成 0;
如果原来是 0,就变成 1;
其他位不变。

2. 用取反实现LED翻转

如果 LED 接在 P3.7,可以写:

c 复制代码
P3 ^= 0x80;

这样 P3.7 每执行一次就翻转一次。

也可以用 sbit 写:

c 复制代码
LED = ~LED;

不过在实际项目中,更推荐写明确的开关函数:

c 复制代码
void LED_On(void)
{
    LED = 0;
}

void LED_Off(void)
{
    LED = 1;
}

这样代码可读性更好,也不容易因为高低有效逻辑产生混淆。


十五、按位取反:~

~ 是按位取反。

例如:

text 复制代码
0x80 = 1000 0000
~0x80 = 0111 1111

所以:

c 复制代码
P3 &= ~0x80;

意思就是:

text 复制代码
把 P3 的 bit7 清 0,其他位保持不变。

再比如:

c 复制代码
P3 &= ~0x04;

其中:

text 复制代码
0x04 = 0000 0100
~0x04 = 1111 1011

所以它表示:

text 复制代码
把 P3.2 清 0。

十六、位操作常用模板

实际项目中可以直接记下面这些模板。

先定义常用位:

c 复制代码
#define BIT0    0x01
#define BIT1    0x02
#define BIT2    0x04
#define BIT3    0x08
#define BIT4    0x10
#define BIT5    0x20
#define BIT6    0x40
#define BIT7    0x80

1. 某位置1

c 复制代码
REG |= BITn;

例如:

c 复制代码
P3 |= BIT7;

表示:

text 复制代码
P3.7 置 1

2. 某位清0

c 复制代码
REG &= ~BITn;

例如:

c 复制代码
P3 &= ~BIT7;

表示:

text 复制代码
P3.7 清 0

3. 某位取反

c 复制代码
REG ^= BITn;

例如:

c 复制代码
P3 ^= BIT7;

表示:

text 复制代码
P3.7 翻转

4. 判断某位是否为1

c 复制代码
if(REG & BITn)
{
    // 这一位为 1
}

例如:

c 复制代码
if(P3 & BIT2)
{
    // P3.2 是高电平
}

5. 判断某位是否为0

c 复制代码
if((REG & BITn) == 0)
{
    // 这一位为 0
}

例如:

c 复制代码
if((P3 & BIT2) == 0)
{
    // P3.2 是低电平
}

十七、sbit和位操作哪个更常用?

对于单个 IO,使用 sbit 更直观。

例如:

c 复制代码
sbit LED = P3^7;

LED = 0;
LED = 1;

这比下面这种写法更容易读:

c 复制代码
P3 &= ~0x80;
P3 |= 0x80;

但是对于配置寄存器,例如:

c 复制代码
P3M0
P3M1
AUXR
TMOD
IE

通常更常用位操作。

例如:

c 复制代码
P3M1 &= ~0x80;
P3M0 |= 0x80;

因为配置寄存器时,经常只想修改其中一位,而不影响其他位。

所以可以这样记:

text 复制代码
控制单个 IO 状态:sbit 更直观
配置寄存器某些位:位操作更常用

十八、GPIO模式配置:理解P3M0和P3M1

很多 STC 单片机的 IO 模式由两个寄存器控制,比如:

c 复制代码
P3M0
P3M1

对于 P3.7 来说:

text 复制代码
P3M1.7 和 P3M0.7 共同决定 P3.7 的 IO 模式

常见配置如下:

PnM1 PnM0 IO模式
0 0 准双向口
0 1 推挽输出
1 0 高阻输入
1 1 开漏输出

如果想把 P3.7 设置为推挽输出:

text 复制代码
P3M1.7 = 0
P3M0.7 = 1

代码可以写成:

c 复制代码
P3M1 &= ~0x80;
P3M0 |= 0x80;

这两句的意思是:

text 复制代码
P3M1 的 bit7 清 0
P3M0 的 bit7 置 1

也就是:

text 复制代码
把 P3.7 配置成推挽输出

十九、为什么不能直接写P3M0 = 0x80?

假设想把 P3.7 设置为推挽输出,可能会想直接写:

c 复制代码
P3M0 = 0x80;

这确实会把 P3M0.7 置 1。

但是它也会把 P3M0 的其他位全部清 0。

如果其他引脚的模式已经配置好了,这样写可能会破坏其他引脚的配置。

所以更安全的写法是:

c 复制代码
P3M0 |= 0x80;

它只修改 bit7,其他位保持不变。

同理,清 bit7 时不要随便写:

c 复制代码
P3M1 = 0x00;

更推荐写:

c 复制代码
P3M1 &= ~0x80;

这样只清 bit7,不影响其他位。


二十、GPIO初始化函数示例

假设:

text 复制代码
LED 接 P3.7,需要配置成推挽输出
KEY 接 P3.2,需要配置成高阻输入

根据前面的模式表:

text 复制代码
推挽输出:PnM1 = 0,PnM0 = 1
高阻输入:PnM1 = 1,PnM0 = 0

P3.7 推挽输出:

c 复制代码
P3M1 &= ~0x80;
P3M0 |= 0x80;

P3.2 高阻输入:

c 复制代码
P3M1 |= 0x04;
P3M0 &= ~0x04;

完整函数如下:

c 复制代码
void GPIO_Init(void)
{
    /* P3.7 设置为推挽输出 */
    P3M1 &= ~0x80;
    P3M0 |= 0x80;

    /* P3.2 设置为高阻输入 */
    P3M1 |= 0x04;
    P3M0 &= ~0x04;
}

这里需要能看懂:

text 复制代码
0x80 对应 bit7,也就是 P3.7
0x04 对应 bit2,也就是 P3.2

二十一、用宏定义让位操作更清楚

直接写 0x800x04 可以,但代码多了以后不够直观。

可以定义:

c 复制代码
#define BIT0    0x01
#define BIT1    0x02
#define BIT2    0x04
#define BIT3    0x08
#define BIT4    0x10
#define BIT5    0x20
#define BIT6    0x40
#define BIT7    0x80

然后 GPIO 初始化可以写成:

c 复制代码
void GPIO_Init(void)
{
    /* P3.7 设置为推挽输出 */
    P3M1 &= ~BIT7;
    P3M0 |= BIT7;

    /* P3.2 设置为高阻输入 */
    P3M1 |= BIT2;
    P3M0 &= ~BIT2;
}

这样比直接写:

c 复制代码
P3M1 &= ~0x80;
P3M0 |= 0x80;

更容易看出操作的是哪一位。


二十二、按键输入怎么读取?

假设按键接在 P3.2:

c 复制代码
sbit KEY = P3^2;

如果硬件是按下为低电平,那么:

text 复制代码
KEY = 1:未按下
KEY = 0:按下

代码可以这样写:

c 复制代码
if(KEY == 0)
{
    // 按键按下
}
else
{
    // 按键未按下
}

也可以不用 sbit,用位操作判断:

c 复制代码
if((P3 & 0x04) == 0)
{
    // P3.2 为低电平,按键按下
}

因为:

text 复制代码
0x04 = 0000 0100

它对应 P3.2。

不过读取按键时,用 sbit 更直观:

c 复制代码
if(KEY == 0)
{
    // 按下
}

二十三、完整例子:LED输出 + 按键输入

假设:

text 复制代码
LED 接 P3.7,低电平点亮
KEY 接 P3.2,按下为低电平

代码如下:

c 复制代码
#include "STC8G.H"

#define BIT2    0x04
#define BIT7    0x80

#define LED_ON_LEVEL     0
#define LED_OFF_LEVEL    1

sbit LED = P3^7;
sbit KEY = P3^2;

void GPIO_Init(void);

void main(void)
{
    GPIO_Init();

    while(1)
    {
        if(KEY == 0)
        {
            LED = LED_ON_LEVEL;
        }
        else
        {
            LED = LED_OFF_LEVEL;
        }
    }
}

void GPIO_Init(void)
{
    /* P3.7 设置为推挽输出 */
    P3M1 &= ~BIT7;
    P3M0 |= BIT7;

    /* P3.2 设置为高阻输入 */
    P3M1 |= BIT2;
    P3M0 &= ~BIT2;
}

这个程序的功能是:

text 复制代码
按键按下:LED 点亮
按键松开:LED 熄灭

注意:这个程序还没有按键消抖。按键消抖后面可以单独学习。


二十四、完整例子:用函数封装IO操作

为了让代码更规范,可以进一步封装函数。

c 复制代码
#include "STC8G.H"

#define BIT2    0x04
#define BIT7    0x80

#define LED_ON_LEVEL     0
#define LED_OFF_LEVEL    1

#define KEY_PRESS_LEVEL  0

sbit LED = P3^7;
sbit KEY = P3^2;

void GPIO_Init(void);
void LED_On(void);
void LED_Off(void);
bit Key_IsPressed(void);

void main(void)
{
    GPIO_Init();

    while(1)
    {
        if(Key_IsPressed() == 1)
        {
            LED_On();
        }
        else
        {
            LED_Off();
        }
    }
}

void GPIO_Init(void)
{
    /* P3.7 设置为推挽输出 */
    P3M1 &= ~BIT7;
    P3M0 |= BIT7;

    /* P3.2 设置为高阻输入 */
    P3M1 |= BIT2;
    P3M0 &= ~BIT2;
}

void LED_On(void)
{
    LED = LED_ON_LEVEL;
}

void LED_Off(void)
{
    LED = LED_OFF_LEVEL;
}

bit Key_IsPressed(void)
{
    if(KEY == KEY_PRESS_LEVEL)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

这个版本比前一个版本更加规范。

因为:

text 复制代码
LED_On() 负责点亮 LED
LED_Off() 负责熄灭 LED
Key_IsPressed() 负责判断按键是否按下
GPIO_Init() 负责初始化 IO 模式

每个函数只做一件明确的事情,后期更容易维护。


二十五、第三天最容易出错的地方

1. 把P3^7写成P3.7

错误写法:

c 复制代码
sbit LED = P3.7;

正确写法:

c 复制代码
sbit LED = P3^7;

原因:

text 复制代码
C51 中定义 sbit 时,用 P3^7 表示 P3.7。

2. 以为^永远表示位编号

错误理解:

text 复制代码
所有地方的 ^ 都表示第几位。

正确理解:

text 复制代码
在 sbit 定义中,P3^7 表示 P3 的第 7 位。
在普通表达式中,^ 表示按位异或。

例如:

c 复制代码
sbit LED = P3^7;   // 这里表示 P3.7

P3 ^= 0x80;        // 这里表示 P3 与 0x80 异或

3. 直接写P3M0 = 0x80

这样可能会影响其他位。

更推荐:

c 复制代码
P3M0 |= 0x80;

因为它只把 bit7 置 1,不影响其他位。


4. 清位时写错掩码

要清 bit7,正确写法:

c 复制代码
P3M1 &= ~0x80;

或者:

c 复制代码
P3M1 &= 0x7F;

不要写成:

c 复制代码
P3M1 &= 0x80;

因为这会把其他位都清掉,只保留 bit7,结果完全不同。


5. 忘记IO模式配置

在 STC8G 这类单片机上,如果需要比较明确的输出能力,建议配置 IO 模式。

例如 LED 输出:

c 复制代码
P3M1 &= ~BIT7;
P3M0 |= BIT7;

如果不配置,有时也能正常输出,但工程上不够严谨。


二十六、第三天必须掌握的10个重点

1. 寄存器是什么?

text 复制代码
寄存器是单片机内部控制硬件的特殊存储单元。

2. sfr是什么?

text 复制代码
sfr 用来定义特殊功能寄存器。

例如:

c 复制代码
sfr P3 = 0xB0;

表示:

text 复制代码
P3 这个名字对应地址 0xB0 的特殊功能寄存器。

3. sbit是什么?

text 复制代码
sbit 用来定义某个寄存器中的某一位。

例如:

c 复制代码
sbit LED = P3^7;

表示:

text 复制代码
LED 代表 P3.7。

4. P3和P3^7的区别

text 复制代码
P3 是整个 8 位端口。
P3^7 是 P3 的第 7 位。

5. 0x80对应bit7

text 复制代码
0x80 = 1000 0000

6. 0x04对应bit2

text 复制代码
0x04 = 0000 0100

7. 置1用 |=

c 复制代码
P3 |= 0x80;

表示:

text 复制代码
P3.7 置 1。

8. 清0用 &= ~

c 复制代码
P3 &= ~0x80;

表示:

text 复制代码
P3.7 清 0。

9. 取反用 ^=

c 复制代码
P3 ^= 0x80;

表示:

text 复制代码
P3.7 翻转。

10. 修改寄存器某一位时,尽量不要影响其他位

推荐:

c 复制代码
P3M0 |= 0x80;
P3M1 &= ~0x80;

不推荐:

c 复制代码
P3M0 = 0x80;
P3M1 = 0x00;

除非明确知道整个寄存器其他位都不需要保留。


二十七、第三天练习任务

任务1:判断掩码对应哪一位

请判断下面数值分别对应哪一位:

text 复制代码
0x01
0x02
0x04
0x08
0x10
0x20
0x40
0x80

答案:

text 复制代码
0x01 → bit0
0x02 → bit1
0x04 → bit2
0x08 → bit3
0x10 → bit4
0x20 → bit5
0x40 → bit6
0x80 → bit7

任务2:写出P3.5置1的代码

P3.5 对应:

text 复制代码
bit5 = 0x20

答案:

c 复制代码
P3 |= 0x20;

任务3:写出P3.5清0的代码

答案:

c 复制代码
P3 &= ~0x20;

任务4:写出P3.5取反的代码

答案:

c 复制代码
P3 ^= 0x20;

任务5:用sbit定义P3.5

答案:

c 复制代码
sbit TEST_IO = P3^5;

任务6:将P3.5配置为推挽输出

推挽输出:

text 复制代码
P3M1.5 = 0
P3M0.5 = 1

P3.5 对应:

text 复制代码
0x20

答案:

c 复制代码
P3M1 &= ~0x20;
P3M0 |= 0x20;

任务7:将P3.5配置为高阻输入

高阻输入:

text 复制代码
P3M1.5 = 1
P3M0.5 = 0

答案:

c 复制代码
P3M1 |= 0x20;
P3M0 &= ~0x20;

二十八、第三天完整示例程序

下面是今天的完整练习程序。

功能:

text 复制代码
LED 接 P3.7,低电平点亮
KEY 接 P3.2,按下为低电平
按键按下时 LED 点亮
按键松开时 LED 熄灭

代码:

c 复制代码
#include "STC8G.H"

#define BIT2    0x04
#define BIT7    0x80

#define LED_ON_LEVEL     0
#define LED_OFF_LEVEL    1
#define KEY_PRESS_LEVEL  0

sbit LED = P3^7;
sbit KEY = P3^2;

void GPIO_Init(void);
void LED_On(void);
void LED_Off(void);
bit Key_IsPressed(void);

void main(void)
{
    GPIO_Init();

    while(1)
    {
        if(Key_IsPressed() == 1)
        {
            LED_On();
        }
        else
        {
            LED_Off();
        }
    }
}

void GPIO_Init(void)
{
    /* P3.7 设置为推挽输出 */
    P3M1 &= ~BIT7;
    P3M0 |= BIT7;

    /* P3.2 设置为高阻输入 */
    P3M1 |= BIT2;
    P3M0 &= ~BIT2;
}

void LED_On(void)
{
    LED = LED_ON_LEVEL;
}

void LED_Off(void)
{
    LED = LED_OFF_LEVEL;
}

bit Key_IsPressed(void)
{
    if(KEY == KEY_PRESS_LEVEL)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

这段程序非常适合作为第三天的综合练习。

需要重点看懂下面几句:

c 复制代码
sbit LED = P3^7;
sbit KEY = P3^2;
P3M1 &= ~BIT7;
P3M0 |= BIT7;
P3M1 |= BIT2;
P3M0 &= ~BIT2;

只要这些能看懂,第三天的核心内容就基本掌握了。


二十九、第三天总结

今天学习的是 C51 中非常核心的硬件控制基础:

text 复制代码
sfr
sbit
寄存器
端口
位操作
IO 模式配置

可以这样总结:

text 复制代码
sfr 是给 8 位特殊功能寄存器起名字。
sbit 是给寄存器中的某一位起名字。
P3 表示整个 P3 端口。
P3^7 在 sbit 定义中表示 P3.7。
0x80 对应 bit7,0x04 对应 bit2。
|= 用来置 1。
&= ~ 用来清 0。
^= 用来取反。
修改寄存器某一位时,要尽量不影响其他位。

今天最重要的一句话是:

单片机 C 语言控制硬件,本质上就是通过 sfr 找到寄存器,再通过 sbit 或位操作修改寄存器中的某一位。

第三天达到下面程度就算合格:

text 复制代码
知道 sfr 是特殊功能寄存器
知道 sbit 是寄存器位定义
能看懂 sbit LED = P3^7
知道 P3 = 0xFF 是操作整个端口
知道 P3 |= 0x80 是把 P3.7 置 1
知道 P3 &= ~0x80 是把 P3.7 清 0
知道 P3 ^= 0x80 是把 P3.7 翻转
能看懂 P3M1 &= ~BIT7 和 P3M0 |= BIT7
能写出 LED 输出和按键输入的简单程序

后续可以继续学习 GPIO 输出控制,重点包括高低电平、推挽输出、准双向口、高阻输入、开漏输出,以及如何从原理图判断 LED 和按键的有效电平。

相关推荐
yoothey2 小时前
异常学习笔记:为什么自定义异常后还要 throw?
笔记·学习
bkspiderx3 小时前
Windows DLL核心技术:深入理解__declspec(dllexport)与__declspec(dllimport)
windows·stm32·单片机·dllimport·dllexport·windows dll·__declspec
WangN23 小时前
【通识】宇树G1_29DOF速度跟踪训练—逐章学习手册
人工智能·python·学习·机器人·具身智能
望眼欲穿的程序猿4 小时前
ESP32-S3 定时器中断
单片机·嵌入式硬件
电气_空空4 小时前
基于 LabVIEW 的深海气密采水器测控系统
单片机·嵌入式硬件·毕业设计·labview
lazy H4 小时前
Spring Boot 项目如何连接 Redis?新手入门配置和常见错误总结
ide·spring boot·redis·后端·学习·intellij-idea
雾沉川4 小时前
Flutter 入门开发环境完整搭建教程
学习·flutter
星夜夏空994 小时前
STM32单片机学习(37) —— PWR和BKP
stm32·单片机·学习
万岳科技4 小时前
教育培训系统开发流程详解:平台建设关键环节解析
数据库·后端·学习