前言
本文基于STM32F429VET6单片机和RT-Thread Studio集成开发环境,使用CubeMX工具配置系统时钟,SConscript构建目录,通过RTThread的UART设备、GPS RMC软件包,实现了GPS模组ATGM336H定位信息的实时获取。
1. 开发环境搭建
- 硬件平台:STM32F429VET6
- 软件版本:rt-thread-v4.1.0
- 开发环境:RT-Thread Studio 2.2.4
- GPS模组:ATGM336H-5N-31
2. RTT Studio创建工程
为了更好的理解RT-Thread Studio手动构建工程的过程,创建工程时选择基于芯片,而不要选择基于开发板。根据实际情况选择一个串口作为串口终端,按正常流程创建工程后,首先需要对系统时钟进行配置。
遇到问题:在新建的工程里无法打开CubeMX Setting,双击后进度条一闪而过!(如果是基于开发板创建工程,则未遇到此问题。)
此问题应该是RT-Thread Studio在不同操作系统上链接CubeMX.exe时产生的兼容性问题,网上查找一番后在RT-Thread社区找到答案,虽然该贴描述的是Win7系统,但我是Win10系统也得到解决,在此感谢社区网友。如果以下链接中的cubemx.exe无法下载,也可以评论区留下邮箱。(也可以尝试重新安装最新版RT-Thread Studio)
解决方法:https://club.rt-thread.org/ask/question/136adc86e4d06132.html
3. CubeMX配置系统时钟
RTT Studio创建的工程默认使用内部高速时钟HSI,相对于外部晶振产生的时钟精度更低。配置系统时钟时有两种方式:
方式一:直接修改drv_clk.c文件,此方式适合于对时钟树熟练的用户;
方式二:通过STM32CubeMX配置,此方式适合于熟悉CubeMX工具的用户。(本文采用)
系统时钟初始化过程:
hw_board_init() -> clk_init() -> system_clock_config()
1)board.h头文件:
定义系统时钟相关宏,默认使用高速内部时钟,主频为最大180MHz。
/*-------------------------- CLOCK CONFIG BEGIN --------------------------*/
#define BSP_CLOCK_SOURCE ("HSI")
#define BSP_CLOCK_SOURCE_FREQ_MHZ ((int32_t)0)
#define BSP_CLOCK_SYSTEM_FREQ_MHZ ((int32_t)180)
/*-------------------------- CLOCK CONFIG END --------------------------*/
2)board.c源文件:
在hw_board_init初始过程中调用clk_init()。
void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq)
{
...
/* enable interrupt */
__set_PRIMASK(0);
/* System clock initialization */
clk_init(clock_src, clock_src_freq, clock_target_freq);
/* disbale interrupt */
__set_PRIMASK(1);
...
前后省略部分代码
}
3)clk_dvr.c源文件:
在clk_init中调用system_clock_config()。
void clk_init(char *clk_source, int source_freq, int target_freq)
{
system_clock_config(target_freq); //在clk_dvr.c中实现
}
注:在boad.c中调用rt_hw_board_init时将上述三个参数传入,由此可见上述三个宏只使用了一个参数target_freq,即目标频率
hw_board_init(BSP_CLOCK_SOURCE,BSP_CLOCK_SOURCE_FREQ_MHZ, BSP_CLOCK_SYSTEM_FREQ_MHZ);
4)CubeMX配置时钟:
在此使用HSE外部时钟,系统时钟设置最大为180MHz。生成代码时工配置勾选生成单独的.c和.h文件,以及只生需要的库文件。
生成代码后,直接关闭CubeMX软件即可。第一次配置cubemx关闭软件时会提示产生stm32f4xx_hal_conf_bak.h文件,点击确认即可,也就是说后续将使用cubemx目录下的stm32f4xx_hal_conf.h文件配置。
回到RTT Studio项目资源管理器,右击->刷新,此时使用CubeMX工具生成的完整目录会自动更新到工程中。这时在项目中存在两个main.c和两份HAL驱动库,需要通过SConscript来构建目录选择需要的文件,所以先不执行编译。
注:项目中bsp目录是手动添加的,新建工程不存在此目录。此时,如果直接编译工程会报出几百个错误!
4)调用系统时钟配置函数:
有两种方式:
一种是将SystemClock_Config()函数中的内容替换system_clock_config()函数中的内容,但是如果修改CubeMX时钟配置后,每次都需要重新再修改一次system_clock_config()函数,因此选择下面的方式。
另一种方式是外部声明并直接调用SystemClock_Config()函数,注释原来的system_clock_config()调用,这样更新CubeMX配置后也不需要再修改代码。
void clk_init(char *clk_source, int source_freq, int target_freq)
{
//注释原来的时钟配置,使用CubeMX的时钟配置
//system_clock_config(target_freq);
extern void SystemClock_Config(void);
SystemClock_Config();
}
注:SystemClock_Config()和system_clock_config()是两个不同的函数。
4. SConscript构建目录
1)构建目录
完成时钟配置后,通过SConscript文件重新构建cubemx目录,以解决main.c和HAL库重复、以及stm32f4xx_it.c、stm32f4xx_hal_msp.c、system_stm32f4xx.c等文件无需引用的问题。这里需要对SCon的内置函数有一些了解,SConscript 文件使用Python语法,可以实现控制源码文件的加入,并且可以指定文件的Group(类似于MDK5/IAR中的Group)。最快捷的方式是复制工程中其它SConscript文件进行修改。
SConscript文件源码:
import os
from building import *
cwd = GetCurrentDir()
src = Glob('*.c')
src = Split('''
Src/main.c
''')
path = [cwd]
path += [cwd + '/Inc']
group = DefineGroup('cubemx', src, depend = [''], CPPPATH = path)
Return('group')
注:因为编译时只需要用到main.c文件中的函数,所以Src目录下其它的.c文件不需要添加构建。
**DefineGroup(name, src, depend,parameters)函数参数描述:
参数 | 描述 |
---|---|
name | Group 的名字 |
src | Group 中包含的文件,一般指的是 C/C++ 源文件。 |
depend | Group 编译时所依赖的选项 |
parameters | 配置其他参数 |
parameters可加入的标志:
标志 | 描述 |
---|---|
CCFLAGS | C源文件编译参数 |
CPPPATH | 头文件路径 |
CPPDEFINES | 链接时参数 |
LIBRARY | 将组件生成的目标文件打包成库文件 |
在cubemx目录下添加SConcript文件后,右击->更新软件包,即可完成目录构建。构建结果如下:
提示:main.c中的SystemClock_Config()函数在clk_init()中调用,即上述配置的系统时钟初始化;
2)修改main函数
构建目录后工程中还包含两个main函数,一个是applications/main.c文件中的,一个是cubemx/main.c文件中的。因此我们需要注释cubemx/main.c中的main函数,或在其前面添加__WEAK关键词将其弱化。但是每次配置CubeMX工具生成代码后,都会更新cubemx中的main.c文件,所以每次需要重新修改main函数。
修改main函数:
__WEAK int main(void)
提示:第一次配置cubemx生成的代码,自动会在main函数前添加WEAK关键字!
3)配置HAL串口模块
如果此时直接编译工程会提示串口结构体未定义,因为在CubeMX配置生成代码后,工程会在drivers目录下备份一个stm32f4xx_hal_conf_bak.h文件(即上述提到的首次关闭cubemx时的提示。),而使用cubemx/Inc目录下新生成的stm32f4xx_hal_conf.h文件,但是在配置系统时钟时并没有对串口进行配置,即未开启HAL_UART_MODULE_ENABLED宏定义。
此时有两种方式可以解决:
方式一:重新打开cubemx工具配置一个串口,即新建工程时选择的终端串口号;
方式二:手动开启cubemx/stm32f4xx_hal_conf.h头文件中的串口模块宏定义。(本文采用)
stm32f4xx_hal_conf.h文件第78行:
#define HAL_UART_MODULE_ENABLED
此时再次编译工程即可通过,下面进行串口驱动设备程序的开发。
5. UART设备驱动程序
1)使能UART设备驱动
在创建工程时默认已经开启了UART设备,因为新建工程时已经选择了一个串口作为终端控制的端口。
2)定义UART引脚
在board.h文件配置相应的串口引脚,原文中注释详细描述了如何定义串口引脚和使用DMA方式,在此根据自己的实际情况选择相应的串口引脚。以下定义了两组串口,Console终端串口和GPS模块串口。
//终端控制串口
#define BSP_USING_UART1
#define BSP_UART1_TX_PIN "PA9"
#define BSP_UART1_RX_PIN "PA10"
//GPS控制串口
#define BSP_USING_UART8
#define BSP_UART8_TX_PIN "PE1"
#define BSP_UART8_RX_PIN "PE0"
/*-------------------------- UART CONFIG END --------------------------*/
3)添加GPS软件包
打开RT-Thread Setting界面,搜索找到GPS RMC: Analysis of GPS RMC information。开启此项功能,并修改Uart Port Name为上述定义的GPS模块串口号,保存并更新软件包。
此时在项目资源管理器中会自动添加packages目录,各文件功能描述如下:
- gps_rmc.c:实现GPS RMC数据格式转换、定位信息解析等方法;
- gps_rmc.h:定义GPS RMC信息解析数据存储结构体;
- rtt_gps_rmc_example.c:GPS组件初始化调试例程。
注:在rtt_gps_rmc_example.c文件中默认定义了GPS串口设备名称为uart6,而在启用GPS软件包时配置了串口8,所以在rtconfig.h文件中已经定义了GPS_RMC_SAMPLE_UART_NAME为“uart8”,两处二选一并不冲突,避免误解尽量改为一致。以下为两个文件的定义:
rtconfig.h文件:
/* tools packages */
#define PKG_USING_GPS_RMC
#define PKG_USING_GPS_RMC_LATEST_VERSION
#define GPS_RMC_USING_SAMPLE
#define GPS_RMC_SAMPLE_UART_NAME "uart8"
/* end of tools packages */
rtt_gps_rmc_example.c文件部分源码:
/* 定义GPS设备名称 */
#ifndef GPS_RMC_SAMPLE_UART_NAME
#define GPS_RMC_SAMPLE_UART_NAME "uart8" //手动修改为uart8,保持一致。
#endif
/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
/* GPS线程入口函数 */
void gps_rmc_sample_entry(void *p)
{
char buff[128] = {0}, *buff_p = buff, ch = 0;
struct gps_info info_data = {0};
gps_info_t info = &info_data;
while (1)
{
/* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
while (rt_device_read(uart, -1, &ch, 1) != 1)
{
/* 阻塞等待接收信号量,等到信号量后再次读取数据 */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
if (ch == '\n')
{
/* 在接收数据中查找“RMC字符串” */
if (rt_strstr((const char *)buff, "RMC"))
{
/* 解析并打印定位信息 */
if (gps_rmc_parse(info, buff))
gps_print_info(info);
}
rt_memset(buff, 0, 128);
rt_memset(info, 0, sizeof(struct gps_info));
buff_p = buff;
continue;
}
*buff_p++ = ch;
}
}
/* GPS线程初始化 */
int gps_rmc_sample_entry_init(void)
{
uart = rt_device_find(GPS_RMC_SAMPLE_UART_NAME);
if (uart == RT_NULL)
{
rt_kprintf("Not find %s device.\r\n", GPS_RMC_SAMPLE_UART_NAME);
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&rx_sem, GPS_RMC_SAMPLE_UART_NAME"_rx", 0, RT_IPC_FLAG_FIFO);
/* 设置GPS串口波特率为9600 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
config.baud_rate = BAUD_RATE_9600;
rt_device_control(uart, RT_DEVICE_CTRL_CONFIG, &config);
/* 中断接收方式开启打开串口设备 */
rt_device_open(uart, RT_DEVICE_FLAG_INT_RX);
/* 注册串接收回调函数 */
rt_device_set_rx_indicate(uart, uart_input);
/* 创建GPS线程 */
rt_thread_t t = rt_thread_create("gps_rmc_p", gps_rmc_sample_entry, RT_NULL,2048, 20, 10);
if (t == RT_NULL)
{
rt_kprintf("Failde to create gps rmc info procees thread.\r\n");
return RT_ERROR;
}
/* 启动GPS线程 */
if (rt_thread_startup(t) != RT_EOK)
{
rt_kprintf("Failde to startup gps rmc info procees thread.\r\n");
rt_thread_delete(t);
return RT_ERROR;
}
return RT_EOK;
}
/* 应用初始化:该函数返回值需要为int型,否则会报警告 */
INIT_APP_EXPORT(gps_rmc_sample_entry_init);
rtt_gps_rmc_example.c文件主要内容:
以应用初始化方式创建了“gps_rmc_p”线程,以中断方式接收串口数据(即GPS模块数据),并对GPS定位信息进行了解析,通过终端打印出来。也就是说在GPS模块默认使用9600波特率时,不需要做任何代码修改,即可实现GPS定位信息输出,这也充分体现了使用开源开发方式的高效。
6. 获取GPS定位信息
以上工程配置编译通过后,只需要连接GPS硬件模块即可输出GPS定位信息,实测设备上电后约10秒为首次获取定位时间,输出无效数据,之后每秒更新一次GPS定位有效数据。
修改代码,打印GPS原始数据:
GNRMC数据格式说明:
$GNRMC,090515.000,A,3110.34110,N,12122.03227,E,0.00,94.34,171122,,,A*46
1:$GNRMC, 格式ID,表示该格式为建议的最低特定GPS / TRANSIT数据(RMC)推荐最低定位信息
2: UTC时间, 格式hhmmss.ssss,代表时分秒.毫秒
3: 状态 A:代表定位成功 V:代表定位失败
4: 纬度 ddmm.mmmmmm 度格式(如果前导位数不足,则用0填充)
5: 纬度 N(北纬) S(南纬)
6: 经度 dddmm.mmmmmm 度格式(如果前导位数不足,则用0填充)
7: 经度 E(东经) W(西经)
8: 速度(也为1.852 km / h)
9: 方位角,度(二维方向,等效于二维罗盘)
10: UTC日期 DDMMYY 天月年
11: 磁偏角(000-180)度,如果前导位数不足,则用0填充)
12: 磁偏角方向E =东W =西
13: 模式,A=自主模式,E=估算模式,N=数据模式,D=差分模式,M=未定位
14: 校验和
15: 回车换行符
小结
学习RT-Thread设备驱动时,需要先了解drivers驱动库与HAL库之间的关系,两者之间可以通过CubeMX工具作为桥梁,合理利用CubeMX可有效提高开发效率。此外,RT-Thread Studio中的开源软件包资源丰富,本文只对GPS模块功能进行测试,其它类似外设驱动也可以参照此方法实现快速开发。