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两种方式实现异步任务机制. ...
随机推荐
- 如何在gradle项目中添加额外非开源jar包并提交代码
前提:项目开发中,遇到一个地方需要用到公司自定义的jar包,然后要放到代码里又不方便提交到私服 具体实施: 首先在项目中增加一个 libs目录,然后把这种非开源又不在私服上的jar包扔进去, 然后打开 ...
- 有效使用Django的QuerySets
对象关系映射 (ORM) 使得与SQL数据库交互更为简单,不过也被认为效率不高,比原始的SQL要慢. 要有效的使用ORM,意味着需要多少要明白它是如何查询数据库的.本文我将重点介绍如何有效使用 Dja ...
- 如何使用Pythonapi函数写股票策略
如何使用Python api 函数写股票策略 写策略需要了解的语法包括两方面,一方面是语言本身的语法(包括相关库),另一方面是量化平台提供的api.量化平台提供的api帮助文件里都有了,本文主要介绍写 ...
- Python之Numpy:线性代数/矩阵运算
当你知道工具的用处,理论与工具如何结合的时候,通常会加速咱们对两者的学习效率. 零 numpy 那么,Numpy是什么? NumPy(Numerical Python) 是 Python 语言的一个扩 ...
- CommonJS、AMD、CMD、ES6——JavaScript模块化
CommonJS规范:Node AMD规范:RequireJS CMD规范:SeaJS ES6模块
- C# 线程thread
一.问题总结 1. 在WinForm开发过程中用到线程时,往往需要在线程中访问线程外的控件,比如:设置textbox的Text值等等.如果直接访问UI控件会报出“从不是创建控件的线程访问它”错误.控件 ...
- Python flask 与 GO WEB服务器性能对比
测试环境: 系统: CentOS 7.1 Mem: 8G CPU: 虚拟机16核 Python版本: python3.6 Flask版本: 0.12.2 Golang版本: 1.6.3 1.首先写一个 ...
- Django模板系统-标签Tags
for <ul> {% for user in user_list %} <li>{{ user.name }}</li> {% endfor %} </ul ...
- nginx +域名
文件nginx.config: server { listen 80 ; server_name dns19.hichina.com;location / { proxy_pass http://12 ...
- 【AMAD】splinter -- 用于测试web app的python框架
简介 动机 作用 用法 热度分析 个人评分 简介 Splinter1是一个开源工具,使用Python编写,用于测试web apps.它可以用来对浏览器实现自动化操作,比如访问URLs,和按钮等交互. ...