訪問串口設(shè)備
應(yīng)用程序通過 RT-Thread提供的** I/O 設(shè)備管理接口**來訪問串口硬件奔则,相關(guān)接口如下所示:
函數(shù) | 描述 |
---|---|
rt_device_find() | 查找設(shè)備 |
rt_device_open() | 打開設(shè)備 |
rt_device_read() | 讀取數(shù)據(jù) |
rt_device_write() | 寫入數(shù)據(jù) |
rt_device_control() | 控制設(shè)備 |
rt_device_set_rx_indicate() | 設(shè)置接收回調(diào)函數(shù) |
rt_device_set_tx_complete() | 設(shè)置發(fā)送完成回調(diào)函數(shù) |
rt_device_close() | 關(guān)閉設(shè)備 |
說明:這些函數(shù)都放在kernel內(nèi)核的device.c里面,在rrthread系統(tǒng)中光督,這些函數(shù)楚堤,都是給應(yīng)用層的用戶程序疫蔓,比如用戶線程中含懊,去調(diào)用的。
查找串口設(shè)備
應(yīng)用程序根據(jù)串口設(shè)備名稱獲取設(shè)備句柄衅胀,進(jìn)而可以操作串口設(shè)備绢要,查找設(shè)備函數(shù)如下所示,
rt_device_t rt_device_find(const char* name);
參數(shù) | 描述 |
---|---|
name | 設(shè)備名稱 |
返回 | —— |
設(shè)備句柄 | 查找到對應(yīng)設(shè)備將返回相應(yīng)的設(shè)備句柄 |
RT_NULL | 沒有找到相應(yīng)的設(shè)備對象 |
一般情況下拗小,注冊到系統(tǒng)的串口設(shè)備名稱為 uart0,uart1等樱哼,使用示例如下所示:
#define SAMPLE_UART_NAME "uart1" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
/* 查找串口設(shè)備 哀九,通過串口設(shè)備名稱查找*/
serial = rt_device_find(SAMPLE_UART_NAME);
打開串口設(shè)備
通過設(shè)備句柄,應(yīng)用程序可以打開和關(guān)閉設(shè)備搅幅,打開設(shè)備時阅束,會檢測設(shè)備是否已經(jīng)初始化,沒有初始化則會默認(rèn)調(diào)用初始化接口初始化設(shè)備茄唐。通過如下函數(shù)打開設(shè)備:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
參數(shù) | 描述 |
---|---|
dev | 設(shè)備句柄 |
oflags | 設(shè)備模式標(biāo)志息裸,串口工作模式設(shè)置 |
返回 | —— |
RT_EOK | 設(shè)備打開成功 |
-RT_EBUSY | 如果設(shè)備注冊時指定的參數(shù)中包括 RT_DEVICE_FLAG_STANDALONE 參數(shù),此設(shè)備將不允許重復(fù)打開 |
其他錯誤碼 | 設(shè)備打開失敗 |
輪詢方式沪编,為默認(rèn)方式呼盆,不選擇中斷方式和DMA方式時,默認(rèn)選中.
oflags 參數(shù)支持下列取值 (可以采用或的方式支持多種取值):
#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ā)送模式 */
串口數(shù)據(jù)接收和發(fā)送數(shù)據(jù)的模式分為 3 種:中斷模式蚁廓、輪詢模式访圃、DMA 模式。在使用的時候相嵌,這 3 種模式只能選其一腿时,若串口的打開參數(shù) oflags 沒有指定使用中斷模式或者 DMA 模式,則默認(rèn)使用輪詢模式饭宾。
DMA(Direct Memory Access)即直接存儲器訪問批糟。 DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現(xiàn)場和恢復(fù)現(xiàn)場的過程看铆,通過 DMA 控制器為 RAM 與 I/O 設(shè)備開辟一條直接傳送數(shù)據(jù)的通路徽鼎,這就節(jié)省了 CPU 的資源來做其他操作。使用 DMA 傳輸可以連續(xù)獲取或發(fā)送一段信息而不占用中斷或延時弹惦,在通信頻繁或有大段信息要傳輸時非常有用纬傲。
注意事項(xiàng):
- RT_DEVICE_FLAG_STREAM:流模式用于向串口終端輸出字符串:當(dāng)輸出的字符是 "\n" (對應(yīng) 16 進(jìn)制值為 0x0A)時,自動在前面輸出一個 "\r"(對應(yīng) 16 進(jìn)制值為 0x0D) 做分行肤频。
- 流模式 RT_DEVICE_FLAG_STREAM 可以和接收發(fā)送模式參數(shù)使用或 “|” 運(yùn)算符一起使用叹括。
以中斷接收及輪詢發(fā)送模式使用串口設(shè)備的示例如下所示:
#define SAMPLE_UART_NAME "uart1" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
/* 查找串口設(shè)備 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以中斷接收及輪詢發(fā)送模式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
若串口要使用 DMA 接收模式,oflags 取值 RT_DEVICE_FLAG_DMA_RX宵荒。以DMA 接收及輪詢發(fā)送模式使用串口設(shè)備的示例如下所示:
#define SAMPLE_UART_NAME "uart1" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
/* 查找串口設(shè)備 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以 DMA 接收及輪詢發(fā)送模式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
控制串口設(shè)備
通過命令控制字汁雷,應(yīng)用程序可以對串口設(shè)備進(jìn)行配置净嘀,通過如下函數(shù)完成:
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
參數(shù) | 描述 |
---|---|
dev | 設(shè)備句柄 |
cmd | 命令控制字,可取值:RT_DEVICE_CTRL_CONFIG |
arg | 控制的參數(shù),可取類型: struct serial_configure |
返回 | —— |
RT_EOK | 函數(shù)執(zhí)行成功 |
-RT_ENOSYS | 執(zhí)行失敗侠讯,dev 為空 |
其他錯誤碼 | 執(zhí)行失敗 |
控制參數(shù)結(jié)構(gòu)體 struct serial_configure 原型如下:
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; /* 保留位 */
};
RT-Thread 提供的默認(rèn)宏配置如下:
#define RT_SERIAL_CONFIG_DEFAULT \
{ \
BAUD_RATE_115200, /* 115200 bits/s */ \
DATA_BITS_8, /* 8 databits */ \
STOP_BITS_1, /* 1 stopbit */ \
PARITY_NONE, /* No parity */ \
BIT_ORDER_LSB, /* LSB first sent */ \
NRZ_NORMAL, /* Normal mode */ \
RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
0 \
}
注意事項(xiàng)
接收數(shù)據(jù)緩沖區(qū)大小默認(rèn)64字節(jié)挖藏。若一次性數(shù)據(jù)接收字節(jié)數(shù)很多,沒有及時讀取數(shù)據(jù)厢漩,那么緩沖區(qū)的數(shù)據(jù)將會被新接收到的數(shù)據(jù)覆蓋膜眠,造成數(shù)據(jù)丟失,建議調(diào)大緩沖區(qū)溜嗜。
配置串口硬件參數(shù)如數(shù)據(jù)位宵膨、校驗(yàn)位、停止位等的示例程序如下:
#define SAMPLE_UART_NAME "uart2" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 配置參數(shù) */
/* 查找串口設(shè)備 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以中斷接收及輪詢發(fā)送模式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
config.baud_rate = BAUD_RATE_115200;
config.data_bits = DATA_BITS_8;
config.stop_bits = STOP_BITS_2;
config.parity = PARITY_NONE;
/* 打開設(shè)備后才可修改串口配置參數(shù) */
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
發(fā)送數(shù)據(jù)
向串口中寫入數(shù)據(jù)炸宵,可以通過如下函數(shù)完成:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
參數(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 來判斷錯誤狀態(tài) |
調(diào)用這個函數(shù),會把緩沖區(qū) buffer 中的數(shù)據(jù)寫入到設(shè)備 dev 中裹匙,寫入數(shù)據(jù)的大小是 size瑞凑。
向串口寫入數(shù)據(jù)示例程序如下所示:
#define SAMPLE_UART_NAME "uart2" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
char str[] = "hello RT-Thread!\r\n";
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 配置參數(shù) */
/* 查找串口設(shè)備 */
serial = rt_device_find(SAMPLE_UART_NAME); /* 返回設(shè)備的句柄 */
/* 以中斷接收及輪詢發(fā)送模式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 發(fā)送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
設(shè)置發(fā)送完成回調(diào)函數(shù)
在應(yīng)用程序調(diào)用 rt_device_write() 寫入數(shù)據(jù)時,如果底層硬件能夠支持自動發(fā)送概页,那么上層應(yīng)用可以設(shè)置一個回調(diào)函數(shù)拨黔。這個回調(diào)函數(shù)會在底層硬件數(shù)據(jù)發(fā)送完成后 (例如 DMA 傳送完成或 FIFO 已經(jīng)寫入完畢產(chǎn)生完成中斷時) 調(diào)用〈铝ぃ可以通過如下函數(shù)設(shè)置設(shè)備發(fā)送完成指示 :
rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
參數(shù) | 描述 |
---|---|
dev | 設(shè)備句柄 |
tx_done | 回調(diào)函數(shù)指針 |
返回 | —— |
RT_EOK | 設(shè)置成功 |
調(diào)用這個函數(shù)時篱蝇,回調(diào)函數(shù)由調(diào)用者提供,當(dāng)硬件設(shè)備發(fā)送完數(shù)據(jù)時徽曲,由設(shè)備驅(qū)動程序回調(diào)這個函數(shù)并把發(fā)送完成的數(shù)據(jù)塊地址 buffer 作為參數(shù)傳遞給上層應(yīng)用零截。上層應(yīng)用(線程)在收到指示時會根據(jù)發(fā)送 buffer 的情況,釋放 buffer 內(nèi)存塊或?qū)⑵渥鳛橄乱粋€寫數(shù)據(jù)的緩存秃臣。
設(shè)置接收回調(diào)函數(shù)
可以通過如下函數(shù)來設(shè)置數(shù)據(jù)接收指示涧衙,當(dāng)串口收到數(shù)據(jù)時,通知上層應(yīng)用線程有數(shù)據(jù)到達(dá) :
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));
參數(shù) | 描述 |
---|---|
dev | 設(shè)備句柄 |
rx_ind | 回調(diào)函數(shù)指針 |
dev | 設(shè)備句柄(回調(diào)函數(shù)參數(shù)) |
size | 緩沖區(qū)數(shù)據(jù)大邪麓恕(回調(diào)函數(shù)參數(shù)) |
返回 | —— |
RT_EOK | 設(shè)置成功 |
該函數(shù)的回調(diào)函數(shù)由調(diào)用者提供弧哎。若串口以中斷接收模式打開,當(dāng)串口接收到一個數(shù)據(jù)產(chǎn)生中斷時稚虎,就會調(diào)用回調(diào)函數(shù)撤嫩,并且會把此時緩沖區(qū)的數(shù)據(jù)大小放在 size 參數(shù)里,把串口設(shè)備句柄放在 dev 參數(shù)里供調(diào)用者獲取蠢终。
若串口以 DMA 接收模式打開序攘,當(dāng) DMA 完成一批數(shù)據(jù)的接收后會調(diào)用此回調(diào)函數(shù)露戒。
一般情況下接收回調(diào)函數(shù)可以發(fā)送一個信號量或者事件通知串口數(shù)據(jù)處理線程有數(shù)據(jù)到達(dá)峭拘。
使用示例如下所示:
#define SAMPLE_UART_NAME "uart2" /* 串口設(shè)備名稱 */
static rt_device_t serial; /* 串口設(shè)備句柄 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信號量 */
/* 接收數(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ā)送接收信號量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
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);
/* 初始化信號量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 設(shè)置接收回調(diào)函數(shù) */
rt_device_set_rx_indicate(serial, uart_input);
}
接收數(shù)據(jù)
可調(diào)用如下函數(shù)讀取串口接收到的數(shù)據(jù):
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
參數(shù) | 描述 |
---|---|
dev | 設(shè)備句柄 |
pos | 讀取數(shù)據(jù)偏移量删豺,此參數(shù)串口設(shè)備未使用 |
buffer | 緩沖區(qū)指針剔氏,讀取的數(shù)據(jù)將會被保存在緩沖區(qū)中 |
size | 讀取數(shù)據(jù)的大小 |
返回 | —— |
讀到數(shù)據(jù)的實(shí)際大小 | 如果是字符設(shè)備为朋,返回大小以字節(jié)為單位 |
0 | 需要讀取當(dāng)前線程的 errno 來判斷錯誤狀態(tài) |
讀取數(shù)據(jù)偏移量 pos 針對字符設(shè)備無效疤孕,此參數(shù)主要用于塊設(shè)備中。
串口使用中斷接收模式并配合接收回調(diào)函數(shù)的使用示例如下所示:
static rt_device_t serial; /* 串口設(shè)備句柄 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信號量 */
/* 接收數(shù)據(jù)的線程 */
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* 從串口讀取一個字節(jié)的數(shù)據(jù)距境,沒有讀取到則等待接收信號量 */
while (rt_device_read(serial, -1, &ch, 1) != 1)
{
/* 阻塞等待接收信號量申尼,等到信號量后再次讀取數(shù)據(jù) */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* 讀取到的數(shù)據(jù)通過串口錯位輸出 */
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}
關(guān)閉串口設(shè)備
當(dāng)應(yīng)用程序完成串口操作后,可以關(guān)閉串口設(shè)備肮疗,通過如下函數(shù)完成:
rt_err_t rt_device_close(rt_device_t dev);
參數(shù) | 描述 |
---|---|
dev | 設(shè)備句柄 |
返回 | —— |
RT_EOK | 關(guān)閉設(shè)備成功 |
-RT_ERROR | 設(shè)備已經(jīng)完全關(guān)閉,不能重復(fù)關(guān)閉設(shè)備 |
其他錯誤碼 | 關(guān)閉設(shè)備失敗 |
關(guān)閉設(shè)備接口和打開設(shè)備接口需配對使用扒接,打開一次設(shè)備對應(yīng)要關(guān)閉一次設(shè)備伪货,這樣設(shè)備才會被完全關(guān)閉,否則設(shè)備仍處于未關(guān)閉狀態(tài)钾怔。
串口設(shè)備使用示例
中斷接收及輪詢發(fā)送
示例代碼的主要步驟如下所示:
- 首先查找串口設(shè)備獲取設(shè)備句柄碱呼。
- 初始化回調(diào)函數(shù)發(fā)送使用的信號量,然后以讀寫及中斷接收方式打開串口設(shè)備宗侦。
- 設(shè)置串口設(shè)備的接收回調(diào)函數(shù)愚臀,之后發(fā)送字符串,并創(chuàng)建讀取數(shù)據(jù)線程矾利。
- 讀取數(shù)據(jù)線程會嘗試讀取一個字符數(shù)據(jù)姑裂,如果沒有數(shù)據(jù)則會掛起并等待信號量,當(dāng)串口設(shè)備接收到一個數(shù)據(jù)時會觸發(fā)中斷并調(diào)用接收回調(diào)函數(shù)男旗,此函數(shù)會發(fā)送信號量喚醒線程舶斧,此時線程會馬上讀取接收到的數(shù)據(jù)。
- 此示例代碼不局限于特定的 BSP察皇,根據(jù) BSP 注冊的串口設(shè)備茴厉,修改示例代碼宏定義 SAMPLE_UART_NAME 對應(yīng)的串口設(shè)備名稱即可運(yùn)行。
運(yùn)行序列圖如下圖所示:
/*
* 程序清單:這是一個 串口 設(shè)備使用例程
* 例程導(dǎo)出了 uart_sample 命令到控制終端
* 命令調(diào)用格式:uart_sample uart2
* 命令解釋:命令第二個參數(shù)是要使用的串口設(shè)備名稱什荣,為空則使用默認(rèn)的串口設(shè)備
* 程序功能:通過串口輸出字符串"hello RT-Thread!"矾缓,然后錯位輸出輸入的字符
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart2"
/* 用于接收消息的信號量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
/* 接收數(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ā)送接收信號量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
/* 用戶線程入口函數(shù) */
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* 從串口讀取一個字節(jié)的數(shù)據(jù)嗜闻,沒有讀取到則等待接收信號量 */
while (rt_device_read(serial, -1, &ch, 1) != 1) /*正確讀取后返回讀取字節(jié)數(shù)1 */
{
/* 阻塞等待接收信號量,等到信號量后再次讀取數(shù)據(jù) */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* 讀取到的數(shù)據(jù)通過串口錯位輸出 */
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}
/* 串口msh命令執(zhí)行函數(shù) */
static int uart_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* 查找系統(tǒng)中的串口設(shè)備 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 初始化信號量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中斷接收及輪詢發(fā)送模式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 設(shè)置接收回調(diào)函數(shù) */
rt_device_set_rx_indicate(serial, uart_input);
/* 發(fā)送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 創(chuàng)建 serial 線程 */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* 創(chuàng)建成功則啟動線程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 導(dǎo)出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_sample, uart device sample);
DMA 接收及輪詢發(fā)送
- 當(dāng)串口通過DMA接收到一批數(shù)據(jù)后會調(diào)用接收回調(diào)函數(shù)桅锄,接收回調(diào)函數(shù)會把此時緩沖區(qū)的數(shù)據(jù)大小通過消息隊(duì)列發(fā)送給等待的數(shù)據(jù)處理線程泞辐。線程獲取到消息后被激活笔横,并讀取數(shù)據(jù)。
- 一般情況下 DMA 接收模式會結(jié)合 DMA 接收完成中斷和串口空閑中斷完成數(shù)據(jù)接收咐吼。
- 此示例代碼不局限于特定的 BSP吹缔,根據(jù) BSP 注冊的串口設(shè)備,修改示例代碼宏定義 SAMPLE_UART_NAME 對應(yīng)的串口設(shè)備名稱即可運(yùn)行锯茄。
運(yùn)行序列圖如下圖所示:
/*
* 程序清單:這是一個串口設(shè)備 DMA 接收使用例程
* 例程導(dǎo)出了 uart_dma_sample 命令到控制終端
* 命令調(diào)用格式:uart_dma_sample uart3
* 命令解釋:命令第二個參數(shù)是要使用的串口設(shè)備名稱厢塘,為空則使用默認(rèn)的串口設(shè)備
* 程序功能:通過串口輸出字符串"hello RT-Thread!",并通過串口輸出接收到的數(shù)據(jù)肌幽,然后打印接收到的數(shù)據(jù)晚碾。
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart3" /* 串口設(shè)備名稱 */
/* 串口接收消息結(jié)構(gòu)*/
struct rx_msg
{
rt_device_t dev;
rt_size_t size;
};
/* 串口設(shè)備句柄 */
static rt_device_t serial;
/* 消息隊(duì)列控制塊 */
static struct rt_messagequeue rx_mq;
/* 接收數(shù)據(jù)回調(diào)函數(shù) */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
struct rx_msg msg;
rt_err_t result;
msg.dev = dev;
msg.size = size;
result = rt_mq_send(&rx_mq, &msg, sizeof(msg));
if ( result == -RT_EFULL)
{
/* 消息隊(duì)列滿 */
rt_kprintf("message queue full!\n");
}
return result;
}
static void serial_thread_entry(void *parameter)
{
struct rx_msg msg;
rt_err_t result;
rt_uint32_t rx_length;
static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];
while (1)
{
rt_memset(&msg, 0, sizeof(msg));
/* 從消息隊(duì)列中讀取消息*/
result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
if (result == RT_EOK)
{
/* 從串口讀取數(shù)據(jù)*/
rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
rx_buffer[rx_length] = '\0';
/* 通過串口設(shè)備 serial 輸出讀取到的消息 */
rt_device_write(serial, 0, rx_buffer, rx_length);
/* 打印數(shù)據(jù) */
rt_kprintf("%s\n",rx_buffer);
}
}
}
static int uart_dma_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
static char msg_pool[256];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* 查找串口設(shè)備 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 初始化消息隊(duì)列 */
rt_mq_init(&rx_mq, "rx_mq",
msg_pool, /* 存放消息的緩沖區(qū) */
sizeof(struct rx_msg), /* 一條消息的最大長度 */
sizeof(msg_pool), /* 存放消息的緩沖區(qū)大小 */
RT_IPC_FLAG_FIFO); /* 如果有多個線程等待喂急,按照先來先得到的方法分配消息 */
/* 以 DMA 接收及輪詢發(fā)送方式打開串口設(shè)備 */
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
/* 設(shè)置接收回調(diào)函數(shù) */
rt_device_set_rx_indicate(serial, uart_input);
/* 發(fā)送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 創(chuàng)建 serial 線程 */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* 創(chuàng)建成功則啟動線程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 導(dǎo)出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_dma_sample, uart device dma sample);
串口設(shè)備應(yīng)用層接口與驅(qū)動層的硬件固件庫調(diào)用關(guān)系
rt_hw_usart_init -> rt_hw_serial_register -> rt_device_register -> rt_object_init
串口初始化
int rt_hw_usart_init(void)
{
rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart);
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
stm32_uart_get_dma_config();
for (int i = 0; i < obj_num; i++)
{
uart_obj[i].config = &uart_config[i];
uart_obj[i].serial.ops = &stm32_uart_ops;
uart_obj[i].serial.config = config;
#if defined(RT_SERIAL_USING_DMA)
if (uart_obj[i].uart_dma_flag)
{
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_DMA_RX
, &uart_obj[i]);
}
else
#endif
{
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX
, &uart_obj[i]);
}
RT_ASSERT(result == RT_EOK);
}
return result;
}
串口設(shè)備注冊
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
const char *name,
rt_uint32_t flag,
void *data)
{
rt_err_t ret;
struct rt_device *device;
RT_ASSERT(serial != RT_NULL);
device = &(serial->parent);
device->type = RT_Device_Class_Char; //串口屬于字符型設(shè)備
device->rx_indicate = RT_NULL;
device->tx_complete = RT_NULL;
#ifdef RT_USING_DEVICE_OPS
device->ops = &serial_ops;
#else
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;
#endif
device->user_data = data;
/* register a character device */
ret = rt_device_register(device, name, flag);
#if defined(RT_USING_POSIX)
/* set fops */
device->fops = &_serial_fops;
#endif
return ret;
}
將串口的操作接口格嘁,與應(yīng)用層的接口映射起來。
在rt_serial_init廊移,rt_serial_open糕簿,rt_serial_close,rt_serial_read狡孔,rt_serial_write懂诗,rt_serial_control
接口中,其實(shí)是通過serial->ops->control函數(shù)指針苗膝,調(diào)用了下面對應(yīng)的函數(shù)結(jié)構(gòu)體中的函數(shù)殃恒。
static const struct rt_uart_ops stm32_uart_ops =
{
.configure = stm32_configure,
.control = stm32_control,
.putc = stm32_putc,
.getc = stm32_getc,
};
而stm32_configure, stm32_control辱揭,stm32_putc离唐,stm32_getc
接口,就是直接對串口的寄存器進(jìn)行的操作问窃。
設(shè)備注冊
rt_err_t rt_device_register(rt_device_t dev,
const char *name,
rt_uint16_t flags)
{
if (dev == RT_NULL)
return -RT_ERROR;
if (rt_device_find(name) != RT_NULL)
return -RT_ERROR;
rt_object_init(&(dev->parent), RT_Object_Class_Device, name);
dev->flag = flags;
dev->ref_count = 0;
dev->open_flag = 0;
#if defined(RT_USING_POSIX)
dev->fops = RT_NULL;
rt_wqueue_init(&(dev->wait_queue));
#endif
return RT_EOK;
}
RTM_EXPORT(rt_device_register);