代码整理_2
055.7.2 代码整理(2)
/* ==================== main.c:业务流程与串口接收中断 ====================
* 整体目的:初始化 UART 后,每隔约 1 秒发送一行测试字符串;串口收到指定命令字节时,
* 在中断中控制 LED、蜂鸣器和继电器。
* 模块关系:本文件只调用 delay.h 与 uart.h 暴露的接口;延时、发送和初始化的具体实现
* 分别保留在 delay.c、uart.c 中,正是本节“代码整理”的结果。
* 硬件前提:LED、蜂鸣器和继电器均为低电平有效;因此写入 0 是使相应引脚灌电流、设备动作。
*/
#include "reg52.h" /* 引入 8051/52 特殊功能寄存器声明;本文件中的 RI、SBUF 等名称由此提供。 */
#include <intrins.h> /* 保留配套源码的 Keil C51 内在函数头文件;本文件当前没有直接调用其中的函数。 */
#include "delay.h" /* 引入 Delay_xms() 的函数声明,使 main.c 能按正确参数调用延时模块。 */
#include "main.h" /* 引入 uchar、公共引脚别名,以及 recv 的外部声明。 */
#include "uart.h" /* 引入 UART_Init()、UART_Send_Str() 等串口模块对外接口。 */
uchar recv; /* 真正定义并分配 1 字节存储空间;UART_Routine() 把最新接收的数据保存于此。 */
void main() /* C51 的用户入口;启动代码完成复位后的准备工作后会进入此函数。 */
{ /* 主函数体开始。 */
UART_Init(); /* 配置串口、定时器 1 和串口中断,使发送与异步接收都具备工作条件。 */
while (1) /* 条件恒为真;单片机程序需要持续运行,不能执行一次后退出。 */
{ /* 无限主循环的一轮开始。 */
UART_Send_Str("I am wfeng!\r\n"); /* 逐字节发送字符串;\r\n 使常见串口助手换到下一行行首。 */
Delay_xms(1000); /* 忙等约 1000 ms,避免字符串以人眼难以阅读的速度连续刷出。 */
} /* 本轮结束后立即回到 while (1) 顶部,重复发送。 */
} /* 语法边界;正常运行时不会离开无限循环。 */
/*
* 函数目的:响应 UART 串口中断,并把收到的单字节命令转换成对应的板载设备动作。
* 调用时机:串口硬件完整接收一个字节后置位 RI;因 UART_Init() 已打开 ES、EA,CPU 会跳到这里。
* 重要限制:本例只检查 RI,未处理发送完成标志 TI;因为主循环采用查询 TI 的方式完成发送。
*/
void UART_Routine(void) interrupt 4 /* Keil C51 的 interrupt 4 指定 8051 串口中断向量(地址 0023H)。 */
{ /* 串口中断服务程序体开始;应尽量短小,以免阻塞其他实时工作。 */
if (RI == 1) /* 仅当硬件确认“已收到一个完整字节”时才读取接收缓冲寄存器。 */
{ /* 接收事件的处理分支开始。 */
RI = 0; /* 软件清除接收完成标志,为接收下一个字节做好准备。 */
recv = SBUF; /* 从串口数据缓冲寄存器读出刚收到的 8 位数据,保存为命令码。 */
switch (recv) /* 按命令码选择一个确定的外设控制动作。 */
{ /* switch 的各命令分支开始。 */
case 0x01: /* 命令 0x01:请求点亮 LED1。 */
LED1 = 0; /* P1.0 输出低电平;LED1 低电平有效,因此点亮。 */
break; /* 结束本分支,防止继续执行后面的 LED2 分支。 */
case 0x02: /* 命令 0x02:请求点亮 LED2。 */
LED2 = 0; /* P1.1 输出低电平,点亮 LED2;其他 LED 状态保持原样。 */
break; /* 结束 0x02 的处理。 */
case 0x03: /* 命令 0x03:请求点亮 LED3。 */
LED3 = 0; /* P1.2 输出低电平,点亮 LED3。 */
break; /* 结束 0x03 的处理。 */
case 0x04: /* 命令 0x04:请求点亮 LED4。 */
LED4 = 0; /* P1.3 输出低电平,点亮 LED4。 */
break; /* 结束 0x04 的处理。 */
case 0x05: /* 命令 0x05:请求关闭四路 LED。 */
P1 |= 0x0F; /* 将 P1.0~P1.3 置 1 而不改 P1.4~P1.7;低四位 LED 因高电平而熄灭。 */
break; /* 结束 0x05 的处理。 */
case 0x06: /* 命令 0x06:请求打开蜂鸣器。 */
BEEP = 0; /* P1.6 输出低电平;蜂鸣器低电平有效,因此开始鸣叫。 */
break; /* 结束 0x06 的处理。 */
case 0x07: /* 命令 0x07:请求关闭蜂鸣器。 */
BEEP = 1; /* P1.6 输出高电平,撤销蜂鸣器的有效控制电平。 */
break; /* 结束 0x07 的处理。 */
case 0x08: /* 命令 0x08:请求吸合继电器 1。 */
JDQ1 = 0; /* P2.0 输出低电平;经驱动电路后使继电器线圈得电吸合。 */
break; /* 结束 0x08 的处理。 */
case 0x09: /* 命令 0x09:请求释放继电器 1。 */
JDQ1 = 1; /* P2.0 输出高电平;经驱动电路后使继电器线圈断电释放。 */
break; /* 结束 0x09 的处理。 */
default: /* 其他命令码没有在本实验定义功能。 */
break; /* 不改变任何外设状态,直接结束本次中断处理。 */
} /* switch 结束。 */
} /* RI 接收处理结束;若此次仅由 TI 触发,则不会进入此块。 */
} /* 中断服务程序结束;CPU 返回被中断的主循环位置。 */
/* ==================== main.h:跨模块共享的声明 ==================== */
#ifndef _MAIN_H_ /* 若本翻译单元尚未包含 main.h,则保留下方正文。 */
#define _MAIN_H_ /* 定义保护宏;同一翻译单元再次包含本文件时会跳过正文。 */
#include "reg52.h" /* 为 P1、P2 及 sbit 位定义提供寄存器声明。 */
typedef unsigned char uchar; /* 给 8 位无符号类型起短名;本工程用它表示一个字节。 */
typedef unsigned int uint; /* 给 16 位无符号类型起短名;可表示本例的毫秒延时参数。 */
extern uchar recv; /* 声明 recv 的实体在其他 .c 文件中;本工程的定义位于 main.c。 */
sbit LED1 = P1^0; /* 将 LED1 映射为可位寻址的 P1.0;^ 是 C51 位定位语法。 */
sbit LED2 = P1^1; /* 将 LED2 映射为 P1.1。 */
sbit LED3 = P1^2; /* 将 LED3 映射为 P1.2。 */
sbit LED4 = P1^3; /* 将 LED4 映射为 P1.3。 */
sbit BEEP = P1^6; /* 将蜂鸣器控制信号映射为 P1.6。 */
sbit JDQ1 = P2^0; /* 将继电器 1 的控制信号映射为 P2.0。 */
#endif /* 与 #ifndef _MAIN_H_ 配对,结束 main.h 的防重复包含区域。 */
/* ==================== Delay/delay.h:延时模块接口 ==================== */
#ifndef _DELAY_H_ /* 检查 delay.h 是否已经在当前翻译单元展开过。 */
#define _DELAY_H_ /* 定义延时模块的保护宏。 */
#include "main.h" /* 取得 uint 类型定义,确保下面的函数声明能被正确解析。 */
void Delay_xms(uint xms); /* 仅声明延时接口;具体函数体在 Delay/delay.c,单位是约毫秒。 */
#endif /* 结束 delay.h 的防重复包含区域。 */
/* ==================== Delay/delay.c:软件延时实现 ====================
* 函数目的:按 xms 指定的次数执行约 1 ms 忙等,为简单演示提供时间间隔。
* 时间前提:内部循环参数针对 11.0592 MHz、12T 内核及 Keil C51 生成代码设计;时钟或优化改变时需重新校准。
* 使用限制:忙等期间 CPU 不执行其他前台任务,精确计时或并发任务应使用定时器。
*/
#include "delay.h" /* 引入本模块自己的函数声明和所需的 uint、uchar 类型定义。 */
#include <intrins.h> /* 引入 _nop_() 的 Keil C51 内在函数声明。 */
void Delay_xms(uint xms) /* 定义延时函数;xms 是要重复执行“约 1 ms”循环的次数。 */
{ /* 延时函数体开始。 */
uchar data i, j; /* 定义两个 8 位计数器;data 是 C51 对片内直接寻址 RAM 的存储限定。 */
while (xms) /* 只要还有未完成的毫秒数,就继续执行一轮校准后的空转。 */
{ /* 一轮约 1 ms 延时开始。 */
_nop_(); /* 执行一条空操作指令,用于补齐本延时循环的指令周期。 */
i = 2; /* 初始化外层计数器;该常数是延时校准参数的一部分。 */
j = 199; /* 初始化内层计数器;它决定每次外层循环的空转次数。 */
do /* 使用后判断循环,使内层计数至少执行一次。 */
{ /* 外层 do 循环体开始。 */
while (--j); /* 每次先递减 j;非零继续空循环,到零才退出内层循环。 */
} while (--i); /* j 用尽后递减 i;i 非零时重入循环,8 位 j 在下一次 --j 时由 0 回绕为 255,形成下一轮空转。 */
xms--; /* 已完成约 1 ms,减少剩余延时计数。 */
} /* xms 为 0 时跳出,延时结束。 */
} /* 返回调用位置。 */
/* ==================== Uart/uart.h:串口模块接口 ==================== */
#ifndef _UART_H_ /* 检查 uart.h 是否已经在当前翻译单元展开过。 */
#define _UART_H_ /* 定义串口模块的保护宏。 */
#include "main.h" /* 取得 uchar 类型定义,供下方串口函数的参数使用。 */
void UART_Send_Byte(uchar send_byte); /* 声明“发送一个字节”的接口。 */
void UART_Send_Str(uchar *send_str); /* 声明“发送以 \0 结尾字符串”的接口;参数是首字符地址。 */
void UART_Init(void); /* 声明 UART、波特率定时器和相关中断的初始化接口。 */
#endif /* 结束 uart.h 的防重复包含区域。 */
/* ==================== Uart/uart.c:串口初始化与发送实现 ==================== */
#include "uart.h" /* 引入本模块的对外声明及其依赖的公共类型。 */
#include "main.h" /* 引入 SFR 相关的公共定义;按配套源码保留此直接包含。 */
/* 函数目的:将一个 8 位数据写入 UART 发送缓冲区,并等待硬件把它从 TXD 引脚完整发送出去。 */
void UART_Send_Byte(uchar send_byte) /* send_byte 是本次要发送的一个字节。 */
{ /* 单字节发送函数体开始。 */
SBUF = send_byte; /* 写入串口数据缓冲寄存器,硬件随后按 UART 格式从 TXD 移出该字节。 */
while (!TI); /* 查询发送完成标志 TI;分号表示等待期间故意不执行循环体。 */
TI = 0; /* 软件清除 TI,避免下一次发送误把旧的完成状态当作新完成。 */
} /* 单字节发送结束。 */
/* 函数目的:从字符串首地址开始,逐个发送字符,直到遇到 C 字符串结束符 \0。 */
void UART_Send_Str(uchar *send_str) /* 指针参数指向待发送字符串的第一个字符。 */
{ /* 字符串发送函数体开始。 */
while (*send_str != '\0') /* 解引用读取当前字符;不是结束符时才继续发送。 */
{ /* 当前字符的处理体开始。 */
UART_Send_Byte(*send_str++); /* 取当前字符发送,再把指针后移到下一个字符;后缀 ++ 在取值后生效。 */
} /* 遇到 \0 后退出;结束符本身不发送。 */
} /* 字符串发送结束。 */
/*
* 函数目的:配置 8051 UART 的方式 1,并以定时器 1 的自动重装溢出率生成 9600 bit/s 波特率。
* 参数前提:11.0592 MHz 晶振、传统 12T 8051 时序、SMOD=0;此时 TH1=TL1=0xFD 对应 9600 bit/s。
* 中断关系:ES 只允许串口中断源,EA 是总开关;二者同时为 1 后 RI 可触发 UART_Routine()。
*/
void UART_Init(void) /* 初始化不接收参数,也不向调用者返回数值。 */
{ /* UART 初始化函数体开始。 */
SCON = 0x50; /* 设置串口方式 1(8 位 UART、可变波特率),并置 REN=1 允许接收。 */
PCON &= 0x7F; /* 清除 SMOD 位,使波特率不倍增;保留 PCON 的其余位状态。 */
TMOD &= 0x0F; /* 清除定时器 1 的高四位配置,保留定时器 0 的低四位配置。 */
TMOD |= 0x20; /* 将定时器 1 设为方式 2:8 位自动重装,作为 UART 波特率发生器。 */
TH1 = 0xFD; /* 写入定时器 1 重装值;溢出后硬件从此值重新开始计数。 */
TL1 = 0xFD; /* 初始化当前计数值,避免首次溢出的时间与后续周期不同。 */
TR1 = 1; /* 置位定时器 1 运行控制位,使其开始产生 UART 所需的溢出节拍。 */
ES = 1; /* 打开串口中断源;接收完成时允许提出中断请求。 */
EA = 1; /* 打开 CPU 总中断开关;未置位时任何已使能外设中断都不会响应。 */
recv = 0; /* 清空共享接收变量,使上电后的初始命令状态明确。 */
} /* 初始化完成,返回 main(),随后主循环可安全调用发送函数。 */
一、代码整理
1.1 精简代码
延时函数已经放入 Delay/delay.c,公共类型、引脚名称和跨文件变量声明集中放入 main.h。这里所谓“精简”,不是删除功能,而是把不同职责交给不同模块,使 main.c只保留程序入口、主循环和本讲仍放在主模块中的串口接收中断。
整理后的 main.c主体非常直接:
void main()
{
UART_Init();
while (1)
{
UART_Send_Str("I am wfeng!\r\n");
Delay_xms(1000);
}
}
从主函数可以立即看出程序行为:先初始化UART,再每隔约1秒发送一次字符串。延时算法和串口寄存器配置都被隐藏在对应模块内部。
视频中把 sbit引脚定义移入 main.h后,编译器提示 P1未定义。原因不是LED引脚接线发生变化,而是C编译器处理 main.h时,必须先知道符号 P1代表什么。
reg52.h、P1与LED别名的依赖关系如下:
reg52.h
│
├── 声明P0、P1、P2、P3等51单片机特殊功能寄存器
│
▼
main.h
├── sbit LED1 = P1^0;
├── sbit LED2 = P1^1;
├── sbit LED3 = P1^2;
└── sbit LED4 = P1^3;
因此 main.h需要先包含器件寄存器头文件:
#include "reg52.h"
在Keil C51语法中:
sbit LED1 = P1^0;
表示把位变量 LED1映射到P1口的第0位。此处的 P1^0位于Keil的 sbit声明语法中,表示SFR的位地址,并不是普通C表达式中的按位异或计算。
P1特殊功能寄存器
位序号: 7 6 5 4 3 2 1 0
┌────┬────┬────┬────┬────┬────┬────┬────┐
P1: │P1.7│P1.6│P1.5│P1.4│P1.3│P1.2│P1.1│P1.0│
└────┴────┴────┴────┴────┴────┴────┴────┘
│ │ │ │
LED4 LED3 LED2 LED1
一个头文件应尽量做到“自包含”:它公开的声明用到了什么类型或寄存器,就应包含能够提供这些名称的头文件,不能依赖调用者碰巧先包含了某个文件。否则改变 #include顺序就可能出现难以理解的未定义错误。
main.h中使用的名称 | 来源 |
|---|---|
P1、P2 | reg52.h |
uchar、uint | main.h自己的 typedef |
recv | 在 main.c中定义,在 main.h中用 extern声明 |
LED1~LED4、BEEP、JDQ1 | main.h中的Keil sbit声明 |
1.2 将代码放到不同的文件
串口相关代码可封装为独立的Uart模块:
Uart/
├── uart.h 对外公开“可以调用什么”
└── uart.c 保存“具体怎样实现”
uart.h公开三个接口:
void UART_Send_Byte(uchar send_byte);
void UART_Send_Str(uchar *send_str);
void UART_Init(void);
uart.c保存三个函数的完整实现。其他文件只要包含 uart.h,就能按照统一的函数签名调用这些接口。
flowchart LR
M[main.c] -->|包含并调用| H[uart.h 公共接口]
C[uart.c] -->|包含并实现| H
M -->|调用UART_Init| O1[uart.obj中的UART_Init]
M -->|调用UART_Send_Str| O2[uart.obj中的UART_Send_Str]
O2 --> O3[UART_Send_Byte]
O3 --> S[SBUF与TXD硬件]
完整操作顺序如下:
- 在工程目录建立
Uart文件夹。 - 使用Keil或纯文本编辑器创建
uart.c和uart.h,确认不存在空格或隐藏的.txt后缀。 - 把
uart.c加入Keil的Source Group,使其参与编译。 - 在
Options for Target → C51 → Include Paths中加入./Uart。 - 把串口发送函数放入
uart.c,把相应声明放入uart.h。 uart.c和调用串口接口的main.c都包含uart.h。- 把串口寄存器配置封装为
UART_Init(),并在main()进入循环前调用。
.c文件必须加入工程构建;.h不单独编译,只需能被 #include找到。把头文件显示在工程树中可以方便浏览,但不是链接函数实现的必要条件。
声明、定义和调用的写法
/* uart.h:函数声明,分号结尾 */
void UART_Init(void);
/* uart.c:函数定义,带函数体 */
void UART_Init(void)
{
/* 串口初始化代码 */
}
/* main.c:函数调用,只写实参列表和分号 */
UART_Init();
声明与定义中的 void表示该函数不接收参数;调用时括号里没有实参,不能写成 UART_Init(void);。后者在函数体内会被当作声明形式,而不是正常的函数调用语句。
UART_Init()封装了什么
flowchart TD
I[调用UART_Init] --> A[SCON=0x50<br/>方式1并允许接收]
A --> B[PCON.SMOD=0<br/>波特率不加倍]
B --> C[定时器1方式2<br/>TH1=TL1=0xFD]
C --> D[TR1=1<br/>启动定时器1]
D --> E[ES=1、EA=1<br/>允许串口中断]
E --> F[UART可收发数据]
在11.0592 MHz、传统12T、SMOD=0的条件下,0xFD产生9600 bit/s。模块化只把这些语句移入函数,并未改变寄存器配置。
配套代码最后才执行 recv = 0;,但此前已经执行 EA = 1。更稳妥的初始化顺序是先给共享状态设置初值,最后再开放中断,避免在极短的时间窗口内发生接收中断后,刚收到的数据又被初始化语句覆盖:
recv = 0;
ES = 1;
EA = 1;
Keil中的 Go to Definition可以根据工程符号信息跳到函数定义。若无法跳转,应先确认:
- 函数声明与定义的名称、大小写和参数完全一致;
- 对应
.c已经加入工程; - 工程已经成功编译并建立浏览信息;
- 头文件搜索路径正确。
1.3 外部声明
每个 .c独立编译。main.c中的全局变量 recv不会因为另一个文件也属于同一工程,就自动在 uart.c中变成已知名称。跨文件共享普通全局变量需要“一处定义,多处 extern声明”。
/* main.c:定义,分配唯一的存储空间 */
uchar recv;
/* main.h:外部声明,不再分配一份存储空间 */
extern uchar recv;
/* uart.c:包含声明后即可引用同一变量 */
#include "main.h"
void UART_Init(void)
{
recv = 0;
}
编译和链接过程可以这样理解:
flowchart LR
A[main.c<br/>uchar recv] --> A1[main.obj<br/>提供符号recv及存储]
B[main.h<br/>extern uchar recv] --> C[uart.c使用recv]
C --> C1[uart.obj<br/>产生对recv的外部引用]
A1 --> L[链接器]
C1 --> L
L --> R[把uart.obj中的recv引用<br/>连接到main.obj的唯一存储]
定义与声明的区别
| 写法 | 性质 | 是否分配存储 | 应放在哪里 |
|---|---|---|---|
uchar recv; | 定义 | 是 | 只放在一个 .c中 |
uchar recv = 0; | 带初值的定义 | 是 | 只放在一个 .c中 |
extern uchar recv; | 外部声明 | 否 | 可放在公共 .h中 |
static uchar recv; | 文件内部定义 | 是 | 仅当前 .c可见,不能作为同名外部变量共享 |
extern不是“把变量变成全局变量”的魔法。它告诉编译器:该名称和类型已经声明,真正定义由链接器在其他目标文件中寻找。要成功连接,必须满足:
- 某一个
.c中确实存在且只存在一个外部定义; - 声明与定义的类型完全一致;
- 定义不能带
static内部链接; - 提供定义的
.c必须参与工程构建。
为什么 extern声明不应写初始化值
推荐写法是:
/* main.h */
extern uchar recv;
/* main.c */
uchar recv = 0;
不要在公共头文件中写:
extern uchar recv = 0;
在标准C的文件作用域中,带初始化器的 extern会成为定义,而不再是单纯的外部声明;某些C51编译器还可能直接拒绝这种形式。若头文件被多个 .c包含,就可能生成多份定义并导致编译或链接错误。因此,初始化值应写在唯一的 .c定义处。
类型必须先被声明
编译器从上到下处理声明。下面的顺序正确:
typedef unsigned char uchar;
extern uchar recv;
如果 extern uchar recv;出现在 uchar类型别名之前,编译器尚不知道 uchar是什么,会在类型附近报错。
与中断共享时的 volatile
recv由串口中断函数写入。如果主循环或其他普通函数也要持续读取它,建议声明成:
/* main.c */
volatile uchar recv;
/* main.h */
extern volatile uchar recv;
volatile告诉编译器,该变量可能被中断等当前执行流之外的因素改变,每次使用时都应按规则重新访问存储位置。声明和定义必须同时保留相同的 volatile限定。
volatile只解决编译器优化可见性,不自动保证复杂操作的原子性。若主程序与中断共同读写多字节变量或执行“读—改—写”操作,还要考虑临界区或短暂屏蔽相关中断。
1.4 任务信息资源导图组件库总结
头文件保护的工作过程
以 main.h为例:
#ifndef _MAIN_H_
#define _MAIN_H_
/* main.h的有效内容 */
#endif
#ifndef的意思是“如果这个宏尚未定义”。第一次包含时,保护宏还不存在,正文被保留,并立即执行 #define;同一翻译单元再次包含时,条件为假,正文被跳过。
main.c的一次预处理
第1次遇到main.h
_MAIN_H_未定义 → 定义宏 → 展开正文
第2次遇到main.h
_MAIN_H_已定义 → 跳过正文
第3次遇到main.h
_MAIN_H_已定义 → 跳过正文
配套代码存在多条间接包含路径:
flowchart TD
M[main.c] --> D[delay.h]
M --> U[uart.h]
M --> H[main.h]
D --> H
U --> H
没有头文件保护时,main.h可能在编译 main.c时展开三次;有保护后,只有第一次保留正文。
头文件保护有两个重要边界:
- 它防止的是同一翻译单元中的重复展开。
main.c和uart.c分别编译时,各自都会正常处理一次main.h。 - 它不能修复头文件中不应出现的普通全局变量定义。共享变量仍应采用
.c中唯一一次定义、.h中extern声明的方式。
保护宏必须唯一。配套代码使用 _MAIN_H_、_DELAY_H_、_UART_H_;编写自己的头文件时,建议避免以下划线加大写字母开头,采用更不易冲突的名称:
#ifndef VIDEO_LEARN_UART_H
#define VIDEO_LEARN_UART_H
/* 声明 */
#endif
双引号与尖括号
#include "uart.h"
#include <intrins.h>
两者的核心区别是编译器采用的搜索路径和顺序,不是文件内容本身发生变化。通常约定:
"文件名"用于工程自己的头文件,优先从当前工程或源文件附近寻找,再查配置的包含目录。<文件名>用于编译器、标准库或芯片工具链提供的头文件,主要从系统和配置的包含目录寻找。
具体搜索顺序由编译器实现决定。Keil C51中,双引号形式会先搜索当前目录和源文件目录,找不到后再按尖括号形式继续搜索;Include Paths决定额外的头文件目录。
reg52.h通常也是Keil提供的器件头文件,因此写成 <reg52.h>更能表达它来自工具链;配套工程使用 "reg52.h"仍可由Keil的搜索规则找到对应文件。intrins.h提供 _nop_()等Keil内在函数声明。
整理后的关键规则
| 规则 | 正确做法 |
|---|---|
| 功能拆分 | 一个模块使用对应的 .c/.h组织实现与接口 |
| 函数接口 | .h声明,.c定义,调用者包含 .h |
| 源文件构建 | 每个实现函数所在的 .c必须加入工程 |
| 头文件路径 | 把头文件所在目录加入C51的Include Paths |
| 公共变量 | 一个 .c定义,公共 .h中写 extern声明 |
| 中断共享变量 | 根据访问方式考虑 volatile与原子性 |
| 重复包含 | 每个头文件使用唯一保护宏 |
| 依赖顺序 | 头文件先获得它所使用的类型、寄存器和宏定义 |
| 排查错误 | 从第一条有效错误开始,重新编译后再看后续问题 |
经过整理,main.c只负责组织流程,Delay模块负责延时,Uart模块负责串口初始化和发送,公共头文件负责接口与共享声明。模块之间通过明确的声明连接,而不是靠复制代码或偶然的包含顺序工作。