简介
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,这里我用的是STM32F103RC
- RCC中开启外部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/CSDN_PlatformIO_CubeMX_demo