RT-Thread中的串口DMA分析
这里分析一下RT-Thread中串口DMA方式的实现,以供做新处理器串口支持时的参考。
背景
在如今的芯片性能和外设强大功能的情况下,串口不实现DMA/中断方式操作,我认为在实际项目中基本是不可接受的,但遗憾的是,rt-thread现有支持的实现中,基本上没有支持串口的DMA,文档也没有关于串口DMA支持相关的说明,这里以STM32实现为背景,梳理一下串口DMA的实现流程,以供新处理器实现时以作参考。
DMA接收准备
启用DMA接收,需要在打开设备的时候做一些处理,入口函数为rt_device_open()。主体实现是:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)
{
......
result = device_init(dev);
......
result = device_open(dev, oflag);
......
}
device_init()就是rt_serial_init()函数,其主要是调用configure()函数,
static rt_err_t rt_serial_init(struct rt_device *dev)
{
......
if (serial->ops->configure)
result = serial->ops->configure(serial, &serial->config);
......
}
在stm32下,其configure()函数是stm32_configure(),其根据设备打开参数,配置STM32外设的寄存器。包括波特率、校验等串口工作参数。
device_open()函数就是rt_serial_open()函数,其主要实现是:
static rt_err_t rt_serial_open(struct rt_device *dev, rt_uint16_t oflag)
{
......
#ifdef RT_SERIAL_USING_DMA
else if (oflag & RT_DEVICE_FLAG_DMA_RX)
{
if (serial->config.bufsz == 0) {
struct rt_serial_rx_dma* rx_dma;
rx_dma = (struct rt_serial_rx_dma*) rt_malloc (sizeof(struct rt_serial_rx_dma));
RT_ASSERT(rx_dma != RT_NULL);
rx_dma->activated = RT_FALSE;
serial->serial_rx = rx_dma;
} else {
struct rt_serial_rx_fifo* rx_fifo;
rx_fifo = (struct rt_serial_rx_fifo*) rt_malloc (sizeof(struct rt_serial_rx_fifo) +
serial->config.bufsz);
RT_ASSERT(rx_fifo != RT_NULL);
rx_fifo->buffer = (rt_uint8_t*) (rx_fifo + 1);
rt_memset(rx_fifo->buffer, 0, serial->config.bufsz);
rx_fifo->put_index = 0;
rx_fifo->get_index = 0;
rx_fifo->is_full = RT_FALSE;
serial->serial_rx = rx_fifo;
/* configure fifo address and length to low level device */
serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *) RT_DEVICE_FLAG_DMA_RX);
}
dev->open_flag |= RT_DEVICE_FLAG_DMA_RX;
}
#endif /* RT_SERIAL_USING_DMA */
......
#ifdef RT_SERIAL_USING_DMA
else if (oflag & RT_DEVICE_FLAG_DMA_TX)
{
struct rt_serial_tx_dma* tx_dma;
tx_dma = (struct rt_serial_tx_dma*) rt_malloc (sizeof(struct rt_serial_tx_dma));
RT_ASSERT(tx_dma != RT_NULL);
tx_dma->activated = RT_FALSE;
rt_data_queue_init(&(tx_dma->data_queue), 8, 4, RT_NULL);
serial->serial_tx = tx_dma;
dev->open_flag |= RT_DEVICE_FLAG_DMA_TX;
/* configure low level device */
serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *)RT_DEVICE_FLAG_DMA_TX);
}
#endif /* RT_SERIAL_USING_DMA */
......
}
可见,其主要工作是为DMA接收准备FIFO缓冲区;为DMA发送准备发送数据缓冲队列,但是好像STM32中断并没有用到发送数据缓冲。
DMA配置数据来源是rt_hw_usart_init()函数,缺省的配置参数由宏RT_SERIAL_CONFIG_DEFAULT决定, 这里决定了缺省的接收缓冲区参数是64字节,通讯缺省参数是:115200,8N1。
#define RT_SERIAL_RB_BUFSZ 64
DMA接收
DMA接收我们从DMA中断开始分析,DMA接收中断服务函数为UARTn_DMA_RX_IRQHandler(),其调用HAL库的DMA处理函数HAL_DMA_IRQHandler(),该函数调用回调函数HAL_UART_RxCpltCallback()或HAL_UART_RxHalfCpltCallback(),这两个函数进入真正的中断服务处理函数dma_isr(struct rt_serial_device *),主体代码如下:
static void dma_isr(struct rt_serial_device *serial)
{
......
/* 如果是DMA-RX中断 */
if ((__HAL_DMA_GET_IT_SOURCE(&(uart->dma_rx.handle), DMA_IT_TC) != RESET) ||
(__HAL_DMA_GET_IT_SOURCE(&(uart->dma_rx.handle), DMA_IT_HT) != RESET))
{
level = rt_hw_interrupt_disable();
/* 得到本次接收到的数据量 */
recv_total_index = serial->config.bufsz - __HAL_DMA_GET_COUNTER(&(uart->dma_rx.handle));
if (recv_total_index == 0)
{
/* 这一句代码,是什么意思? */
recv_len = serial->config.bufsz - uart->dma_rx.last_index;
}
else
{
/* 减去以前接收到的数据量,得到本次接收到的数据数量 */
recv_len = recv_total_index - uart->dma_rx.last_index;
}
/* 更新接收历史数据量 */
uart->dma_rx.last_index = recv_total_index;
rt_hw_interrupt_enable(level);
if (recv_len)
{
/* 如果有新数据,调用serial设备模块的通用处理 */
rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_DMADONE | (recv_len << 8));
}
}
}
在serial模块的函数rt_hw_serial_isr()中,主体代码是:
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
......
case RT_SERIAL_EVENT_RX_DMADONE:
{
int length;
rt_base_t level;
/* get DMA rx length */
length = (event & (~0xff)) >> 8;
if (serial->config.bufsz == 0)
{
/* 这个case的处理逻辑不知道怎么应用,看起来STM32实现并没有处理这个case */
struct rt_serial_rx_dma* rx_dma;
rx_dma = (struct rt_serial_rx_dma*) serial->serial_rx;
RT_ASSERT(rx_dma != RT_NULL);
RT_ASSERT(serial->parent.rx_indicate != RT_NULL);
serial->parent.rx_indicate(&(serial->parent), length);
rx_dma->activated = RT_FALSE;
}
else
{
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* update fifo put index, 将数据放入接收缓冲区 */
rt_dma_recv_update_put_index(serial, length);
/* calculate received total length, 更新缓冲区信息 */
length = rt_dma_calc_recved_len(serial);
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* invoke callback, 通知上层,有新数据到达 */
if (serial->parent.rx_indicate != RT_NULL)
{
serial->parent.rx_indicate(&(serial->parent), length);
}
}
break;
}
......
}
上层接到通知后,读取函数最终调用驱动读函数rt_serial_read()函数,在DMA的条件下,调用_serial_dma_rx()从缓冲区读取数据。其代码为:
static rt_size_t rt_serial_read(struct rt_device *dev, rt_off_t pos, void *buffer, rt_size_t size)
{
......
else if (dev->open_flag & RT_DEVICE_FLAG_DMA_RX)
{
return _serial_dma_rx(serial, (rt_uint8_t *)buffer, size);
}
......
}
DMA发送
DMA发送从驱动写函数rt_serial_write()开始,在DMA的条件下,调用_serial_dma_tx(),_serial_dma_tx()再调用操作的DMA发送函数发送数据,代码为:
static rt_size_t rt_serial_write(struct rt_device *dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
......
else if (dev->open_flag & RT_DEVICE_FLAG_DMA_TX)
{
return _serial_dma_tx(serial, (const rt_uint8_t *)buffer, size);
}
......
}
rt_inline int _serial_dma_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{
......
/* make a DMA transfer */
serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);
......
}
STM32的dma_transmit()实现函数是stm32_dma_transmit(),其实现就是简单调用HAL_UART_Transmit_DMA(),代码为:
static rt_size_t stm32_dma_transmit(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction)
{
......
if (HAL_UART_Transmit_DMA(&uart->handle, buf, size) == HAL_OK)
......
}
实现非常简单。
RT-Thread中的串口DMA分析的更多相关文章
- STM32 + RT Thread OS 串口通讯
1. 创建项目 a) 禁用Finsh和console b) 默认情况下,项目文件包含了finsh,它使用COM1来通讯,另外,console输出(rt_kprintf)也使用了COM1.因 ...
- STM32 + RT Thread OS 学习笔记[二]
串口通讯例程 通过上面的练习,对STM32项目开发有了一个直观印象,接下来尝试对串口RS232进行操作. 1. 目标需求: 开机打开串口1,侦听上位机(使用电脑串口测试软件)发送的信息,然后原样输 ...
- linux串口驱动分析
linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...
- linux的串口驱动分析
1.串口驱动中的数据结构 • UART驱动程序结构:struct uart_driver 驱动 • UART端口结构: struct uart_port 串口 • UART相关操作函数结构: st ...
- 【原】Spark中Client源码分析(二)
继续前一篇的内容.前一篇内容为: Spark中Client源码分析(一)http://www.cnblogs.com/yourarebest/p/5313006.html DriverClient中的 ...
- STM32CubeMX HAL库串口+DMA数据发送不定长度数据接收
参考资料:1.ST HAL库官网资料 2.https://blog.csdn.net/u014470361/article/details/79206352#comments 一.STM32CubeM ...
- linux串口驱动分析【转】
转自:http://blog.csdn.net/hanmengaidudu/article/details/11946591 硬件资源及描述 s3c2440A 通用异步接收器和发送器(UART)提供了 ...
- Handler Thread 内部类引起内存泄露分析
非静态内部类引起内存泄漏的原因 内部类的实现其实是通过编译器的语法糖(Syntactic sugar)实现的,通过生成相应的子类即以OutClassName$InteriorClassName命名的C ...
- 我的Android进阶之旅------>Android中AsyncTask源码分析
在我的<我的Android进阶之旅------>android异步加载图片显示,并且对图片进行缓存实例>文章中,先后使用了Handler和AsyncTask两种方式实现异步任务机制. ...
随机推荐
- SQL学习(七)试图
试图是基于SQL语句的结果集的可视化表. 1.创建试图 create view 试图名 as select 语句 如: create view ticketresult as select * fro ...
- 使用gimp画线、矩形、圆等
使用gimp画线.矩形.圆等 https://blog.csdn.net/tody_guo/article/details/7628508 2012年06月03日 19:08:47 Tody Guo ...
- 如何用 putty 连接远程 Linux 系统
如何用 putty 连接远程 Linux 系统 Putty 简介 Putty 是一个免费的.Windows x86 平台下的 Telnet.SSH 和 Rlogin 客户端,但是功能丝毫不逊色于商业的 ...
- mysql实现id自增
用下面这句话就可以实现插入mysql数据时候实现id自增 create table shuju (id int(10) auto_increment primary key,name varchar( ...
- phpfpm和nginx设置开机自动启动
## 添加执行权限 chmod a+x /etc/init.d/nginx chmod a+x /etc/init.d/php-fpm ## 加入服务 chkconfig --add nginx ch ...
- python数据存储--JSON
HTML正文存储为两种格式:JSON和CSV. 存储为JSON: 首先利用Requests访问http://seputu.com获取HTML文档: #!coding:utf-8 import requ ...
- windows客户端如果通过cmd窗口连接到远程linux服务器,可以使用telnet;
linux系统打开telnet端口的方法 2016-03-11 16:02:25 标签:linux telnet 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明. ...
- SQL常见面试题(学生表_课程表_总表)
问题描述: 为管理岗位业务培训信息,建立3个表: S (S#,SN,SD,SA) S#,SN,SD,SA 分别代表学号.学员姓名.所属单位.学员年龄 C (C#,CN ) C#,CN 分别代表课程编号 ...
- 扩展Caylay定理
参考资料: https://hyscere.github.io/2019/09/05/%E6%89%A9%E5%B1%95Caylay%E5%AE%9A%E7%90%86/ https://www.c ...
- 一加手机刷入第三方Rec
首先阐述一下刷机的整体流程: 备份数据(可选):短信.联系人.通话记录.图片.应用数据的云端同步. 解锁 刷入第三方Recovery(简称Rec). 进入第三方Rec,刷第三方ROM. 刷机成功 解锁 ...