RTOS(ENV)配置STM32串口DMA接收模式
环境:
- RTOS 4.0.3
- Keil5
- ENV
- STm32l475
ENV配置
- 使能串口:
2. 使能DMA,并设置接收缓冲区大小:
- 创建工程
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 <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <drv_lcd.h>
#include <rttlogo.h>
#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显示线程;
-
串口线程接收和解析GPS设备通过串口发来的数据(以GPRMC为主),发送给邮箱;
-
LCD显示线程获取邮箱里的邮件消息,提取相关信息(如 经纬度位置、速度、方向、当前时间等),显示到LCD屏幕上。
-
使用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 <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <drv_lcd.h>
#include <rttlogo.h>
#include <stdio.h>
#include <string.h>
#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;
}