硬件工作模式選擇分析
在serial_v2中,串口設(shè)備以應(yīng)用層視角,即阻塞模式或非阻塞模式來作為該串口設(shè)備的開啟標(biāo)志.
// 使用阻塞接收阻塞發(fā)送模式開啟uart_dev設(shè)備
rt_device_open(uart_dev,RT_DEVICE_FLAG_TX_BLOCKING|RT_DEVICE_FLAG_TX_BLOCKING);
以下為所有的設(shè)備開啟模式選擇:
#define RT_DEVICE_FLAG_RX_BLOCKING
#define RT_DEVICE_FLAG_RX_NON_BLOCKING
#define RT_DEVICE_FLAG_TX_BLOCKING
#define RT_DEVICE_FLAG_TX_NON_BLOCKING
對(duì)于其在底層傳輸中到底是采用輪詢,中斷,還是DMA,則取決于在rtconfig中對(duì)發(fā)送與接收緩沖區(qū)以及DMA相關(guān)的宏進(jìn)行配置.
//開啟DMA
#define RT_SERIAL_USING_DMA
//將串口1的發(fā)送緩沖區(qū)與接收緩沖區(qū)的大小設(shè)置為128
#define BSP_UART1_RX_BUFSIZE 128
#define BSP_UART1_TX_BUFSIZE 128
serial_v2串口設(shè)備框架對(duì)底層硬件工作模式的選擇原則大致可總結(jié)為:
(該原則對(duì)于TX與RX通用 )
緩沖區(qū)大小被設(shè)置為0,則使用輪詢模式.
若開啟DMA,緩沖區(qū)大小不為0,則優(yōu)先使用DMA模式。
若未開啟DMA,且緩存又并非為0,則使用中斷模式。
接下來,筆者將詳細(xì)分析,串口設(shè)備框架與驅(qū)動(dòng)是如何選擇硬件工作模式的.
在RT-Thread中,如果我們想使用串口,那我們的第一步將會(huì)是:
//假設(shè)以阻塞接收和阻塞發(fā)送模式打開串口
rt_device_open(uart_dev,RT_DEVICE_RX_BLOCKING|RT_DEVICE_TX_BLOCKING)
阻塞模式作為一種應(yīng)用層的視角,其底層可以由輪詢,中斷,或DMA中任意一種硬件工作模式實(shí)現(xiàn).
在serial_v2中,rt_device_open()函數(shù)將會(huì)調(diào)用框架層的rt_serial_init()與rt_serial_open()函數(shù),從而根據(jù)已有的配置,從三種硬件工作模式中選擇其中一種。
//串口初始化函數(shù)
//先將發(fā)送與接收緩沖區(qū)初始化為NULL, 然后對(duì)波特率等參數(shù)進(jìn)行設(shè)置
static rt_err_t rt_serial_init(struct rt_device * dev)
{
...
//先發(fā)送緩沖區(qū)與接收緩緩沖區(qū)賦值為空,方便日后初始化
serial->serial_rx = RT_NULL;
serial->serial_tx = RT_NULL;
...
if (serial->ops->configure)
//此次跳轉(zhuǎn)到驅(qū)動(dòng)層的函數(shù),對(duì)波特率等參數(shù)進(jìn)行配置
result = serial->ops->configure(serial, &serial->config);
}
//串口開啟函數(shù), 主要調(diào)用rt_serial_rx_enable與rt_serial_tx_enable函數(shù)會(huì)對(duì)硬件工作模式做出選擇
static rt_err_t rt_serial_open(struct rt_device *dev, rt_uint16_t oflag)
{
//根據(jù)設(shè)備開啟模式,對(duì)RX進(jìn)行初始化
if (serial->serial_rx == RT_NULL)
rt_serial_rx_enable(dev, dev->open_flag &(RT_SERIAL_RX_BLOCKINGRT_SERIAL_RX_NON_BLOCKING));
//根據(jù)設(shè)備開啟模式,對(duì)TX進(jìn)行初始化
if (serial->serial_tx == RT_NULL)
rt_serial_tx_enable(dev, dev->open_flag & (RT_SERIAL_TX_BLOCKING|RT_SERIAL_TX_NON_BLOCKING));
}
接下來我們先具體分析一下在rt_serial_open()中調(diào)用的rt_serial_rx_enable()如何選擇RX硬件工作模式
static rt_err_t rt_serial_rx_enable(struct rt_device * dev, rt_uint16_t rx_oflag) {
// 第一種選擇:如果接收緩沖區(qū)的大小為0,則首先使用輪詢接收
if (serial->config.rx_bufsz == 0) {
...
//將設(shè)備的接收函數(shù)指針指向輪詢接收
dev->read = _serial_poll_rx;
dev->open_flag |= RT_SERIAL_RX_BLOCKING;
...
}
//為接收緩沖區(qū)分配內(nèi)存,其緩沖區(qū)大小為在rtconfig中進(jìn)行設(shè)置
rx_fifo = (struct rt_serial_rx_fifo * ) rt_malloc
(sizeof(struct rt_serial_rx_fifo) + serial->config.rx_bufsz);
//初始化完成量,用于阻塞等待
rt_completion_init(&(rx_fifo->rx_cpt));
//調(diào)用 control(),根據(jù)已有的配置,決定使用DMA或中斷
serial->ops->control(serial,RT_DEVICE_CTRL_CONFIG, (void *) RT_SERIAL_RX_BLOCKING )
}
serial->ops->control會(huì)調(diào)用驅(qū)動(dòng)層的stm32_control(),對(duì)DMA或中斷進(jìn)行選擇,筆者僅將關(guān)鍵部分列出 :
static rt_err_t stm32_control(struct rt_serial_device *serial,
int cmd, void arg)
{
//RX模式下的工作模式選擇
if(ctrl_arg&( RT_DEVICE_FLAG_RX_BLOCKING | RT_DEVICE_FLAG_RX_NON_BLOCKING))
{
...
//先判斷DMA相關(guān)的宏是否被定義,如果是則將ctrl_arg賦值為DMA相關(guān)標(biāo)志,為下文初始化做準(zhǔn)備
if (uart->uart_dma_flag & RT_DEVICE_FLAG_DMA_RX)
ctrl_arg = RT_DEVICE_FLAG_DMA_RX;
else
ctrl_arg = RT_DEVICE_FLAG_INT_RX;
...
}
...
//如果ctrl_arg被賦予了DMA標(biāo)志相關(guān)的值,則選擇DMA模式,對(duì)DMA進(jìn)行初始化
if (ctrl_arg & (RT_DEVICE_FLAG_DMA_RX | RT_DEVICE_FLAG_DMA_TX)) {
#ifdef RT_SERIAL_USING_DMA
//具體分析見下文
stm32_dma_config(serial, ctrl_arg);
}
else
{
//如果未開啟DMA.則選擇中斷模式,則對(duì)中斷相關(guān)的函數(shù)進(jìn)行初始化
//具體分析見下文
stm32_control(serial, RT_DEVICE_CTRL_SET_INT, (void )ctrl_arg);
break;
}
}
stm32_control()對(duì)中斷接收進(jìn)行初始化:
static rt_err_t stm32_control(struct rt_serial_device *serial, int cmd, void *arg)
{
...
case RT_DEVICE_CTRL_SET_INT:
//設(shè)置單個(gè)中斷搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)
HAL_NVIC_SetPriority(uart->config->irq_type, 1, 0);
//設(shè)置使能中斷通道
HAL_NVIC_EnableIRQ(uart->config->irq_type);
//使能RXNE相關(guān)中斷
if (ctrl_arg == RT_DEVICE_FLAG_INT_RX)
__HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_RXNE);
break;
}
stm32_dma_config()對(duì)DMA接收進(jìn)行初始化
static void stm32_dma_config(struct rt_serial_device *serial, rt_ubase_t flag)
{
...
if (RT_DEVICE_FLAG_DMA_RX == flag)
{
//設(shè)置DMA接收數(shù)據(jù)所用的RX緩沖區(qū)
DMA_Handle = &uart->dma_rx.handle;
dma_config = uart->config->dma_rx;
...
if (RT_DEVICE_FLAG_DMA_RX == flag)
{
__HAL_LINKDMA(&(uart->handle), hdmarx, uart->dma_rx.handle);
}
}
...
//使能DMA相關(guān)中斷
if (flag == RT_DEVICE_FLAG_DMA_RX)
{
rx_fifo = (struct rt_serial_rx_fifo )serial->serial_rx;
RT_ASSERT(rx_fifo != RT_NULL);
/ Start DMA transfer /
if (HAL_UART_Receive_DMA(&(uart->handle), rx_fifo->buffer, serial->config.rx_bufsz) != HAL_OK)
{
/ Transfer error in reception process */
RT_ASSERT(0);
}
CLEAR_BIT(uart->handle.Instance->CR3, USART_CR3_EIE);
__HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_IDLE);
}
...
//此處省略其他的初始化函數(shù)
}
以上就是RX的硬件工作模式選擇,與對(duì)應(yīng)硬件初始化的部分代碼
接下來分析一下rt_serial_tx_enable()如何選擇硬件工作模式與初始化硬件:
static rt_err_t rt_serial_tx_enable(struct rt_device*dev, rt_uint16_t rx_oflag){
// 第一種情況:如果發(fā)送緩沖區(qū)的大小為0,則使用輪詢發(fā)送
if (serial->config.tx_bufsz == 0)
{
...
dev->read = _serial_poll_tx;
dev->open_flag |= RT_SERIAL_TX_BLOCKING;
...
}
//如果是阻塞模式,接下來就將調(diào)用底層control函數(shù),選擇DMA或中斷
if (tx_oflag == RT_SERIAL_TX_BLOCKING) {
optmode = serial->ops->control(serial,RT_DEVICE_CHECK_OPTMODE,
(void *)RT_DEVICE_FLAG_TX_BLOCKING);
*補(bǔ)充 : serial->ops->control()函數(shù)的源碼
*即如何在DMA與中斷中做出選擇
*RT_DEVICE_CHECK_OPTMODE:
- {
//如果開啟DMA,那么優(yōu)先使用DMA
if (ctrl_arg & RT_DEVICE_FLAG_DMA_TX)
return RT_SERIAL_TX_BLOCKING_NO_BUFFER;
else
//未開啟則使用中斷
return RT_SERIAL_TX_BLOCKING_BUFFER;
- }
//optmode的值,即上述調(diào)用函數(shù)的返回值有兩種情況:
//RT_SERIAL_TX_BLOCKING_BUFFER,使用框架層緩沖區(qū),即中斷發(fā)送,
//RT_SERIAL_TX_BLOCKING_NO_BUFFER,不使用框架層緩沖區(qū),即使用DMA發(fā)送
//中斷與DMA發(fā)送在軟件層面的一大區(qū)別是中斷發(fā)送需要使用框架層的發(fā)送緩沖區(qū)而DMA發(fā)送直接使用應(yīng)用層緩沖區(qū),不需要框架層分配緩沖區(qū)
if (optmode == RT_SERIAL_TX_BLOCKING_BUFFER){
//使用中斷發(fā)送
//則首先為tx緩沖區(qū)分配有tx_bufsz大小的空間
tx_fifo = (struct rt_serial_tx_fifo ) rt_malloc
(sizeof(struct rt_serial_tx_fifo)+serial->config.tx_bufsz);
//將設(shè)備的寫函數(shù)指針指向中斷發(fā)送函數(shù)
dev->write = _serial_fifo_tx_blocking_buf;
...
}
else
{
//使用DMA模式發(fā)送,此處緩沖區(qū)沒有分配tx_bufsz大小的空間
//DMA使用的是應(yīng)用層的buffer
tx_fifo = (struct rt_serial_tx_fifo ) rt_malloc
(sizeof(struct rt_serial_tx_fifo));
//初始化DMA
serial->ops->control(serial,RT_DEVICE_CTRL_CONFIG,
(void *)RT_SERIAL_TX_BLOCKING);
//因?yàn)槭褂貌皇褂每蚣軐咏邮站彌_區(qū),因此將其設(shè)置為NULL
rt_memset(&tx_fifo->rb, RT_NULL, sizeof(tx_fifo->rb));
...
}
//將激活標(biāo)識(shí)設(shè)置為false,激活標(biāo)識(shí)用于標(biāo)記發(fā)送緩存是否被使用
tx_fifo->activated = RT_FALSE;
//阻塞發(fā)送需要初始化完成量
rt_completion_init(&(tx_fifo->tx_cpt));
...
}
}
stm32_control對(duì)中斷發(fā)送進(jìn)行初始化:
static rt_err_t stm32_control(struct rt_serial_device *serial, int cmd, void *arg)
{
...
case RT_DEVICE_CTRL_SET_INT:
//設(shè)置單個(gè)中斷搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)
HAL_NVIC_SetPriority(uart->config->irq_type, 1, 0);
//設(shè)置使能中斷通道
HAL_NVIC_EnableIRQ(uart->config->irq_type);
//使能TXE相關(guān)中斷
if (ctrl_arg == RT_DEVICE_FLAG_INT_TX)
__HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_TXE);
break;
}
stm32_dma_config對(duì)DMA發(fā)送進(jìn)行初始化:
static void stm32_dma_config(struct rt_serial_device *serial, rt_ubase_t flag)
{
...
else //RT_DEVICE_FLAG_DMA_TX == flag
{
//對(duì)相關(guān)句柄進(jìn)行賦值
DMA_Handle = &uart->dma_tx.handle;
dma_config = uart->config->dma_tx;
}
...
if (RT_DEVICE_FLAG_DMA_TX == flag)
{
//設(shè)置好DMA的發(fā)送緩沖區(qū)
__HAL_LINKDMA(&(uart->handle), hdmatx, uart->dma_tx.handle);
}
...
//此處省略其他的初始化函數(shù)
}
目前通過分析,我們理清楚了串口設(shè)備框架是如何與驅(qū)動(dòng)合作,對(duì)底層的硬件工作方式,根據(jù)已有的配置做出選擇,并對(duì)選擇的硬件工作方式進(jìn)行相關(guān)的初始化工作,接下來,我們將分析serial_v2在阻塞模式下的具體工作流程.
阻塞模式
阻塞模式下的數(shù)據(jù)接收
數(shù)據(jù)接收函數(shù)分析
static rt_ssize_t rt_serial_read(struct rt_device *dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
...
//如果設(shè)置了接收緩沖區(qū),則調(diào)用_serial_fifo_rx(),即使用中斷或DMA模式接收
if (serial->config.rx_bufsz)
{
return _serial_fifo_rx(dev, pos, buffer, size);
}
//否則緩沖區(qū)大小為0,使用輪詢模式,此處結(jié)論與上文一致
return _serial_poll_rx(dev, pos, buffer, size);
}
輪詢
輪詢,就是CPU通過不斷地查詢某個(gè)外部設(shè)備的狀態(tài),如果外部設(shè)備準(zhǔn)備好,就可以向其發(fā)送數(shù)據(jù)或者讀取數(shù)據(jù)。然而,這種方式由于CPU不斷查詢總線,導(dǎo)致指令執(zhí)行受到影響,效率較低。 以下是serial_v2中輪詢_serial_poll_rx()函數(shù)的部分源碼.
//驅(qū)動(dòng)框架層函數(shù)
rt_ssize_t _serial_poll_rx(struct rt_device *dev,
rt_off_t pos,
void *buffer,
rt_size_t size) {
...
while(size)
{
//調(diào)用驅(qū)動(dòng)層函數(shù)去讀一個(gè)數(shù)據(jù)
getc_element = serial->ops->getc(serial);
if (getc_element == -1) break;
*getc_buffer = getc_element;
++ getc_buffer;
-- size;
}
return getc_size - size;
}
其中serial->ops->getc(serial)在底層驅(qū)動(dòng)中調(diào)用stm32_getc()
以下為stm32_getc()部分源碼
//驅(qū)動(dòng)層函數(shù)
static int stm32_getc(struct rt_serial_device *serial){
...
ch = -1;
if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET)
ch = UART_GET_RDR(&uart->handle,stm32_uart_get_mask(...));
return ch;
}
通過以上代碼,我們可以發(fā)現(xiàn),輪詢的底層實(shí)現(xiàn)就是就是不斷檢查RXNE(接收數(shù)據(jù)寄存器非空標(biāo)志位),然后從RDR(接收數(shù)據(jù)寄存器)中讀取數(shù)據(jù),如果某一次RXNE寄存器被復(fù)位,那么_serial_poll_rx()函數(shù)會(huì)直接返回已經(jīng)讀到的數(shù)據(jù)的數(shù)量,結(jié)束輪詢接收。
中斷
中斷方式克服了CPU輪詢外部設(shè)備的缺點(diǎn),正常情況下,CPU執(zhí)行指令,不會(huì)主動(dòng)去檢查外部設(shè)備的狀態(tài)。外部設(shè)備準(zhǔn)備好之后,向CPU發(fā)送中斷信號(hào),CPU收到中斷信號(hào)后停止當(dāng)前的工作,會(huì)根據(jù)中斷信號(hào)指定的設(shè)備號(hào)處理相應(yīng)的設(shè)備。這種處理方式既不影響CPU的工作,也能保證外部設(shè)備的數(shù)據(jù)得到及時(shí)處理,工作效率很高.
由上文rt_serial_read()函數(shù)分析可知,中斷接收是由_serial_fifo_rx(dev, pos, buffer, size)實(shí)現(xiàn)的。
//驅(qū)動(dòng)框架層中斷接收部分代碼
static rt_ssize_t _serial_fifo_rx(struct rt_device *dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
if (dev->open_flag & RT_SERIAL_RX_BLOCKING)
{
...
//此處可以注意到,阻塞接收函數(shù)第一步是檢查應(yīng)用層想讀的數(shù)據(jù)量是否大于緩存,
//如果大于,函數(shù)將不工作,直接返回,并提示需要改變緩沖區(qū)大小設(shè)置
if (size > serial->config.rx_bufsz)
{
LOG_W("(%s) serial device received data:[%d] larger than " "rx_bufsz:[%d], please increase the BSP_UARTx_RX_BUFSIZE option",dev->parent.name, size, serial->config.rx_bufsz);
return 0;
}
//計(jì)算中斷接收到緩沖區(qū)的字節(jié)數(shù)量
recv_len = rt_ringbuffer_data_len(&(rx_fifo->rb));
//如果收到的數(shù)據(jù)小于應(yīng)用層要求的數(shù)據(jù),那么調(diào)用rt_completion_wait陷入等待
//直至有足夠的數(shù)據(jù)才退出
if (recv_len < size)
{
rx_fifo->rx_cpt_index = size;
//與下文中斷服務(wù)函數(shù)中完成量的喚醒遙相呼應(yīng),從而在應(yīng)用層視角看是以阻塞模式進(jìn)行數(shù)據(jù)接收
rt_completion_wait(&(rx_fifo->rx_cpt),RT_WAITING_FOREVER);
}
//關(guān)中斷,確保多線程下不會(huì)出現(xiàn)競(jìng)態(tài)條件
level = rt_hw_interrupt_disable();
//將緩沖區(qū)的數(shù)據(jù)轉(zhuǎn)移到應(yīng)用層緩沖區(qū)
recv_len = rt_ringbuffer_get(&(rx_fifo->rb), buffer, size);
rt_hw_interrupt_enable(level);
}
}
看到這里可能有人會(huì)問,怎么數(shù)據(jù)突然就跑到接收緩沖區(qū)了,數(shù)據(jù)是在什么時(shí)候被放進(jìn)去的,詳細(xì)的流程是什么?接下來,筆者就通過一個(gè)場(chǎng)景以及相關(guān)的代碼大致來說明中斷接收的整個(gè)流程.
說明:
RDR: RX Data Register 接收數(shù)據(jù)寄存器
RXNE: RX Data Register Not Empty 接收數(shù)據(jù)寄存器為空
uart_isr() : 驅(qū)動(dòng)層中斷服務(wù)函數(shù)
rt_hw_serial_isr() : 驅(qū)動(dòng)框架層中斷服務(wù)函數(shù)
rx_fifo: 接收緩沖區(qū)
假設(shè)接收緩沖區(qū)大小為128,即在etconfig中設(shè)置#define BSP_UART1_RX_BUFSIZE 128
調(diào)用rt_device_open(uart_dev, RT_SERIAL_RX_BLOCKING),與rt_device_read(uart_dev,buffer,128),即應(yīng)用層一次讀取128個(gè)數(shù)據(jù)
用戶輸入一段字符
設(shè)備收到字符觸發(fā)RXNE中斷
驅(qū)動(dòng)層中斷服務(wù)函數(shù)uart_isr()接收RXNE中斷,將字符傳入緩沖區(qū),并調(diào)用rt_hw_serial_isr()
框架層中斷服務(wù)函數(shù)rt_hw_serial_isr()根據(jù)rx_fifo接收的數(shù)據(jù)量,來判斷阻塞接收是否結(jié)束,若接收足夠的數(shù)據(jù)則喚醒完成量,并設(shè)置回調(diào)函數(shù)的參數(shù)
若rx_fifo中未獲取足夠的數(shù)據(jù),重復(fù)4 - 6步,直至接收完成,此時(shí)rt_device_read()函數(shù)結(jié)束,返回接收字節(jié)數(shù)量.
//驅(qū)動(dòng)層中斷服務(wù)函數(shù)
static void uart_isr(struct rt_serial_device *serial)
{
...
//如果RXNE未被復(fù)位,且接收到RXNE中斷,則從RDR寄存器中取數(shù)據(jù)
if ((__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET) &&
(__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_RXNE) != RESET))
{
//調(diào)用環(huán)形緩沖區(qū)ringbuff的putchar(),將讀取的一個(gè)字節(jié)放入ringbuffer
rt_ringbuffer_putchar(&(rx_fifo->rb), UART_GET_RDR(&
uart->handle, stm32_uart_get_mask(...)));
//調(diào)用設(shè)備框架層中斷服務(wù)函數(shù),做后續(xù)的處理,決定讀取是否完成,見下一個(gè)代碼塊
rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);
}
...
}
//框架層中斷服務(wù)函數(shù)
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
...
case RT_SERIAL_EVENT_RX_IND:
//計(jì)算RX緩沖區(qū)的數(shù)據(jù)量
rx_length = rt_ringbuffer_data_len(&rx_fifo->rb);
if (serial->parent.open_flag & RT_SERIAL_RX_BLOCKING)
{
//如果ringbuffer緩沖區(qū)中有足夠的數(shù)據(jù),那么可以調(diào)用完成量的喚醒函數(shù)
//喚醒上文中rt_serial_read()中等待的完成量
if (rx_fifo->rx_cpt_index && rx_length >= rx_fifo->rx_cpt_index)
{
rx_fifo->rx_cpt_index = 0;
//代表此次數(shù)據(jù)接收完成
//此處的喚醒與上文完成量的等待遙相呼應(yīng)
rt_completion_done(&(rx_fifo->rx_cpt));
}
if (serial->parent.rx_indicate != RT_NULL)
//為回調(diào)函數(shù)函數(shù)設(shè)置參數(shù)
serial->parent.rx_indicate(&(serial->parent), rx_length);
}
}
總結(jié),驅(qū)動(dòng)層保證數(shù)據(jù)從RDR寄存器到rx_fifo,即接收緩沖區(qū),框架層則統(tǒng)計(jì)tx_fifo中的數(shù)據(jù)是否符合應(yīng)用層要求,,如果符合要求,則將數(shù)據(jù)復(fù)制到應(yīng)用層緩沖區(qū),二者有機(jī)結(jié)合,形成阻塞讀取.
DMA
中斷方式效率雖然很高,但是對(duì)于大量數(shù)據(jù)的傳輸就顯得力不從心。大量的中斷會(huì)導(dǎo)致CPU忙于處理中斷而減少對(duì)指令的處理,效率會(huì)變的很低。對(duì)于大量的數(shù)據(jù)傳輸可以不通過CPU而直接傳送到內(nèi)存,這種方式叫做DMA(DIrect Memory Access)。使用DMA方式,外部設(shè)備在數(shù)據(jù)準(zhǔn)備好之后只需向DMA控制器發(fā)送一個(gè)命令,把數(shù)據(jù)的地址和大小傳送過去,由DMA控制器負(fù)責(zé)把數(shù)據(jù)從外部設(shè)備直接存放到內(nèi)存因此,DMA方式適合處理大量的數(shù)據(jù)。
由上文rt_serial_read()函數(shù)分析可知,DMA接收是由_serial_fifo_rx(dev, pos, buffer, size)實(shí)現(xiàn)的.雖然DMA模式接收的串口設(shè)備框架代碼與中斷接收一致,但是在中斷服務(wù)函數(shù)部分還是有很多不同.
//驅(qū)動(dòng)框架層函數(shù)
static rt_ssize_t _serial_fifo_rx(struct rt_device *dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
//中斷接收部分代碼
if (dev->open_flag & RT_SERIAL_RX_BLOCKING)
{
...
//此處可以注意到,阻塞接收的函數(shù)第一步是檢查應(yīng)用層想讀的數(shù)據(jù)量是否大于緩存,
//如果大于,函數(shù)將直接返回,并提示需要改變緩沖區(qū)大小設(shè)置
if (size > serial->config.rx_bufsz)
{
LOG_W("(%s) serial device received data:[%d] larger than " "rx_bufsz:[%d], please increase the BSP_UARTx_RX_BUFSIZE option",dev->parent.name, size, serial->config.rx_bufsz);
return 0;
}
//計(jì)算中斷接收到緩沖區(qū)的字節(jié)數(shù)量
recv_len = rt_ringbuffer_data_len(&(rx_fifo->rb));
//如果收到的數(shù)據(jù)小于應(yīng)用層要求的數(shù)據(jù),那么調(diào)用rt_completion_wait陷入等待
//直至有足夠的數(shù)據(jù)才退出
if (recv_len < size)
{
rx_fifo->rx_cpt_index = size;
//與下文中斷服務(wù)函數(shù)中完成量的喚醒遙相呼應(yīng),從而在應(yīng)用層視角看是以阻塞模式進(jìn)行數(shù)據(jù)接收
rt_completion_wait(&(rx_fifo->rx_cpt), RT_WAITING_FOREVER);
}
//關(guān)中斷,確保多線程下不會(huì)出現(xiàn)競(jìng)態(tài)條件
level = rt_hw_interrupt_disable();
//將緩沖區(qū)的數(shù)據(jù)轉(zhuǎn)移到應(yīng)用層緩沖區(qū)
recv_len = rt_ringbuffer_get(&(rx_fifo->rb), buffer, size);
rt_hw_interrupt_enable(level);
}
}
同樣,我們?cè)俅瓮ㄟ^一個(gè)場(chǎng)景以及相關(guān)的代碼大致來說明DMA接收的整個(gè)流程
說明:
IDLE: 檢測(cè)到總線空閑中斷
uart_isr() : 驅(qū)動(dòng)層中斷服務(wù)函數(shù)
dma_recv_isr() : 驅(qū)動(dòng)層DMA接收中斷處理函數(shù)
rt_hw_serial_isr() : 驅(qū)動(dòng)框架層中斷服務(wù)函數(shù)
rx_fifo: 接收緩沖區(qū)
調(diào)rt_device_open(),rt_device_read()
DMA外設(shè)讀取用戶輸入的一批字符
DMA將字符放入緩沖區(qū)后觸發(fā)IDLE中斷
中斷服務(wù)函數(shù)uart_isr()接收IDLE中斷,調(diào)用dma_recv_isr()計(jì)算接收字符數(shù)量
dma_recv_isr()最后調(diào)用rt_hw_serial_isr(),根據(jù)緩沖區(qū)數(shù)據(jù)數(shù)量來決定是否喚醒完成量,即如果數(shù)據(jù)不夠則不喚醒,繼續(xù)接收數(shù)據(jù),最后設(shè)置回調(diào)函數(shù)的參數(shù)
重復(fù)2 - 5步, 直至接收完成,此時(shí)rt_device_read()中的完成量被喚醒,讀取函數(shù)結(jié)束,返回接收字節(jié)數(shù)量
static void uart_isr(struct rt_serial_device *serial)
{
#ifdef RT_SERIAL_USING_DMA
//查看IDLE中斷是否發(fā)生
if ((uart->uart_dma_flag)&&(__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_IDLE) != RESET)&&
(__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_IDLE) != RESET))
{
//調(diào)用設(shè)備框架層中斷處理函數(shù)
dma_recv_isr(serial, UART_RX_DMA_IT_IDLE_FLAG);
__HAL_UART_CLEAR_IDLE_FLAG(&uart->handle);
}
#endif
}
static void dma_recv_isr(struct rt_serial_device *serial,
rt_uint8_t isr_flag) {
...
//省略計(jì)算接收數(shù)據(jù)量的過程
if (recv_len) {
//將接收的數(shù)據(jù)量一同發(fā)往串口設(shè)備框架層
uart->dma_rx.remaining_cnt = counter;
rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_DMADONE
| (recv_len << 8));
}
...
}
//串口設(shè)備框架層中斷服務(wù)函數(shù)
void rt_hw_serial_isr(struct rt_serial_device * serial, int event) {
//獲取DMA接收的數(shù)據(jù)量
rx_length = (event & (~0xff)) >> 8;
if (rx_length)
{
//關(guān)中斷,避免并發(fā)bug
level = rt_hw_interrupt_disable();
//更新環(huán)形緩沖區(qū)的索引
rt_serial_update_write_index(&(rx_fifo->rb),rx_length);
//更新完成后再開中斷
rt_hw_interrupt_enable(level);
}
if (serial->parent.open_flag & RT_SERIAL_RX_BLOCKING)
{
//如果緩沖區(qū)數(shù)據(jù)足夠則喚醒完成量
if (rx_fifo->rx_cpt_index && rx_length >= rx_fifo->rx_cpt_index )
{
rx_fifo->rx_cpt_index = 0;
//喚醒完成量
rt_completion_done(&(rx_fifo->rx_cpt));
}
}
}
總結(jié),DMA接收與中斷接收很多代碼是復(fù)用的,其思路與中斷接收及其相似,但在接收數(shù)據(jù)方面,DMA獨(dú)立自行接收數(shù)據(jù),不需要像中斷接收一樣,每次讀取數(shù)據(jù)都占用CPU,從RDR寄存器拿到數(shù)據(jù),再放入緩沖區(qū)中.在DMA模式下,中斷處理程序接收到TC中斷時(shí),數(shù)據(jù)已然在接收緩沖區(qū)中.因此DMA接收的效率比中斷更高.
阻塞模式下的數(shù)據(jù)發(fā)送
數(shù)據(jù)發(fā)送函數(shù)分析
數(shù)據(jù)發(fā)送也遵循最開始提出的硬件工作模式選擇,即緩沖區(qū)為0則使用輪詢,開啟DMA則優(yōu)先使用DMA,否則使用中斷.
static rt_ssize_t rt_serial_write(struct rt_device *dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
...
if (serial->config.tx_bufsz == 0)
{
//發(fā)送緩沖區(qū)為0則,使用輪詢
return _serial_poll_tx(dev, pos, buffer, size);
}
if (dev->open_flag & RT_SERIAL_TX_BLOCKING)
{
//如果tx緩沖區(qū)為空指針,則使用DMA發(fā)送
//因?yàn)镈MA發(fā)送只需要應(yīng)用層緩沖區(qū),不需要框架層緩沖區(qū)
if ((tx_fifo->rb.buffer_ptr) == RT_NULL)
{
return _serial_fifo_tx_blocking_nbuf(dev, pos, buffer, size);
}
//需要用到框架層緩沖區(qū),則使用中斷發(fā)送
return _serial_fifo_tx_blocking_buf(dev, pos, buffer, size);
}
...
}
輪詢
//框架層輪詢發(fā)送函數(shù)
rt_ssize_t _serial_poll_tx(struct rt_device *dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
...
//核心代碼
while (size)
{
//調(diào)用驅(qū)動(dòng)層函數(shù),一次將一個(gè)字節(jié)發(fā)送出去,詳解見下文
serial->ops->putc(serial, *putc_buffer);
++ putc_buffer;
-- size;
}
return putc_size - size;
}
//驅(qū)動(dòng)層函數(shù)
//serial->ops->putc實(shí)際為串口驅(qū)動(dòng)中的stm32_putc函數(shù):
static int stm32_putc(struct rt_serial_device *serial, char c)
{
//重置傳輸完成中斷標(biāo)志(ISR寄存器中)
UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);
//將一個(gè)字節(jié)的數(shù)據(jù)放入TDR寄存器中
UART_SET_TDR(&uart->handle, c);
//陷入等待,直到獲取到傳輸完成標(biāo)志位(TC),此處體現(xiàn)了阻塞發(fā)送,即未發(fā)送完成則一直等待
while (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);
return 1;
}
中斷
//框架層中斷發(fā)送函數(shù)
static rt_ssize_t _serial_fifo_tx_blocking_buf(
struct rt_device *dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
...
//當(dāng)激活標(biāo)識(shí)為TRUE,代表tx_fifo即發(fā)送緩沖區(qū)正在被使用,因此直接返回0,不發(fā)送數(shù)據(jù)
if (tx_fifo->activated == RT_TRUE) return 0;
//之前tx_fifo未被使用,則現(xiàn)在將其設(shè)置為TRUE,表示被當(dāng)前發(fā)送線程中的中斷發(fā)送占用
tx_fifo->activated = RT_TRUE;
//阻塞發(fā)送的核心代碼
while (size)
{
//將應(yīng)用層中的數(shù)據(jù)先復(fù)制一份到發(fā)送緩沖區(qū)中
tx_fifo->put_size = rt_ringbuffer_put(&(tx_fifo->rb),
(rt_uint8_t *)buffer + offset,size);
//調(diào)用底層驅(qū)動(dòng)的傳輸函數(shù),發(fā)送數(shù)據(jù)
serial->ops->transmit(serial,
(rt_uint8_t *)buffer + offset,
tx_fifo->put_size,
RT_SERIAL_TX_BLOCKING);
//更新已經(jīng)發(fā)送的數(shù)據(jù)量
offset += tx_fifo->put_size;
size -= tx_fifo->put_size;
//等待傳輸完成
rt_completion_wait(&(tx_fifo->tx_cpt), RT_WAITING_FOREVER);
}
return length;
}
關(guān)于上文的函數(shù)調(diào)用:
serial->ops->transmit()函數(shù)會(huì)調(diào)用驅(qū)動(dòng)層的stm32_transmit()
stm_32transmit()又直接會(huì)調(diào)用驅(qū)動(dòng)層的stm32_control()對(duì)中斷進(jìn)行初始化.,從而進(jìn)行發(fā)送
我們直接看stm32_control(serial, RT_DEVICE_CTRL_SET_INT, (void * )tx_flag);
//驅(qū)動(dòng)層中斷發(fā)送函數(shù)
static rt_err_t stm32_control(struct rt_serial_device *serial,
int cmd, void *arg)
{
...
case RT_DEVICE_CTRL_SET_INT:
//設(shè)置單個(gè)中斷搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)
HAL_NVIC_SetPriority(uart->config->irq_type, 1, 0);
//設(shè)置使能中斷通道
HAL_NVIC_EnableIRQ(uart->config->irq_type);
if (ctrl_arg == RT_DEVICE_FLAG_INT_TX)
//開啟TXE中斷
__HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_TXE);
break;
...
}
此處,我們結(jié)合中斷,來梳理中斷發(fā)送的大致流程:
說明:
TDR: TX Data Register 發(fā)送數(shù)據(jù)寄存器
TXE: TX Data Register Empty 發(fā)送數(shù)據(jù)寄存器為空
TC: Transmission Complete 發(fā)送完成
uart_isr() : 驅(qū)動(dòng)層中斷服務(wù)函數(shù)
rt_hw_serial_isr() : 驅(qū)動(dòng)框架層中斷服務(wù)函數(shù)
tx_fifo: 發(fā)送緩沖區(qū)
假設(shè)有1024字節(jié)的數(shù)據(jù)通過串口發(fā)送,發(fā)送緩沖區(qū)設(shè)置為128;
用戶調(diào)用rt_device_open(uart_dev,RT_SERIAL_TX_BLOCKING)與rt_device_write(uart_dev,buffer,1024);
1024字節(jié)中先有128個(gè)字節(jié)先被復(fù)制到緩沖區(qū),調(diào)用驅(qū)動(dòng)層stm32_transmit(),初始化中斷相關(guān)函數(shù),啟動(dòng)發(fā)送
中斷服務(wù)函數(shù)uart_isr()檢測(cè)到TXE中斷,將一個(gè)字節(jié)放入TDR中
(在TDR中的數(shù)據(jù)后續(xù)會(huì)被轉(zhuǎn)移至移位寄存器,TDR因此又會(huì)空出來)
中斷服務(wù)函數(shù)uart_isr()會(huì)接收后續(xù)的TXE中斷,從而將tx_fifo中的數(shù)據(jù)都被依次送往TDR寄存器
當(dāng)緩沖區(qū)中沒有可發(fā)送的數(shù)據(jù)時(shí),關(guān)閉TXE中斷,開啟TC中斷
如果檢測(cè)到TC中斷,則代表移位寄存器中所有的數(shù)據(jù)都被發(fā)送完,此時(shí)關(guān)閉TC中斷,驅(qū)動(dòng)層中斷服務(wù)函數(shù)uart_isr()調(diào)用應(yīng)用層中斷服務(wù)函數(shù)rt_hw_serial_isr()
在rt_hw_serial_isr()中,檢測(cè)tx_fifo是否為空,為空喚醒完成量,rt_device_write()進(jìn)行新一輪的組阻塞發(fā)送
當(dāng)所有1024個(gè)數(shù)據(jù)被分8次發(fā)送完成,rt_device_write()返回,否則重復(fù)3-8步
static void uart_isr(struct rt_serial_device *serial)
{
//TXE標(biāo)志位被設(shè)立,且檢測(cè)到TXE中斷
if ((__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TXE) != RESET) &&(__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_TXE)) != RESET)
{
...
//如果能夠從tx_fifo中讀到一個(gè)字節(jié)的數(shù)據(jù),則將其發(fā)送到TDR中,
if (rt_ringbuffer_getchar(&(tx_fifo->rb), &put_char))
{
UART_SET_TDR(&uart->handle, put_char);
}
else
{
//如果沒有數(shù)據(jù)可以發(fā)送,則關(guān)TXE中斷,開TC中斷
__HAL_UART_DISABLE_IT(&(uart->handle), UART_IT_TXE);
__HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_TC);
}
}
...
//TC標(biāo)志位被設(shè)立,且檢測(cè)到TC中斷
if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) &&
(__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_TC) != RESET))
{
//傳輸完成,則關(guān)閉TC中斷,調(diào)用驅(qū)動(dòng)框架層中斷服務(wù)函數(shù)rt_hw_serial_isr()
__HAL_UART_DISABLE_IT(&(uart->handle), UART_IT_TC);
rt_hw_serial_isr(serial, RT_SERIAL_EVENT_TX_DONE);
}
}
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
case RT_SERIAL_EVENT_TX_DONE:
{
//獲取tx_fifo中的數(shù)據(jù)長度
tx_length = rt_ringbuffer_data_len(&tx_fifo->rb);
if (tx_length == 0)
{
tx_fifo->activated = RT_FALSE;
//tx_fifo中的數(shù)據(jù)全部發(fā)送完,則喚醒完成量,進(jìn)行下一次發(fā)送
if (serial->parent.tx_complete != RT_NULL)
serial->parent.tx_complete(&serial->parent, RT_NULL);
//設(shè)置回調(diào)函數(shù)
if (serial->parent.open_flag & RT_SERIAL_TX_BLOCKING)
rt_completion_done(&(tx_fifo->tx_cpt));
break;
}
}
}
一個(gè)可能存在的問題:
因?yàn)橹袛喟l(fā)送應(yīng)用層數(shù)據(jù)量可能會(huì)大于發(fā)送緩沖區(qū)大小,因此可能需要多次發(fā)送,例如上文發(fā)送1024個(gè)數(shù)據(jù)需要將數(shù)據(jù)分8次拷貝到緩存中,然而,在上述代碼中我們可以發(fā)現(xiàn),在一次發(fā)送完,tx_fifo的激活標(biāo)識(shí)就被設(shè)置為flase了,這意味者,還在工作的tx_fifo被標(biāo)注為不工作,那么在多線程環(huán)境下,這很可能會(huì)導(dǎo)致惡性的bug,針對(duì)此問題,筆者已經(jīng)提了相關(guān)的pr(#7997).
//有問題的代碼
if (tx_length == 0)
{
tx_fifo->activated = RT_FALSE;
...
}
總結(jié),中斷發(fā)送跟輪詢發(fā)送明顯的區(qū)別在于,中斷發(fā)送是直接將數(shù)據(jù)放入RDR(RX Data Register),然后返回,而輪詢發(fā)送,有用while死等TC中斷這一過程.因此中斷發(fā)送相較于輪詢發(fā)送,減少了對(duì)于CPU的占用率.
//中斷發(fā)送
UART_SET_TDR(&uart->handle, put_char);
//輪詢發(fā)送
UART_SET_TDR(&uart->handle, c);
while (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);
DMA
//DMA發(fā)送 框架層函數(shù)
static rt_ssize_t _serial_fifo_tx_blocking_nbuf(
struct rt_device *dev, rt_off_t pos,
const void *buffer, rt_size_t size)
{
if (tx_fifo->activated == RT_TRUE) return 0;
//將激活標(biāo)識(shí)設(shè)置為TRUE
tx_fifo->activated = RT_TRUE;
//開啟DMA發(fā)送
rst = serial->ops->transmit(serial,
(rt_uint8_t *)buffer,
size,
RT_SERIAL_TX_BLOCKING);
//等待完成量的喚醒
rt_completion_wait(&(tx_fifo->tx_cpt), RT_WAITING_FOREVER);
return rst;
}
//DMA發(fā)送 驅(qū)動(dòng)層函數(shù)
static rt_ssize_t stm32_transmit(struct rt_serial_device *serial,
rt_uint8_t *buf,
rt_size_t size,
rt_uint32_t tx_flag)
{
if (uart->uart_dma_flag & RT_DEVICE_FLAG_DMA_TX)
{
//調(diào)用DMA傳輸
HAL_UART_Transmit_DMA(&uart->handle, buf, size);
return size;
}
}
DMA的大致傳輸流程:
說明:
TC: Transmission Complete
假設(shè)有256字節(jié)的數(shù)據(jù)通過串口發(fā)送,發(fā)送緩沖區(qū)設(shè)置為256
用戶調(diào)用rt_device_open(uart_dev,RT_SERIAL_TX_BLOCKING)與rt_device_write(uart_dev,buffer,256);
驅(qū)動(dòng)層調(diào)用HAL_UART_Transmit_DMA(),開啟DMA數(shù)據(jù)發(fā)送
數(shù)據(jù)發(fā)送完成后,驅(qū)動(dòng)層中斷服務(wù)函數(shù)uart_isr()會(huì)接收TC中斷,繼而調(diào)用HAL_UART_TxCpltCallback函數(shù)中的rt_hw_serial_isr().在驅(qū)動(dòng)框架層的中斷服務(wù)函數(shù)中,會(huì)喚醒完成量,結(jié)束一次DMA發(fā)送
//DMA發(fā)送 驅(qū)動(dòng)層函數(shù)
static void uart_isr(struct rt_serial_device *serial)
{
...
//接收到TC中斷
if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) &&
(__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_TC) != RESET))
{
if (uart->uart_dma_flag & RT_DEVICE_FLAG_DMA_TX)
{
//HAL_UART_TxCpltCallback()將被調(diào)用
HAL_UART_IRQHandler(&(uart->handle));
}
}
}
//DMA發(fā)送 驅(qū)動(dòng)層函數(shù)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
//計(jì)算發(fā)送數(shù)量
level = rt_hw_interrupt_disable();
trans_total_index = __HAL_DMA_GET_COUNTER(&(uart->dma_tx.handle));
rt_hw_interrupt_enable(level);
if (trans_total_index) return;
//調(diào)用驅(qū)動(dòng)層中斷服務(wù)函數(shù)
rt_hw_serial_isr(serial, RT_SERIAL_EVENT_TX_DMADONE);
}
//DMA發(fā)送驅(qū)動(dòng)框架層函數(shù)
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
case RT_SERIAL_EVENT_TX_DMADONE:
{
//將激活標(biāo)志設(shè)置為FALSE,其他線程可以使用串口發(fā)送數(shù)據(jù)
tx_fifo->activated = RT_FALSE;
//設(shè)置回調(diào)函數(shù)的實(shí)參
if (serial->parent.tx_complete != RT_NULL)
serial->parent.tx_complete(&serial->parent, RT_NULL);
if (serial->parent.open_flag & RT_SERIAL_TX_BLOCKING)
{
//喚醒完成量
rt_completion_done(&(tx_fifo->tx_cpt));
break;
}
}
}
DMA發(fā)送只需要調(diào)用HAL_UART_Transmit_DMA(),開啟DMA發(fā)送,然后等待TC中斷,發(fā)送的具體過程都由DMA自行處理,不需要像中斷一樣,還需要進(jìn)行持續(xù)接收TXE中斷,將數(shù)據(jù)放入TDR寄存器,以及關(guān)閉TXE中斷開啟TC中斷等一系列操作,因此進(jìn)一步降低了CPU的占用率,節(jié)省系統(tǒng)資源.
寫在最后
以上就是對(duì)于串口設(shè)備框架serial_v2的源碼分析,鑒于篇幅,只分析了阻塞模式,非阻塞模式的整體框架與設(shè)置模式是相同的。
-
驅(qū)動(dòng)器
+關(guān)注
關(guān)注
52文章
8101瀏覽量
145822 -
緩沖器
+關(guān)注
關(guān)注
6文章
1911瀏覽量
45425 -
STM32
+關(guān)注
關(guān)注
2264文章
10854瀏覽量
354305 -
串口中斷
+關(guān)注
關(guān)注
0文章
64瀏覽量
13847 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1261瀏覽量
39840
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論