前言
“serialX” 我起的名字,起個(gè)名字想破頭。
在前一篇文章里,大致提出了我的串口驅(qū)動(dòng)框架理論。里面做了一些對(duì)串口驅(qū)動(dòng)特性的幻想。也在 NUC970 芯片下通過了中斷模式的實(shí)踐驗(yàn)證。但是,因?yàn)?NUC970 的 uart 自帶 fifo 。用它測(cè)試效果好,并不能真正說明驅(qū)動(dòng)框架通過驗(yàn)證了。
然后,緊接著筆者在 STM32F429 完成了中斷和 DMA 兩種模式。今天,我把一些測(cè)試結(jié)果和移植說明發(fā)出來,征求全網(wǎng)公測(cè)。
測(cè)試配置:DMA 二級(jí)緩存 32 個(gè)字節(jié),串口收發(fā)緩存各 512 字節(jié)。
注:本串口驅(qū)動(dòng)工作特性請(qǐng)參閱前一篇文章rt-thread 驅(qū)動(dòng)篇(一) serialX 框架理論
STM32 中斷模式測(cè)試
以下是三組連續(xù)發(fā)收測(cè)試:
1. 定時(shí)間隔20ms,發(fā)送250字節(jié)數(shù)據(jù),持續(xù)發(fā)送2600w,接收發(fā)送數(shù)據(jù)量相等
2. 定時(shí)間隔50ms,發(fā)送250字節(jié)數(shù)據(jù),持續(xù)發(fā)送600w,接收發(fā)送數(shù)據(jù)量相等
3. 定時(shí)間隔80ms,發(fā)送1000字節(jié)數(shù)據(jù),持續(xù)發(fā)送600w,接收發(fā)送數(shù)據(jù)量相等
注:剛剛跟我們小伙伴求證了一下,串口調(diào)試助手的定時(shí)間隔是固定周期。如果是這樣的,以上測(cè)試是有意義的,如果不是,那就沒達(dá)到串口帶寬上限。
STM32 DMA模式測(cè)試
1. 讀寫測(cè)試,串口調(diào)試助手定時(shí) 10ms ,發(fā)送40字節(jié)數(shù)據(jù),持續(xù)發(fā)送129w
2. 串口調(diào)試助手定時(shí) 50ms ,發(fā)送500字節(jié)數(shù)據(jù),持續(xù)發(fā)送527w
3. 串口調(diào)試助手定時(shí) 40ms ,發(fā)送500字節(jié)數(shù)據(jù),持續(xù)發(fā)送261w
4. 串口調(diào)試助手定時(shí) 40ms ,發(fā)送1000字節(jié)數(shù)據(jù),持續(xù)發(fā)送262w
串口調(diào)試助手上發(fā)送和接收數(shù)量不相等,接著我在代碼中添加了個(gè)斷點(diǎn),單獨(dú)發(fā)送了一個(gè)字節(jié) ‘Z’ 。
代碼中接收和發(fā)送數(shù)量相等,都等于串口調(diào)試助手的接收量。這個(gè)缺少的部分是串口調(diào)試助手發(fā)送失敗數(shù)量,還是串口驅(qū)動(dòng)接收丟失了?
接下來,修改成中斷接收發(fā)送模式,其它不做修改進(jìn)行相同的測(cè)試,也是有數(shù)量差。進(jìn)一步檢查串口驅(qū)動(dòng)里,接收緩存有溢出現(xiàn)象。應(yīng)用層沒來得及把數(shù)據(jù)取走,就刪掉了最舊的數(shù)據(jù)。
接口詳解及移植說明
rtdef.h 添加幾個(gè)宏定義
添加阻塞打開相關(guān)標(biāo)志
#define RT_DEVICE_OFLAG_BLOCKING 0x000 /**< blocking io mode */
#define RT_DEVICE_OFLAG_NONBLOCKING 0x004 /**< non-blocking io mode */
...
#define RT_DEVICE_CTRL_BLOCKING 0x05 /**< blocking io */
serialX.h
添加串口驅(qū)動(dòng)緩存和 DMA 二級(jí)緩存大小定義(放棄使用 `RT_SERIAL_RB_BUFSZ`):
#ifndef RT_SERIAL_FIFO_BUFSZ
#define RT_SERIAL_FIFO_BUFSZ 512
#endif
#ifndef RT_SERIAL_DMA_BUFSZ
#define RT_SERIAL_DMA_BUFSZ 32
#endif
串口接收和發(fā)送使用的緩存大小是一樣的,如果想改變串口緩存大小,請(qǐng)修改 `RT_SERIAL_FIFO_BUFSZ` 的值。
如果想改變 DMA 二級(jí)緩存大小,請(qǐng)修改 `RT_SERIAL_DMA_BUFSZ` 的值。
定義一個(gè)收發(fā)通用 fifo:
struct rt_serial_fifo
{
rt_uint32_t buf_sz;
/* software fifo */
rt_uint8_t *buffer;
rt_uint16_t put_index, get_index;
rt_bool_t is_full;
};
重新定義 `rt_serial_device` 定義:
struct rt_serial_device
{
struct rt_device parent;
const struct rt_uart_ops *ops;
struct serial_configure config;
void *serial_rx; // 串口接收緩存
void *serial_tx; // 串口發(fā)送緩存
#ifdef RT_SERIAL_USING_DMA // 串口收發(fā)緩存和 DMA 使用的二級(jí)緩存分開
rt_size_t dma_idx_rx;
rt_uint8_t serial_dma_rx[RT_SERIAL_DMA_BUFSZ]; // DMA 接收緩存
rt_uint8_t serial_dma_tx[RT_SERIAL_DMA_BUFSZ]; // DMA 發(fā)送緩存
#endif
cb_serial_tx _cb_tx; // 寫過程回調(diào)函數(shù)指針
cb_serial_rx _cb_rx; // 讀過程回調(diào)函數(shù)指針
struct rt_completion completion_tx; // 發(fā)送完成
struct rt_completion completion_rx; // 接收到新數(shù)據(jù)
};
typedef struct rt_serial_device rt_serial_t;
串口驅(qū)動(dòng)通用框架和硬件底層接口定義
struct rt_uart_ops
{
// 用于配置外設(shè)寄存器,引腳功能復(fù)用,啟用外設(shè)等等
rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
// 用于使能禁用中斷,初始配置 DMA
rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
// 串口外設(shè)寫數(shù)據(jù)寄存器*為空*,把數(shù)據(jù)放入寫數(shù)據(jù)寄存器。*不為空*,死等
int (*putc)(struct rt_serial_device *serial, char c);
// 串口外設(shè)讀數(shù)據(jù)寄存器*不為空*,讀出讀數(shù)據(jù)寄存器的值。*為空*,返回 -1
int (*getc)(struct rt_serial_device *serial);
// 啟動(dòng)發(fā)送,多數(shù)是開啟串口外設(shè)發(fā)送寄存器空中斷
void (*start_tx)(struct rt_serial_device *serial);
// 結(jié)束發(fā)送,多數(shù)是關(guān)閉串口外設(shè)發(fā)送寄存器空中斷
void (*stop_tx)(struct rt_serial_device *serial);
#ifdef RT_SERIAL_USING_DMA
// 判斷 DMA 是否在發(fā)送過程中,就像上一篇里筆者多次提示的,必須有效檢測(cè) DMA 是否在發(fā)送數(shù)據(jù)中
rt_bool_t (*is_dma_txing)(struct rt_serial_device *serial);
// 啟動(dòng) DMA 發(fā)送
void (*start_dma_tx)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size);
// 停止 DMA 發(fā)送
void (*stop_dma_tx)(struct rt_serial_device *serial);
#endif
// 使能串口外設(shè)中斷
void (*enable_interrupt)(struct rt_serial_device *serial);
// 禁用串口外設(shè)中斷
void (*disable_interrupt)(struct rt_serial_device *serial);
};
移植 serialX 到新芯片上,必須按照 `rt_uart_ops` 的定義實(shí)現(xiàn)上述幾個(gè)接口。函數(shù)功能不能隨意更改。
`rt_hw_serial_isr`
這個(gè)中斷只接收 `RT_SERIAL_EVENT_RX_IND` `RT_SERIAL_EVENT_RX_IND` `RT_SERIAL_EVENT_RX_DMADONE` `RT_SERIAL_EVENT_TX_DMADONE` 四種中斷狀態(tài)。
- `RT_SERIAL_EVENT_RX_IND` 接收寄存器不空中斷
- `RT_SERIAL_EVENT_TX_DONE` 發(fā)送寄存器空中斷,為了兼容自帶 fifo 的芯片,event 參數(shù)的高三字節(jié)代表 fifo 容量
- `RT_SERIAL_EVENT_RX_DMADONE` 串口接收 DMA 中斷。 這個(gè)可以兼容接收半傳輸和全傳輸?shù)榷喾N中斷。event 參數(shù)的高三字節(jié)代表 DMA fifo 接收數(shù)據(jù)數(shù)量(1-RT_SERIAL_DMA_BUFSZ)。
- `RT_SERIAL_EVENT_TX_DMADONE` 串口發(fā)送 DMA 中斷。這個(gè)應(yīng)該保證 DMA 發(fā)送完本次 DMA 緩存中的所有數(shù)據(jù),也就是對(duì)于 stm32 芯片是 DMA 計(jì)數(shù)達(dá)到 0。
使用注意
- `RT_SERIAL_FIFO_BUFSZ` `RT_SERIAL_DMA_BUFSZ` 兩個(gè)的定義和實(shí)際是否合適,小數(shù)據(jù)量通信可以定義小點(diǎn)兒,數(shù)據(jù)量大的情況適當(dāng)調(diào)整這兩個(gè)值。
- `rt_uart_ops` 接口定義,功能實(shí)現(xiàn)必須匹配。
- 阻塞模式,收發(fā)是一致的。默認(rèn)是阻塞模式。想使用非阻塞模式請(qǐng) open 的時(shí)候添加 `RT_DEVICE_OFLAG_NONBLOCKING` flag。
- 使用 `RT_DEVICE_FLAG_INT` `RT_DEVICE_FLAG_DMA_RX` `RT_DEVICE_FLAG_INT_TX` `RT_DEVICE_FLAG_DMA_TX` 四個(gè) open flag 指定收發(fā)模式,是用中斷還是 DMA。
- **特別提醒**,非阻塞模式下,read 可能返回 0。write 返回值可能不是目標(biāo)寫入 size。read/write 還可能返回 `RT_EXXX` 錯(cuò)誤值。
- **特別提醒**,阻塞模式下,read 返回值可能不是期望數(shù)據(jù)量 size。筆者也曾經(jīng)提供過可靠處理流數(shù)據(jù)的方案,詳見 rt-thread 使用寶典(2022-0516更新)
使用完成量進(jìn)入阻塞漏洞分析
> PS: 謝謝 @HelloBye 的及時(shí)糾正,`rt_completion` 不存在本小節(jié)描述的漏洞。各位看官可以直接跳過本小節(jié)了。
串口驅(qū)動(dòng)里有幾個(gè)阻塞點(diǎn),進(jìn)入阻塞都使用的 `rt_completion` ,如下代碼:
serial->ops->enable_interrupt(serial);
ret = rt_completion_wait(&(serial->completion_rx), RT_WAITING_FOREVER);// 或者 serial->completion_tx
首先開中斷,調(diào)用 `rt_completion_wait` 等待完成量進(jìn)入阻塞。這樣是有個(gè)漏洞的,當(dāng)開中斷后有個(gè)串口中斷,中斷處理函數(shù)里調(diào)用 `rt_completion_done` 是沒有任何反應(yīng)的,`rt_completion_done` 直接返回退出。
進(jìn)而回到原線程才執(zhí)行 `rt_completion_wait`。之后,如果有第二次接收(或發(fā)送)中斷發(fā)生時(shí)才會(huì)結(jié)束上一次的阻塞。但是,第二次什么時(shí)候出現(xiàn)也就是個(gè)未知數(shù)了。即便前一次可能已經(jīng)收全了全部想要的數(shù)據(jù),但是會(huì)不定期阻塞下去。
解決方法有兩個(gè):一、不用永久阻塞,換成 10ms 或者幾 ms 等待;二、用二值信號(hào)量替代。
但是!!!我沒有用上述方法中的任何一個(gè)進(jìn)行改進(jìn),原因是:
第一種方法無疑要引入一個(gè)循環(huán),`rt_completion_wait` 超時(shí)返回的時(shí)候循環(huán)繼續(xù)阻塞。還有就是等待時(shí)間沒有理論支持。最重要的是用循環(huán)方式補(bǔ)漏洞的方式不美觀。
沒使用二值信號(hào)量的原因是,rt-thread 的信號(hào)量實(shí)現(xiàn)沒有真正的“二值”,如果中斷已經(jīng)多次 release 了,然后應(yīng)用層才來一次 take,之后還可能成功 take 多次(雖然應(yīng)該是要阻塞的,但是實(shí)際不阻塞,反而會(huì)循環(huán) take 多次)。
結(jié)束語
現(xiàn)筆者將打碼開放出來 gitee 倉庫 [serialX]( https://gitee.com/thewon/serialX ),求全論壇公測(cè)。期待各位大佬提出各種測(cè)試方案對(duì)它蹂躪。
有問題可以在倉庫里提 [issue]( https://gitee.com/thewon/serialX/issues ) ,或者到 rt-thread 官方論壇上進(jìn)行討論。
審核編輯:湯梓紅
-
STM32
+關(guān)注
關(guān)注
2270文章
10923瀏覽量
357068 -
中斷
+關(guān)注
關(guān)注
5文章
900瀏覽量
41648 -
串口驅(qū)動(dòng)
+關(guān)注
關(guān)注
2文章
82瀏覽量
18713 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1305瀏覽量
40307
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論