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

目录

  • 前言
  • 1. 开发环境搭建
  • 2. RTT Studio创建工程
  • 3. CubeMX配置系统时钟
  • 4. SConscript构建目录
  • 5. UART设备驱动程序
  • 6. 获取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)函数参数描述:

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

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【RT-Thread】STM32的UART设备读取GPS数据

    发表评论