串口設(shè)備驅(qū)動

訪問串口設(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):

  1. RT_DEVICE_FLAG_STREAM:流模式用于向串口終端輸出字符串:當(dāng)輸出的字符是 "\n" (對應(yīng) 16 進(jìn)制值為 0x0A)時,自動在前面輸出一個 "\r"(對應(yīng) 16 進(jìn)制值為 0x0D) 做分行肤频。
  2. 流模式 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ā)送

示例代碼的主要步驟如下所示:

  1. 首先查找串口設(shè)備獲取設(shè)備句柄碱呼。
  2. 初始化回調(diào)函數(shù)發(fā)送使用的信號量,然后以讀寫及中斷接收方式打開串口設(shè)備宗侦。
  3. 設(shè)置串口設(shè)備的接收回調(diào)函數(shù)愚臀,之后發(fā)送字符串,并創(chuàng)建讀取數(shù)據(jù)線程矾利。
  4. 讀取數(shù)據(jù)線程會嘗試讀取一個字符數(shù)據(jù)姑裂,如果沒有數(shù)據(jù)則會掛起并等待信號量,當(dāng)串口設(shè)備接收到一個數(shù)據(jù)時會觸發(fā)中斷并調(diào)用接收回調(diào)函數(shù)男旗,此函數(shù)會發(fā)送信號量喚醒線程舶斧,此時線程會馬上讀取接收到的數(shù)據(jù)。
  5. 此示例代碼不局限于特定的 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);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侯繁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泡躯,更是在濱河造成了極大的恐慌贮竟,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件较剃,死亡現(xiàn)場離奇詭異咕别,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)写穴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門惰拱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啊送,你說我怎么就攤上這事偿短⌒拦拢” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵昔逗,是天一觀的道長降传。 經(jīng)常有香客問我,道長勾怒,這世上最難降的妖魔是什么婆排? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮笔链,結(jié)果婚禮上段只,老公的妹妹穿的比我還像新娘。我一直安慰自己鉴扫,他們只是感情好赞枕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坪创,像睡著了一般炕婶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上误堡,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天古话,我揣著相機(jī)與錄音雏吭,去河邊找鬼锁施。 笑死,一個胖子當(dāng)著我的面吹牛杖们,可吹牛的內(nèi)容都是我干的悉抵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼摘完,長吁一口氣:“原來是場噩夢啊……” “哼姥饰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起孝治,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤列粪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谈飒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岂座,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年杭措,在試婚紗的時候發(fā)現(xiàn)自己被綠了费什。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡手素,死狀恐怖鸳址,靈堂內(nèi)的尸體忽然破棺而出瘩蚪,到底是詐尸還是另有隱情,我是刑警寧澤稿黍,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布疹瘦,位于F島的核電站,受9級特大地震影響闻察,放射性物質(zhì)發(fā)生泄漏拱礁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一辕漂、第九天 我趴在偏房一處隱蔽的房頂上張望呢灶。 院中可真熱鬧,春花似錦钉嘹、人聲如沸鸯乃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缨睡。三九已至,卻和暖如春陈辱,著一層夾襖步出監(jiān)牢的瞬間奖年,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工沛贪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陋守,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓利赋,卻偏偏與公主長得像水评,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子媚送,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 串口操作 串口操作需要的頭文件 #include /*標(biāo)準(zhǔn)輸入輸出定義*/ #include /*標(biāo)準(zhǔn)函數(shù)庫定...
    旅行家John閱讀 1,324評論 0 3
  • 大學(xué)的時候中燥,幫朋友寫的操作系統(tǒng)調(diào)研的作業(yè),最近整理過去的文檔時候偶然發(fā)現(xiàn)塘偎,遂作為博客發(fā)出來疗涉。 從串口驅(qū)動到Linu...
    free_will閱讀 7,391評論 7 59
  • # STM32之串口DMA接收不定長數(shù)據(jù) ## 引言 在使用stm32或者其他單片機(jī)的時候,會經(jīng)常使用到串口通訊吟秩,...
    杰杰T_T閱讀 535評論 0 0
  • 姓名:周崇杰 學(xué)號:16140120059 專業(yè):機(jī)械設(shè)計(jì)制造及其自動化 轉(zhuǎn)載自:http://blog.csd...
    CJbaby閱讀 3,492評論 0 3
  • 姓名:莫益彰 學(xué)號:16030140019 【嵌牛導(dǎo)讀】:串口通信是指外設(shè)和計(jì)算機(jī)間咱扣,通過數(shù)據(jù)信號線 、地線峰尝、控制...
    換個名字消消毒閱讀 1,592評論 1 5