嵌入式IDE-PlatformIO + STM32CubeMX +RT-thread RTOS 操作系统

tech2022-08-13  124

简介

PlatformIO是下一代的嵌入式IDE,关于其基本介绍,欢迎查看我的上一篇文章:使用下一代的嵌入式IDE-PlatformIO 教程

STM32CubeMX是ST官方的代码生成工具,作为一个从前端转嵌入式的程序员,对各种寄存器配置真的是感到无语。比如开启串口1,明明一个函数能完成的事,非要让你去写几十行代码,配置RCC、配置GPIO、配置USART三种加起来数十个寄存器,而且每种寄存器的配置内容、配置顺序还隐藏的很深,官方文档也是一笔带过。 这实在是不符合软件工程的解耦思想。STM32CubeMX则可以让你点点点就能完成上述的工作。

这篇博客将要介绍的是结合使用PlatformIO和STM32CubeMX。

预先需要了解的知识

PlatformIO IDE的安装及基本使用STM32CubeMX 的安装及基本使用STM32系列MCU的基本编程方法arm-none-eabi-gcc编译器的概念,gcc编译器的常用参数makefile的基本概念scons构建系统的基本概念python的基础语法

整体架构图

解释:使用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配置文件

在上面生成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

此时你应该可以正常进行编译和调试了。

三.再多一点,接入rt-thread RTOS操作系统

今天(20200903)公司聚餐,也算是为几位即将离职的同事送行。 以前在美团,对离职都比较避讳;现在到了小公司,周围的同事对离职反而看的很开。有四位将要离职的同事,一位是和我关系很不错的实习生,哎,我也想再回到学校体验无忧无虑的生活;一位是刚入职3周的新同事,有更好的机会,领导也放人并且祝福他;一位是华为过来的同事,他走了之后,我就是唯一一个来自大公司的了;最后一位则是我的直属领导,虽然技术能力并没有出神入化,但是有很强的技术精神,我比较敬佩。回家路上我又想了很多,我19年本科毕业就进入了美团,没有进入到最理想的BAT一线公司。在美团干了1年前端之后,发现自己其实对前端不感兴趣;来到小公司干了嵌入式之后,虽然对嵌入式比较感兴趣,但是薪资比同龄人低了一大截。 哎,人生到底追求的是什么,赢得什么才算胜利呢?

废话少说,言归正传。下面记录一下怎么再PlatformIO+STM32CubeMX的基础上再加上RT-Thread。

一般而言,对于真正的嵌入式应用,RTOS是不可或缺的一环,需要把这一环打通,才能把PlatformIO投入真正的使用。

在上述第一步、第二步的项目的基础上,我们进行进一步的修改:

3.1 增加RT-thread的源文件,请参考官方链接:基于 CubeMX 移植 RT-Thread Nano:

使用CubeMX打开第二步中的项目,参考上述链接,添加rt-thread软件包。参考上述链接,在最左侧Additional Software处勾选软件包参考上述链接,System Core - NVIC - Code generation选项,取消勾选Time base: System tick timer、Pendable request for system service 、Hard fault interrupt三个中断的生成在Connectivity中开启串口1用于rtthread shell交互。在System Core - NVIC - NVIC中Enable串口1中断在System Core - NVIC - Code generation取消勾选串口1中断的Call HAL handle,我们需要自己处理串口中断。切换到 Project Manager页面,勾选 Do not generate the main(),我们需要自己写main函数。再切换到 Project Manager页面下的 Advanced Settings,会看到有三个函数MX_GPIO_INIT、SystemClock_Config、MX_USART1_UART_init,均勾选上Not Generate Function Call ,我们需要手动调用外设初始化。还是在上述页面,均 取消勾选 Visibility(Static),我们需要手动调用外设初始化,所以函数不能是Static静态函数。最后点击生成工程

3.2 将rt-thread源文件添加到编译链

这一步很简单,只需要在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/>

3.3 移植rtthread:

这一步其实和PlatformIO没多大关系了,主要是rtthread的移植工作,所以我将其放在附录,有兴趣的请移步附录A。

四.做技术就是要贪心,再接入Modbus协议栈

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)

附录A: 移植RT-thread

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_MESSAGEQUEUE

4.增加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" ])

附录B:

前两步的工程代码: https://github.com/jiladahe1997/_PlatformIO_CubeMX_demo

最新回复(0)