单片机实践
把基础语法放进计数、按键、采样和温控场景中。
依据《单片机C语言基础学习_最新版》整理 · 12 个主题 · 每节约 6–12 分钟
标明“桌面示例”的完整程序可在 C 编译器运行。其他片段用于理解逻辑,硬件函数、端口与启动代码由目标工程提供,不可直接烧录。
- 变量与常量:计数器
- 运算符:条件与赋值
- if:温控输出完整赋值
- switch:按键选择模式
- for:清空显示缓冲区
- while:等待需要退出条件
- break / continue:筛选任务
- 数组:采样数据与查找表
- 函数:输入、输出与范围
- 宏定义:名称表达硬件含义
- 位运算:只修改目标位
- 指针:通过地址修改变量
温控边界实验
规则:温度 ≤ 20 ℃ 加热,温度 > 30 ℃ 散热,其余待机。先预测,再验证边界值。
25 > 30 → 假;25 <= 20 → 假 fan = 0; heater = 0;
01 · 变量与常量:计数器
计数、温度和按键状态都是变量。使用前赋初值,并根据范围选择类型。
unsigned char cnt = 0;
int sum = 0;
cnt = cnt + 1;
sum = sum + cnt;
执行后 cnt = 1,sum = 1。0~255 以 8 位 unsigned char 为前提;实际范围可查看 limits.h 中的 UCHAR_MAX。
动手验证:初始 cnt 改为 9,执行后两个变量分别是多少?
查看参考结果
cnt = 10,sum = 10。长期累计还应估算最大值,避免计数回绕。
02 · 运算符:条件与赋值
算术得到数值,比较与逻辑运算得到真假。先判断条件,再执行赋值。
int a = 5, b = 3;
int c = a + b;
if (a > b && b > 0) {
c = a - b;
}
c 先是 8,条件成立后变成 2。== 比较,= 赋值;& 不等于逻辑与 &&。
动手验证:把 b 改为 0,最终 c 是多少?
查看参考结果
c = 5。b > 0 为假,分支不执行。
03 · if:温控输出完整赋值
温度决定风扇与加热器状态。每次给两个输出赋值,避免上一次状态残留。
void control(int temp, unsigned char *fan,
unsigned char *heater)
{
*fan = 0;
*heater = 0;
if (temp > 30) {
*fan = 1;
} else if (temp <= 20) {
*heater = 1;
}
}
普通变量逻辑示例,不直接驱动 GPIO。调用时传入两个有效变量的地址。原文部分分支未重置另一输出,此处已修正。真实温控还需故障保护和回差。
动手验证:依次输入 15、35、25 ℃,每次 fan / heater 是多少?
查看参考结果
依次为 0/1、1/0、0/0。20 ℃ 加热;30 ℃ 不开启风扇。
04 · switch:按键选择模式
按键编号是离散值,适合 switch。每个分支用 break 防止意外继续执行。
switch (key) {
case 1: step = 1; break;
case 2: step = 2; break;
default: step = 0; break;
}
key、step 需事先声明。这里只处理已识别的按键编号;扫描、去抖和有效电平取决于硬件。
动手验证:key = 2 与 key = 9 时,分别得到什么模式?
查看参考结果
分别是 step = 2 与 step = 0。default 为未知输入提供明确结果。
05 · for:清空显示缓冲区
已知次数的重复操作用 for,把下标限制在数组范围内。
unsigned char display_buffer[10];
unsigned char i;
for (i = 0; i < 10; i++) {
display_buffer[i] = 0;
}
执行 10 次,最后访问下标 9。变量声明放在循环前,也便于阅读传统 C 编译环境的代码。
动手验证:如果条件误写为 i <= 10,会发生什么?
查看参考结果
会访问不存在的 display_buffer[10],发生越界。
06 · while:等待需要退出条件
while 适合次数不确定的重复,do-while 至少执行一次。无期限等待会阻塞后续任务。
unsigned int tries = 0;
while (flag == 0 && tries < 100) {
wait_ms(1);
tries++;
}
if (flag == 0) {
report_timeout();
}
flag、wait_ms 和 report_timeout 由具体工程提供,片段不能单独编译。100 次仅为重试上限,不保证精确 100 ms;中断共享变量还需考虑 volatile 和原子性。
动手验证:flag 初始为 1,wait_ms 执行几次?
查看参考结果
0 次。do-while 则先执行一次循环体再判断。
07 · break / continue:筛选任务
break 结束当前循环;continue 跳过当前一轮剩余代码,只影响所在那一层循环。
unsigned char i;
for (i = 0; i < 100; i++) {
if (i == 50) break;
if (i % 2 == 0) continue;
work(i);
}
work 是工程提供的函数。传入的是 1、3、5……49,遇到 50 循环结束。
动手验证:work 共调用几次?会处理 50 吗?
查看参考结果
共 25 次,不处理 50,因为调用前已经 break。
08 · 数组:采样数据与查找表
数组把同类型数据放在一起,读写前先确认下标范围。
unsigned char table[5] = {10, 20, 30, 40, 50};
unsigned char x;
table[0] = 99;
x = table[2];
table[0] 是第一个元素,table[2] 是第三个。修改第一个元素不影响第三个。
动手验证:这 5 个元素相加是多少?
查看参考结果
99 + 20 + 30 + 40 + 50 = 239。累计采样值时应选择容量足够的类型。
09 · 函数:输入、输出与范围
一个函数完成一件事。小类型相加也可能产生更大的结果,避免截断返回值。
#include <stdio.h>
unsigned int add(unsigned char a, unsigned char b)
{
return (unsigned int)a + (unsigned int)b;
}
int main(void)
{
printf("%u\n", add(200, 100));
return 0;
}
面向常见 8 位 unsigned char 环境的桌面示例,输出 300。返回类型改为 unsigned int;桌面程序用 int main(void),不照搬特定嵌入式环境的 void main。
动手验证:8 位 unsigned char 环境中,若仍用 unsigned char 返回,300 变成多少?
查看参考结果
按 256 取模得到 44。这是无符号转换,不应当作期望结果。
10 · 宏定义:名称表达硬件含义
用名字代替魔法数字。数值常量和硬件别名要分清,宏本身不会创建变量。
#define MAX_COUNT 100
#define LED_ON 0
#define LED_OFF 1
unsigned int cnt = 101;
if (cnt > MAX_COUNT) {
cnt = 0;
}
LED_ON = 0 只是假设低电平有效,须核对电路。原文 LED_PORT 对 P1 的别名仅适用于存在 P1 定义的目标环境。
动手验证:cnt = 100 会清零吗?
查看参考结果
不会,因为判断是 > 而不是 >=。定义宏时末尾不加分号。
11 · 位运算:只修改目标位
设置、清零、翻转用不同操作。把掩码写成二进制,观察目标位与其他位。
unsigned char reg = 0;
unsigned char hi;
reg |= 0x01;
reg &= (unsigned char)~0x02u;
reg ^= 0x04;
hi = reg >> 4;
结果 reg = 5(00000101),hi = 0。reg 是普通变量,不是真实寄存器;真实寄存器可能有写 1 清零、只读等语义,须查芯片手册。
动手验证:从 reg = 0xFF 开始执行三步,结果是多少?
查看参考结果
0xFF → 0xFF → 0xFD → 0xF9。高 4 位为 0x0F。
12 · 指针:通过地址修改变量
指针保存地址。先指向有效对象,再解引用读取或修改。
unsigned char v = 5;
unsigned char *p = &v;
unsigned char y = *p;
*p = 9;
y 保存当时读到的值 5,不是对 v 的持续引用。*p = 9 后,v 为 9,y 仍为 5。
动手验证:此时执行 y = *p,y 是多少?能解引用未初始化的指针吗?
查看参考结果
y 变为 9。不能解引用未初始化或无效指针,本例不需要猜测内存地址。