PlatformIO是下一代的嵌入式IDE,关于其基本介绍,欢迎查看我的上一篇文章:使用下一代的嵌入式IDE-PlatformIO 教程
STM32CubeMX是ST官方的代码生成工具,作为一个从前端转嵌入式的程序员,对各种寄存器配置真的是感到无语。比如开启串口1,明明一个函数能完成的事,非要让你去写几十行代码,配置RCC、配置GPIO、配置USART三种加起来数十个寄存器,而且每种寄存器的配置内容、配置顺序还隐藏的很深,官方文档也是一笔带过。 这实在是不符合软件工程的解耦思想。STM32CubeMX则可以让你点点点就能完成上述的工作。
这篇博客将要介绍的是结合使用PlatformIO和STM32CubeMX。
解释:使用STM32CubeMX和PlatformIO生成同一个工程。之后的scons构建工具、gcc编译器、jlink、GDB等工具都是已经集成到了PlatformIO中了。 生成了工程之后,需要配置一下scons构建工具的配置文件,然后点击编译调试,platfromIO会自动的帮我们完成之后的工作:调用scons、调用gcc、调用jlink、启动GDB server等。
1.1 打开STM32CubeMX:
选择你的MCU,这里我用的是STM32F103RCRCC中开启外部HSE时钟,外部时钟比HSI更稳定些开启DEBUG 4线打开串口1用于打印调试信息,波特率115200,校验位0,停止位1切换到Project Manage选项,Code Generator中选择Copy only the necessary library files切换到Project, Toolchain/IDE选择makefile最后点击生成工程。1.2 生成PlatformIO工程:
打开vscode,会自动弹出PlatformIO的主页。点击New Project,生成一个项目,弹出项目基本信息填写框。
这里的基本信息需要注意,Name项目名称需要和CubeMX中的一样,Board芯片也必须选择正确,Location项目目录需要和CubeMX中的一样。这样生成的工程和STM32CubeMX可以无缝结合。
作者注:还有另外一个骚气一点的办法。由于PlatformIO是根据项目中有没有platformio.ini文件来判断是否是PlatformIO项目的。同时这个文件又十分的简单,只有几行代码。所以另外一种办法是手动在CubeMX生成的工程中加上platformio.ini文件即可。
在上面生成PlatformIO项目时,细心的读者已经发现了有一个Framework选项我没有提到。
其实原因是这样:STM32CubeMX的原理是,解析你在GUI界面上选择的外设,生成使用HAL库的代码,并同时将HAL库添加到工程中;而PlatformIO的原理是,不生成任何代码,但自动下载HAL库,并放在C:/.platformio目录下去,并在编译时自动使用C:/platformio下的HAL库。
这样你会发现,两者使用的HAL库其实不是同一个,两者的细微差异会导致编译失败。而由于我们实际使用的是CubeMX生成的代码,所以我们需要使用是 CubeMX的HAL库。
综上所述,需要修改platformIO的配置文件:platfomio.ini如下(每一行代码的作用见注释):
[platformio] src_dir = ./ [env:genericSTM32F103RC] platform = ststm32 board = genericSTM32F103RC /* 注释掉下面framework这一行(ini文件中分号表示注释)*/ /* 表示不使用plateformIO的HAL库 */ ;framework = stm32cube /* 表示使用项目目录下的HAL库以及RT-threa */ build_flags = -D STM32F103xE -IInc -IDrivers/CMSIS/Include -IDrivers/CMSIS/Device/ST/STM32F1xx/Include -IDrivers/STM32F1xx_HAL_Driver/Inc -IDrivers/STM32F1xx_HAL_Driver/Inc/Legacye/ /* 表示使用项目目录下的HAL库以及RT-thread */ src_filter = +<src/> +<startup_stm32f103xe.s> +<Drivers/> +<Middlewares/> /* 表示使用项目目录下的链接文件 */ board_build.ldscript = ./STM32F103RCTx_FLASH.ld debug_tool = jlink此时你应该可以正常进行编译和调试了。
今天(20200903)公司聚餐,也算是为几位即将离职的同事送行。 以前在美团,对离职都比较避讳;现在到了小公司,周围的同事对离职反而看的很开。有四位将要离职的同事,一位是和我关系很不错的实习生,哎,我也想再回到学校体验无忧无虑的生活;一位是刚入职3周的新同事,有更好的机会,领导也放人并且祝福他;一位是华为过来的同事,他走了之后,我就是唯一一个来自大公司的了;最后一位则是我的直属领导,虽然技术能力并没有出神入化,但是有很强的技术精神,我比较敬佩。回家路上我又想了很多,我19年本科毕业就进入了美团,没有进入到最理想的BAT一线公司。在美团干了1年前端之后,发现自己其实对前端不感兴趣;来到小公司干了嵌入式之后,虽然对嵌入式比较感兴趣,但是薪资比同龄人低了一大截。 哎,人生到底追求的是什么,赢得什么才算胜利呢?
废话少说,言归正传。下面记录一下怎么再PlatformIO+STM32CubeMX的基础上再加上RT-Thread。
一般而言,对于真正的嵌入式应用,RTOS是不可或缺的一环,需要把这一环打通,才能把PlatformIO投入真正的使用。
在上述第一步、第二步的项目的基础上,我们进行进一步的修改:
这一步很简单,只需要在platformio.ini文件中加入即可:
在build_flag中加入相关头文件: build_flags = -D STM32F103xE -IInc -IDrivers/CMSIS/Include -IDrivers/CMSIS/Device/ST/STM32F1xx/Include -IDrivers/STM32F1xx_HAL_Driver/Inc -IDrivers/STM32F1xx_HAL_Driver/Inc/Legacy /* 增加下面两行 */ -IMiddlewares/Third_Party/RealThread_RTOS_RT-Thread/components/finsh/ -IMiddlewares/Third_Party/RealThread_RTOS_RT-Thread/include/ 在 src_filter 中加入Middlewares文件夹: src_filter = +<src/> +<startup_stm32f103xe.s> +<Drivers/> +<Middlewares/>这一步其实和PlatformIO没多大关系了,主要是rtthread的移植工作,所以我将其放在附录,有兴趣的请移步附录A。
TODO,这部分就不详细说明了,如果你有需要欢迎在下面留言,我会根据情况更新的。 这部分只需要在第三步的基础上,在编译链中加上Modbus的相关文件,然后移植Modbus协议栈即可,请参考我的这篇文章:STM32 MODBUS协议-简介及接入 FreeMODBUS。
参考CubeMX_script.py:
Import("env") import os env.Prepend(CCFLAGS=[ "-IFreeModbus/port", "-IFreeModbus/modbus/include", "-IFreeModbus/modbus/rtu" ]) modbusfiles = [ os.path.join("$BUILD_SRC_DIR", "./FreeModbus/modbus/mb_m.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/modbus/rtu/mbcrc.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/modbus/rtu/mbrtu_m.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/modbus/functions/mbfuncother.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/modbus/functions/mbfuncinput_m.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/modbus/functions/mbutils.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/port/rtt/port.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/port/rtt/portevent_m.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/port/rtt/portserial_m.c"), os.path.join("$BUILD_SRC_DIR", "./FreeModbus/port/rtt/porttimer_m.c"), ] env.Append(PIOBUILDFILES= modbusfiles)1.Middlewares\Third_Party\RealThread_RTOS_RT-Thread\bsp\board.c中 修改rt_hw_board_init函数:增加初始化外设部分代码
#include "main.h" extern void SystemClock_Config(void); extern void MX_GPIO_Init(void); extern void MX_USART1_UART_Init(void); extern UART_HandleTypeDef huart1; /* 调试串口1接收数据的消息队列buffer */ static uint8_t consoleInputBuffer[256]; struct rt_messagequeue consoleInputMQ; void rt_hw_board_init() { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); /* 使用串口1作为调试串口,初始化一个消息队列保存串口1接收到的数据,并手动开启串口中断 */ rt_err_t error = rt_mq_init(&consoleInputMQ,"consoleInputMQ",consoleInputBuffer, 1,sizeof(consoleInputBuffer),RT_IPC_FLAG_FIFO); RT_ASSERT(error == RT_EOK); SET_BIT(huart1.Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE); /* System Clock Update */ SystemCoreClockUpdate(); /* System Tick Configuration */ _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); /* Call components board initial (use INIT_BOARD_EXPORT()) */ #ifdef RT_USING_COMPONENTS_INIT rt_components_board_init(); #endif #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); #endif }增加一个函数用于输出调试信息:
void rt_hw_console_output(const char *str) { rt_size_t i = 0, size = 0; char a = '\r'; __HAL_UNLOCK(&huart1); size = rt_strlen(str); for (i = 0; i < size; i++) { if (*(str + i) == '\n') { HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 50); } HAL_UART_Transmit(&huart1, (uint8_t *)(str + i), 1, 50); } }2.Src\stm32f1xx_it.c中: 修改串口1中断函数:
#include "rtthread.h" extern struct rt_messagequeue consoleInputMQ; void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ if(huart1.Instance->SR & USART_SR_RXNE) { uint8_t data = (uint8_t)(huart1.Instance->DR & (uint8_t)0x00FF); rt_mq_send(&consoleInputMQ, &data, 1); } /* USER CODE END USART1_IRQn 0 */ /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ }3.Inc\rtconfig.h中:修改注释,开启FINSH控制台和消息队列。
/*注释掉 __CC_ARM,强制开启FINSH控制台*/ //#if defined(__CC_ARM) || defined(__CLANG_ARM) #include "RTE_Components.h" #if defined(RTE_USING_FINSH) #define RT_USING_FINSH #endif //RTE_USING_FINSH //#endif //(__CC_ARM) || (__CLANG_ARM) ... /* 开启消息队列 */ // <c1>Using Message Queue // <i>Using Message Queue #define RT_USING_MESSAGEQUEUE4.增加main.c中的main函数:
#include "rtthread.h" int main(void) { rt_kprintf("Hello RT-thread! \r\n"); while(1) { } }5.修改startup_stm32f103xe.s启动文件
99行 bl main修改为
bl entry参考:此链接第99行。 这一行的意思是,初始化静态变量完成之后的跳转位置,默认是main,需要修改成rtthread中components.c 中的 157 行 entry 函数。
6.修改链接脚本,在.text段中增加如下代码:意思是保留 rti_fn段的代码,这是rtthread中的要求的。rtthread中的INIT_BOARD_INIT的原理其实就是把函数声明成rti_fn段,然后启动的时候再去寻找rti_fn段代码执行。 参考:此链接
.text{ ... ... ... /* section information for finsh shell */ . = ALIGN(4); __fsymtab_start = .; KEEP(*(FSymTab)) __fsymtab_end = .; . = ALIGN(4); __vsymtab_start = .; KEEP(*(VSymTab)) __vsymtab_end = .; /* section information for initial. */ . = ALIGN(4); __rt_init_start = .; KEEP(*(SORT(.rti_fn*))) __rt_init_end = .; ... ... ... }7.修改链接link参数,增加一个链接参数: 参考:此链接 首先在platformio.ini中增加一个脚本
extra_scripts = pre:CubeMX_script.py然后写这个脚本的代码:CubeMX_script.py
Import("env") env.Prepend(LINKFLAGS=[ "--specs=nosys.specs" ])前两步的工程代码: https://github.com/jiladahe1997/_PlatformIO_CubeMX_demo