本文以stm32f4xx平台介绍串口驱动,主要目的是:1、RTT中如何编写中断处理程序;2、如何编写RTT设备驱动接口代码;3、了解串行设备的常见处理机制。所涉及的主要源码文件有:驱动框架文件(usart.c,usart.h),底层硬件驱动文件(serial.c,serial.h)。应用串口设备驱动时,需要在rtconfig.h中宏定义#define RT_USING_SERIAL。

一、RTT的设备驱动程序概述

编写uart的驱动程序,首先需要了解RTT的设备框架,这里以usart的驱动来具体分析RTT的IO设备管理。注:参考《RTT实时操作系统编程指南》 I/O设备管理一章。

我们可以将USART的硬件驱动分成两个部分,如下图所示

+----------------------+
  | rtt下的usart设备驱动     |
  |---------------------- |
  | usart硬件初始化代码      |
  |---------------------- |
  | usart 硬件                  |
  +----------------------+

实际上,在缺乏操作系统的平台,即裸机平台上,我们通常只需要编写USART硬件初始化代码即可。而引入了RTOS,如RTT后,RTT中自带IO设备管理层,它是为了将各种各样的硬件设备封装成具有统一的接口的逻辑设备,以方便管理及使用。让我们从下向上看,先来看看USART硬件初始化程序,这部分代码位于usart.c和usart.h中。

二、USART硬件初始化

假如在接触RTT之前,你已经对stm32很熟悉了,那么此文件中定义的函数名一定让你倍感亲切。这里实现的函数有:

  • static void RCC_Configuration(void);

  • static void GPIO_Configuration(void);

  • static void NVIC_Configuration(void);

  • static void DMA_Configuration(void);

  • void stm32_hw_usart_init();

前四个函数,是跟ST官方固件库提供的示例代码的名字保持一致。这些函数内部也是直接调用官方库代码实现的。具体不再赘述。对STM32裸机开发不太熟悉的朋友,建议先去官方网站下载官方固件源码包,以及应用手册和示例程序,ST提供了大量的文档和示例代码,利用好这些资源可以极大地加快开发。

下面重点看一下 usart.c中stm32_hw_usart_init():

int stm32_hw_usart_init(void)
{struct stm32_uart *uart;struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;RCC_Configuration();GPIO_Configuration();#ifdef RT_USING_UART2uart = &uart2;serial2.ops    = &stm32_uart_ops;serial2.config = config;NVIC_Configuration(&uart2);/* register UART2 device */rt_hw_serial_register(&serial2,"uart2",RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, //dev->flag (not dev->open_flag)
                          uart);
#endif /* RT_USING_UART2 */#ifdef RT_USING_UART3
...
#endif /* RT_USING_UART3 */return 0;
}
//INIT_BOARD_EXPORT(stm32_hw_usart_init); //it must be invoked in board.c(rt_hw_board_init for setting CONSOLE_DEVICE)

首先该函数调用RCC_Configuration()和GPIO_Configuration()打开串口外设时钟和IO口配置;调用NVIC_Configuration(&uart2)设置串口中断,其中参数&uart2为一个自定义结构体类型:

/* STM32 uart driver */
struct stm32_uart
{USART_TypeDef *uart_device;IRQn_Type irq;
};

接着初始化设备类对象serial2中的ops和config两个参数,并在usart.c中实现了stm32_uart_ops中的四个函数。

static const struct rt_uart_ops stm32_uart_ops =
{stm32_configure,stm32_control,stm32_putc,stm32_getc,
};

struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;

最后注册串口设备serial2,注册函数位于serial.c中:

/* register UART2 device */
rt_hw_serial_register(&serial2,"uart2",RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, //dev->flag (not dev->open_flag)uart);

显然,函数 stm32_hw_usart_init(),顾名思义,是用于初始化USART硬件的函数,因此这个函数一定会在USART使用之前被调用。搜索工程发现,这个函数是在board.c中rt_hw_board_init函数中被调用,而rt_hw_board_init函数又是在startup.c里的 rtthread_startup函数中调用的。进一步在startup.c的main函数中调用的,我们将实际的路径调用过程绘制如下。

  startup.c main()---> startup.c rtthread_startup()---> board.c   rt_hw_board_init() ---> usart.c   rt_hw_usart_init()

到这里,USART硬件的初始化工作已经完成完成了99%,下一步,我们需要为USART编写代码,将其纳入到RTT的设备管理层之中,正如前面所说,这部分代码在serial.c中实现。我们来重点分析这一文件。

三、在RTT下使用USART,将USART纳入RTT的IO设备层中

相对于stm32的内核来说,USART是一种低速的串行设备,并且为了最大的发挥的MCU的性能,因此使用查询方式发送、中断方式接收(发送也可以使用DMA方式)。这些已经在usart.c中使能了。首先看一些serial.h中的重要数据结构:

/** Serial FIFO mode */
struct rt_serial_rx_fifo
{/* software fifo */rt_uint8_t *buffer;rt_uint16_t put_index, get_index;
};struct rt_serial_tx_fifo
{struct rt_completion completion;
};/* * Serial DMA mode*/
struct rt_serial_rx_dma
{rt_bool_t activated;
};struct rt_serial_tx_dma
{rt_bool_t activated;struct rt_data_queue data_queue;
};struct rt_serial_device
{struct rt_device          parent;const struct rt_uart_ops *ops;struct serial_configure   config;void *serial_rx;void *serial_tx;
};
typedef struct rt_serial_device rt_serial_t;

/*** uart operators*/
struct rt_uart_ops
{rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);int (*putc)(struct rt_serial_device *serial, char c);int (*getc)(struct rt_serial_device *serial);rt_size_t (*dma_transmit)(struct rt_serial_device *serial, const rt_uint8_t *buf, rt_size_t size, int direction);
};

在serial.c中主要实现rtthread系统的IO设备统一接口函数,并注册串口设备:

/** serial register*/
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,const char              *name,rt_uint32_t              flag,void                    *data)
{struct rt_device *device;RT_ASSERT(serial != RT_NULL);device = &(serial->parent);device->type        = RT_Device_Class_Char;device->rx_indicate = RT_NULL;device->tx_complete = RT_NULL;device->init        = rt_serial_init;device->open        = rt_serial_open;device->close       = rt_serial_close;device->read        = rt_serial_read;device->write       = rt_serial_write;device->control     = rt_serial_control;device->user_data   = data;/* register a character device */return rt_device_register(device, name, flag);
}

对于串口发送数据,默认采用查询方式。因为串口设备注册的时候,其设备标志为RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,没有RT_DEVICE_FLAG_DMA_TX或RT_DEVICE_FLAG_INT_TX标志。 数据发送流程为:rt_device_write()-->rt_serial_write()-->_serial_poll_tx()-->stm32_putc()。

考虑串口接受数据的情况,串口收到一个字节的数据,就会触发串口中断USARTx_IRQHandler,数据字节会存放于串口的硬件寄存器中。但是在RTOS中,通常存在多个线程,如果某个处理串口数据的线程在没有串口数据时阻塞,当下一串口数据到来时,如果该数据线程依然没有唤醒并启动,并读取串口字节,则上一个串口字节丢失了,因此这不是一个优良的设计,我们需要设计一种机制来解决这种潜在的问题。实际上,缓冲机制可以大大缓解这个问题。数据读取流程为:rt_device_read()-->rt_serial_read()-->rt_hw_serial_isr()-->stm32_getc()。

所谓缓冲机制,简略的来说,即开辟一个缓冲区,可以是静态数组,也可以是malloc(或mempool)申请的动态缓冲区。在串口中断中,先从串口的硬件寄存器中读取数据,并保存到缓冲区中。这种情况下,我们需要两个变量,一个用于标记当前写入的位置,另外一个用来表示已经被处理的数据的位置。这样当数据处理线程阻塞时,连续收到的数据会保存到缓冲区中而避免了丢失。当中断中已经接收到了一些串口数据后,数据处理线程终于就绪,并开始处理数据,通常来说处理数据的速度必然比接受数据要快,因此这样就能解决前面所说的问题。聪明的读者发现了,还有一个小问题,缓冲区的长度必然是有限的,终归会有用到头的时候,那该怎么办呢?别担心,缓冲区前面已经被处理过的数据所占用的空间按自然可以重复使用,即,当接收指针指向了缓冲区末尾时,只要缓冲区头的数据已经被处理过了,自然可以直接将缓冲区指针从新设置为头,对于表示已处理的指针变量同理。这样这个缓冲区也就成为了一个环形缓冲区。

下面重点看一下中断函数:

#if defined(RT_USING_UART2)
/* UART2 device driver structure */
struct stm32_uart uart2 =
{USART2,USART2_IRQn,
};
struct rt_serial_device serial2;void USART2_IRQHandler(void)
{struct stm32_uart *uart;uart = &uart2;/* enter interrupt */rt_interrupt_enter();if (USART_GetITStatus(uart->uart_device, USART_IT_RXNE) != RESET){rt_hw_serial_isr(&serial2, RT_SERIAL_EVENT_RX_IND);//USART_IT_RXNE is cleared automatically by reading USART_DR in stm32_getc()
    }if (USART_GetITStatus(uart->uart_device, USART_IT_TC) != RESET){/* clear interrupt */USART_ClearITPendingBit(uart->uart_device, USART_IT_TC);}/* leave interrupt */rt_interrupt_leave();
}
#endif /* RT_USING_UART2 */

该中断函数在usart.c中,在RTT下的每一个中断服务子程序的入口都调用了rt_interrupt_enter(),在中断函数的子程序的出口则调用了rt_interrupt_leave()。

/* ISR for serial interrupt */
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{switch (event & 0xff){case RT_SERIAL_EVENT_RX_IND:{int ch = -1;rt_base_t level;struct rt_serial_rx_fifo* rx_fifo;rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;RT_ASSERT(rx_fifo != RT_NULL);/* interrupt mode receive */RT_ASSERT(serial->parent.open_flag & RT_DEVICE_FLAG_INT_RX);while (1){ch = serial->ops->getc(serial);if (ch == -1) break;/* disable interrupt */level = rt_hw_interrupt_disable();rx_fifo->buffer[rx_fifo->put_index] = ch;rx_fifo->put_index += 1;if (rx_fifo->put_index >= serial->config.bufsz) rx_fifo->put_index = 0;/* if the next position is read index, discard this 'read char' */if (rx_fifo->put_index == rx_fifo->get_index){rx_fifo->get_index += 1;if (rx_fifo->get_index >= serial->config.bufsz) rx_fifo->get_index = 0;}/* enable interrupt */rt_hw_interrupt_enable(level);}/* invoke callback */if (serial->parent.rx_indicate != RT_NULL){rt_size_t rx_length;/* get rx length */level = rt_hw_interrupt_disable();rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):(serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));rt_hw_interrupt_enable(level);serial->parent.rx_indicate(&serial->parent, rx_length);}break;}case RT_SERIAL_EVENT_TX_DONE:{struct rt_serial_tx_fifo* tx_fifo;tx_fifo = (struct rt_serial_tx_fifo*)serial->serial_tx;rt_completion_done(&(tx_fifo->completion));break;}case RT_SERIAL_EVENT_TX_DMADONE:{const void *data_ptr;rt_size_t data_size;const void *last_data_ptr;struct rt_serial_tx_dma* tx_dma;tx_dma = (struct rt_serial_tx_dma*) serial->serial_tx;rt_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0);if (rt_data_queue_peak(&(tx_dma->data_queue), &data_ptr, &data_size) == RT_EOK){/* transmit next data node */tx_dma->activated = RT_TRUE;serial->ops->dma_transmit(serial, data_ptr, data_size, RT_SERIAL_DMA_TX);}else{tx_dma->activated = RT_FALSE;}/* invoke callback */if (serial->parent.tx_complete != RT_NULL){serial->parent.tx_complete(&serial->parent, (void*)last_data_ptr);}break;}case RT_SERIAL_EVENT_RX_DMADONE:{int length;struct rt_serial_rx_dma* rx_dma;rx_dma = (struct rt_serial_rx_dma*)serial->serial_rx;/* get DMA rx length */length = (event & (~0xff)) >> 8;serial->parent.rx_indicate(&(serial->parent), length);rx_dma->activated = RT_FALSE;break;}}
}

该函数位于serial.c中,默认情况下usart的rt_device结构体中rx_indicate域被置空,因此不会运行这一段代码。如果使用rt_device_set_rx_indicate(rt_device_t dev, rt_err_t(* rx_ind)(rt_device_t dev, rt_size_t size))函数为一个串口设备注册了接收事件回调函数,在该串口接收到数据后,就会调用之前注册的rx_ind函数,将当前设备指针以及待读取的数据长度作为调用参数传递给用户。

转载于:https://www.cnblogs.com/King-Gentleman/p/4653011.html

RT thread 设备驱动组件之USART设备相关推荐

  1. linux设备驱动之总线、设备、驱动

    文章转载至多个地方,网上拼凑的一篇文章,说的好听一些的话那就叫自己总结的文章,只 是多次引用啊,哈哈,哎,不管了,反正这个有利用学习进步就好,这是重要的,文章转载过来要经过一篇大脑才能成为自己的,以后 ...

  2. 新字符设备驱动实验(自动分配设备号、自动创建应用层设备节点、新字符设备注册到内核的结构体)

    目录 自动分配和释放设备号 示例代码 新的字符设备注册到内核方法 字符设备结构体(前面的设备号也放进来) cdev_init结构体初始化函数 cdev_add 添加到linux内核 cdev_del内 ...

  3. Linux 设备驱动篇之I2c设备驱动

    ******************************************************************************************** 装载声明:希望 ...

  4. linux设备usb节点和硬件接口,所谓设备驱动即驱使硬件设备行动,带你深入理解linux的设备驱动......

    原标题:所谓设备驱动即驱使硬件设备行动,带你深入理解linux的设备驱动... 设备驱动最通俗的解释就是"驱使硬件设备行动".操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏 ...

  5. 设备驱动模型:总线-设备-驱动

    1 设备驱动模型简介 参考 以下内容: Linux 笔记: https://xuesong.blog.csdn.net/article/details/109522945?spm=1001.2014. ...

  6. Linux设备驱动01:Linux设备驱动概述

    目录 1. 设备驱动的作用 2. 有无操作系统时的设备驱动 2.1 无操作系统 2.1.1 硬件.驱动和应用程序的关系 2.1.2 单任务软件典型架构 2.2 有操作系统 2.2.1 硬件.驱动.操作 ...

  7. linux设备驱动第一篇:设备驱动程序简介

    为什么80%的码农都做不了架构师?>>>    首先,我们知道驱动是内核的一部分,那么驱动在内核中到底扮演了什么角色呢? 设备驱动程序在内核中的角色:他们是一个个独立的"黑 ...

  8. Linux 设备驱动--- 阻塞型字符设备驱动 --- O_NONBLOCK --- 非阻塞标志【转】

    阅读目录 1,以阻塞方式运行: 2,以非阻塞方式运行: 转自:http://blog.csdn.net/yikai2009/article/details/8653697 版权声明:本文为博主原创文章 ...

  9. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...

  10. linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点

    嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...

最新文章

  1. matlab各名称,Matlab-Simulink各模块对应的中文名称及介绍
  2. 磨刀不误砍柴功:App开发者必备之8大利器
  3. 兔子不吃窝边草,跳槽不跳窝边槽。。。
  4. 计算机网络与通信这门课难不难,【知乎】计算机网络这门课为何如此之难
  5. RPM ,yum工具
  6. C#数据本地存储方案之SQLite
  7. ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程
  8. 服务器kvm切换器维修,KVM切换器常见问题
  9. update 没有索引导致业务崩了,老板骂了一个小时
  10. 水烟炭行业调研报告 - 市场现状分析与发展前景预测
  11. 全网首发:怎样制作CDKEY(6)-CDKEY破解
  12. brother打印机清零步骤_兄弟打印机清零方法兄弟打印机清零方法步骤
  13. C#实现模糊PID算法的代码
  14. 网件6250刷Tomato 系统
  15. openGL细分着色器详解
  16. 微信卡包跳转小程序实现及解决
  17. 立创eda专业版学习笔记(3)(隐藏部分飞线)
  18. SD/SDIO/EMMC
  19. 抗锯齿插件MadGoat-SSAA使用需要注意的地方
  20. SX126x-数字接口SPI和控制功能

热门文章

  1. mysql+cls()_MySQL性能优化之show processlist(一)
  2. access用扫描枪输入_工业级高精度扫描枪适用在哪些环境
  3. 【渝粤教育】国家开放大学2018年秋季 0169-22T工程制图基础 参考试题
  4. 【渝粤教育】电大中专跨境电子商务理论与实务 (30)作业 题库
  5. 【渝粤题库】陕西师范大学152201 公共行政学
  6. 2017北理复试机试题
  7. [Android] Android 任务栈 【转载】
  8. 几校联考——day1题解
  9. 迪士尼收购福克斯,传媒巨头江山瓦解?
  10. Codeforces Round #273 (Div. 2) D. Red-Green Towers DP