下面这版已经整理成 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; P3、P3.7、P3^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
二十一、用宏定义让位操作更清楚
直接写 0x80、0x04 可以,但代码多了以后不够直观。
可以定义:
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 和按键的有效电平。