串口是通信中最常用的通信方式,大家可能写串口的驱动,能写几十种方法, 查询方式,中断方式, DMA方式,定时器方式。可能也其中几种方式的组合形式,经典的用法是: 发送用查询方式, 接收用中断方式。 下面我说下这几种方式的优点和确定吧。
1. 查询方式
1) 优点: 可靠性很高,也许要考虑下个数据包覆盖上一个数据包的问题,小数据量,在10个字节以内,可以这样考虑, 很简单,很方便,很可靠,我也这样使用多年
2)缺陷: 数据量大的时候,程序阻塞的时间特别长,影响其他比较重要的外设的处理
2. 中断方式
1)优点: 中断方式 , 不占用系统资源,但是如果数据量大,会频繁中断cpu, 会其他高优先的数据处理造成影响
2) 缺陷: 没有DMA,不占用资源的好处, 如果没有串口队列的实现,必须通过标志位判断上一个包数据是否发送完成,在把新的数据覆盖到串口的缓冲区
3. DMA方式
1)优点: 不占用系统资源,减少CPU对中断的响应
2)缺陷: 如何不建立数据包的队列,还是会出现,需要等待阻塞的问题
实现代码,下载链接: 通过git工具进行克隆代码
直接放大招
git clone https://github.com/yangang123/stm32_driver
模拟Linux操作系统和nuttx,rtthread操作系统的串口驱动实现:
发送和接收,都采用fifo的模式。
1. 发送函数
void write(int fd, uint8_t *buf, uint16_t len)
write函数是1个非阻塞的api, 直接写入数据到串口驱动的缓冲区,驱动层有数据后会自动从串口通过中断的方式发送数据出去
2. 接收函数
void read(int fd, uint8_t *buf, uint16_t len)
read是1个非阻塞的api, 可以从底层缓冲区去读取数。
3. ioctrl机制
int ioctl(int fd, int cmd, uint32_t *arg)
ioctrl机制,我可以通过发送命令到这个api.获取可读的缓冲区数据的长度
典型应用:
#include "serial.h"
#include "uart.h"
/*
fd : 0 是串口1
fd : 2 是串口3
*/
void uart_test()
{
/* 初始化串口 */
uart_init1(115200);
uart_init3(9600);
uint8_t buf[10];
for (uint8_t i =0; i < 10; i++) {
buf[i] = i;
}
/* 发送数据 */
write(0, buf, sizeof(buf));
while(1) {
/* 获取数据的长度 */
uint32_t data_cnt = 0;
if( ioctl(0, AVAILABLE_READ_LEN_CMD, &data_cnt) < 0) {
continue;
}
/* 获取数据 */
uint8_t buf[10];
if (data_cnt > 0) {
read((int)0, &buf[0], data_cnt);
}
}
}
渗入理解,串口TXE标志位:
串口发送的核心中断是TXE, 移位寄存器为空,就会触发中断,我们可以使用这个中断作为发送数据的标志,外部使能中断,中断调用需要发送的数据,发送数据的时候从buffer中读取数据, 最后1个数据发送完成,先进行关闭TXE中断。
void uart_common(struct uart_dev_s *dev)
{
/* recevied data */
if (USART_GetITStatus(dev->uart, USART_IT_RXNE)!= RESET) {
uint8_t val = (uint8_t)dev->uart->DR;
uart_rx_ringbuffer_push_from_usart(dev, &val);
}
/* transmit data */
if(USART_GetITStatus(dev->uart, USART_IT_TXE) != RESET) {
if(uart_tx_ringbuffer_pop_to_usart(dev) == 0) {
USART_ITConfig(dev->uart, USART_IT_TXE, DISABLE);
}
}
}