六、嵌入式C语言基础
1、USB-CDC串口printf函数实现
1.1 功能概述
printf函数是开发调试的核心工具,可通过USB-CDC串口快速打印变量值和调试信息,实现"免冷启动下载"调试。
1.2 启用步骤
- 打开USB库配置文件
- 找到printf宏定义位置(通常在第6行附近)
- 移除行首的反斜杠
\ 以启用功能
- 重新编译工程
常见问题排查:
- 配置文件选项错误
- liba库文件未正确添加
- 误删关键系统文件
1.3 printf重定向原理
工程中printf被重定向为 define宏替换:
// 示例:printf被重定向到USB输出函数
#define printf USB_Printf_HID
define的作用:在预处理阶段,将代码中所有 printf替换为 USB_Printf_HID。
1.4 格式化字符串语法
1.4.1 基本结构
printf("格式控制字符串", 参数列表);
1.4.2 字符类型
-
普通字符:原样输出
printf("Hello World\n"); // 输出:Hello World(并换行)
-
转换说明:以 %开头,用于插入变量值
%s:输出字符串
%d:以十进制整数形式输出
%u:无符号十进制整数
-
转义字符:
\n:换行符(Newline)
\t:水平制表符(Tab,通常=4个空格)
\\:输出反斜杠本身
%%:输出百分号本身(转义)
1.4.3 参数数量规则
N个转换说明符 = N+1个参数(格式字符串本身算第一个参数)
// 正确示例:3个%对应3个额外参数,共4个参数
printf("今天是%d年%d月%d日", 2025, 11, 9); // 输出:今天是2025年11月9日
1.4.4 实战示例
// 接收到数据后原样返回并打印自定义信息
if (接收标志) {
USB_SendData(接收缓冲区); // 原路返回数据
printf("STC YYDS\n"); // 打印自定义字符串
清空接收区();
}
1.5 串口调试技巧
1.5.1 文本模式 vs 十六进制模式
- 文本模式:直接显示ASCII字符
- 十六进制模式:将每个字符转换为HEX值显示
示例:
文本模式显示:STC YYDS
十六进制模式:53 54 43 20 59 59 44 53
(53='S', 54='T', 43='C', 20='空格', 59='Y', 44='D')
应用场景:调试数值变量时,十六进制模式更直观。
1.5.2 免冷启动下载配置
- 下载软件中勾选三个关键选项(自动下载相关)
- 选择生成的
.obj文件
- 选择24MHz主频
- 识别USB-CDC虚拟串口号(如COM3)
- 波特率任意设置(USB-CDC不受波特率限制)
警告:printf不宜放在无延时的循环外部,可能导致系统崩溃。
2、数的进制系统
2.1 进制转换方法
通用公式
任意N进制数 → 十进制:
数值 = 每位数字 × N^位权 的总和
2.1.1 二进制 → 十进制
示例:1011b
1×2³ + 0×2² + 1×2¹ + 1×2⁰ = 8 + 0 + 2 + 1 = 11
2.1.2 十六进制 → 十进制
示例:0x3A4
3×16² + 10×16¹ + 4×16⁰ = 768 + 160 + 4 = 932
2.2 快捷转换工具
推荐使用程序员计算器:
- 十进制83 → 十六进制0x53
- 十六进制0x53 → 十进制83
2.3 十六进制对照表(必须熟记)
| 十进制 |
十六进制 |
十进制 |
十六进制 |
| 0 |
0x0 |
8 |
0x8 |
| 1 |
0x1 |
9 |
0x9 |
| 2 |
0x2 |
10 |
0xA |
| 3 |
0x3 |
11 |
0xB |
| 4 |
0x4 |
12 |
0xC |
| 5 |
0x5 |
13 |
0xD |
| 6 |
0x6 |
14 |
0xE |
| 7 |
0x7 |
15 |
0xF |
3、C语言基本数据类型
3.1 类型长度与范围(32位编译器)
| 类型 |
位数 |
有符号范围 |
无符号范围 |
常用宏定义 |
| char |
8 |
-128 ~ 127 |
0 ~ 255 |
int8_t / U8 |
| short |
16 |
-32768 ~ 32767 |
0 ~ 65535 |
int16_t / U16 |
| int |
16/32 |
依赖编译器 |
0 ~ 65535/2³²-1 |
int / U32 |
| long |
32 |
-2³¹ ~ 2³¹-1 |
0 ~ 2³²-1 |
int32_t |
| float |
32 |
IEEE 754浮点 |
- |
float |
| double |
64 |
IEEE 754浮点 |
- |
double |
关键提示:32位编译器默认不支持64位变量,使用 double需在文件头添加特殊编译指令(如 #pragma pack(8))。
3.2 变量定义语法
数据类型 变量名;
示例:
unsigned char x; // 定义无符号字符变量x(0-255)
int y; // 定义整型变量y
3.3 类型简化技巧(宏定义)
使用 define简化复杂类型声明:
#define U8 unsigned char // 定义U8代替unsigned char
#define U16 unsigned int // 定义U16代替unsigned int
// 后续可简洁定义变量
U8 x = 20; // 等效于 unsigned char x = 20;
U16 y = 1000; // 等效于 unsigned int y = 1000;
3.4 变量作用域规则
// 全局变量:定义在最外层大括号,整个文件可见
U8 global_var = 100;
void function() {
// 局部变量:仅在本大括号内有效
U8 local_var = 50;
if (1) {
U8 inner_var = 10; // 仅在if块内有效
}
// inner_var在此已失效
}
核心原则:大括号 {}界定变量生命周期。
4、C语言常用运算符
4.1 算术运算符
| 运算符 |
说明 |
示例(X=20, Y=10) |
结果 |
+ |
加法 |
X + Y |
30 |
- |
减法 |
X - Y |
10 |
* |
乘法 |
X * Y |
200 |
/ |
整数除法 |
X / Y |
2 |
% |
取模(余数) |
X % Y |
0 |
++ |
自增 |
X++ → X=21 |
- |
-- |
自减 |
X-- → X=19 |
- |
关键特性:
- 除法
/结果只保留整数部分
- 取模
%获取除法余数
- 避免溢出:8位×8位可能溢出,建议强制转换为16位
U8 x = 200, y = 10;
U16 result = (U16)x * y; // 强制转换防止溢出
// 错误示例:U8 result = x * y; // 结果会错误(2000>255)
4.2 关系运算符
| 运算符 |
说明 |
示例(X=20, Y=10) |
结果 |
== |
等于 |
X == Y |
假(0) |
!= |
不等于 |
X != Y |
真(1) |
> |
大于 |
X > Y |
真(1) |
< |
小于 |
X < Y |
假(0) |
>= |
大于等于 |
X >= Y |
真(1) |
<= |
小于等于 |
X <= Y |
假(0) |
核心原则:结果为真(1)或假(0),0为假,非0为真。
4.3 逻辑运算符
| 运算符 |
说明 |
示例(A=5, B=10) |
结果 |
&& |
逻辑与 |
(A>0) && (B>0) |
真(1) |
| ` |
|
` |
逻辑或 |
! |
逻辑非 |
!A |
假(0) |
运算规则:
&&:两边都为真才是真
||:一边为真就是真
!:真变假,假变真
4.4 位运算符
| 运算符 |
说明 |
示例(按位操作) |
& |
按位与 |
0101 & 1100 = 0100 |
| ` |
` |
按位或 |
^ |
按位异或 |
0101 ^ 1100 = 1001 |
~ |
按位取反 |
~0101 = 1010 |
<< |
左移 |
0101 << 1 = 1010(低位补0) |
>> |
右移 |
0101 >> 1 = 0010(高位补0) |
记忆口诀:
- 按位与
&:全1为1,有0为0
- 按位或
|:有1为1,全0为0
- 按位异或
^:相同为0,相异为1
4.5 赋值运算符
| 运算符 |
等价形式 |
示例 |
= |
直接赋值 |
C = A + B |
+= |
C = C + A |
C += A |
-= |
C = C - A |
C -= A |
*= |
C = C * A |
C *= A |
/= |
C = C / A |
C /= A |
%= |
C = C % A |
C %= A |
4.6 条件运算符
条件 ? 表达式1 : 表达式2
逻辑:条件为真执行表达式1,否则执行表达式2。
4.7 if-else控制结构
if (条件表达式) {
// 条件为真(非0)时执行
printf("条件为真\n");
} else {
// 条件为假(0)时执行
printf("条件为假\n");
}
关键要点:
- 条件可以是任意表达式(算术、关系、逻辑运算)
- else必须与if配对使用,不能单独存在
- 代码块需用英文大括号
{}包围
- 遵循缩进原则提高可读性
5、注意事项
5.1 数据溢出陷阱
// 错误示例:8位变量溢出
U8 x = 200; // 最大值255
U8 y = 10;
U8 result = x * y; // 200*10=2000 > 255,结果错误为208
// 正确做法:强制类型转换
U16 result = (U16)x * y; // 使用16位变量存储
溢出特征:当计算结果超过变量类型最大值时,高位数据丢失,无法恢复。
5.2 调试技巧总结
- printf定位:在关键位置打印变量值
- 十六进制模式:调试数值变量更直观
- 格式符组合:
%d整数、%u无符号、%s字符串必须掌握
- 转义字符:
\n换行、\t制表符规范输出格式
| 知识点 |
核心内容 |
常见错误 |
| printf |
重定向到USB-CDC,格式符%对应参数 |
中文引号、参数数量不匹配 |
| 进制转换 |
2^位权求和,熟记0-F对照 |
忘记16的幂次计算 |
| 数据类型 |
U8=8位, U16=16位, 注意范围 |
溢出导致数据错误 |
| 运算符 |
/取整, %取余, &&||逻辑, &|位运算 |
混淆逻辑与位运算 |
| if语句 |
0为假, 非0为真, else配if |
使用中文符号, 作用域混乱 |