# 第十一节:一个在单片机上练习 C 语言的模板程序
## 【11.1 一套完整的模板源代码。】
先给大家附上一套完整的模板源代码,后面章节练习 C 语言的模板程序就直接复制此完整的源代码,此源代码适合的单片机型号是 STC89C52RC,晶振是 11.0592MHz,串口波特率是 9600,初学者只需修改代码里从 “C 语言学习区域的开始” 到 “C 语言学习区域的结束” 的区域,其它部分不要更改。可复制的源代码请到网上论坛原贴处直接下载本教程的文件压缩包,解压文件压缩包后,直接用 WPS 办公软件打开 “可编辑的 WPS 文档教程” 这个文档,就可以复制里面相关章节的源代码。在网上搜索 “从单片机基础到程序框架” 就可以找到论坛原贴的出处,也可以直接到我的个人网站那里下载(www.dumenmen.com)。一套完整的模板源代码如下:
```c
#include "REG52.H"
void View (unsigned long u32ViewData);
void to_BufferData (unsigned long u32Data, unsigned char *pu8Buffer,
unsigned char u8Type);
void SendString (unsigned char *pu8String);
/*---C 语言学习区域的开始。-----------------------------------------------*/
void main () // 主函数
{
unsigned char a; // 定义一个变量 a。
unsigned int b; // 定义一个变量 b。
unsigned long c; // 定义一个变量 c。
a = 100; // 给变量 a 赋值。
b = 10000; // 给变量 b 赋值。
c = 1000000000; // 给变量 c 赋值。
View (a); // 把第 1 个数 a 发送到电脑端的串口助手软件上观察。
View (b); // 把第 2 个数 b 发送到电脑端的串口助手软件上观察。
View (c); // 把第 3 个数 c 发送到电脑端的串口助手软件上观察。
while (1) {
}
}
/*---C 语言学习区域的结束。-----------------------------------------------*/
void View (unsigned long u32ViewData) {
static unsigned char Su8ViewBuffer [43];
code unsigned char Cu8_0D_0A [] = {0x0d, 0x0a, 0x00};
code unsigned char Cu8Start [] = {"开始..."};
static unsigned char Su8FirstFlag = 0;
static unsigned int Su16FirstDelay;
if (0 == Su8FirstFlag) {
Su8FirstFlag = 1;
for (Su16FirstDelay = 0; Su16FirstDelay < 10000; Su16FirstDelay++)
;
SendString (Cu8Start);
SendString (Cu8_0D_0A);
SendString (Cu8_0D_0A);
}
to_BufferData (u32ViewData, Su8ViewBuffer, 1);
SendString (Su8ViewBuffer);
to_BufferData (u32ViewData, Su8ViewBuffer, 2);
SendString (Su8ViewBuffer);
to_BufferData (u32ViewData, Su8ViewBuffer, 3);
SendString (Su8ViewBuffer);
to_BufferData (u32ViewData, Su8ViewBuffer, 4);
SendString (Su8ViewBuffer);
SendString (Cu8_0D_0A);
}
void to_BufferData (unsigned long u32Data, unsigned char *pu8Buffer, unsigned char u8Type) {
code unsigned char Cu8Array1 [] = {0xB5, 0xDA, 0x4E, 0xB8,
0xF6, 0xCA, 0xFD, 0x00};
code unsigned char Cu8Array2 [] = "十进制:";
code unsigned char Cu8Array3 [] = "十六进制:";
code unsigned char Cu8Array4 [] = "二进制:";
static unsigned char Su8SerialNumber = 1;
static unsigned int Su16BufferCnt;
static unsigned int Su16TempCnt;
static unsigned int Su16TempSet;
static unsigned long Su32Temp1;
static unsigned long Su32Temp2;
static unsigned long Su32Temp3;
static unsigned char Su8ViewFlag;
if (1 == u8Type) {
for (Su16BufferCnt = 0; Su16BufferCnt < 7; Su16BufferCnt++) {
pu8Buffer [Su16BufferCnt] = Cu8Array1 [Su16BufferCnt];
}
pu8Buffer [2] = Su8SerialNumber + '0';
pu8Buffer [Su16BufferCnt] = 0x0d;
pu8Buffer [Su16BufferCnt + 1] = 0x0a;
pu8Buffer [Su16BufferCnt + 2] = 0;
Su8SerialNumber++;
return;
} else if (2 == u8Type) {
for (Su16BufferCnt = 0; Su16BufferCnt < 7; Su16BufferCnt++) {
pu8Buffer [Su16BufferCnt] = Cu8Array2 [Su16BufferCnt];
}
Su32Temp1 = 1000000000;
Su32Temp2 = 10;
Su16TempSet = 10;
} else if (3 == u8Type) {
for (Su16BufferCnt = 0; Su16BufferCnt < 9; Su16BufferCnt++) {
pu8Buffer [Su16BufferCnt] = Cu8Array3 [Su16BufferCnt];
}
Su32Temp1 = 0x10000000;
Su32Temp2 = 0x00000010;
Su16TempSet = 8;
} else {
for (Su16BufferCnt = 0; Su16BufferCnt < 7; Su16BufferCnt++) {
pu8Buffer [Su16BufferCnt] = Cu8Array4 [Su16BufferCnt];
}
Su32Temp1 = 0x80000000;
Su32Temp2 = 0x00000002;
Su16TempSet = 32;
}
Su8ViewFlag = 0;
for (Su16TempCnt = 0; Su16TempCnt < Su16TempSet; Su16TempCnt++) {
Su32Temp3 = u32Data / Su32Temp1 % Su32Temp2;
if (Su32Temp3 < 10) {
pu8Buffer [Su16BufferCnt] = Su32Temp3 + '0';
} else {
pu8Buffer [Su16BufferCnt] = Su32Temp3 - 10 + 'A';
}
if (0 == u32Data) {
Su16BufferCnt++;
break;
} else if (0 == Su8ViewFlag) {
if ('0' != pu8Buffer [Su16BufferCnt]) {
Su8ViewFlag = 1;
Su16BufferCnt++;
}
} else {
Su16BufferCnt++;
}
Su32Temp1 = Su32Temp1 / Su32Temp2;
}
pu8Buffer [Su16BufferCnt] = 0x0d;
pu8Buffer [Su16BufferCnt + 1] = 0x0a;
pu8Buffer [Su16BufferCnt + 2] = 0;
}
void SendString (unsigned char *pu8String) {
static unsigned int Su16SendCnt;
static unsigned int Su16Delay;
SCON = 0x50;
TMOD = 0X21;
TH1 = TL1 = 256 - (11059200 / 12 / 32 / 9600);
TR1 = 1;
ES = 0;
TI = 0;
for (Su16SendCnt = 0; Su16SendCnt < 43; Su16SendCnt++) {
if (0 == pu8String [Su16SendCnt]) {
break;
} else {
SBUF = pu8String [Su16SendCnt];
for (Su16Delay = 0; Su16Delay < 800; Su16Delay++)
;
TI = 0;
}
}
}
```
【11.2 模板程序的使用说明。】
![](https://img.kancloud.cn/a0/9d/a09df4b7c62f66eefe6c05b535fda28d_877x559.png)
图 11.2.1 带串口的单片机最小系统
大多数初学者在学习 C 语言的时候,往往是在电脑端安装上 VC 平台软件来练习 C 语言,这种方法只要在代码里调用 printf 语句,编译后就可以看到被 printf 语句调用的变量,挺方便的。本教程没有用这种方法,既然本教程的 C 语言主要针对单片机,所以我想出了另外一种方法,这种方法就是直接在单片机上练习 C 语言,这样会让初学者体验更深刻。这种方法对硬件平台要求不高,只要 51 学习板上有一个 9 针的串口就可以,这个串口既可以用来烧录程序,也可以用来观察代码里的某个变量,只要在代码里调用 View 函数就可以达到类似 VC 平台软件下 printf 语句的效果,View 函数可以向串口输出某个变量的十进制,十六进制和二进制,大家只要在电脑端的串口助手软件就可以看到某个变量的这些信息,View 函数能查看的变量最大数值范围是 4 个字节的 unsigned long 变量,十进制的范围是从 0 到 4294967295,也可以查看 unsigned int 和 unsigned char 的类型变量(数据的进制以及 long,int,char 等知识点大家目前还没接触到,因此不懂也没关系,当前只要有个大概的认识就可以,暂时不用深入理解,后面章节还会详细介绍)。View 函数是我整个模板程序的其中一部分,所以要用这种方法就必须先复制我整个模板程序,初学者练习代码的活动范围仅仅局限于模板程序里的 “C 语言学习区域”,在此区域里有一个 main 主函数,main 主函数内有一个初始化区域,初学者往往在这个初始化区域里练习 C 语言就够了,初学者最大的活动范围不能超过从 “C 语言学习区域的开始” 到 “C 语言学习区域的结束” 这个范围,
这个范围之外其它部分的代码主要用来实现数据处理和串口发送的功能,大家暂时不用读懂它,直接复制过来就可以了。比如:
```c
/*---C 语言学习区域的开始。-----------------------------------------------*/
void main () // 主函数
{
//... 初始化区域,也就是主要用来给初学者学习 C 语言的区域。
while (1) {
}
}
/*---C 语言学习区域的结束。-----------------------------------------------*/
```
上述例子中,初学者练习代码只能在从 “C 语言学习区域的开始” 到 “C 语言学习区域的结束” 这个范围,此范围外的代码直接复制过来不要更改。我们再来分析分析下面节选的 main 函数源代码:
```c
/*---C 语言学习区域的开始。-----------------------------------------------*/
void main () // 主函数
{
unsigned char a; // 定义一个变量 a。
unsigned int b; // 定义一个变量 b。
unsigned long c; // 定义一个变量 c。
a = 100; // 给变量 a 赋值。
b = 10000; // 给变量 b 赋值。
c = 1000000000; // 给变量 c 赋值。
View (a); // 在电脑串口端查看第 1 个数 a。
View (b); // 在电脑串口端查看第 2 个数 b。
View (c); // 在电脑串口端查看第 3 个数 c。
while (1) {
}
}
/*---C 语言学习区域的结束。-----------------------------------------------*/
```
上述节选的 main 函数代码里,比如 “a=100; // 给变量 a 赋值。” 这行代码,所谓的 “赋值” 就是 “=” 这个语句,它表面上像我们平时用的等于号,实际上不是等于号,而是代表 “给” 的意思,把 “=” 符号右边的数复制一份给左边的变量,比如 “a=100;” 就是代表把 100 这个数值复制一份给变量 a,执行这条指令后,a 就等于 100 了。这里的分号 “;” 代表一条程序指令的结束。 而双斜线 “//” 是注释语句,双斜线 “//” 这行后面的文字或字符都是用来注释用的,编译器会忽略双斜线 “//” 这一行后面的文字或字符,编译器不把注释文字或字符列入源代码,也就是 “//” 这一行中后面的文字或字符是不占单片机内存的。当然 “//” 仅仅局限于当前一行代码。上面除了 “//” 是注释语句外,上面的 “/*” 和 “*/” 之间也是注释语句,跟”//” 的作用一样,只不过 “/*” 是注释开始,“*/” 是注释结束,它们的范围不局限于一行,而是从 “/*” 到 “*/” 的范围,因此可以用于注释连着的多行文字或者字符。
接着在分析上述代码中最重要的函数,也是本节最核心最重要的函数 View (某个变量)。比如 “ View (a); ” 这行代码,View (a) 就是要把变量 a 的十进制,十六进制和二进制的数值都发送到串口,我们通过 USB 转串口线让学习板连接上电脑,在电脑串口助手软件上就能看到被 View 函数调用的变量 a 的信息。
【11.3 如何在电脑上使用串口助手软件查看被 View 函数调用的变量?】
前面章节在讲烧录程序时提到一个叫 “stc-isp-15xx-v6.85I” 的上位机软件,这个软件除了用来烧录程序,还集成了串口助手软件的功能。所以本节直接共用烧录程序时的 USB 转串口线和 “stc-isp-15xx-v6.85I” 软件就可以了,无需额外再购买新的 USB 转串口线和下载其它串口助手软件,但是如何设置这个 “stc-isp-15xx-v6.85I” 上位机软件,还是有一些需要特别注意的地方的,现在把这个详细的步骤介绍给大家。
第一步:设置烧录软件的选项。
按前面章节介绍烧录程序时所需的步骤,用 USB 转串口线连接 51 学习板和电脑,记录 COM 号,打开 “stc-isp-15xx-v6.85I” 软件,选择单片机型号,选择对应的串口号(COM 号),设置最低波特率和最高波特率,这部分的内容跟烧录程序时的配置步骤是一样的,唯一必须要特别注意的是最高波特率必须选择 9600!最低波特率建议选择 2400。否则在烧录完程序后,当上位机集成软件自动切换到串口助手软件窗口时,接收区域显示的一些汉字信息可能会出现乱码。
\---------------------------------- 步骤之间的分割线 ----------------------------------------
![](https://img.kancloud.cn/f9/21/f921b839dd01a82e8c67cbe99ac5f73a_683x446.jpeg)
图 11.3.2 设置上位机的串口助手选项
第二步:设置串口助手软件的选项。
先点击右上方选中 “串口助手” 选项切换到串口助手的窗口,接收缓冲区选择 “文本模式”,串口选择匹配的 COM 号(跟烧录软件一致的 COM 号),波特率必须选择 9600,勾选上 “编程完成后自动打开串口” 选项,最后点击 “打开串口” 按钮使之切换到显示 “关闭串口” 的文字状态,至此串口助手软件的设置完毕。接下来就是按烧录程序的流程,打开新的 HEX 程序文件,程序烧录完成后上位机软件会自动切换到串口助手的串口,就可以观察到 View 函数从单片机上发送过来的某个变量的十进制,十六进制,二进制的信息了。接收缓冲区的窗口比较小,如果收到的信息比较多,只要在上下方向拖动窗口右边的滑块就可以依次看到全部的信息。如果想让单片机重新发送数据,只要让 51 学习板断电重启就可以重发一次数据,当串口助手的接收区接收的信息太多影响观察时,大家可以点击 “清空接收区” 的按钮来清屏,然后断电重启让它再重发一次数据。在电脑的串口助手软件里观察到的数据格式大概是什么样子的呢?比如编译完本章节上述完整的模板源代码程序后,会在串口助手软件里看到 a,b,c 三个变量的信息如下:
开始...
第 1 个数
十进制:100
十六进制:64
二进制:1100100
第 2 个数
十进制:10000
十六进制:2710
二进制:10011100010000
第 3 个数
十进制:1000000000
十六进制:3B9ACA00
二进制:111011100110101100101000000000
多说一句,烧录程序后,当软件自动切换到串口助手软件选项的窗口时,串口助手窗口显示单片机返回的信息,这时有可能第一行的文字 “开始...” 会丢失或者显示不出来,但是后面其它的关键信息不受影响,我猜测可能是串口助手软件本身的某个环节存在的小 bug,跟我们没关系,我们不用深究原因,因为不会影响我们的使用,此时也有一种解决办法,就是只要让单片机断电重启重发一次数据就可以正确地看到第一行的文字 “开始...”。
【11.4 如何利用现有的工程编辑编译新的源代码?】
本教程后面有很多章节的源代码,是不是每个章节都要重新建一个工程?其实不用。我们只要用一个工程就可以编译编辑本教程所有章节的源代码。方法很简单,就是打开一个现有的工程,用快捷组合键 “Ctrl+A” 把原工程里面的 C 源代码全部选中,再按 “Backspace” 清空原来的代码,然后再复制本教程相关章节的代码粘贴到工程的 C 文档里,重新编译一次就可以得到对应的 Hex 格式的烧录文件。用这种方法的时候,建议大家做好每个程序代码的备份。每完成一个项目的小进度,都要及时把源代码存储到电脑硬盘里,电脑硬盘里每个项目对应一个项目文件夹,每个项目文件夹里包含很多不同版本编号的源代码文件,每个源代码文件名都有流水编号,方便识别最新版本的程序,每天下班前都要把最新版本的源代码文件上传到自己的网盘里备份,在互联网时代,把源代码存到自己的网盘,可以随时异地存取,即使遇到电脑故障损坏也不担心数据永久丢失。
【11.5 编辑源代码的 5 个常用快捷键。】
介绍一下常用的快捷键,好好利用这 5 个快捷键,会让你在编辑源代码时效率明显提高。
(1)选中整篇所有的内容:组合键 Ctrl+A。
(2)把选中的内容复制到临时剪贴板:组合键 Ctrl+C。
(3)把临时剪贴板的内容粘贴到光标开始处:组合键 Ctrl+V。
(4)把选中的一行或者几行内容整体往右边移动:单键 Tab。每按一次就移动几个空格,很实用。
(5)把选中的一行或者几行内容整体往左边移动:组合键 Shift+Tab。每按一次就移动几个空格,很实用。
- 首页
- 第一节:我的价值观
- 第二节:初学者的疑惑
- 第三节:单片机最重要的一个特性
- 第四节:平台软件和编译器软件的简介
- 第五节:用Keil2软件关闭,新建,打开一个工程的操作流程
- 第六节:把.c源代码编译成.hex机器码的操作流程
- 第七节:本节预留
- 第八节:把.hex机器码程序烧录到单片机的操作流程
- 第九节:本节预留
- 第十节:程序从哪里开始,要到哪里去?
- 第十一节:一个在单片机上练习C语言的模板程序
- 第十二节:变量的定义和赋值
- 【TODO】第十三节:赋值语句的覆盖性
- 【TODO】第十四节:二进制与字节单位,以及常用三种变量的取值范围
- 【TODO】第十五节:二进制与十六进制
- 【TODO】第十六节:十进制与十六进制
- 【TODO】第十七节:加法运算的5种常用组合
- 【TODO】第十八节:连加、自加、自加简写、自加1
- 【TODO】第十九节:加法运算的溢出
- 【TODO】第二十节:隐藏中间变量为何物?
- 【TODO】第二十一节:减法运算的5种常用组合。
- 【TODO】第二十二节:连减、自减、自减简写、自减1
- 【TODO】第二十三节:减法溢出与假想借位
- 【TODO】第二十四节:借用unsigned long类型的中间变量可以减少溢出现象
- 【TODO】第二十五节:乘法运算中的5种常用组合
- 【TODO】第二十六节:连乘、自乘、自乘简写,溢出
- 【TODO】第二十七节:整除求商
- 【TODO】第二十八节:整除求余
- 【TODO】第二十九节:“先余后商”和“先商后余”提取数据某位,哪家强?
- 【TODO】第三十节:逻辑运算符的“与”运算
- 【TODO】第三十一节:逻辑运算符的“或”运算
- 【TODO】第三十二节:逻辑运算符的“异或”运算
- 【TODO】第三十三节:逻辑运算符的“按位取反”和“非”运算
- 【TODO】第三十四节:移位运算的左移
- 【TODO】第三十五节:移位运算的右移
- 【TODO】第三十六节:括号的强制功能---改变运算优先级
- 【TODO】第三十七节:单字节变量赋值给多字节变量的疑惑
- 【TODO】第三十八节:第二种解决“运算过程中意外溢出”的便捷方法
- 【TODO】第三十九节:if判断语句以及常量变量的真假判断
- 【TODO】第四十节:关系符的等于“==”和不等于“!=”
- 【TODO】第四十一节:关系符的大于“>”和大于等于“>=”
- 【TODO】第四十二节:关系符的小于“<”和小于等于“<=”
- 【TODO】第四十三节:关系符中的关系符:与“&&”,或“||”
- 【TODO】第四十四节:小括号改变判断优先级
- 【TODO】第四十五节: 组合判断if...else if...else
- 【TODO】第四十六节: 一维数组
- 【TODO】第四十七节: 二维数组
- 【TODO】第四十八节: while循环语句
- 【TODO】第四十九节: 循环语句do while和for
- 【TODO】第五十节: 循环体内的continue和break语句
- 【TODO】第五十一节: for和while的循环嵌套
- 【TODO】第五十二节: 支撑程序框架的switch语句
- 【TODO】第五十三节: 使用函数的三要素和执行顺序
- 【TODO】第五十四节: 从全局变量和局部变量中感悟“栈”为何物
- 【TODO】第五十五节: 函数的作用和四种常见书写类型
- 【TODO】第五十六节: return在函数中的作用以及四个容易被忽略的功能
- 【TODO】第五十七节: static的重要作用
- 【TODO】第五十八节: const(./book/或code)在定义数据时的作用
- 【TODO】第五十九节: 全局“一键替换”功能的#define
- 【TODO】第六十节: 指针在变量(./book/或常量)中的基础知识
- 【TODO】第六十一节: 指针的中转站作用,地址自加法,地址偏移法
- 【TODO】第六十二节: 指针,大小端,化整为零,化零为整
- 【TODO】第六十三节: 指针“化整为零”和“化零为整”的“灵活”应用
- 【TODO】第六十四节: 指针让函数具备了多个相当于return的输出口
- 【TODO】第六十五节: 指针作为数组在函数中的入口作用
- 【TODO】第六十六节: 指针作为数组在函数中的出口作用
- 【TODO】第六十七节: 指针作为数组在函数中既“入口”又“出口”的作用
- 【TODO】第六十八节: 为函数接口指针“定向”的const关键词
- 【TODO】第六十九节: 宏函数sizeof(./book/)
- 【TODO】第七十节: “万能数组”的结构体
- 【TODO】第七十一节: 结构体的内存和赋值
- 【TODO】第七十二节: 结构体的指针
- 【TODO】第七十三节: 结构体数据的传输存储和还原
- 【TODO】第七十四节: 结构体指针在函数接口处的频繁应用
- 【TODO】第七十五节: 指针的名义(例:一维指针操作二维数组)
- 【TODO】第七十六节: 二维数组的指针
- 【TODO】第七十七节: 指针唯一的“单向输出”通道return
- 【TODO】第七十八节: typedef和#define和enum
- 【TODO】第七十九节: 各种变量常量的命名规范
- 【TODO】第八十节: 单片机IO口驱动LED
- 【TODO】第八十一节: 时间和速度的起源(指令周期和晶振频率)
- 【TODO】第八十二节: Delay“阻塞”延时控制LED闪烁
- 【TODO】第八十三节: 累计主循环的“非阻塞”延时控制LED闪烁
- 【TODO】第八十四节: 中断与中断函数
- 【TODO】第八十五节: 定时中断的寄存器配置
- 【TODO】第八十六节: 定时中断的“非阻塞”延时控制LED闪烁
- 【TODO】第八十七节: 一个定时中断产生N个软件定时器
- 【TODO】第八十八节: 两大核心框架理论(四区一线,switch外加定时中断)
- 【TODO】第八十九节: 跑马灯的三种境界
- 【TODO】第九十节: 多任务并行处理两路跑马灯
- 【TODO】第九十一节: 蜂鸣器的“非阻塞”驱动
- 【TODO】第九十二节: 独立按键的四大要素(自锁,消抖,非阻塞,清零式滤波)
- 【TODO】第九十三节: 独立按键鼠标式的单击与双击
- 【TODO】第九十四节: 两个独立按键构成的组合按键
- 【TODO】第九十五节: 两个独立按键的“电脑键盘式”组合按键
- 【TODO】第九十六节: 独立按键“一键两用”的短按与长按
- 【TODO】第九十七节: 独立按键按住不松手的连续均匀触发
- 【TODO】第九十八节: 独立按键按住不松手的“先加速后匀速”的触发
- 【TODO】第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)
- 【TODO】第一百节: “行列扫描式”矩阵按键的单个触发(优化版)
- 【TODO】第一百零一节: 矩阵按键鼠标式的单击与双击
- 【TODO】第一百零二节: 两个“任意行输入”矩阵按键的“有序”组合触发
- 【TODO】第一百零三节: 两个“任意行输入”矩阵按键的“无序”组合触发
- 【TODO】第一百零四节: 矩阵按键“一键两用”的短按与长按
- 【TODO】第一百零五节: 矩阵按键按住不松手的连续均匀触发
- 【TODO】第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发
- 【TODO】第一百零七节: 开关感应器的识别与软件滤波
- 【TODO】第一百零八节: 按键控制跑马灯的启动和暂停和停止
- 【TODO】第一百零九节: 按键控制跑马灯的方向
- 【TODO】第一百一十节: 按键控制跑马灯的速度
- 第一百一十一节: 工业自动化设备的开关信号的运动控制
- 【TODO】第一百一十二节: 数码管显示的基础知识
- 【TODO】第一百一十三节: 动态扫描的数码管显示数字
- 【TODO】第一百一十四节: 动态扫描的数码管显示小数点
- 【TODO】第一百一十五节: 按键控制数码管的秒表
- 【TODO】第一百一十六节: 按键控制数码管的倒计时
- 【TODO】第一百一十七节: 按键切换数码管窗口来设置参数
- 【TODO】第一百一十八节: 按键让某位数码管闪烁跳动来设置参数
- 【TODO】第一百一十九节: 一个完整的人机界面的程序框架的脉络
- 【TODO】第一百二十节: 按键切换窗口切换局部来设置参数
- 【TODO】第一百二十一节: 可调参数的数码管倒计时
- 【TODO】第一百二十二节: 利用定时中断做的“时分秒”数显时钟
- 【TODO】第一百二十三节: 一种能省去一个lock自锁变量的按键驱动程序
- 【TODO】第一百二十四节: 数显仪表盘显示“速度、方向、计数器”的跑马灯
- 【TODO】第一百二十五节: “双线”的肢体接触通信
- 【TODO】第一百二十六节: “单线”的肢体接触通信
- 【TODO】第一百二十七节: 单片机串口接收数据的机制
- 【TODO】第一百二十八节: 接收“固定协议”的串口程序框架
- 【TODO】第一百二十九节: 接收带“动态密匙”与“累加和”校验数据的串口程序框架
- 【TODO】第一百三十节: 接收带“动态密匙”与“异或”校验数据的串口程序框架
- 【TODO】第一百三十一节: 灵活切换各种不同大小“接收内存”的串口程序框架
- 【TODO】第一百三十二节:“转发、透传、多种协议并存”的双缓存串口程序框架
- 【TODO】第一百三十三节:常用的三种串口发送函数
- 【TODO】第一百三十四节:“应用层半双工”双机串口通讯的程序框架