MCU全套代码工程详解:从原理到实践的综合指南
之前有个项目在USART中断里调了这个函数,结果因为优先级问题导致中断嵌套,直接把系统干趴了。后来用逻辑分析仪抓波形才发现,中断里操作GPIO的时间比想象中长得多。),之前有哥们把栈大小当字节设置,结果跑着跑着任务自己消失了。这里有个坑,_estack定义在链接脚本里,要是堆栈设太小,跑RTOS分分钟死给你看。曾经遇到个奇葩问题,某款国产MCU的osKernelStart()实现有bug,不加这个
MCU全套代码工程
最近在整理手头的MCU项目代码,突然发现很多新人拿到开发板后只会用现成的HAL库点灯。今天咱们就聊聊怎么从裸机开始搭个完整的MCU工程,绝对比直接复制例程有意思多了。
先看工程目录结构:
Project/
├── Startup/ # 启动文件
├── Driver/ # 外设驱动
├── Middlewares/ # 中间件
├── Application/ # 应用层
└── Makefile # 老派的编译方式
启动文件这个黑盒子得拆开看看。以STM32F103的startup_stm32f103xe.s为例,关键部分长这样:
Reset_Handler:
ldr sp, =_estack ; 设置堆栈指针
bl SystemInit ; 系统时钟初始化
bl __libc_init_array ; C库初始化
bl main ; 跳转到C的世界
bx lr
这里有个坑,_estack定义在链接脚本里,要是堆栈设太小,跑RTOS分分钟死给你看。之前有个项目因为ADC采样开大数组,直接爆栈导致HardFault,调试了两天才发现是这里没改。
外设驱动咱们自己封装个GPIO操作试试:
// Driver/gpio.c
void GPIO_Toggle(GPIO_TypeDef* GPIOx, uint16_t Pin)
{
static uint8_t pin_state[16] = {0};
if(pin_state[Pin] == 0){
GPIOx->BSRR = (1 << Pin); // 置位
pin_state[Pin] = 1;
} else {
GPIOx->BSRR = (1 << (Pin + 16)); // 复位
pin_state[Pin] = 0;
}
}
这个实现比HAL库的HALGPIOTogglePin()骚在哪?注意静态变量保存状态,直接操作寄存器BSRR原子性地切换电平。用HAL库的话得先读当前状态再写,中间可能被中断打断导致逻辑错乱。不过要注意GPIOx的时钟必须提前使能,否则直接操作寄存器会卡死。
MCU全套代码工程
中间件层举个FreeRTOS的任务创建例子:
// Middlewares/rtos_tasks.c
void StartDefaultTask(void const * argument)
{
/* 这里放初始化代码 */
for(;;)
{
GPIO_Toggle(LED_GPIO_Port, LED_Pin);
vTaskDelay(500); // 不用HAL_Delay是怕阻塞其他任务
}
}
xTaskCreate(StartDefaultTask, "LED_Task", 128, NULL, 1, NULL);
注意任务栈大小设128字(不是字节!),之前有哥们把栈大小当字节设置,结果跑着跑着任务自己消失了。用vTaskDelay而不是HAL_Delay是为了让出CPU时间,毕竟RTOS里阻塞式延时是大忌。
最后在应用层main函数里把这些串起来:
// Application/main.c
int main(void)
{
SystemClock_Config(); // 自己实现的时钟配置
MX_GPIO_Init(); // 自动生成的初始化
osKernelStart(); // 启动RTOS内核
while(1)
{
/* 这里永远不会执行到 */
}
}
这个while(1)看起来多余?其实有些RTOS在空闲时会回到这里。曾经遇到个奇葩问题,某款国产MCU的osKernelStart()实现有bug,不加这个死循环直接跑飞。
整个工程编译建议用Makefile而不是IDE,这样换芯片型号时改起来方便。不过要处理那些该死的依赖关系,可以试试这个骚操作:
C_SOURCES = $(shell find ./ -name '*.c')
# 生成依赖文件
DEPFILES = $(C_SOURCES:%.c=%.d)
-include $(DEPFILES)
这样修改头文件后会自动触发重新编译相关源文件,比IDE的自动检测还灵敏。不过第一次生成依赖文件时可能会报错,加个--keep-going参数就能跳过了。
最后说个血泪教训:千万别在中断服务函数里调用自己写的GPIO_Toggle()!之前有个项目在USART中断里调了这个函数,结果因为优先级问题导致中断嵌套,直接把系统干趴了。后来用逻辑分析仪抓波形才发现,中断里操作GPIO的时间比想象中长得多。

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)