【RT-Thread】STM32的UART设备读取GPS数据


前言

本文基于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)函数参数描述:

参数描述
nameGroup 的名字
srcGroup 中包含的文件,一般指的是 C/C++ 源文件。
dependGroup 编译时所依赖的选项
parameters配置其他参数

parameters可加入的标志:

标志描述
CCFLAGSC源文件编译参数
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模块功能进行测试,其它类似外设驱动也可以参照此方法实现快速开发。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
STM32单片机读写 ATGM336H(GPS)模块+液晶显示DEMO软件例程源码,可做为你的学习设计参考。 int main(void) { uint32_t lcdid; char cStr[100]; double deg_lat;//转换成[degree].[degree]格式的纬度 double deg_lon;//转换成[degree].[degree]格式的经度 nmeaINFO info; //GPS解码后得到的信息 nmeaPARSER parser; //解码时使用的数据结构 uint8_t new_parse=0; //是否有新的解码数据标志 nmeaTIME beiJingTime; //北京时间 /* 复位所有外设,初始化Flash接口和系统滴答定时器 */ HAL_Init(); /* 配置系统时钟 */ SystemClock_Config(); /* 初始化3.5寸TFT液晶模组,一般优先于调试串口初始化 */ lcdid=BSP_LCD_Init(); /* 初始化串口并配置串口中断优先级 */ MX_DEBUG_USART_Init(); MX_SPIFlash_Init(); MX_USARTx_Init(); /* 初始化LED */ LED_GPIO_Init(); /* 调用格式化输出函数打印输出数据 */ printf("LCD ID=0x%08X\n",lcdid); LCD_Clear(0,0,LCD_DEFAULT_WIDTH,LCD_DEFAULT_HEIGTH,BLACK); /* 开背光 */ LCD_BK_ON(); /* 设置用于输出调试信息的函数 */ nmea_property()->trace_func = &trace; nmea_property()->error_func = &error; nmea_property()->info_func = &gps_info; /* 初始化GPS数据结构 */ nmea_zero_INFO(&info); nmea_parser_init(&parser); /* 使用DMA传输数据到电脑端 */ HAL_UART_Receive_DMA(&husartx,gps_rbuff,GPS_RBUFF_SIZE); while(1) { if(GPS_HalfTransferEnd) /* 接收到GPS_RBUFF_SIZE一半的数据 */ { /* 进行nmea格式解码 */ nmea_parse(&parser, (const char*)&gps_rbuff[0], HALF_GPS_RBUFF_SIZE, &info); GPS_HalfTransferEnd = 0; //清空标志位 new_parse = 1; //设置解码消息标志 } else if(GPS_TransferEnd) /* 接收到另一半数据 */ { nmea_parse(&parser, (const char*)&gps_rbuff[HALF_GPS_RBUFF_SIZE], HALF_GPS_RBUFF_SIZE, &info); GPS_TransferEnd = 0; new_parse =1; } if(new_parse ) //有新的解码消息 { /* 对解码后的时间进行转换,转换成北京时间 */ GMTconvert(&info.utc,&beiJingTime,8,1); /* 输出解码得到的信息 */ printf("\r\n时间%d-%02d-%02d,%d:%d:%d\r\n", beiJingTime.year+1900, beiJingTime.mon,beiJingTime.day,beiJingTime.hour,beiJingTime.min,beiJingTime.sec); //info.lat lon中的格式为[degree][min].[sec/60],使用以下函数转换成[deg
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值