stm32里串口是个很常见的通信接口
基本介绍可见:串口介绍https://blog.csdn.net/ARM_qiao/article/details/125103127?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168877617116800213055546%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=168877617116800213055546&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-125103127-null-null.268v1control&utm_term=%E4%B8%B2%E5%8F%A3&spm=1018.2226.3001.4450
rt-thread里是通过应用程序通过 RT-Thread提供的 I/O 设备管理接口来访问串口硬件(也可以自己编写串口驱动)
相关接口如下所示:
函数 | 描述 |
---|---|
rt_device_find() | 查找设备 |
rt_device_open() | 打开设备 |
rt_device_read() | 读取数据 |
rt_device_write() | 写入数据 |
rt_device_control() | 控制设备 |
rt_device_set_rx_indicate() | 设置接收回调函数 |
rt_device_set_tx_complete() | 设置发送完成回调函数 |
rt_device_close() | 关闭设备 |
查找设备
rt_device_t rt_device_find(const char* name);
例子
rt_device_t tid = rt_device_find("uart2");
打开驱动
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数 | 描述 |
---|---|
dev | 设备句柄 |
oflags | 设备模式标志 |
返回 | —— |
RT_EOK | 设备打开成功 |
-RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE |
其他错误码 | 设备打开失败 |
oflags参数如下
#define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式 */
/* 接收模式参数 */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收模式 */
/* 发送模式参数 */
#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送模式 */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送模式 */
控制设备
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数 | 描述 |
---|---|
dev | 设备句柄 |
cmd | 命令控制字,可取值:RT_DEVICE_CTRL_CONFIG |
arg | 控制的参数,可取类型: struct serial_configure |
返回 | —— |
RT_EOK | 函数执行成功 |
-RT_ENOSYS | 执行失败,dev 为空 |
其他错误码 | 执行失败 |
控制参数结构体 struct serial_configure 原型如下:
struct serial_configure
{
rt_uint32_t baud_rate; /* 波特率 */
rt_uint32_t data_bits :4; /* 数据位 */
rt_uint32_t stop_bits :2; /* 停止位 */
rt_uint32_t parity :2; /* 奇偶校验位 */
rt_uint32_t bit_order :1; /* 高位在前或者低位在前 */
rt_uint32_t invert :1; /* 模式 */
rt_uint32_t bufsz :16; /* 接收数据缓冲区大小 */
rt_uint32_t reserved :4; /* 保留位 */
};
其参数选择如下
/* 波特率可取值 */
#define BAUD_RATE_2400 2400
#define BAUD_RATE_4800 4800
#define BAUD_RATE_9600 9600
#define BAUD_RATE_19200 19200
#define BAUD_RATE_38400 38400
#define BAUD_RATE_57600 57600
#define BAUD_RATE_115200 115200
#define BAUD_RATE_230400 230400
#define BAUD_RATE_460800 460800
#define BAUD_RATE_921600 921600
#define BAUD_RATE_2000000 2000000
#define BAUD_RATE_3000000 3000000
/* 数据位可取值 */
#define DATA_BITS_5 5
#define DATA_BITS_6 6
#define DATA_BITS_7 7
#define DATA_BITS_8 8
#define DATA_BITS_9 9
/* 停止位可取值 */
#define STOP_BITS_1 0
#define STOP_BITS_2 1
#define STOP_BITS_3 2
#define STOP_BITS_4 3
/* 极性位可取值 */
#define PARITY_NONE 0
#define PARITY_ODD 1
#define PARITY_EVEN 2
/* 高低位顺序可取值 */
#define BIT_ORDER_LSB 0
#define BIT_ORDER_MSB 1
/* 模式可取值 */
#define NRZ_NORMAL 0 /* normal mode */
#define NRZ_INVERTED 1 /* inverted mode */
/* 接收数据缓冲区默认大小 */
#define RT_SERIAL_RB_BUFSZ 64
RT-Thread 提供的默认串口配置如下,即 RT-Thread 系统中默认每个串口设备都使用如下配置:
#define RT_SERIAL_CONFIG_DEFAULT \
{ \
BAUD_RATE_115200, /* 波特率115200 bits/s */ \
DATA_BITS_8, /* 数据位8 databits */ \
STOP_BITS_1, /* 停止位1 stopbit */ \
PARITY_NONE, /* 无奇偶校验位No parity */ \
BIT_ORDER_LSB, /* 低位先出LSB first sent */ \
NRZ_NORMAL, /* 正常模式Normal mode */ \
RT_SERIAL_RB_BUFSZ, /* 缓冲区大小默认64字节Buffer size */ \
0 \
}
初始化配置与库函数配置串口类似
库函数配置
void usart2_init(u32 baudrate)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//gpio初始化
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//复用设置
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
//串口结构体初始化
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_InitStruct.USART_BaudRate = baudrate;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;
USART_InitStruct.USART_StopBits =USART_StopBits_1;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART2,&USART_InitStruct);
//中断使能
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
//配置嵌套向量中断控制器
NVIC_InitTypeDef NVIC_InitStruct;
//中断通道
NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
//优先级
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
//使能串口2
USART_Cmd(USART2,ENABLE);
}
官方例程
#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
static rt_device_t serial; /* 串口设备句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
/* step1:查找串口设备 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* step2:修改串口配置参数 */
config.baud_rate = BAUD_RATE_9600; //修改波特率为 9600
config.data_bits = DATA_BITS_8; //数据位 8
config.stop_bits = STOP_BITS_1; //停止位 1
config.bufsz = 128; //修改缓冲区 buff size 为 128
config.parity = PARITY_NONE; //无奇偶校验位
/* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
/* step4:打开串口设备。以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
发送数据
库函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
发送一个字符
int send_char(int ch)
{
USART_SendData(USART2,ch);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);
return ch;
}
发送字符串
int send_str(char *p)
{
while(p++)
{
send_char(*p);
}
return 0;
}
串口也可通过重定义fputc实现printf打印
//重定向printf
//即将数据发送到USART2,并显示
int fputc(int ch, FILE *f)
{
USART_SendData(USART2,ch);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);
return ch;
}
int main(void)
{
USART_init(115200);
printf("usart1...\n");
while(1);
}
rt-thread发送数据
向串口中写入数据,可以通过如下函数完成:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
参数 | 描述 |
---|---|
dev | 设备句柄 |
pos | 写入数据偏移量,此参数串口设备未使用 |
buffer | 内存缓冲区指针,放置要写入的数据 |
size | 写入数据的大小 |
返回 | —— |
写入数据的实际大小 | 如果是字符设备,返回大小以字节为单位; |
0 | 需要读取当前线程的 errno 来判断错误状态 |
例子 |
char *str = "hello"
rt_device_write(serial, 0, str, (sizeof(str) - 1));
设置发送完成回调函数
在应用程序调用 rt_device_write() 写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示 :
rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
参数 | 描述 |
---|---|
dev | 设备句柄 |
rx_ind | 回调函数指针 |
dev | 设备句柄(回调函数参数) |
size | 缓冲区数据大小(回调函数参数) |
返回 | —— |
RT_EOK | 设置成功 |
接收数据
库函数编程方式可以在中断服务函数里面进行接收数据
通过检测中断状态来读取数据
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
buffer=USART_ReceiveData(USART1);
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
需要获取字符串,我们可以自己定义缓存区来进行
通常串口收发是由\r\n结尾,我们获取字符串即可检测结尾的\r\n
代码如下:
#define MAXSIZE 200
volatile char buffer[MAXSIZE]={0};
volatile u8 number=0;
volatile u8 flag=1; \\读取标准为0表示接收一组字符串或者缓冲区已满
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
if (number >= MAXSIZE)
flag=0;
if(flag)
{
buffer[number]=USART_ReceiveData(USART1);
number++;
}
if(number>=2)
{
if(buffer[number-1]=='\n'&&buffer[number-2]=='\r')
{
buffer[number-1]='\0';
buffer[number-2]='\0';
number=number-2;
flag=0;
}
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
void get_str(char *p)
{
u8 i =0;
if(flag==0)
{
while(buffer[i++]!='\0')
{
p[i]=buffer[i];
}
flag=1;
}
}
rt-thread 里面发送数据使用write 接收自然就是read了
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
参数 | 描述 |
---|---|
dev 设备句柄 | |
pos | 读取数据偏移量,此参数串口设备未使用 |
buffer | 缓冲区指针,读取的数据将会被保存在缓冲区中 |
size | 读取数据的大小 |
返回 | —— |
读到数据的实际大小 | 如果是字符设备,返回大小以字节为单位 |
0 | 需要读取当前线程的 errno 来判断错误状态 |
读取数据偏移量 pos 针对字符设备无效,此参数主要用于块设备中。
官方例程
static rt_device_t serial; /* 串口设备句柄 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */
/* 接收数据的线程 */
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
while (rt_device_read(serial, -1, &ch, 1) != 1)
{
/* 阻塞等待接收信号量,等到信号量后再次读取数据 */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* 读取到的数据通过串口错位输出 */
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}
设置接收回调函数
可以通过如下函数来设置数据接收指示,当串口收到数据时,通知上层应用线程有数据到达 :
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
参数 | 描述 |
---|---|
dev | 设备句柄 |
rx_ind | 回调函数指针 |
dev | 设备句柄(回调函数参数) |
size | 缓冲区数据大小(回调函数参数) |
返回 | —— |
RT_EOK | 设置成功 |
该函数的回调函数由调用者提供。若串口以中断接收模式打开,当串口接收到一个数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把串口设备句柄放在 dev 参数里供调用者获取
一般情况下接收回调函数可以发送一个信号量或者事件通知串口数据处理线程有数据到达
关闭设备
rt_err_t rt_device_close(rt_device_t dev);
参数 | 描述 |
---|---|
dev | 设备句柄 |
返回 | —— |
RT_EOK | 关闭设备成功 |
-RT_ERROR | 设备已经完全关闭,不能重复关闭设备 |
其他错误码 | 关闭设备失败 |
串口设备使用 官方示例
串口接收不定长数据
串口接收不定长数据需要用户在应用层进行处理,一般会有特定的协议,比如一帧数据可能会有起始标记位、数据长度位、数据、终止标记位等,发送数据帧时按照约定的协议进行发送,接收数据时再按照协议进行解析。
以下是一个简单的串口接收不定长数据示例代码,仅做了数据的结束标志位 DATA_CMD_END,如果遇到结束标志,则表示一帧数据结束。示例代码的主要步骤如下所示:
1、首先查找串口设备获取设备句柄。
2、初始化回调函数发送使用的信号量,然后以读写及中断接收方式打开串口设备。
3、设置串口设备的接收回调函数,之后发送字符串,并创建解析数据线程。
解析数据线程会尝试读取一个字符数据,如果没有数据则会挂起并等待信号量,当串口设备接收到一个数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。在解析数据时,判断结束符,如果结束,则打印数据。
此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。
当一帧数据长度超过最大长度时,这将是一帧不合格的数据,因为后面接收到的字符将覆盖最后一个字符。
/*
* 程序清单:这是一个串口设备接收不定长数据的示例代码
* 例程导出了 uart_dma_sample 命令到控制终端
* 命令调用格式:uart_dma_sample uart2
* 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
* 程序功能:通过串口 uart2 输出字符串"hello RT-Thread!",并通过串口 uart2 输入一串字符(不定长),再通过数据解析后,使用控制台显示有效数据。
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart2"
#define DATA_CMD_END '\r' /* 结束位设置为 \r,即回车符 */
#define ONE_DATA_MAXLEN 20 /* 不定长数据的最大长度 */
/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
/* 接收数据回调函数 */
static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
if (size > 0)
{
rt_sem_release(&rx_sem);
}
return RT_EOK;
}
static char uart_sample_get_char(void)
{
char ch;
while (rt_device_read(serial, 0, &ch, 1) == 0)
{
rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL);
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
return ch;
}
/* 数据解析线程 */
static void data_parsing(void)
{
char ch;
char data[ONE_DATA_MAXLEN];
static char i = 0;
while (1)
{
ch = uart_sample_get_char();
rt_device_write(serial, 0, &ch, 1);
if(ch == DATA_CMD_END)
{
data[i++] = '\0';
rt_kprintf("data=%s\r\n",data);
i = 0;
continue;
}
i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i;
data[i++] = ch;
}
}
static int uart_data_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* 查找系统中的串口设备 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_rx_ind);
/* 发送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("serial", (void (*)(void *parameter))data_parsing, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_data_sample, uart device sample);
注意rt-thread更新了uart v2的版本
新旧版本串口使用区别
1、在设备打开函数rt_devide_open的ofalge参数有区别
// 旧版本 oflags 的参数取值
RT_DEVICE_FLAG_INT_RX
RT_DEVICE_FLAG_INT_TX
RT_DEVICE_FLAG_DMA_RX
RT_DEVICE_FLAG_DMA_TX
// 新版本 oflags 的参数取值
RT_DEVICE_FLAG_RX_NON_BLOCKING
RT_DEVICE_FLAG_RX_BLOCKING
RT_DEVICE_FLAG_TX_NON_BLOCKING
RT_DEVICE_FLAG_TX_BLOCKING
2、缓冲区宏定义区别
旧版本接收缓冲区统一为 RT_SERIAL_RB_BUFSZ ,旧版本没有发送缓冲区的设置。
新版本缓冲区进行了分离接收和发送,并且也可以对各个串口进行单独设置,例如
// 设置 串口 2 的发送缓冲区为 256 字节,接收缓冲区为 1024 字节,见 rtconfig.h
#define BSP_UART2_RX_BUFSIZE 256
#define BSP_UART2_TX_BUFSIZE 1024
3、串口配置 serial_configure 成员变量 bufsz 的区别
旧版本的 bufsz 是整个串口的缓冲区大小,新版本讲收发缓冲区分开,定义了rx_bufsz和tx_bufsz
// 旧版本
struct serial_configure
{
rt_uint32_t baud_rate;
rt_uint32_t data_bits :4;
rt_uint32_t stop_bits :2;
rt_uint32_t parity :2;
rt_uint32_t bit_order :1;
rt_uint32_t invert :1;
rt_uint32_t bufsz :16;
rt_uint32_t reserved :6;
};
// 新版本
struct serial_configure
{
rt_uint32_t baud_rate;
rt_uint32_t data_bits :4;
rt_uint32_t stop_bits :2;
rt_uint32_t parity :2;
rt_uint32_t bit_order :1;
rt_uint32_t invert :1;
rt_uint32_t rx_bufsz :16;
rt_uint32_t tx_bufsz :16;
rt_uint32_t reserved :6;
};
详细内容可见官网:rt-thread官网:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart_v2/uart?id=%e6%96%b0%e6%97%a7%e7%89%88%e6%9c%ac%e4%b8%b2%e5%8f%a3%e4%bd%bf%e7%94%a8%e5%8c%ba%e5%88%ab