第4章:C语言基础回顾——指针与数组、结构体与联合体、中断服务函数、volatile关键字、位操作
各位同学,咱们今天聊点实在的。做DSP嵌入式开发,C语言基础不牢,后面写PID控制器代码会非常痛苦。我见过太多人,算法推导得头头是道,一上板子就崩,十有八九是C语言细节没吃透。
这一章,我挑几个最关键的、跟DSP开发强相关的知识点,带大家过一遍。别嫌基础,这些坑我当年都踩过。
4.1 指针与数组:别把它们当成一回事儿
很多人觉得指针和数组就是一回事。其实不然。数组名是个常量指针,指向首元素,但你不能给它赋值。指针是个变量,可以随便指来指去。
在DSP里,指针最大的用处是操作数据缓冲区。比如ADC采样数据,通常是个数组,你用指针去遍历,效率比下标高。
// 用指针遍历数组,效率更高
int adc_buffer[256];
int *ptr = adc_buffer;
for(int i = 0; i < 256; i++) {
*(ptr + i) = read_adc(); // 等价于 adc_buffer[i]
}
我个人习惯:在DSP的循环里,能用指针就别用下标。编译器优化后,指针操作往往少几条指令。你想想看,PID控制器的计算周期就那么短,能省一条是一条。
4.2 结构体与联合体:数据打包的艺术
结构体在PID控制器里太常用了。你把比例、积分、微分系数,还有限幅值、误差累积值,打包成一个结构体,代码瞬间清爽。
typedef struct {
float Kp; // 比例系数
float Ki; // 积分系数
float Kd; // 微分系数
float integral; // 积分累积
float prev_error; // 上次误差
float output_limit; // 输出限幅
} PID_Controller;
联合体呢?说白了就是让不同数据类型共用同一块内存。在DSP里,我常用它来解析通信协议。比如接收一个32位的数据,你可以用联合体把它拆成4个字节,或者两个16位整数。
typedef union {
uint32_t word;
uint8_t bytes[4];
int16_t half_words[2];
} Data_Parser;
我建议:在DSP的寄存器配置里,联合体特别好用。你可以直接操作整个寄存器,也可以单独操作某个位域。嗯,这里要注意字节序的问题,不同DSP可能不一样。
4.3 中断服务函数:别在里面干重活
中断服务函数(ISR)是DSP的灵魂。PID控制器的定时采样,通常就在定时器中断里完成。
写ISR有个铁律:快进快出。你想想看,中断服务函数里如果跑个浮点运算,或者调个printf,那其他中断就得排队等着,实时性就没了。
// 定时器中断服务函数
interrupt void timer_isr(void) {
// 1. 清除中断标志
// 2. 读取ADC值
// 3. 设置一个标志位,主循环里处理PID计算
adc_ready = 1;
// 4. 退出中断
}
4.4 volatile关键字:别让编译器“优化”掉你的代码
volatile,这词儿看着就让人头疼。但做嵌入式,你必须懂它。
它的作用就一句话:告诉编译器,这个变量随时可能被意外改变,别给我优化掉。
哪些地方要用?
- 中断里修改的全局变量
- 硬件寄存器地址
- 多任务共享的变量
volatile unsigned int *adc_reg = (unsigned int *)0x4000;
volatile int adc_ready = 0; // 中断里会修改这个标志
我遇到过:一个同事没加volatile,结果编译器把adc_ready这个标志优化成了寄存器变量,中断改了内存里的值,但主循环一直读寄存器,永远等不到标志位。查了两天,加个volatile就解决了。
4.5 位操作:DSP里的“微操”
位操作是嵌入式工程师的基本功。配置寄存器、设置标志位、打包数据,都离不开它。
常用的操作就几个:
- 置位:
REG |= (1 << 3);// 将第3位置1 - 清零:
REG &= ~(1 << 3);// 将第3位清0 - 翻转:
REG ^= (1 << 3);// 将第3位取反 - 读取:
if(REG & (1 << 3))// 判断第3位是否为1
在PID控制器里,位操作用得最多的地方是配置PWM模块和ADC模块的寄存器。比如设置PWM的极性、死区时间、ADC的采样触发源等等。
// 配置PWM模块:使能输出,设置极性
PWM_CTRL_REG |= (1 << 0); // 使能PWM输出
PWM_CTRL_REG &= ~(1 << 1); // 设置极性为高有效
我个人习惯:写位操作时,一定要用宏定义把位掩码写清楚。别直接写数字,不然过一个月你自己都看不懂。
#define PWM_ENABLE_BIT (0)
#define PWM_POLARITY_BIT (1)
PWM_CTRL_REG |= (1 << PWM_ENABLE_BIT);
PWM_CTRL_REG &= ~(1 << PWM_POLARITY_BIT);
本章知识体系
下面这张图,把本章的核心知识点串起来了。你可以看到,指针和数组是数据访问的基础,结构体和联合体是数据组织的工具,中断服务函数是实时响应的关键,volatile保证数据一致性,位操作则是硬件控制的“最后一公里”。
好了,这一章的内容就到这里。这些C语言基础,是后面写PID控制器代码的“砖瓦”。你把这些吃透了,后面写代码会顺手很多。
公众号:蓝海资料掘金营,微信deep3321