C51单片机复习-DAY2
C51单片机复习-DAY2
下面这版已经整理成适合 CSDN Markdown 的文章格式,可以直接复制粘贴发布。我已把项目背景统一改成了“常规小项目 / 小型控制项目”,没有使用你要求避开的那个词。
51单片机C语言重新入门:第二天学习C51数据类型、变量与嵌入式编程习惯
前言
这是我重新学习 51 单片机 C 语言的第二天笔记。
第一天主要学习了 51 单片机 C 程序的基本结构,包括 main()、while(1)、sbit、IO 高低电平控制以及 LED 闪烁程序。
第二天开始进入 C 语言基础部分,不过这里不会单纯讲语法,而是结合 51 单片机实际开发场景来理解数据类型、变量、常量、溢出和嵌入式编程习惯。
在单片机程序中,数据类型的选择非常重要。因为单片机资源有限,变量类型选得是否合适,会直接影响程序的 RAM 占用、Flash 占用、运行速度和稳定性。
本篇文章主要记录以下内容:
- C51 常用数据类型;
bit、unsigned char、unsigned int的典型用法;- 为什么 51 单片机中要少用
float; - 全局变量和局部变量;
- 宏定义常量;
- 数据溢出问题;
- 整数运算中的注意事项;
- 如何在一个常规小项目中合理定义变量。
一、为什么单片机里要特别重视数据类型?
在电脑上写程序时,可能经常随手定义变量:
int a;
float b;
double c;
一般问题不大,因为电脑的内存很大,CPU 性能也很强。
但是 51 单片机不一样。
51 单片机资源比较有限,尤其是传统 8051 架构,RAM、Flash 和运算能力都比较宝贵。
所以在 51 单片机程序中,数据类型选得是否合适,会直接影响:
程序占用 RAM 大小
程序运行速度
代码占用 Flash 大小
是否容易出现溢出
程序是否稳定
例如,亮度等级只有 0 到 20,如果定义成:
unsigned char brightness;
就已经完全够用了。
如果定义成:
long brightness;
虽然程序也可能能运行,但是明显浪费资源。
所以嵌入式 C 语言里有一个很重要的习惯:
变量类型够用就好,不要盲目使用大类型。
二、C51常用数据类型总表
51 单片机 C 语言常见数据类型如下:
| 数据类型 | 位宽 | 取值范围 | 常见用途 |
|---|---|---|---|
bit |
1 bit | 0 或 1 | 标志位 |
char |
8 bit | -128 到 127 | 小范围有符号数 |
unsigned char |
8 bit | 0 到 255 | 亮度、模式、状态、计数 |
int |
16 bit | -32768 到 32767 | 一般有符号整数 |
unsigned int |
16 bit | 0 到 65535 | 延时、计数、ADC 原始值 |
long |
32 bit | -2147483648 到 2147483647 | 大范围计算 |
unsigned long |
32 bit | 0 到 4294967295 | 大范围无符号计算 |
float |
32 bit | 小数 | 小数计算,51 中慎用 |
对于 51 单片机初学阶段,最常用的是下面三个:
unsigned char
unsigned int
bit
可以先记住下面这句话:
状态、等级、模式:unsigned char
计数、时间、ADC:unsigned int
开关、标志:bit
三、bit类型:最适合做开关标志
1. bit是什么?
bit 是 C51 特有的数据类型,用来表示 1 位数据。
它只有两个值:
0
1
所以它非常适合表示一些开关类状态,例如:
开 / 关
真 / 假
有 / 无
按下 / 未按下
低电量 / 正常电量
功能开启 / 功能关闭
示例:
bit key_pressed = 0;
bit low_battery_flag = 0;
bit power_on_flag = 0;
bit function_enable = 1;
这些变量的含义分别是:
key_pressed:按键是否按下
low_battery_flag:是否低电量
power_on_flag:系统是否开机
function_enable:某个功能是否使能
2. bit的通俗理解
可以把 bit 理解成一个小开关:
0:关
1:开
例如:
bit led_state = 0;
表示:
LED 当前状态为关闭
如果后面写:
led_state = 1;
表示:
LED 当前状态为打开
3. bit的典型用法
例如低电量报警:
bit low_battery_flag = 0;
void Check_Battery(void)
{
if(battery_voltage < 2700)
{
low_battery_flag = 1;
}
else
{
low_battery_flag = 0;
}
}
这段代码可以理解为:
如果电池电压低于 2700mV,就把低电量标志置 1;
否则把低电量标志清 0。
后面的主程序可以根据这个标志决定是否执行报警逻辑:
if(low_battery_flag == 1)
{
// 执行低电量报警逻辑
}
四、unsigned char:51单片机中最常用的类型
1. unsigned char是什么?
unsigned char 是无符号 8 位整数。
它的范围是:
0 ~ 255
它不能表示负数。
在 51 单片机中,unsigned char 非常常用,因为 51 单片机是 8 位架构,处理 8 位数据比较自然。
它适合表示:
亮度等级
模式编号
按键编号
状态编号
小范围计数值
数组下标
PWM 占空比
2. 示例:亮度等级
假设一个小型控制项目里有亮度调节功能:
亮度等级范围:0 ~ 20
那么亮度变量可以这样定义:
unsigned char brightness = 0;
因为亮度最大也只有 20,unsigned char 的范围是 0 到 255,完全够用。
没有必要写成:
int brightness;
更没有必要写成:
long brightness;
3. 示例:模式变量
假设系统有两种工作模式:
0:普通模式
1:低功耗模式
可以定义:
unsigned char work_mode = 0;
使用时:
if(work_mode == 0)
{
// 普通模式
}
else if(work_mode == 1)
{
// 低功耗模式
}
不过更推荐使用宏定义,让代码更清晰:
#define MODE_NORMAL 0
#define MODE_LOW_POWER 1
unsigned char work_mode = MODE_NORMAL;
这样判断时可以写成:
if(work_mode == MODE_NORMAL)
{
// 普通模式
}
else if(work_mode == MODE_LOW_POWER)
{
// 低功耗模式
}
这种写法比直接写 0 和 1 更容易理解。
4. 示例:按键状态
可以定义按键值:
#define KEY_NONE 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_MODE 3
unsigned char key_value = KEY_NONE;
后面可以这样使用:
if(key_value == KEY_UP)
{
brightness++;
}
else if(key_value == KEY_DOWN)
{
brightness--;
}
else if(key_value == KEY_MODE)
{
work_mode = MODE_LOW_POWER;
}
使用宏定义后,代码可读性更好。
如果直接写:
if(key_value == 1)
以后再看代码时可能会忘记 1 代表什么。
而写成:
if(key_value == KEY_UP)
就很清楚。
五、char和unsigned char的区别
1. char
char 是 8 位有符号整数。
它的范围通常是:
-128 ~ 127
它可以表示负数。
例如:
char temperature = -10;
表示温度为 -10。
2. unsigned char
unsigned char 是 8 位无符号整数。
它的范围是:
0 ~ 255
它不能表示负数。
例如:
unsigned char brightness = 20;
表示亮度等级为 20。
3. 通俗区别
可以这样理解:
char:可以表示负数,但是正数范围小一些;
unsigned char:不能表示负数,但是正数范围大一些。
如果变量不会出现负数,优先使用:
unsigned char
例如:
亮度等级不会是 -1
模式编号不会是 -1
按键编号不会是 -1
PWM 占空比不会是 -1
这些变量都适合用 unsigned char。
六、unsigned int:适合时间、计数和ADC
1. unsigned int是什么?
unsigned int 是无符号 16 位整数。
它的范围是:
0 ~ 65535
它适合表示:
延时时间
定时器计数
毫秒计数
ADC 原始值
电压值 mV
较大的循环变量
2. 示例:延时函数参数
第一天学习过一个简单延时函数:
void Delay_ms(unsigned int ms)
{
unsigned int i;
unsigned int j;
for(i = 0; i < ms; i++)
{
for(j = 0; j < 1000; j++)
{
;
}
}
}
这里 ms 使用 unsigned int 比较合适。
因为实际调用时可能会写:
Delay_ms(1000);
Delay_ms(2000);
Delay_ms(5000);
这些数值已经超过了 unsigned char 的最大范围 255。
如果 ms 写成:
unsigned char ms
那么:
Delay_ms(500);
就会出问题,因为 unsigned char 最大只能表示 255,500 装不进去。
3. 示例:ADC原始值
假设 ADC 是 12 位,则原始值范围是:
0 ~ 4095
这个范围超过了 unsigned char 的最大值 255,所以要用:
unsigned int adc_value = 0;
如果电池电压用 mV 表示,比如:
3000mV
也应该使用:
unsigned int battery_mv = 3000;
而不是 unsigned char。
七、int和unsigned int的区别
1. int
int 是有符号 16 位整数。
范围通常是:
-32768 ~ 32767
它可以表示负数。
例如:
int offset = -20;
2. unsigned int
unsigned int 是无符号 16 位整数。
范围是:
0 ~ 65535
它不能表示负数,但正数范围更大。
3. 单片机中怎么选?
如果数值可能为负,用:
int
例如:
int temperature = -10;
int error_value = -5;
如果数值不会为负,用:
unsigned int
例如:
unsigned int time_ms = 1000;
unsigned int adc_value = 2048;
unsigned int battery_mv = 3000;
嵌入式程序里很多变量都不会是负数,所以 unsigned int 很常用。
八、long和unsigned long
1. long
long 是 32 位有符号整数。
它的范围比较大。
例如:
long result = 0;
适合用于较大范围计算。
2. unsigned long
unsigned long 是 32 位无符号整数。
它的范围是:
0 ~ 4294967295
适合大范围无符号计数。
3. 什么时候需要用long?
比如 ADC 电压换算:
battery_mv = adc_value * 3300 / 4095;
如果 adc_value 最大为 4095,那么中间乘法结果为:
4095 × 3300 = 13513500
这个中间结果已经超过了 unsigned int 的最大值 65535。
所以更安全的写法是:
unsigned int adc_value;
unsigned int battery_mv;
battery_mv = (unsigned long)adc_value * 3300 / 4095;
这里:
(unsigned long)adc_value
表示先把 adc_value 转成 unsigned long,再参与计算。
这样可以防止中间乘法溢出。
4. 通俗理解
虽然最终结果可能只有 3300mV,不算大。
但是计算过程中会先做乘法:
4095 × 3300 = 13513500
这个中间值很大。
所以在嵌入式程序中要注意:
不仅要看最终结果,还要看中间计算是否会溢出。
九、float:51单片机里尽量少用
1. float是什么?
float 是小数类型。
例如:
float voltage = 3.3;
看起来很方便。
2. 为什么51里面不推荐频繁使用float?
因为 51 单片机没有像电脑 CPU 那样强大的浮点运算能力。
使用 float 会带来几个问题:
运算速度慢
占用代码空间大
占用 RAM 多
调试时容易引入误差
所以在 51 单片机中,通常不建议大量使用 float。
3. 电压小数怎么表示?
例如电池电压是:
3.30V
不推荐这样写:
float battery_voltage = 3.30;
更推荐使用整数表示 mV:
unsigned int battery_mv = 3300;
这样:
3300mV 就等于 3.300V
2700mV 就等于 2.700V
4200mV 就等于 4.200V
判断低电量时也很方便:
if(battery_mv < 2700)
{
low_battery_flag = 1;
}
而不是:
if(battery_voltage < 2.7)
{
low_battery_flag = 1;
}
在单片机中,整数方式更可靠、更高效。
十、变量是什么?
变量可以理解为:
程序运行过程中可以变化的数据。
例如:
unsigned char brightness = 5;
这里定义了一个变量:
变量名:brightness
类型:unsigned char
初始值:5
它表示当前亮度等级是 5。
后面可以改变它:
brightness = 6;
brightness = 7;
brightness++;
brightness--;
变量就像一个小盒子:
盒子名字:brightness
盒子大小:unsigned char
盒子里面的值:5
以后可以往这个盒子里放新的值。
十一、变量的定义格式
变量定义的一般格式是:
数据类型 变量名 = 初始值;
例如:
unsigned char brightness = 5;
unsigned char work_mode = 0;
unsigned int battery_mv = 3000;
bit low_battery_flag = 0;
也可以先定义,再赋值:
unsigned char brightness;
brightness = 5;
但是在嵌入式项目中,建议尽量初始化:
unsigned char brightness = 5;
这样程序刚开始运行时,变量状态更明确。
十二、变量命名建议
变量名不要随便写成:
a
b
c
x
y
z
除非只是临时循环变量。
更推荐使用有意义的名字:
brightness
work_mode
key_value
battery_mv
low_battery_flag
timer_count
例如下面这段代码:
unsigned char a = 5;
unsigned char b = 1;
很难看出 a 和 b 分别代表什么。
如果写成:
unsigned char brightness = 5;
unsigned char work_mode = MODE_NORMAL;
就非常清楚。
好的变量命名可以显著提高代码可读性。
十三、全局变量和局部变量
1. 全局变量
定义在函数外面的变量叫全局变量。
例如:
unsigned char brightness = 5;
bit low_battery_flag = 0;
void main(void)
{
while(1)
{
}
}
这里的:
brightness
low_battery_flag
就是全局变量。
全局变量的特点是:
可以被多个函数使用
程序运行期间一直存在
适合保存系统状态
例如下面这些系统状态适合用全局变量:
当前亮度等级
当前工作模式
低电量标志
开关机状态
2. 局部变量
定义在函数内部的变量叫局部变量。
例如:
void Delay_ms(unsigned int ms)
{
unsigned int i;
unsigned int j;
}
这里的:
i
j
就是局部变量。
局部变量的特点是:
只在当前函数里面有效
函数执行结束后一般不再使用
适合临时计算
3. 示例对比
unsigned char brightness = 5; // 全局变量
void Set_Brightness(unsigned char level)
{
unsigned char pwm_value; // 局部变量
pwm_value = level * 10;
}
这里:
brightness 是系统状态,需要长期保存,所以适合做全局变量;
pwm_value 只是函数内部临时计算,所以适合做局部变量。
十四、常量是什么?
常量就是程序运行过程中不希望改变的值。
例如:
#define BRIGHTNESS_MAX_NORMAL 20
#define BRIGHTNESS_MAX_LOW_POWER 10
#define MODE_NORMAL 0
#define MODE_LOW_POWER 1
这些值在程序运行过程中不应该被修改。
1. 为什么要用宏定义常量?
不推荐这样写:
if(brightness < 20)
{
brightness++;
}
因为以后别人看代码时,可能不知道 20 是什么意思。
更推荐这样写:
#define BRIGHTNESS_MAX_NORMAL 20
if(brightness < BRIGHTNESS_MAX_NORMAL)
{
brightness++;
}
这样一看就知道:
20 表示普通模式下的最大亮度等级。
2. 常用宏定义示例
#define MODE_NORMAL 0
#define MODE_LOW_POWER 1
#define KEY_NONE 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_MODE 3
#define BRIGHTNESS_MAX_NORMAL 20
#define BRIGHTNESS_MAX_LOW 10
#define BATTERY_LOW_MV 2700
这些宏定义可以让代码更清楚。
例如:
if(work_mode == MODE_NORMAL)
{
brightness_max = BRIGHTNESS_MAX_NORMAL;
}
else if(work_mode == MODE_LOW_POWER)
{
brightness_max = BRIGHTNESS_MAX_LOW;
}
比下面这种代码更容易维护:
if(work_mode == 0)
{
brightness_max = 20;
}
else if(work_mode == 1)
{
brightness_max = 10;
}
十五、数据溢出:今天最重要的难点
1. 什么是溢出?
溢出就是:
变量装不下这个数了,结果发生回绕。
例如:
unsigned char a = 255;
a = a + 1;
unsigned char 最大只能表示 255。
那么 255 再加 1 会变成多少?
结果不是 256,而是:
0
这就叫溢出。
2. 通俗理解
unsigned char 就像一个只能显示 0 到 255 的计数器。
当它显示 255 时,再加 1,就会回到 0。
类似汽车里程表:
99999 再加 1,回到 00000
所以:
unsigned char a = 255;
a++;
结果是:
a = 0
3. 亮度变量的溢出问题
假设亮度等级是 0 到 20。
如果写:
unsigned char brightness = 20;
brightness++;
结果会变成 21。
虽然没有超过 unsigned char 的最大范围,但已经超过了业务范围。
所以正确写法应该是:
if(brightness < BRIGHTNESS_MAX_NORMAL)
{
brightness++;
}
这样亮度最大只会到 20。
同理,降低亮度时不要直接写:
brightness--;
如果 brightness 已经是 0,再减 1:
unsigned char brightness = 0;
brightness--;
结果会变成:
255
这也是溢出。
所以正确写法是:
if(brightness > 0)
{
brightness--;
}
4. 这个问题非常常见
例如:
unsigned char brightness = 0;
brightness--;
本来以为亮度会保持 0,结果它变成了 255。
如果后面用这个值查亮度表:
pwm_value = brightness_table[brightness];
那就可能访问数组外面的数据,程序可能异常。
所以以后做按键调节亮度时,一定要写边界保护。
十六、整数除法:结果会丢掉小数
在 C 语言里,如果两个整数相除,结果还是整数。
例如:
unsigned int a = 5;
unsigned int b = 2;
unsigned int c;
c = a / b;
结果是:
c = 2
不是 2.5。
因为 c 是整数,小数部分会被直接舍掉。
1. ADC电压计算示例
假设:
adc_value = 2048;
参考电压是 3300mV,12 位 ADC 最大值是 4095。
电压计算:
battery_mv = adc_value * 3300 / 4095;
这里结果是整数 mV。
这种写法在嵌入式中很常见。
如果想减少误差,要注意计算顺序。
推荐:
battery_mv = (unsigned long)adc_value * 3300 / 4095;
不推荐:
battery_mv = adc_value / 4095 * 3300;
因为:
adc_value / 4095
大多数情况下会先算成 0。
例如:
2048 / 4095 = 0
0 × 3300 = 0
结果就完全错误。
所以整数运算要记住:
先乘后除,必要时用 long 防止中间溢出。
十七、类型选择规则总结
以后写 51 程序,可以按照下面规则选择类型。
1. 开关标志
例如:
是否开机
是否低电量
是否按键按下
是否开启某个功能
使用:
bit
示例:
bit power_on_flag = 0;
bit low_battery_flag = 0;
bit key_pressed_flag = 0;
bit function_enable = 1;
2. 小范围状态
例如:
工作模式
亮度等级
按键编号
状态机状态
使用:
unsigned char
示例:
unsigned char work_mode = MODE_NORMAL;
unsigned char brightness = 5;
unsigned char key_value = KEY_NONE;
unsigned char system_state = 0;
3. 中等范围计数
例如:
毫秒计数
ADC 值
电池电压 mV
延时时间
使用:
unsigned int
示例:
unsigned int timer_ms = 0;
unsigned int adc_value = 0;
unsigned int battery_mv = 3000;
4. 大范围计算
例如:
ADC 换算中间结果
累计运行时间
较大乘法计算
使用:
unsigned long
示例:
unsigned long temp_calc = 0;
5. 小数
51 单片机中尽量少用 float。
如果必须表示小数,优先改成整数单位。
例如:
3.3V 写成 3300mV
1.2A 写成 1200mA
0.5s 写成 500ms
这样程序更高效,也更稳定。
十八、结合常规小项目定义一组变量
假设现在要做一个简单的小型控制项目,功能包括:
LED 指示
按键调节
模式切换
电池电压检测
低电量提示
可以先定义一些变量:
#define MODE_NORMAL 0
#define MODE_LOW_POWER 1
#define BRIGHTNESS_MAX_NORMAL 20
#define BRIGHTNESS_MAX_LOW 10
#define KEY_NONE 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_MODE 3
#define BATTERY_LOW_MV 2700
unsigned char work_mode = MODE_NORMAL;
unsigned char brightness = 5;
unsigned char key_value = KEY_NONE;
unsigned char brightness_max = BRIGHTNESS_MAX_NORMAL;
unsigned int battery_mv = 3000;
unsigned int adc_value = 0;
unsigned int system_tick_ms = 0;
bit power_on_flag = 0;
bit low_battery_flag = 0;
bit function_enable = 1;
每个变量的含义如下:
| 变量名 | 类型 | 含义 |
|---|---|---|
work_mode |
unsigned char |
当前工作模式 |
brightness |
unsigned char |
当前亮度等级 |
key_value |
unsigned char |
当前按键值 |
brightness_max |
unsigned char |
当前模式最大亮度 |
battery_mv |
unsigned int |
电池电压,单位 mV |
adc_value |
unsigned int |
ADC 原始值 |
system_tick_ms |
unsigned int |
系统毫秒计数 |
power_on_flag |
bit |
是否开机 |
low_battery_flag |
bit |
是否低电量 |
function_enable |
bit |
某个功能是否使能 |
这段代码虽然还没有直接控制硬件,但是已经具备了一个嵌入式小项目的变量框架。
十九、今天的完整练习代码
今天不增加复杂外设,只在第一天 LED 闪烁代码基础上加入变量和宏定义。
假设:
LED 接 P3.7
LED 低电平点亮
代码如下:
#include "STC8G.H"
#define LED_ON_LEVEL 0
#define LED_OFF_LEVEL 1
#define MODE_SLOW 0
#define MODE_FAST 1
sbit LED = P3^7;
unsigned char led_mode = MODE_SLOW;
unsigned int delay_time_ms = 500;
bit led_state = 0;
void Delay_ms(unsigned int ms)
{
unsigned int i;
unsigned int j;
for(i = 0; i < ms; i++)
{
for(j = 0; j < 1000; j++)
{
;
}
}
}
void LED_On(void)
{
LED = LED_ON_LEVEL;
led_state = 1;
}
void LED_Off(void)
{
LED = LED_OFF_LEVEL;
led_state = 0;
}
void main(void)
{
while(1)
{
if(led_mode == MODE_SLOW)
{
delay_time_ms = 500;
}
else if(led_mode == MODE_FAST)
{
delay_time_ms = 100;
}
LED_On();
Delay_ms(delay_time_ms);
LED_Off();
Delay_ms(delay_time_ms);
}
}
这段代码目前是慢闪,因为:
unsigned char led_mode = MODE_SLOW;
如果改成:
unsigned char led_mode = MODE_FAST;
LED 就会快闪。
二十、逐行理解练习代码
1. LED电平宏定义
#define LED_ON_LEVEL 0
#define LED_OFF_LEVEL 1
表示:
LED 点亮时输出 0;
LED 熄灭时输出 1。
如果硬件是高电平点亮,只需要改成:
#define LED_ON_LEVEL 1
#define LED_OFF_LEVEL 0
后面 LED_On() 和 LED_Off() 函数不用改。
这就是宏定义的好处。
2. 闪烁模式宏定义
#define MODE_SLOW 0
#define MODE_FAST 1
表示:
0:慢闪模式
1:快闪模式
这样代码更容易读。
3. 模式变量
unsigned char led_mode = MODE_SLOW;
表示当前 LED 闪烁模式是慢闪。
如果写成:
unsigned char led_mode = MODE_FAST;
表示当前 LED 闪烁模式是快闪。
4. 延时时间变量
unsigned int delay_time_ms = 500;
表示当前延时时间是 500ms。
这里不能用 unsigned char,因为 500 超过了 255。
所以用 unsigned int 更合适。
5. LED状态标志
bit led_state = 0;
表示当前 LED 状态。
0:灭
1:亮
这种只有两个状态的变量,适合用 bit。
二十一、亮度变量示例
下面这个例子不一定控制真实 PWM,只用于理解变量范围和边界保护。
#define BRIGHTNESS_MIN 0
#define BRIGHTNESS_MAX 20
unsigned char brightness = 5;
void Brightness_Up(void)
{
if(brightness < BRIGHTNESS_MAX)
{
brightness++;
}
}
void Brightness_Down(void)
{
if(brightness > BRIGHTNESS_MIN)
{
brightness--;
}
}
这段代码非常重要。
以后做按键调节参数时,会经常用到类似逻辑。
1. 为什么不能直接写 brightness++?
假设最大亮度是 20。
如果当前已经是 20,再执行:
brightness++;
就会变成 21,超出业务范围。
所以要写:
if(brightness < BRIGHTNESS_MAX)
{
brightness++;
}
2. 为什么不能直接写 brightness–?
假设当前亮度是 0。
如果执行:
brightness--;
因为 brightness 是 unsigned char,它会从 0 变成 255。
这就是典型溢出。
所以要写:
if(brightness > BRIGHTNESS_MIN)
{
brightness--;
}
二十二、今天必须掌握的8个重点
1. unsigned char
适合:
亮度等级
模式编号
按键编号
状态编号
小范围计数
范围:
0 ~ 255
2. unsigned int
适合:
延时时间
毫秒计数
ADC 值
电池电压 mV
范围:
0 ~ 65535
3. bit
适合:
开关标志
真假状态
是否按下
是否低电量
范围:
0 或 1
4. float
51 单片机里尽量少用。
电压、电流、时间尽量用整数单位表示:
3.3V → 3300mV
1.2A → 1200mA
0.5s → 500ms
5. 变量要初始化
推荐:
unsigned char brightness = 5;
不推荐初学阶段写:
unsigned char brightness;
因为后者初始值不明确,容易出问题。
6. 不要滥用数字
不推荐:
if(work_mode == 1)
推荐:
#define MODE_LOW_POWER 1
if(work_mode == MODE_LOW_POWER)
这样代码更容易维护。
7. 防止溢出
不推荐:
brightness--;
推荐:
if(brightness > 0)
{
brightness--;
}
8. 计算时注意中间结果
不推荐:
battery_mv = adc_value * 3300 / 4095;
在某些情况下,中间乘法可能溢出。
更推荐:
battery_mv = (unsigned long)adc_value * 3300 / 4095;
二十三、今天的小测验
问题1:亮度等级范围是0到20,用什么类型合适?
答案:
unsigned char
问题2:电池电压3300mV,用什么类型合适?
答案:
unsigned int
问题3:低电量标志只有0和1,用什么类型合适?
答案:
bit
问题4:下面代码有什么问题?
unsigned char brightness = 0;
brightness--;
答案:
brightness 会从 0 溢出变成 255。
问题5:下面代码为什么可能有问题?
unsigned int adc_value = 4095;
unsigned int battery_mv;
battery_mv = adc_value * 3300 / 4095;
答案:
adc_value * 3300 的中间结果可能超过 unsigned int 范围,导致溢出。
更安全写法:
battery_mv = (unsigned long)adc_value * 3300 / 4095;
二十四、今天的练习任务
任务1:定义变量
请根据下面含义定义变量:
当前亮度等级,初始值为 5
当前工作模式,初始为普通模式
低电量标志,初始为 0
电池电压,初始为 3000mV
按键值,初始为无按键
参考答案:
#define MODE_NORMAL 0
#define KEY_NONE 0
unsigned char brightness = 5;
unsigned char work_mode = MODE_NORMAL;
bit low_battery_flag = 0;
unsigned int battery_mv = 3000;
unsigned char key_value = KEY_NONE;
任务2:写亮度增加函数
要求:
亮度最大为 20
没有超过 20 时才允许增加
参考答案:
#define BRIGHTNESS_MAX 20
unsigned char brightness = 5;
void Brightness_Up(void)
{
if(brightness < BRIGHTNESS_MAX)
{
brightness++;
}
}
任务3:写亮度降低函数
要求:
亮度最小为 0
大于 0 时才允许降低
参考答案:
#define BRIGHTNESS_MIN 0
unsigned char brightness = 5;
void Brightness_Down(void)
{
if(brightness > BRIGHTNESS_MIN)
{
brightness--;
}
}
任务4:写模式切换函数
要求:
当前是普通模式时,切换到低功耗模式
当前是低功耗模式时,切换到普通模式
参考答案:
#define MODE_NORMAL 0
#define MODE_LOW_POWER 1
unsigned char work_mode = MODE_NORMAL;
void Mode_Switch(void)
{
if(work_mode == MODE_NORMAL)
{
work_mode = MODE_LOW_POWER;
}
else
{
work_mode = MODE_NORMAL;
}
}
任务5:写低电量判断函数
要求:
电池电压低于 2700mV 时,低电量标志置 1
否则低电量标志清 0
参考答案:
#define BATTERY_LOW_MV 2700
unsigned int battery_mv = 3000;
bit low_battery_flag = 0;
void Battery_Check(void)
{
if(battery_mv < BATTERY_LOW_MV)
{
low_battery_flag = 1;
}
else
{
low_battery_flag = 0;
}
}
二十五、第二天完整示例程序
下面把今天的变量、宏定义、函数整合到一个示例里。
这个程序暂时不做真实按键和 ADC,只是演示数据类型和变量管理。
#include "STC8G.H"
#define LED_ON_LEVEL 0
#define LED_OFF_LEVEL 1
#define MODE_NORMAL 0
#define MODE_LOW_POWER 1
#define BRIGHTNESS_MIN 0
#define BRIGHTNESS_MAX_NORMAL 20
#define BRIGHTNESS_MAX_LOW 10
#define BATTERY_LOW_MV 2700
sbit LED = P3^7;
unsigned char work_mode = MODE_NORMAL;
unsigned char brightness = 5;
unsigned char brightness_max = BRIGHTNESS_MAX_NORMAL;
unsigned int battery_mv = 3000;
unsigned int delay_time_ms = 500;
bit low_battery_flag = 0;
bit led_state = 0;
void Delay_ms(unsigned int ms)
{
unsigned int i;
unsigned int j;
for(i = 0; i < ms; i++)
{
for(j = 0; j < 1000; j++)
{
;
}
}
}
void LED_On(void)
{
LED = LED_ON_LEVEL;
led_state = 1;
}
void LED_Off(void)
{
LED = LED_OFF_LEVEL;
led_state = 0;
}
void Mode_Switch(void)
{
if(work_mode == MODE_NORMAL)
{
work_mode = MODE_LOW_POWER;
brightness_max = BRIGHTNESS_MAX_LOW;
if(brightness > brightness_max)
{
brightness = brightness_max;
}
}
else
{
work_mode = MODE_NORMAL;
brightness_max = BRIGHTNESS_MAX_NORMAL;
}
}
void Brightness_Up(void)
{
if(brightness < brightness_max)
{
brightness++;
}
}
void Brightness_Down(void)
{
if(brightness > BRIGHTNESS_MIN)
{
brightness--;
}
}
void Battery_Check(void)
{
if(battery_mv < BATTERY_LOW_MV)
{
low_battery_flag = 1;
}
else
{
low_battery_flag = 0;
}
}
void main(void)
{
while(1)
{
Battery_Check();
if(low_battery_flag == 1)
{
delay_time_ms = 100;
}
else
{
delay_time_ms = 500;
}
LED_On();
Delay_ms(delay_time_ms);
LED_Off();
Delay_ms(delay_time_ms);
}
}
这个程序的逻辑是:
如果电池电压低于 2700mV,LED 快闪;
如果电池电压正常,LED 慢闪。
现在代码中:
unsigned int battery_mv = 3000;
所以 LED 慢闪。
如果改成:
unsigned int battery_mv = 2600;
那么 LED 会快闪。
这就是变量参与程序控制的基本方式。
二十六、第二天总结
今天学习的是 C51 中最常用的数据类型和变量。
可以这样总结:
bit 用来表示开关状态;
unsigned char 用来表示小范围状态和等级;
unsigned int 用来表示时间、计数、电压、ADC;
unsigned long 用来处理较大的中间计算;
float 在 51 单片机里尽量少用。
今天最重要的嵌入式编程习惯是:
变量类型够用就好;
变量要有明确初始值;
小数尽量用整数单位表示;
亮度、模式、按键值要用宏定义增强可读性;
加减变量时要做边界保护;
涉及乘法计算时要注意中间结果是否溢出。
第二天达到下面程度就算合格:
知道 unsigned char、unsigned int、bit 的用途;
能为亮度、模式、按键、电压选择合适类型;
知道 unsigned char 溢出问题;
知道亮度增加和降低要做边界判断;
知道电池电压建议用 mV 整数表示;
能写简单的模式、亮度、电池判断变量逻辑。
后续可以继续学习 C51 的关键语法,例如 sfr、sbit、寄存器和位操作。这部分会直接关系到如何控制 P0、P1、P2、P3 等 IO 口。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)