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

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

前言

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

前两天主要学习了:

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

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

sfr
sbit
寄存器
端口
位操作

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

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

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

一、第三天学习目标

第三天的目标是:

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

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

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


二、什么是寄存器?

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

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

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

IO 端口
定时器
串口
ADC
PWM
中断
时钟
低功耗模块

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

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

例如:

P3 = 0xFF;

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

通俗理解就是:

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

三、寄存器的通俗理解

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

控制柜上有很多开关:

开关1:控制 P3.0
开关2:控制 P3.1
开关3:控制 P3.2
……
开关8:控制 P3.7

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

例如 P3 端口寄存器:

P3.7  P3.6  P3.5  P3.4  P3.3  P3.2  P3.1  P3.0

每一位都可以是:

0:低电平
1:高电平

所以:

P3 = 0xFF;

相当于:

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 所有引脚都输出高电平。

再比如:

P3 = 0x00;

相当于:

P3.7 ~ P3.0 全部输出低电平

四、什么是sfr?

1. sfr的含义

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

Special Function Register

中文可以理解为:

特殊功能寄存器

它的作用是:

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

例如:

sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;

这几句话的意思是:

P0 这个寄存器的地址是 0x80
P1 这个寄存器的地址是 0x90
P2 这个寄存器的地址是 0xA0
P3 这个寄存器的地址是 0xB0

也就是说,当代码中写:

P3 = 0xFF;

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


2. sfr的通俗理解

可以这样理解:

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

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

0xB0 是寄存器的“地址”
P3 是寄存器的“名字”

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

大多数情况下不需要。

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

例如程序开头经常会写:

#include "STC8G.H"

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

sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;

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

P3 = 0xFF;

而不用自己重新定义 P3。

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


五、什么是sbit?

1. sbit的含义

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

例如:

sbit LED = P3^7;

意思是:

把 P3.7 这个引脚命名为 LED。

之后就可以写:

LED = 0;
LED = 1;

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


2. sbit的通俗理解

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

例如 P3 是一整个端口:

P3.7  P3.6  P3.5  P3.4  P3.3  P3.2  P3.1  P3.0

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

sbit LED = P3^7;

这样以后:

LED = 0;

就相当于:

P3.7 = 0

六、sfr和sbit的区别

可以这样理解:

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

通俗说:

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

例如:

P3 = 0xFF;

这是操作整个 P3 端口。

而:

LED = 1;

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


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

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


1. P3

P3

表示整个 P3 端口寄存器。

它有 8 位:

P3.7 P3.6 P3.5 P3.4 P3.3 P3.2 P3.1 P3.0

所以:

P3 = 0xFF;

表示控制整个 P3 口。


2. P3.7

P3.7

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

P3 端口的第 7 位

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

P3.7 = 1;

这是错误的 C 语法。


3. P3^7

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

sbit LED = P3^7;

这里的 P3^7 表示:

P3 端口的第 7 位,也就是 P3.7。

注意:

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

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

例如:

P3 = P3 ^ 0x80;

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

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


八、用sbit控制单个IO

假设 LED 接在 P3.7:

sbit LED = P3^7;

那么:

LED = 0;

表示:

P3.7 输出低电平
LED = 1;

表示:

P3.7 输出高电平

完整例子:

#include "STC8G.H"

sbit LED = P3^7;

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

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

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


九、用sfr操作整个端口

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

例如:

P3 = 0x00;

表示:

P3.0 ~ P3.7 全部输出低电平
P3 = 0xFF;

表示:

P3.0 ~ P3.7 全部输出高电平

如果写:

P3 = 0x80;

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

0x80 = 1000 0000

所以:

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 为高电平,其余都是低电平。


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

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

0x01
0x02
0x04
0x08
0x10
0x20
0x40
0x80

它们分别对应 8 个 bit:

十六进制二进制对应位
0x010000 0001bit0
0x020000 0010bit1
0x040000 0100bit2
0x080000 1000bit3
0x100001 0000bit4
0x200010 0000bit5
0x400100 0000bit6
0x801000 0000bit7

这个表非常重要。

以后看到:

P3M0 |= 0x80;

就应该知道:

这是在操作 bit7。

看到:

P3M0 |= 0x04;

就应该知道:

这是在操作 bit2。

十一、什么是位操作?

位操作就是:

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

比如 P3 是 8 位:

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

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

P3 = 0x80;

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

所以需要使用位操作。

常见位操作有:

|=1
&=0
^=     取反
~      按位取反

十二、置1:使用 |=

1. 基本写法

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

寄存器 |= 掩码;

例如:

P3 |= 0x80;

意思是:

把 P3.7 置 1,其他位保持不变。

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

假设 P3 当前值是:

P3 = 0101 0011

执行:

P3 |= 0x80;

其中:

0x80 = 1000 0000

按位或运算:

  0101 0011
| 1000 0000
------------
  1101 0011

可以看到:

bit7 被置 1
其他位保持原来的值

所以:

P3 |= 0x80;

就是只把 P3.7 置 1。


3. 常见例子

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,可以用:

寄存器 &= ~掩码;

例如要把 P3.7 清 0:

P3 &= ~0x80;

其中:

0x80  = 1000 0000
~0x80 = 0111 1111

2. 为什么可以清bit7?

假设 P3 当前值是:

P3 = 1101 0011

执行:

P3 &= ~0x80;

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

~0x80 = 0111 1111

按位与运算:

  1101 0011
& 0111 1111
------------
  0101 0011

可以看到:

bit7 被清 0
其他位保持不变

所以:

P3 &= ~0x80;

就是只把 P3.7 清 0。


3. 另一种写法

清 bit7 也可以写成:

P3 &= 0x7F;

因为:

0x7F = 0111 1111

所以:

P3 &= 0x7F;

和:

P3 &= ~0x80;

效果一样。

不过更推荐:

P3 &= ~0x80;

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


十四、取反:使用 ^=

1. 基本写法

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

寄存器 ^= 掩码;

例如:

P3 ^= 0x80;

意思是:

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

2. 用取反实现LED翻转

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

P3 ^= 0x80;

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

也可以用 sbit 写:

LED = ~LED;

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

void LED_On(void)
{
    LED = 0;
}

void LED_Off(void)
{
    LED = 1;
}

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


十五、按位取反:~

~ 是按位取反。

例如:

0x80 = 1000 0000
~0x80 = 0111 1111

所以:

P3 &= ~0x80;

意思就是:

把 P3 的 bit7 清 0,其他位保持不变。

再比如:

P3 &= ~0x04;

其中:

0x04 = 0000 0100
~0x04 = 1111 1011

所以它表示:

把 P3.2 清 0。

十六、位操作常用模板

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

先定义常用位:

#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

REG |= BITn;

例如:

P3 |= BIT7;

表示:

P3.7 置 1

2. 某位清0

REG &= ~BITn;

例如:

P3 &= ~BIT7;

表示:

P3.7 清 0

3. 某位取反

REG ^= BITn;

例如:

P3 ^= BIT7;

表示:

P3.7 翻转

4. 判断某位是否为1

if(REG & BITn)
{
    // 这一位为 1
}

例如:

if(P3 & BIT2)
{
    // P3.2 是高电平
}

5. 判断某位是否为0

if((REG & BITn) == 0)
{
    // 这一位为 0
}

例如:

if((P3 & BIT2) == 0)
{
    // P3.2 是低电平
}

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

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

例如:

sbit LED = P3^7;

LED = 0;
LED = 1;

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

P3 &= ~0x80;
P3 |= 0x80;

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

P3M0
P3M1
AUXR
TMOD
IE

通常更常用位操作。

例如:

P3M1 &= ~0x80;
P3M0 |= 0x80;

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

所以可以这样记:

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

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

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

P3M0
P3M1

对于 P3.7 来说:

P3M1.7 和 P3M0.7 共同决定 P3.7 的 IO 模式

常见配置如下:

PnM1PnM0IO模式
00准双向口
01推挽输出
10高阻输入
11开漏输出

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

P3M1.7 = 0
P3M0.7 = 1

代码可以写成:

P3M1 &= ~0x80;
P3M0 |= 0x80;

这两句的意思是:

P3M1 的 bit7 清 0
P3M0 的 bit7 置 1

也就是:

把 P3.7 配置成推挽输出

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

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

P3M0 = 0x80;

这确实会把 P3M0.7 置 1。

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

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

所以更安全的写法是:

P3M0 |= 0x80;

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

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

P3M1 = 0x00;

更推荐写:

P3M1 &= ~0x80;

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


二十、GPIO初始化函数示例

假设:

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

根据前面的模式表:

推挽输出:PnM1 = 0,PnM0 = 1
高阻输入:PnM1 = 1,PnM0 = 0

P3.7 推挽输出:

P3M1 &= ~0x80;
P3M0 |= 0x80;

P3.2 高阻输入:

P3M1 |= 0x04;
P3M0 &= ~0x04;

完整函数如下:

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

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

这里需要能看懂:

0x80 对应 bit7,也就是 P3.7
0x04 对应 bit2,也就是 P3.2

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

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

可以定义:

#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 初始化可以写成:

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

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

这样比直接写:

P3M1 &= ~0x80;
P3M0 |= 0x80;

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


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

假设按键接在 P3.2:

sbit KEY = P3^2;

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

KEY = 1:未按下
KEY = 0:按下

代码可以这样写:

if(KEY == 0)
{
    // 按键按下
}
else
{
    // 按键未按下
}

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

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

因为:

0x04 = 0000 0100

它对应 P3.2。

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

if(KEY == 0)
{
    // 按下
}

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

假设:

LED 接 P3.7,低电平点亮
KEY 接 P3.2,按下为低电平

代码如下:

#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;
}

这个程序的功能是:

按键按下:LED 点亮
按键松开:LED 熄灭

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


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

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

#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;
    }
}

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

因为:

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

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


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

1. 把P3^7写成P3.7

错误写法:

sbit LED = P3.7;

正确写法:

sbit LED = P3^7;

原因:

C51 中定义 sbit 时,用 P3^7 表示 P3.7。

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

错误理解:

所有地方的 ^ 都表示第几位。

正确理解:

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

例如:

sbit LED = P3^7;   // 这里表示 P3.7

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

3. 直接写P3M0 = 0x80

这样可能会影响其他位。

更推荐:

P3M0 |= 0x80;

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


4. 清位时写错掩码

要清 bit7,正确写法:

P3M1 &= ~0x80;

或者:

P3M1 &= 0x7F;

不要写成:

P3M1 &= 0x80;

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


5. 忘记IO模式配置

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

例如 LED 输出:

P3M1 &= ~BIT7;
P3M0 |= BIT7;

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


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

1. 寄存器是什么?

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

2. sfr是什么?

sfr 用来定义特殊功能寄存器。

例如:

sfr P3 = 0xB0;

表示:

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

3. sbit是什么?

sbit 用来定义某个寄存器中的某一位。

例如:

sbit LED = P3^7;

表示:

LED 代表 P3.7。

4. P3和P3^7的区别

P3 是整个 8 位端口。
P3^7 是 P3 的第 7 位。

5. 0x80对应bit7

0x80 = 1000 0000

6. 0x04对应bit2

0x04 = 0000 0100

7. 置1用 |=

P3 |= 0x80;

表示:

P3.7 置 1。

8. 清0用 &= ~

P3 &= ~0x80;

表示:

P3.7 清 0。

9. 取反用 ^=

P3 ^= 0x80;

表示:

P3.7 翻转。

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

推荐:

P3M0 |= 0x80;
P3M1 &= ~0x80;

不推荐:

P3M0 = 0x80;
P3M1 = 0x00;

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


二十七、第三天练习任务

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

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

0x01
0x02
0x04
0x08
0x10
0x20
0x40
0x80

答案:

0x01 → bit0
0x02 → bit1
0x04 → bit2
0x08 → bit3
0x10 → bit4
0x20 → bit5
0x40 → bit6
0x80 → bit7

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

P3.5 对应:

bit5 = 0x20

答案:

P3 |= 0x20;

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

答案:

P3 &= ~0x20;

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

答案:

P3 ^= 0x20;

任务5:用sbit定义P3.5

答案:

sbit TEST_IO = P3^5;

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

推挽输出:

P3M1.5 = 0
P3M0.5 = 1

P3.5 对应:

0x20

答案:

P3M1 &= ~0x20;
P3M0 |= 0x20;

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

高阻输入:

P3M1.5 = 1
P3M0.5 = 0

答案:

P3M1 |= 0x20;
P3M0 &= ~0x20;

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

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

功能:

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

代码:

#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;
    }
}

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

需要重点看懂下面几句:

sbit LED = P3^7;
sbit KEY = P3^2;
P3M1 &= ~BIT7;
P3M0 |= BIT7;
P3M1 |= BIT2;
P3M0 &= ~BIT2;

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


二十九、第三天总结

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

sfr
sbit
寄存器
端口
位操作
IO 模式配置

可以这样总结:

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

今天最重要的一句话是:

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

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

知道 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 和按键的有效电平。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐