3. C语言基础回顾:数据类型、运算符、流程控制、函数、指针、结构体
好,咱们直接进入正题。C语言是嵌入式开发的基石,美容仪这种资源受限的设备,更是离不开它。别觉得基础就简单,我见过太多人栽在细节上。今天咱们把几个核心知识点过一遍,都是我在项目里反复用到的。
3.1 数据类型:选对类型,省一半内存
美容仪的MCU,RAM通常只有几KB到几十KB。你想想看,一个int占4字节,一个char占1字节。如果只是存个温度值(0-100),用int就是浪费。
我个人的习惯是:能用uint8_t,绝不用int。能用uint16_t,绝不用int32_t。别小看这几个字节,积少成多,程序就跑得更稳。
常见数据类型速查表:
| 类型 | 大小(常见32位MCU) | 取值范围 | 典型用途 |
|---|---|---|---|
| uint8_t | 1字节 | 0 ~ 255 | 按键状态、小范围计数 |
| uint16_t | 2字节 | 0 ~ 65535 | ADC采样值、定时器计数值 |
| uint32_t | 4字节 | 0 ~ 42亿 | 时间戳、大范围计数 |
| float | 4字节 | ±3.4e-38 ~ ±3.4e38 | 温度、电压等浮点运算 |
注意:浮点数运算在MCU上很慢。能转成整数运算,就尽量转。比如温度25.3℃,我习惯存成253(乘以10),显示时再处理小数点。
3.2 运算符:别让优先级坑了你
运算符优先级,是新手最容易踩的坑。我曾经调试一个电机控制程序,死活转不起来。最后发现是 if (a & b == c) 这种写法,实际执行的是 if (a & (b == c)),跟我的意图完全相反。
我的建议:别靠记忆,多用括号。代码是给人看的,清晰第一。
// 不推荐
if (a & b == c) { ... }
// 推荐
if ((a & b) == c) { ... }
位运算符在美容仪里很常用。比如控制LED灯珠,一个字节的8个bit,分别控制8颗灯珠。用 |= 和 &=~ 来置位和清零,效率极高。
3.3 流程控制:if-else 与 switch-case 的选择
流程控制,说白了就是让程序按你的想法走。if-else 和 switch-case 都能实现分支,但场景不同。
我个人经验:如果分支少于3个,用if-else。如果分支多,比如按键处理(按下、长按、双击、释放),用switch-case更清晰。
小技巧:在switch-case里,每个case后面别忘了加break。我见过有人漏了,结果程序一路往下执行,闹出不少笑话。
// 按键状态机示例
switch(key_state) {
case KEY_IDLE:
if (key_pressed()) {
key_state = KEY_PRESSED;
timer_start();
}
break;
case KEY_PRESSED:
if (timer_elapsed() > 1000) {
key_state = KEY_LONG_PRESS;
} else if (key_released()) {
key_state = KEY_SHORT_PRESS;
}
break;
// ... 其他状态
}
3.4 函数:模块化是嵌入式开发的灵魂
函数,就是把一段逻辑封装起来,方便复用。在美容仪里,一个函数通常只做一件事。比如 adc_read_temperature() 只负责读温度,motor_set_speed() 只负责调电机速度。
我建议:函数名要见名知意。别用 func1()、do_stuff() 这种名字。三个月后你自己都看不懂。
// 好的命名
uint16_t adc_read_channel(uint8_t channel);
void motor_set_speed(uint8_t speed_percent);
void led_set_brightness(uint8_t brightness);
// 不好的命名
void read();
void set();
void do();
避坑指南:我曾经写过一个函数,参数太多,自己都记不清顺序。后来改成结构体传参,清爽多了。参数超过3个,就考虑用结构体吧。
3.5 指针:C语言的灵魂,也是噩梦
指针,说白了就是地址。你想想看,MCU的寄存器、内存、数组,本质上都是地址。指针就是操作这些地址的工具。
我刚开始学指针时,也觉得难。但后来发现,只要记住两点:
- 指针存的是地址
- *p 是取地址里的值
在美容仪里,指针最常用的场景是:
- 函数指针:实现回调,比如按键中断触发某个函数
- 指针传参:避免复制大结构体,节省栈空间
- 操作寄存器:直接通过地址读写硬件
// 操作寄存器示例
#define GPIOA_BASE 0x40020000
#define GPIOA_ODR (*(volatile uint32_t *)(GPIOA_BASE + 0x14))
// 置位PA0,点亮LED
GPIOA_ODR |= (1 << 0);
注意:指针一定要初始化!野指针是嵌入式开发的噩梦。我见过有人定义了一个指针没赋值,直接往里面写数据,结果程序跑飞了。嗯,这里要注意,指针要么指向有效地址,要么赋NULL。
3.6 结构体:把数据打包,管理更轻松
结构体,就是把多个变量打包成一个。在美容仪里,一个设备的状态通常包含多个参数,比如温度、电机速度、LED亮度、运行模式等。用结构体管理,代码更清晰。
// 美容仪设备状态结构体
typedef struct {
uint8_t mode; // 运行模式
uint16_t temperature; // 当前温度(单位:0.1℃)
uint8_t motor_speed; // 电机速度(0-100%)
uint8_t led_brightness; // LED亮度(0-100%)
uint8_t battery_level; // 电量(0-100%)
} DeviceState_t;
DeviceState_t device; // 定义一个设备状态变量
我习惯:用结构体指针传参,避免复制整个结构体。尤其是在中断服务函数里,栈空间很宝贵。
void update_device_state(DeviceState_t *state) {
state->temperature = adc_read_temperature();
state->motor_speed = get_motor_speed();
// ... 其他更新
}
小技巧:结构体成员按从大到小排列,可以节省内存。因为编译器会做内存对齐。嗯,这个细节很多人不知道,但优化内存时很有用。
好了,C语言基础就回顾到这里。这些知识点,在后续的章节里会反复用到。尤其是指针和结构体,是嵌入式开发的左膀右臂。别怕,多用几次就熟了。