從 UART 設(shè)備開始學(xué)會(huì)使用 RT-Thread I/O 設(shè)備模型 。
目錄
前言
一、UART 設(shè)備操作
1.1 UART 設(shè)備控制塊
1.2 UART 操作函數(shù)
1.2.1 查找 UART 設(shè)備
1.2.2 打開/關(guān)閉 UART 設(shè)備
實(shí)際應(yīng)用中的串口讀寫說(shuō)明
1.2.3 控制 UART 設(shè)備
1.2.4 發(fā)送數(shù)據(jù)
1.2.5 設(shè)置接收回調(diào)函數(shù)
1.2.6 接收數(shù)據(jù)
二、UART 設(shè)備使用步驟
2.1 RT-Thread setting
2.2 board.h 設(shè)置
2.3 應(yīng)用程序流程
三、UART 示例測(cè)試
3.1 與無(wú)線模塊串口通訊
3.2 示例說(shuō)明
結(jié)語(yǔ)
前言
通過(guò)前面的兩篇文章,我們基本上完全明白了 RT-Thread I/O 設(shè)備模型的基本原理,當(dāng)然我們的最終目的還是應(yīng)用,所以本文開始我們就開始進(jìn)行常用設(shè)備的使用學(xué)習(xí)和測(cè)試,就從 UART 設(shè)備開始。
從本文開始,就開始進(jìn)行常用 I/O 設(shè)備的學(xué)習(xí)測(cè)試。
本 RT-Thread 專欄記錄的開發(fā)環(huán)境:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發(fā)環(huán)境 及 配合CubeMX開發(fā)快速上手)
RT-Thread記錄(二、RT-Thread內(nèi)核啟動(dòng)流程 — 啟動(dòng)文件和源碼分析)
RT-Thread 設(shè)備篇系列博文鏈接:
RT-Thread記錄(十、全面認(rèn)識(shí) RT-Thread I/O 設(shè)備模型)
RT-Thread記錄(十一、I/O 設(shè)備模型之UART設(shè)備 — 源碼解析)
一、UART 設(shè)備操作
雖然在上一篇文章中,我們已經(jīng)認(rèn)識(shí)過(guò) RT-Thread UART 的操作函數(shù),但是我們并沒(méi)有對(duì)其參數(shù)進(jìn)行說(shuō)明。
學(xué)習(xí)使用一個(gè)設(shè)備,在 RT-Thread 系統(tǒng)中就是一個(gè)對(duì)象, 還是得按照我們之前的流程進(jìn)行簡(jiǎn)單介紹。
1.1 UART 設(shè)備控制塊
在我們前面許多文章介紹其他內(nèi)核對(duì)象的時(shí)候,我們首先都會(huì)介紹其對(duì)象控制塊,對(duì)于 UART 設(shè)備而言,它也有自己的控制塊。
但是與其他對(duì)象機(jī)制不同的是,UART 屬于 I/O 設(shè)備,對(duì)于上層應(yīng)用程序而言,所有的 I/O 設(shè)備都是屬于 struct rt_device
類。
在我們前面文章《RT-Thread記錄(十、全面認(rèn)識(shí) RT-Thread I/O 設(shè)備模型》初次介紹 I/O 設(shè)備模型的時(shí)候就已經(jīng)說(shuō)明了這個(gè)統(tǒng)一的控制塊:
上面的控制塊是對(duì)于應(yīng)用程序而言,在我們的 UART 設(shè)備的設(shè)備驅(qū)動(dòng)框架層,是有定義了 UART 設(shè)備自己的控制塊,其繼承了rt_device
的內(nèi)容,同時(shí)還增加了 UART 設(shè)備特有的一些配置,操作,回調(diào)函數(shù)之類的內(nèi)容,如下圖:
上面的 UART 設(shè)備控制塊在我們的上一篇文章也有過(guò)分析說(shuō)明。
?? UART 設(shè)備屬于 I/O 設(shè)備大類中的一個(gè)小類,對(duì)于上層應(yīng)用程序而言,UART 設(shè)備控制塊rt_serial_device
并不透明,我們用戶操作的還是 I/O 設(shè)備模型的控制塊rt_device_t
類型。
1.2 UART 操作函數(shù)
因?yàn)?UART 的操作函數(shù) 與 I/O 設(shè)備的操作函數(shù)基本一致,所以本小結(jié)有點(diǎn)類似《RT-Thread記錄(十、全面認(rèn)識(shí) RT-Thread I/O 設(shè)備模型》中的 2.3 訪問(wèn) I/O 設(shè)備相關(guān) API 操作,但是針對(duì) UART 設(shè)備,也有一些獨(dú)有的參數(shù)說(shuō)明。
老規(guī)矩,函數(shù)介紹部分說(shuō)明看注釋。
1.2.1 查找 UART 設(shè)備
需要先定義一個(gè) I/O 設(shè)備結(jié)構(gòu)體(rt_device_t
類型)的指針變量,接收創(chuàng)建好的句柄。
/*
參數(shù) 描述
name 設(shè)備名稱,對(duì)于UART設(shè)備而言,默認(rèn)一般是 uart0,uart1,uart2,uart3 等
返回 ——
設(shè)備句柄 查找到對(duì)應(yīng)設(shè)備將返回相應(yīng)的設(shè)備句柄
RT_NULL 沒(méi)有找到相應(yīng)的設(shè)備對(duì)象
*/
rt_device_t rt_device_find(const char* name);
1.2.2 打開/關(guān)閉 UART 設(shè)備
先說(shuō)打開 UART 設(shè)備:
/**
參數(shù) 描述
dev 設(shè)備句柄
oflags 設(shè)備模式標(biāo)志
oflags可選的的值如下:
#define RT_DEVICE_FLAG_STREAM 0x040 流模式
接收模式參數(shù)
#define RT_DEVICE_FLAG_INT_RX 0x100 中斷接收模式
#define RT_DEVICE_FLAG_DMA_RX 0x200 DMA 接收模式
發(fā)送模式參數(shù)
#define RT_DEVICE_FLAG_INT_TX 0x400 中斷發(fā)送模式
#define RT_DEVICE_FLAG_DMA_TX 0x800 DMA 發(fā)送模式
返回值:
RT_EOK 設(shè)備打開成功
-RT_EBUSY 如果設(shè)備注冊(cè)時(shí)指定的參數(shù)中包括 RT_DEVICE_FLAG_STANDALONE 參數(shù),此設(shè)備將不允許重復(fù)打開
其他錯(cuò)誤碼 設(shè)備打開失敗
*/
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)
打開設(shè)備時(shí),會(huì)檢測(cè)設(shè)備是否已經(jīng)初始化,沒(méi)有初始化則會(huì)默認(rèn)調(diào)用初始化接口初始化設(shè)備。
如果 oflags 沒(méi)有指定使用中斷模式或者 DMA 模式,則默認(rèn)使用輪詢模式。
這里有個(gè)問(wèn)題,流模式是什么情況下使用的?485通訊? 暫時(shí)不知道,希望知道的朋友能夠給個(gè)說(shuō)明。
在官方的文檔中,關(guān)于流模式有如下說(shuō)明:
實(shí)際應(yīng)用中的串口讀寫說(shuō)明
串口RX:
在我們正常的項(xiàng)目使用中,一般都是 中斷接收 或者 DMA 接收,基本上不會(huì)使用 輪詢接收的方式(極大的浪費(fèi)資源,反正我是沒(méi)用過(guò))。
所以我們打開串口設(shè)備的時(shí)候,基本上都是如下兩種:
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
在 RT-Thread 系統(tǒng)中,我們常用信號(hào)量或者消息隊(duì)列 來(lái)標(biāo)志是否接收到串口數(shù)據(jù),這樣的好處是當(dāng)沒(méi)有數(shù)據(jù)的時(shí)候,會(huì)將數(shù)據(jù)處理線程掛機(jī),讓出CPU資源。
串口TX:
對(duì)于串口 TX 來(lái)說(shuō),大部分項(xiàng)目中我自己一直都用的是 輪詢 方式發(fā)送。
對(duì)于串口的中斷發(fā)送方式,在上一篇文章我們分析 UART 源碼,雖然沒(méi)有詳細(xì)說(shuō)明,但是實(shí)際上在設(shè)備驅(qū)動(dòng)層 drv_usart.c
驅(qū)動(dòng)文件里,中斷發(fā)送方式最終還是調(diào)用了該驅(qū)動(dòng)文件里面的stm32_putc
函數(shù):
我感覺還是和輪詢一樣,將數(shù)據(jù)寫入 數(shù)據(jù)寄存器DR,使用while死等發(fā)送完成(雖然時(shí)間很短)。
上面雖然只是 RT-Thread 中的UART設(shè)備驅(qū)動(dòng)文件,也多少能說(shuō)明一些問(wèn)題,中斷發(fā)送最終無(wú)非就是發(fā)送完了多一個(gè)中斷通知。
對(duì)于另外一種 DMA 發(fā)送,我記得以前聽老人提到過(guò),DMA發(fā)送使用不得當(dāng),可能導(dǎo)致發(fā)送數(shù)據(jù)異常,簡(jiǎn)單來(lái)說(shuō)就是 DMA 發(fā)送函數(shù)返回后,數(shù)據(jù)都不一定發(fā)送完成了,如果此時(shí)修改了 DMA 發(fā)送指定的buffer 區(qū)的內(nèi)容,那么后面的數(shù)據(jù)就錯(cuò)誤了。
所以,如果沒(méi)有特殊需求,我們項(xiàng)目中的串口發(fā)送使用 輪詢發(fā)送 即可(有些特殊情況的根據(jù)自己的實(shí)際需求而定)。
所以結(jié)合上面所說(shuō),我們實(shí)際應(yīng)用中,使用以下兩種方式打開串口設(shè)備能滿足大部分場(chǎng)合需求:
/*輪詢方式發(fā)送,中斷接收*/
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/*輪詢方式發(fā)送,DMA接收*/
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
有打開設(shè)備,當(dāng)然也有關(guān)閉設(shè)備:
/**
參數(shù) 描述
dev 設(shè)備句柄
返回 ——
RT_EOK 關(guān)閉設(shè)備成功
-RT_ERROR 設(shè)備已經(jīng)完全關(guān)閉,不能重復(fù)關(guān)閉設(shè)備
其他錯(cuò)誤碼 關(guān)閉設(shè)備失敗
*/
rt_err_t rt_device_close(rt_device_t dev)
關(guān)閉設(shè)備接口和打開設(shè)備接口需配對(duì)使用,打開一次設(shè)備對(duì)應(yīng)要關(guān)閉一次設(shè)備,這樣設(shè)備才會(huì)被完全關(guān)閉,否則設(shè)備仍處于未關(guān)閉狀態(tài)。
當(dāng)然在一般的應(yīng)用場(chǎng)合,用到串口通訊的地方設(shè)備都是需要一直開啟的,所以很多情況下都不需要使用 UART 設(shè)備關(guān)閉函數(shù)。
1.2.3 控制 UART 設(shè)備
rt_device_control
一般用在 rt_device_open
(打開串口設(shè)備)之前,對(duì)需要使用的串口進(jìn)行必要的配置。
/**
參數(shù) 描述
dev 設(shè)備句柄
cmd 命令控制字,可取值:RT_DEVICE_CTRL_CONFIG
arg 控制的參數(shù),可取類型:
struct serial_configure
{
rt_uint32_t baud_rate; 波特率
rt_uint32_t data_bits :4; 數(shù)據(jù)位
rt_uint32_t stop_bits :2; 停止位
rt_uint32_t parity :2; 奇偶校驗(yàn)位
rt_uint32_t bit_order :1; 高位在前或者低位在前
rt_uint32_t invert :1; 模式
rt_uint32_t bufsz :16; 接收數(shù)據(jù)緩沖區(qū)大小
rt_uint32_t reserved :4; 保留位
};
波特率可取值:
#define BAUD_RATE_2400 2400
#define BAUD_RATE_4800 4800
#define BAUD_RATE_9600 9600
#define BAUD_RATE_19200 19200
#define BAUD_RATE_38400 38400
#define BAUD_RATE_57600 57600
#define BAUD_RATE_115200 115200
#define BAUD_RATE_230400 230400
#define BAUD_RATE_460800 460800
#define BAUD_RATE_921600 921600
#define BAUD_RATE_2000000 2000000
#define BAUD_RATE_3000000 3000000
數(shù)據(jù)位可取值:
#define DATA_BITS_5 5
#define DATA_BITS_6 6
#define DATA_BITS_7 7
#define DATA_BITS_8 8
#define DATA_BITS_9 9
停止位可取值:
#define STOP_BITS_1 0
#define STOP_BITS_2 1
#define STOP_BITS_3 2
#define STOP_BITS_4 3
極性位可取值:
#define PARITY_NONE 0
#define PARITY_ODD 1
#define PARITY_EVEN 2
高低位順序可取值:
#define BIT_ORDER_LSB 0
#define BIT_ORDER_MSB 1
模式可取值:
#define NRZ_NORMAL 0
#define NRZ_INVERTED 1
接收數(shù)據(jù)緩沖區(qū)默認(rèn)大?。?#define RT_SERIAL_RB_BUFSZ 64
返回 ——
RT_EOK 函數(shù)執(zhí)行成功
-RT_ENOSYS 執(zhí)行失敗,dev 為空
其他錯(cuò)誤碼 執(zhí)行失敗
*/
rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)
我們已經(jīng)知道,在串口初始化的時(shí)候會(huì)有一個(gè)默認(rèn)配置:
所以在我們使用串口的時(shí)候,如果對(duì)應(yīng)的配置與默認(rèn)的配置不一樣,就需要使用此函數(shù)修改配置。
接收緩沖區(qū):
當(dāng)串口使用中斷接收模式打開時(shí),串口驅(qū)動(dòng)框架會(huì)根據(jù) RT_SERIAL_RB_BUFSZ 大小開辟一塊緩沖區(qū)用于保存接收到的數(shù)據(jù),底層驅(qū)動(dòng)接收到一個(gè)數(shù)據(jù),都會(huì)在中斷服務(wù)程序里面將數(shù)據(jù)放入緩沖區(qū)。
在修改緩沖區(qū)大小時(shí)請(qǐng)注意,緩沖區(qū)大小無(wú)法動(dòng)態(tài)改變,只有在 open 設(shè)備之前可以配置。open 設(shè)備之后,緩沖區(qū)大小不可再進(jìn)行更改。但除緩沖區(qū)之外的其他參數(shù),在 open 設(shè)備前 / 后,均可進(jìn)行更改。
串口控制修改使用官方修改示例說(shuō)明一下:
#define SAMPLE_UART_NAME "uart2" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置參數(shù) */
/* step1:查找串口設(shè)備 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* step2:修改串口配置參數(shù) */
config.baud_rate = BAUD_RATE_9600; //修改波特率為 9600
config.data_bits = DATA_BITS_8; //數(shù)據(jù)位 8
config.stop_bits = STOP_BITS_1; //停止位 1
config.bufsz = 128; //修改緩沖區(qū) buff size 為 128
config.parity = PARITY_NONE; //無(wú)奇偶校驗(yàn)位
/* step3:控制串口設(shè)備。通過(guò)控制接口傳入命令控制字,與控制參數(shù) */
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
/* step4:打開串口設(shè)備。以中斷接收及輪詢發(fā)送模式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
1.2.4 發(fā)送數(shù)據(jù)
/**
參數(shù) 描述
dev 設(shè)備句柄
pos 寫入數(shù)據(jù)偏移量,此參數(shù)串口設(shè)備未使用
buffer 內(nèi)存緩沖區(qū)指針,放置要寫入的數(shù)據(jù)
size 寫入數(shù)據(jù)的大小
返回 ——
寫入數(shù)據(jù)的實(shí)際大小 如果是字符設(shè)備,返回大小以字節(jié)為單位;
0 需要讀取當(dāng)前線程的 errno 來(lái)判斷錯(cuò)誤狀態(tài)
*/
rt_size_t rt_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
寫其實(shí)很好理解,除了多一個(gè)設(shè)備句柄參數(shù),和我們裸機(jī)中使用的發(fā)送函數(shù)一樣,看一下一個(gè)普通的裸機(jī)串口發(fā)送函數(shù):
這里說(shuō)明一下,因?yàn)槲覀兩厦娣治鲞^(guò)實(shí)際應(yīng)用中的串口讀寫,一般都使用輪詢發(fā)送,所以我這里并不打斷介紹 設(shè)置發(fā)送完成回調(diào)函數(shù) 。
1.2.5 設(shè)置接收回調(diào)函數(shù)
/**
參數(shù) 描述
dev 設(shè)備句柄
rx_ind 回調(diào)函數(shù)指針
回調(diào)函數(shù)參數(shù) 描述
dev 設(shè)備句柄
size 緩沖區(qū)數(shù)據(jù)大小
返回 ——
RT_EOK 設(shè)置成功
*/
rt_err_t
rt_device_set_rx_indicate(rt_device_t dev,
rt_err_t (*rx_ind)(rt_device_t dev, rt_size_t size))
若串口以中斷接收模式打開:
當(dāng)串口接收到一個(gè)數(shù)據(jù)產(chǎn)生中斷時(shí),就會(huì)調(diào)用回調(diào)函數(shù),并且會(huì)把此時(shí)緩沖區(qū)的數(shù)據(jù)大小放在 size 參數(shù)里,把串口設(shè)備句柄放在 dev 參數(shù)里供調(diào)用者獲取。
若串口以 DMA 接收模式打開:
當(dāng) DMA 完成一批數(shù)據(jù)的接收后會(huì)調(diào)用此回調(diào)函數(shù)。
在使用 RT-Thread 時(shí)候,一般會(huì)用一個(gè)信號(hào)量通知串口數(shù)據(jù)處理線程有數(shù)據(jù)到達(dá)。
在使用 RT-Thread Nano 的時(shí)候,其實(shí)我也是使用信號(hào)量來(lái)處理數(shù)據(jù)的接收:
具體詳情可查看博文:RT-Thread 應(yīng)用篇 — 在STM32L051上使用 RT-Thread (四、無(wú)線溫濕度傳感器 之 串口通訊)
回調(diào)函數(shù)處理的示例我們使用官方示例說(shuō)明,與下面的接收數(shù)據(jù)函數(shù)一起展示。
1.2.6 接收數(shù)據(jù)
數(shù)據(jù)接收處理函數(shù),在接收回調(diào)函數(shù)運(yùn)行之后運(yùn)行。
/**
參數(shù) 描述
dev 設(shè)備句柄
pos 讀取數(shù)據(jù)偏移量,此參數(shù)串口設(shè)備未使用
buffer 緩沖區(qū)指針,讀取的數(shù)據(jù)將會(huì)被保存在緩沖區(qū)中
size 讀取數(shù)據(jù)的大小
返回 ——
讀到數(shù)據(jù)的實(shí)際大小 如果是字符設(shè)備,返回大小以字節(jié)為單位
0 需要讀取當(dāng)前線程的 errno 來(lái)判斷錯(cuò)誤狀態(tài)
*/
rt_size_t rt_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
我們與上面的設(shè)置接收回調(diào)函數(shù)一起使用官方示例作為說(shuō)明:
#define SAMPLE_UART_NAME "uart2" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信號(hào)量 */
/* 接收數(shù)據(jù)回調(diào)函數(shù) */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* 串口接收到數(shù)據(jù)后產(chǎn)生中斷,調(diào)用此回調(diào)函數(shù),然后發(fā)送接收信號(hào)量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
/* 接收數(shù)據(jù)的線程 */
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* 從串口讀取一個(gè)字節(jié)的數(shù)據(jù),沒(méi)有讀取到則等待接收信號(hào)量 */
while (rt_device_read(serial, -1, &ch, 1) != 1)
{
/* 阻塞等待接收信號(hào)量,等到信號(hào)量后再次讀取數(shù)據(jù) */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* 讀取到的數(shù)據(jù)通過(guò)串口錯(cuò)位輸出 */
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}
static int uart_sample(int argc, char *argv[])
{
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以中斷接收及輪詢發(fā)送模式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 初始化信號(hào)量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 設(shè)置接收回調(diào)函數(shù) */
rt_device_set_rx_indicate(serial, uart_input);
}
示例中使用的是信號(hào)量,收到一個(gè)數(shù)據(jù),便會(huì)喚醒接收數(shù)據(jù)的線程,所以其實(shí)是一個(gè)字節(jié)一個(gè)字節(jié)(一個(gè)字符等于一個(gè)字節(jié))的讀取, 示例處理方式只能使用 RT_DEVICE_FLAG_INT_RX
方式接收。
?? 接收數(shù)據(jù)rt_device_read
函數(shù)的返回值需要注意一下,返回值為讀到的數(shù)據(jù)實(shí)際大小,就是接收到的數(shù)據(jù)長(zhǎng)度。
二、UART 設(shè)備使用步驟
簡(jiǎn)單介紹一下在 RT-Thread Studio 開發(fā)環(huán)境下 UART的使用步驟。
2.1 RT-Thread setting
如果需要使用某個(gè)設(shè)備,是需要在 ENV 工具中配置的,現(xiàn)在有了 RT-Thread Studio ,所以可以直接通過(guò)工程目錄下的 RT-Thread setting 進(jìn)行圖形化界面的配置,如下圖:
因?yàn)镾hell 工具需要使用串口,所以默認(rèn)串口這里已經(jīng)是勾選中的,這里說(shuō)明只是為了讓大家知道,在以后的 I/O 設(shè)備使用的時(shí)候,第一步就是在 RT-Thread setting 中使能設(shè)備。
2.2 board.h 設(shè)置
完成設(shè)備使能,我們還需要使用宏定義進(jìn)行串口的基本設(shè)置,該設(shè)置在board.h
文件中進(jìn)行,如下圖:
board.h
中包括了很多外設(shè)的使用說(shuō)明,除了 UART,還有I2C、SPI、ADC等設(shè)備,我們?cè)诤竺鎸W(xué)習(xí)這些設(shè)備使用的時(shí)候,需要經(jīng)常用到這個(gè)頭文件,一些基本的使能配置都是在這個(gè)文件中用宏定義使能。
2.3 應(yīng)用程序流程
完成上面 2 步的基本配置以后,我們就可以在應(yīng)用程序通過(guò)上文介紹的 UART 設(shè)備操作函數(shù)進(jìn)行串口的使用,具體的步驟概括如下:
UART 設(shè)備使用步驟 :
/
#include "rtdevice.h"
/
1、使用rt_device_find查找串口設(shè)備;
/
2、根據(jù)需求使用rt_device_control設(shè)置串口;
/
3、初始化回調(diào)函數(shù)中使用的信號(hào)量(在接收回調(diào)函數(shù)中 發(fā)送信號(hào)量 喚醒數(shù)據(jù)處理線程),如果使用消息隊(duì)列接收初始化消息隊(duì)列;
/
4、使用rt_device_open打開串口設(shè)備(根據(jù)自己的情況判斷使用什么方式接收,發(fā)送前面分析過(guò)了,一本應(yīng)用使用輪詢發(fā)送即可);
/
5、使用rt_device_set_rx_indicate設(shè)置串口設(shè)備的接收回調(diào)函數(shù)
/
6、創(chuàng)建數(shù)據(jù)讀取的線程。
按照上面的步驟,我進(jìn)行了如下的示例測(cè)試,不要忘記 #include "rtdevice.h"
:
上圖其實(shí)是根據(jù)官方示例代碼,使用的 ESP8266 WIFI 模塊做了一個(gè)簡(jiǎn)單的測(cè)試:
三、UART 示例測(cè)試
在上面介紹應(yīng)用程序流程的時(shí)候,其實(shí)已經(jīng)做了一個(gè)簡(jiǎn)單的示例測(cè)試。
同時(shí)在官方已經(jīng)也提供了3種典型的示例程序:
中斷接收及輪詢發(fā)送、DMA 接收及輪詢發(fā)送、串口接收不定長(zhǎng)數(shù)據(jù)
作為以應(yīng)用為目的系列博文,我自己還是根據(jù)自己的工作需求進(jìn)行串口通訊的測(cè)試,使用的是 Enocean 無(wú)線通訊模塊,當(dāng)時(shí)在 RT-Thread 的應(yīng)用篇,RT-Thread Nano 使用記錄的時(shí)候就使用的這個(gè)無(wú)線模塊。
要說(shuō)明的是,用什么模塊做通訊并不是重點(diǎn),重點(diǎn)在于使用過(guò)程中對(duì)串口數(shù)據(jù)的處理方式。
3.1 與無(wú)線模塊串口通訊
雖然換了一個(gè)通訊設(shè)備,但是官方給的例程:中斷接收及輪詢發(fā)送 還是適用的,我們先來(lái)看一看直接使用官方的例程做的測(cè)試:
/* 接收數(shù)據(jù)回調(diào)函數(shù) */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* 串口接收到數(shù)據(jù)后產(chǎn)生中斷,調(diào)用此回調(diào)函數(shù),然后發(fā)送接收信號(hào)量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void test_thread_entry(void *par){
uint8_t ch;
while (1)
{
/* 從串口讀取一個(gè)字節(jié)的數(shù)據(jù),沒(méi)有讀取到則等待接收信號(hào)量 */
while (rt_device_read(testuart, -1, &ch, 1) != 1)
{
/* 阻塞等待接收信號(hào)量,等到信號(hào)量后再次讀取數(shù)據(jù) */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
rt_kprintf("%x ",ch);
}
}
其測(cè)試結(jié)果如下:
為了更好的做數(shù)據(jù)解析,我們需要對(duì)原始的程序進(jìn)行修改,使得能夠針對(duì)一幀數(shù)據(jù)一幀數(shù)據(jù)進(jìn)行接收處理:
uint8_t USART_Enocean_BUF[64];
uint8_t Enocean_Data = 0;
/* 接收數(shù)據(jù)回調(diào)函數(shù) */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
Enocean_Data = size;
/* 串口接收到數(shù)據(jù)后產(chǎn)生中斷,調(diào)用此回調(diào)函數(shù),然后發(fā)送接收信號(hào)量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void test_thread_entry(void *par){
uint8_t i = 0;
while (1)
{
if(rt_sem_take(&rx_sem, RT_WAITING_FOREVER) == RT_EOK){
while(!rt_sem_take(&rx_sem, 7));
rt_device_read(testuart, -1, USART_Enocean_BUF, Enocean_Data);
}
for (i = 0; i < Enocean_Data; ++i) {
rt_kprintf("%x ",USART_Enocean_BUF[i]);
}
rt_kprintf("\r\n");
rt_memset(&USART_Enocean_BUF, 0, sizeof(USART_Enocean_BUF));
Enocean_Data = 0;
}
}
測(cè)試結(jié)果如下,實(shí)現(xiàn)了我們所需要的的針對(duì)每一幀數(shù)據(jù)的接收(既然都已經(jīng)可以區(qū)別每一幀數(shù)據(jù)了,所那么后續(xù)的處理也就簡(jiǎn)單了):
對(duì)于數(shù)據(jù)的接收處理信號(hào)量只是其中一種。
即便是信號(hào)量,也有多種可實(shí)現(xiàn)行的方式,而上面我測(cè)試使用的方式也只不過(guò)其中的一種:收到第一個(gè)數(shù)據(jù)的時(shí)候等待一定的時(shí)間,然后認(rèn)為是一幀數(shù)據(jù)接收完成。
這也只是判斷一幀數(shù)據(jù)接收完成的方法中的一種 = =!
3.2 示例說(shuō)明
在上面我們用了信號(hào)量作為通知的方式接收串口數(shù)據(jù),官方的示: DMA 接收及輪詢發(fā)送 采用了消息隊(duì)列的方式進(jìn)行處理,表面上看起來(lái)與我們上面那種方式不一樣。
其實(shí)本質(zhì)都是一樣的,都不過(guò)是給線程一個(gè)通知,并沒(méi)有“真正意義上的傳遞了消息”(比如串口接收到的數(shù)據(jù)):
如果想要使用消息隊(duì)列作為緩存正常的傳輸串口接收的數(shù)據(jù),不使用 I/O 設(shè)備模型的情況下更加適合,究其原因,如下圖分析:
如上面表格所說(shuō),使用了I/O 設(shè)備模型之后,我們底層串口初始化的時(shí)候已經(jīng)有了一段數(shù)據(jù)接收的buffer了,所以我們直接使用 rt_device_read 函數(shù)從驅(qū)動(dòng)層的 buffer 讀取數(shù)據(jù),用臨時(shí) buffer 來(lái)處理就可以了(不過(guò)如果需要對(duì)處理程序,單獨(dú)設(shè)計(jì)函數(shù),也可以用一個(gè)全局 buffer 來(lái)處理),也不過(guò)是2個(gè)buffer 的內(nèi)存占用。
所以在官方的示例中,雖然給的是信號(hào)量,和消息隊(duì)列的不同的處理方式,但是究其根本還是一樣的。只是給了一個(gè)通知,這個(gè)其他的 IPC機(jī)制 比如 事件集一樣可以做到,即便不用 IPC 機(jī)制,普通簡(jiǎn)單的應(yīng)用,全局變量也未嘗不可。(對(duì)于消息隊(duì)列傳遞串口接收數(shù)據(jù)的應(yīng)用,以后我還是會(huì)單獨(dú)的說(shuō)明的,本文在于說(shuō)明 UART 基于 I/O 設(shè)備模型的使用,所以就不做測(cè)試了 = =?。?/p>
?? 使用了 UART 設(shè)備模型,最終還是需要使用rt_device_read
函數(shù),從內(nèi)部緩存讀取串口數(shù)據(jù),IPC只不過(guò)是給線程一個(gè)通知。
結(jié)語(yǔ)
一個(gè) UART 設(shè)備畫了兩篇文章,還算是比較值得的,通過(guò)上一篇文章加深對(duì) RT-Thread I/O 設(shè)備模型的理解,通過(guò)本文實(shí)際體驗(yàn)了一把 UART 設(shè)備。
體驗(yàn)上來(lái)說(shuō),還是感覺特別方便簡(jiǎn)單的。但是這個(gè)前提條件時(shí),能夠真正的理解 RT-Thread I/O 設(shè)備模型,理解到位才能用起來(lái)游刃有余,也能夠在以后出問(wèn)題的時(shí)候更容易的發(fā)現(xiàn)問(wèn)題,解決問(wèn)題。
?? 學(xué)會(huì)了使用一個(gè)東西當(dāng)然是一件慶幸的事情,但是能夠理解它才是更加重要的事情! ??
-
uart
+關(guān)注
關(guān)注
22文章
1242瀏覽量
101536 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1300瀏覽量
40264
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論