- 關注嘉友創科技公衆号
第十一章 ESP32的兩個UART實驗 - 源碼位址:https://github.com/HX-IoT/ESP32-Developer-Guide
- ESP32開發指南QQ群:824870185,内有pdf版,排版整潔。
學習目的及目标
序列槽通信的原理
學習ESP32 的UART功能的配置
掌握UART收發測試程式
序列槽通訊協定簡介
序列槽通訊(Serial Communication)是一種裝置間非常常用的串行通訊方式,因為它簡單便捷,大部分電子裝置都支援該通訊方式,電子工程師在調試裝置時也經常使用該通訊方式輸出調試資訊,ESP32自有一個序列槽用于程式下載下傳和log列印,就是這個道理。
在計算機科學裡,大部分複雜的問題都可以通過分層來簡化。如晶片被分為核心層和片上外設;對于通訊協定,我們也以分層的方式來了解,最基本的是把它分為實體層和協定層。實體層規定通訊系統中具有機械、電子功能部分的特性,確定原始資料在實體媒體的傳輸。協定層主要規定通訊邏輯,統一收發雙方的資料打包、解包标準。簡單來說實體層規定我們用嘴巴還是用肢體來交流,協定層則規定我們用中文還是英文來交流。
實體層
序列槽通訊的實體層有很多标準及變種,我們主要講解 RS-232标準 ,RS-232标準主要規定了信号的用途、通訊接口以及信号的電平标準。使用 RS-232标準的序列槽裝置間常見的通訊結構如下。
在上面的通訊方式中,兩個通訊裝置的“DB9 接口”之間通過序列槽信号線建立起連接配接,序列槽信号線中使用“RS-232 标準”傳輸資料信号。由于 RS-232電平标準的信号不能直接被控制器直接識别,是以這些信号會經過一個“電平轉換晶片”轉換成控制器能識别的“TTL校準”的電平信号,才能實作通訊。
協定層
序列槽通訊的資料包由發送裝置通過自身的 TXD 接口傳輸到接收裝置的 RXD 接口。在序列槽通訊的協定層中,規定了資料包的内容,它由啟始位、主體資料、校驗位以及停止位組成,通訊雙方的資料包格式要約定一緻才能正常收發資料,其組成如下:
波特率
本章中主要講解的是序列槽異步通訊,異步通訊中由于沒有時鐘信号(如前面講解的 DB9接口中是沒有時鐘信号的),是以兩個通訊裝置之間需要約定好波特率,即每個碼元的長度,以便對信号進行解碼,上圖中用虛線分開的每一格就是代表一個碼元。常見的波特率為4800、9600、115200等。
通訊的起始和停止信号
序列槽通訊的一個資料包從起始信号開始,直到停止信号結束。資料包的起始信号由一個邏輯 0的資料位表示,而資料包的停止信号可由 0.5、1、1.5或 2 個邏輯 1 的資料位表示,隻要雙方約定一緻即可。
有效資料
在資料包的起始位之後緊接着的就是要傳輸的主體資料内容,也稱為有效資料,有效資料的長度常被約定為 5、6、7或 8位長。
資料校驗
在有效資料之後,有一個可選的資料校驗位。由于資料通信相對更容易受到外部幹擾導緻傳輸資料出現偏差,可以在傳輸過程加上校驗位來解決這個問題。校驗方法有奇校驗(odd)、偶校驗(even)、0校驗(space)、1校驗(mark)以及無校驗(noparity)。在無校驗的情況下,資料包中不包含校驗位。
硬體設計及原理
本實驗闆使用了ESP32的UART1和UART2,下表是我們的程式IO的映射。
UART1 | 功能 | 映射ESP32的引腳 |
TXD | 發送 | IO5 |
RXD | 接收 | IO4 |
UART2 | ||
IO12 | ||
IO13 |
若您使用的實驗闆 UART的連接配接方式或引腳不一樣,隻需根據我們的工程修改引腳即可,程式的控制原理相同。
軟體設計
代碼邏輯
ESP32的UART接口介紹
UART配置函數:uart_param_config();
函數原型 | esp_err_t uart_param_config ( uart_port_t uart_num, const uart_config_t *uart_config ) |
函數功能 | UART配置函數 |
參數 | [in] uart_num:序列槽号,取值 UART_NUM_0 = 0x0, /*序列槽0,下載下傳程式端口*/ UART_NUM_1 = 0x1, /*序列槽1*/ UART_NUM_2 = 0x2,/*序列槽2*/ [in] uart_config:序列槽參數配置 typedef struct { int baud_rate; /*波特率*/ uart_word_length_t data_bits; /*資料位*/ uart_parity_t parity; /*校驗模式*/ uart_stop_bits_t stop_bits; /*停止位*/ uart_hw_flowcontrol_t flow_ctrl; /*硬體流控使能位*/ } uart_config_t; |
傳回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 參數錯誤 |
UART的IO映射設定函數:uart_set_pin();
esp_err_t uart_set_pin int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num | |
UART的IO映射函數 | |
[in] tx_io_num:發送引腳 [in] rx_io_num:接收引腳 [in] rts_io_num:rts流控引腳 [in] cts_io_num:cts流控引腳 | |
UART功能安裝使能函數:uart_driver_install();
esp_err_t uart_driver_install int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t* uart_queue, int intr_alloc_flags | |
UART功能安裝使能函數 | |
[in] uart_num:序列槽号 [in] rx_buffer_size:接收緩存大小 [in] tx_buffer_size:發送緩存大小 [in] queue_size:隊列大小 [in] uart_queue:序列槽隊列指針 [in] intr_alloc_flags:配置設定中斷标記 | |
UART發送函數:uart_write_bytes();
int uart_write_bytes const char* src, size_t size | |
UART發送函數函數 | |
[in] src:發送資料指針 [in]size:發送資料大小 | |
(-1) :參數錯誤 (>=0):資料已放到發送緩存 |
UART讀取函數:uart_read_bytes();
int uart_read_bytes uint8_t* buf, uint32_t length, TickType_t ticks_to_wait | |
UART讀取函數 | |
[in] buf:接收資料指針 [in]length:接收資料最大大小 [in]ticks_to_wait:等待時間 | |
更多更詳細接口請參考官方指南。
序列槽收發代碼編寫
加載序列槽相關的頭檔案、定義序列槽IO映射引腳、定義序列槽緩存等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <stdio.h> #include "esp_system.h" #include "esp_spi_flash.h" #include "esp_wifi.h" #include "esp_event_loop.h" #include "esp_log.h" #include "esp_err.h" #include "nvs_flash.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/ledc.h" #include "driver/uart.h" #include "driver/gpio.h" #include "string.h" //UART1 #define RX1_BUF_SIZE (1024) #define TX1_BUF_SIZE (512) #define TXD1_PIN (GPIO_NUM_5) #define RXD1_PIN (GPIO_NUM_4) //UART2 #define RX2_BUF_SIZE (1024) #define TX2_BUF_SIZE (512) #define TXD2_PIN (GPIO_NUM_12) #define RXD2_PIN (GPIO_NUM_13) |
序列槽配置函數
28 | void uart_init(void) { //序列槽配置結構體 uart_config_t uart1_config,uart2_config; //序列槽參數配置->uart1 uart1_config.baud_rate = 115200; //波特率 uart1_config.data_bits = UART_DATA_8_BITS; //資料位 uart1_config.parity = UART_PARITY_DISABLE; //校驗位 uart1_config.stop_bits = UART_STOP_BITS_1; //停止位 uart1_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; //硬體流控 uart_param_config(UART_NUM_1, &uart1_config); //設定序列槽 //IO映射-> T:IO4 R:IO5 uart_set_pin(UART_NUM_1, TXD1_PIN, RXD1_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); //注冊序列槽服務即使能+設定緩存區大小 uart_driver_install(UART_NUM_1, RX1_BUF_SIZE * 2, TX1_BUF_SIZE * 2, 0, NULL, 0); //序列槽參數配置->uart2 uart2_config.baud_rate = 115200; //波特率 uart2_config.data_bits = UART_DATA_8_BITS; //資料位 uart2_config.parity = UART_PARITY_DISABLE; //校驗位 uart2_config.stop_bits = UART_STOP_BITS_1; //停止位 uart2_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; //硬體流控 uart_param_config(UART_NUM_2, &uart2_config); //設定序列槽 //IO映射-> T:IO12 R:IO13 uart_set_pin(UART_NUM_2, TXD2_PIN, RXD2_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); uart_driver_install(UART_NUM_2, RX2_BUF_SIZE * 2, TX2_BUF_SIZE * 2, 0, NULL, 0); } |
主函數:序列槽初始化、建立兩個任務用于序列槽資料接收、測試序列槽發送資料等。
/* * 應用程式的函數入口 */ void app_main() { //序列槽初始化 uart_init(); //建立序列槽1接收任務 xTaskCreate(uart1_rx_task, "uart1_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL); //建立序列槽2接收任務 xTaskCreate(uart2_rx_task, "uart2_rx_task", 1024*2, NULL, configMAX_PRIORITIES-1, NULL); //序列槽1資料發送測試 uart_write_bytes(UART_NUM_1, "uart1 test OK ", strlen("uart1 test OK ")); //序列槽2資料發送測試 uart_write_bytes(UART_NUM_2, "uart2 test OK ", strlen("uart2 test OK ")); |
兩個序列槽任務
* 序列槽1接收任務 void uart1_rx_task() uint8_t* data = (uint8_t*) malloc(RX1_BUF_SIZE+1);//配置設定記憶體,用于序列槽接收 while (1) { //擷取序列槽1接收的資料 const int rxBytes = uart_read_bytes(UART_NUM_1, data, RX1_BUF_SIZE, 10 / portTICK_RATE_MS); if (rxBytes > 0) { data[rxBytes] = 0;//在序列槽接收的資料增加結束符 //将接收到的資料發出去 uart_write_bytes(UART_NUM_1, (char *)data, rxBytes); } } free(data);//釋放申請的記憶體 * 序列槽2接收任務:基本同上,省略 |
硬體連接配接
可按照IO映射表将序列槽1和序列槽2的IO接到USB轉序列槽電路上,每次可接一個。如下圖是序列槽1接線圖。
效果展示
UART總結
樂鑫已經把序列槽部分的API封裝的非常好,直接在任務重解析資料即可。
序列槽發送32位元組,50ms周期發送1小時無丢包
序列槽發送32位元組,1ms發送5分鐘無當機
序列槽部分初步測試完成