• RTOS(ENV)串口DMA接收GPS数据并解析


    RTOS(ENV)配置STM32串口DMA接收模式


    环境:

    1. RTOS 4.0.3
    2. Keil5
    3. ENV
    4. STm32l475

    ENV配置

    1. 使能串口:

    alt text
    2. 使能DMA,并设置接收缓冲区大小:

    alt text

    1. 创建工程
    scons --target=mdk
    

    工程配置

    1. 创建串口设备句柄

    #define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
    rt_device_t serial; /*句柄*/
    

    2.使用消息队列方式

    /* 串口接收消息结构*/
    struct rx_msg
    {
        rt_device_t dev;
        rt_size_t size;
    };
    /* 串口设备句柄 */
    static rt_device_t serial;
    /* 消息队列控制块 */
    static struct rt_messagequeue rx_mq;
    

    3.打开设备方式

    
           /* 以 DMA 接收及轮询发送方式打开串口设备 */
           rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
           /* 设置接收回调函数 */
           rt_device_set_rx_indicate(serial, uart_input);
           /* 发送字符串 */
           rt_device_write(serial, 0, str, (sizeof(str) - 1));
    

    注意:

    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; // 初始化配置
    /* 控制串口设备。通过控制接口传入命令控制字,与控制参数 */
    rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
    这些步骤是非DMA方式(中断)接收的,使用 rt_device_control函数 会影响DMA共能得使用

    4. 串口接收数据

    /* 接收数据回调函数 */
    static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
    {
        struct rx_msg msg;
        rt_err_t result;
        msg.dev = dev;
        msg.size = size;
    
        result = rt_mq_send(&rx_mq, &msg, sizeof(msg));
        if ( result == -RT_EFULL)
        {
            /* 消息队列满 */
            rt_kprintf("message queue full!\n");
        }
        return result;
    }
    
    static void serial_thread_entry(void *parameter)
    {
        struct rx_msg msg;
            rt_err_t result;
            rt_uint32_t rx_length;
            static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];
    
            while (1)
            {
                rt_memset(&msg, 0, sizeof(msg));
                /* 从消息队列中读取消息*/
                result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
                if (result == RT_EOK)
                {
                    /* 从串口读取数据*/
                    rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
                    rx_buffer[rx_length] = '\0';
                    /* 通过串口设备 serial 输出读取到的消息 */
                    rt_device_write(serial, 0, rx_buffer, rx_length);
                    /* 打印数据 */
                    rt_kprintf("%s\n",rx_buffer);
                }
            }
    }
    

    源code:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define SAMPLE_UART_NAME       "uart2"
    
    /* 串口接收消息结构*/
    struct rx_msg
    {
        rt_device_t dev;
        rt_size_t size;
    };
    /* 串口设备句柄 */
    static rt_device_t serial;
    /* 消息队列控制块 */
    static struct rt_messagequeue rx_mq;
    
    /* 接收数据回调函数 */
    static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
    {
        struct rx_msg msg;
        rt_err_t result;
        msg.dev = dev;
        msg.size = size;
    
        result = rt_mq_send(&rx_mq, &msg, sizeof(msg));
        if ( result == -RT_EFULL)
        {
            /* 消息队列满 */
            rt_kprintf("message queue full!\n");
        }
        return result;
    }
    
    
    static void serial_thread_entry(void *parameter)
    {
        struct rx_msg msg;
            rt_err_t result;
            rt_uint32_t rx_length;
            static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];
    
            while (1)
            {
                rt_memset(&msg, 0, sizeof(msg));
                /* 从消息队列中读取消息*/
                result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
                if (result == RT_EOK)
                {
                    /* 从串口读取数据*/
                    rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
                    rx_buffer[rx_length] = '\0';
                    /* 通过串口设备 serial 输出读取到的消息 */
                    rt_device_write(serial, 0, rx_buffer, rx_length);
                    /* 打印数据 */
                    rt_kprintf("%s\n",rx_buffer);
                }
            }
    }
    
    void main(void)
    {
        static char msg_pool[256];
        char str[] = "hello RT-Thread!666\r\n";
        /* 查找串口设备 */
           serial = rt_device_find(SAMPLE_UART_NAME);
           if (!serial)
           {
               rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME);
           }
    
           /* 初始化消息队列 */
           rt_mq_init(&rx_mq, "rx_mq",
                      msg_pool,                 /* 存放消息的缓冲区 */
                      sizeof(struct rx_msg),    /* 一条消息的最大长度 */
                      sizeof(msg_pool),         /* 存放消息的缓冲区大小 */
                      RT_IPC_FLAG_FIFO);        /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
    
           /* 以 DMA 接收及轮询发送方式打开串口设备 */
           rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
           /* 设置接收回调函数 */
           rt_device_set_rx_indicate(serial, uart_input);
           /* 发送字符串 */
           rt_device_write(serial, 0, str, (sizeof(str) - 1));
    
           /* 创建 serial 线程 */
           rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
           /* 创建成功则启动线程 */
           if (thread != RT_NULL)
           {
               rt_thread_startup(thread);
           }
           else
           {
               rt_kprintf("Create %s Entry failed!\n", SAMPLE_UART_NAME);
           }
    
    }
    
    

    附加例题:RT-Thread使用消息邮箱解析GPS数据

    使用消息邮箱完成以下任务:
    创建2个线程:串口接收和解析线程、LCD显示线程;

    1. 串口线程接收和解析GPS设备通过串口发来的数据(以GPRMC为主),发送给邮箱;

    2. LCD显示线程获取邮箱里的邮件消息,提取相关信息(如 经纬度位置、速度、方向、当前时间等),显示到LCD屏幕上。

    3. 使用PC上位机的串口调试助手发送GPS接收数据,模拟GPS接收机。

    参考资料:GPS数据包格式及数据包解析(https://blog.csdn.net/qq_17308321/article/details/80714560)

    GPS接收机接收到的数据样例:

    $GPRMC,092927.000,A,2235.9058,N,11400.0518,E,0.000,74.11,151216,,D*49 
    $GPVTG,74.11,T,,M,0.000,N,0.000,K,D*0B 
    $GPGGA,092927.000,2235.9058,N,11400.0518,E,2,9,1.03,53.1,M,-2.4,M,0.0,0*6B
    $GPGSA,A,3,29,18,12,25,10,193,32,14,31,,,,1.34,1.03,0.85*31 
    $GPGSV,3,1,12,10,77,192,17,25,59,077,42,32,51,359,39,193,49,157,36*48 
    $GPGSV,3,2,12,31,47,274,25,50,46,122,37,18,45,158,37,14,36,326,18*70 
    $GPGSV,3,3,12,12,24,045,45,26,17,200,18,29,07,128,38,21,02,174,*79
    

    1. 添加GPS数据解析模块

    • 定义数据结构
    /*邮箱控股句柄*/
    rt_mailbox_t uart_gps_mail;
    
    typedef struct GpsData
    {
        char Time[30];
        char Status[30];
        char Latitude[30];
        char N_S[30];
        char Longitude[30];
        char E_W[30];
        char Ground_Speed[30];
        char Course[30];
        char Date[30];
    } GpsData;
    
    
    • 添加GPS数据解析函数
     //解析 NMEA 句子并提取信息的函数
    void parseNMEA(const char *sentence)
    {
    
        char utcTime[12];
        char status;
        double latitude, longitude, groundSpeed, course;
        char utcDate[7];
    
        // 使用 sscanf 函数从 NMEA 句子中提取信息
        sscanf(sentence, "$GPRMC,%[^,],%c,%lf,N,%lf,E,%lf,%lf,%[^,],",
               utcTime, &status, &latitude, &longitude, &groundSpeed, &course, utcDate);
    
        // 提取小时、分钟和秒
        int hour, minute;
        double second;
        sscanf(utcTime, "%2d%2d%lf", &hour, &minute, &second);
    
        // 提取日期
        int year, month, day;
        sscanf(utcDate, "%2d%2d%2d", &day, &month, &year);
    
        // 打印提取的信息
    
        sprintf(gpsData.Time, "Time: %02d:%02d:%06.3lf", hour, minute, second);
        sprintf(gpsData.Status, "Status: %s", (status == 'A') ? "Valid" : "Invalid");
        sprintf(gpsData.Latitude, "Latitude: %.4f  N", latitude);
        sprintf(gpsData.Longitude, "Longitude: %.4f  E", longitude);
        sprintf(gpsData.Ground_Speed, "Speed: %.3f knots", groundSpeed);
        sprintf(gpsData.Course, "Course: %.2f ", course);
        sprintf(gpsData.Date, "Date: %02d-%02d-%02d", year, month, day);
    
        rt_mb_send(uart_gps_mail, (rt_uint32_t)&gpsData);
    }
    

    2. 添加lcd显示线程:

    void lcd_show(void *parameter)
    {
        lcd_clear(WHITE);
        lcd_set_color(WHITE, BLACK);
    
        GpsData *gpsData;
    
        while (1)
        {
            rt_mb_recv(uart_gps_mail, (rt_ubase_t *)&gpsData, RT_WAITING_FOREVER);
            lcd_show_string(10, 10, 16, gpsData->Time);
            lcd_show_string(10, 40, 16, gpsData->Status);
            lcd_show_string(10, 70, 16, gpsData->Latitude);
            lcd_show_string(10, 100, 16, gpsData->Longitude);
            lcd_show_string(10, 130, 16, gpsData->Ground_Speed);
            lcd_show_string(10, 160, 16, gpsData->Course);
            lcd_show_string(10, 190, 16, gpsData->Date);
        }
    }
    

    3. 源code

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define SAMPLE_UART_NAME "uart2"
    
    /* 串口接收消息结构*/
    struct rx_msg
    {
        rt_device_t dev;
        rt_size_t size;
    };
    /* 串口设备句柄 */
    static rt_device_t serial;
    /* 消息队列控制块 */
    static struct rt_messagequeue rx_mq;
    /*邮箱控股句柄*/
    rt_mailbox_t uart_gps_mail;
    
    typedef struct GpsData
    {
        char Time[30];
        char Status[30];
        char Latitude[30];
        char N_S[30];
        char Longitude[30];
        char E_W[30];
        char Ground_Speed[30];
        char Course[30];
        char Date[30];
    } GpsData;
    
    GpsData gpsData;
    
    void lcd_show(void *parameter)
    {
        lcd_clear(WHITE);
        lcd_set_color(WHITE, BLACK);
    
        GpsData *gpsData;
    
        while (1)
        {
            rt_mb_recv(uart_gps_mail, (rt_ubase_t *)&gpsData, RT_WAITING_FOREVER);
            lcd_show_string(10, 10, 16, gpsData->Time);
            lcd_show_string(10, 40, 16, gpsData->Status);
            lcd_show_string(10, 70, 16, gpsData->Latitude);
            lcd_show_string(10, 100, 16, gpsData->Longitude);
            lcd_show_string(10, 130, 16, gpsData->Ground_Speed);
            lcd_show_string(10, 160, 16, gpsData->Course);
            lcd_show_string(10, 190, 16, gpsData->Date);
        }
    }
    
    // 解析 NMEA 句子并提取信息的函数
    void parseNMEA(const char *sentence)
    {
    
        char utcTime[12];
        char status;
        double latitude, longitude, groundSpeed, course;
        char utcDate[7];
    
        // 使用 sscanf 函数从 NMEA 句子中提取信息
        sscanf(sentence, "$GPRMC,%[^,],%c,%lf,N,%lf,E,%lf,%lf,%[^,],",
               utcTime, &status, &latitude, &longitude, &groundSpeed, &course, utcDate);
    
        // 提取小时、分钟和秒
        int hour, minute;
        double second;
        sscanf(utcTime, "%2d%2d%lf", &hour, &minute, &second);
    
        // 提取日期
        int year, month, day;
        sscanf(utcDate, "%2d%2d%2d", &day, &month, &year);
    
        // 打印提取的信息
    
        sprintf(gpsData.Time, "Time: %02d:%02d:%06.3lf", hour, minute, second);
        sprintf(gpsData.Status, "Status: %s", (status == 'A') ? "Valid" : "Invalid");
        sprintf(gpsData.Latitude, "Latitude: %.4f  N", latitude);
        sprintf(gpsData.Longitude, "Longitude: %.4f  E", longitude);
        sprintf(gpsData.Ground_Speed, "Speed: %.3f knots", groundSpeed);
        sprintf(gpsData.Course, "Course: %.2f ", course);
        sprintf(gpsData.Date, "Date: %02d-%02d-%02d", year, month, day);
    
        rt_mb_send(uart_gps_mail, (rt_uint32_t)&gpsData);
    }
    
    /* 接收数据回调函数 */
    static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
    {
        struct rx_msg msg;
        rt_err_t result;
        msg.dev = dev;
        msg.size = size;
    
        result = rt_mq_send(&rx_mq, &msg, sizeof(msg));
        if (result == -RT_EFULL)
        {
            /* 消息队列满 */
            rt_kprintf("message queue full!\n");
        }
        return result;
    }
    
    static void serial_thread_entry(void *parameter)
    {
        struct rx_msg msg;
        rt_err_t result;
        rt_uint32_t rx_length;
        static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];
    
        while (1)
        {
            rt_memset(&msg, 0, sizeof(msg));
            /* 从消息队列中读取消息*/
            result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
            if (result == RT_EOK)
            {
                /* 从串口读取数据*/
                rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
                rx_buffer[rx_length] = '\0';
                /* 通过串口设备 serial 输出读取到的消息 */
                rt_device_write(serial, 0, rx_buffer, rx_length);
                /* 打印数据 */
                // rt_kprintf("%s\n",rx_buffer);
                parseNMEA(rx_buffer);
            }
        }
    }
    
    int main(void)
    {
    
        static char msg_pool[256];
        char str[] = "hello RT-Thread!666\r\n";
        /*动态创建邮箱*/
        uart_gps_mail = rt_mb_create("uart_gps", 1024, RT_IPC_FLAG_FIFO);
    
        /* 查找串口设备 */
        serial = rt_device_find(SAMPLE_UART_NAME);
        if (!serial)
        {
            rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME);
        }
    
        /* 初始化消息队列 */
        rt_mq_init(&rx_mq, "rx_mq",
                   msg_pool,              /* 存放消息的缓冲区 */
                   sizeof(struct rx_msg), /* 一条消息的最大长度 */
                   sizeof(msg_pool),      /* 存放消息的缓冲区大小 */
                   RT_IPC_FLAG_FIFO);     /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
    
        /* 以 DMA 接收及轮询发送方式打开串口设备 */
        rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
        /* 设置接收回调函数 */
        rt_device_set_rx_indicate(serial, uart_input);
        /* 发送字符串 */
        rt_device_write(serial, 0, str, (sizeof(str) - 1));
    
        /* 创建 serial 线程 */
        rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
        rt_thread_t lcd_th = rt_thread_create("lcd", lcd_show, RT_NULL, 1024, 25, 10);
        /* 创建成功则启动线程 */
        if (thread != RT_NULL && lcd_th != RT_NULL)
        {
            rt_thread_startup(thread);
            rt_thread_startup(lcd_th);
        }
        else
        {
            rt_kprintf("Create %s Entry failed!\n", SAMPLE_UART_NAME);
        }
        return RT_EOK;
    }
    
    
  • 相关阅读:
    Windows 下如何调试 PowerShell
    C++ 之多态总结
    [De1CTF2019]Babylfsr
    spring源码环境搭建,测试springmvc
    34【源码】数据可视化:基于 Echarts + Python 动态实时大屏 - 视频平台
    【MVCC】MVCC之通过readview判断事务读取哪个版本(通俗易懂,一文详解!!)
    通过WebSocket实现实时系统通知,以后再也不能装作没看到老板的通知了~~
    【zabbix】docker安装zabbix、yum安装zabbix-agent
    QtCreator设置代码自动格式化
    Unit2_1:动态规划DP
  • 原文地址:https://blog.csdn.net/Xinbaibaiya12138/article/details/139392543