目的
? 串口通信是非常非常常見的一種通信方式,,必須掌握的??梢詮娜缦聨讉€方面掌握串口通信:
串口通信原理,,此處我們只研究異步串口
GD32常見的幾種串口通信配置
異步串口通信原理
1. 配置
? 在了解原理之前,我們先看看串口要如何使用,,如下圖,,只要選擇正確的串口號,,把收發(fā)雙方的波特率、校驗位,、數(shù)據(jù)位,、停止位配置成一致,這么就可以實現(xiàn)雙方通信,。
那么配置的這些參數(shù)分別代表什么意思呢,?
串口號:唯一標(biāo)識一個串口,當(dāng)設(shè)備存在多個串口時,,可以用其標(biāo)識每個串口,。
波特率:每秒鐘傳輸?shù)臄?shù)據(jù)位數(shù)。表示數(shù)據(jù)傳輸?shù)乃俾?,單位bps(位每秒),。比如115200bps就表示1s可以傳輸115200bits的數(shù)據(jù)。
校驗位:
? even 每個字節(jié)傳送整個過程中bit為1的個數(shù)是偶數(shù)個(校驗位調(diào)整個數(shù))
? odd 每個字節(jié)穿送整個過程中bit為1的個數(shù)是奇數(shù)個(校驗位調(diào)整個數(shù))
? none 沒有校驗位
? space 校驗位總為0
? mark 校驗位總為1
數(shù)據(jù)位:5678共4個選擇,,這是歷史原因,,如下
? 5:用于電報機(jī)傳26個英文字母,5位足以
? 6:用于電報機(jī),,識別大小寫字母,,增加一個大小寫位
? 7:用于電腦,ASCII碼7位
? 8:用于電腦,,DBCS碼用于兼容ASCII和支持中文雙字節(jié)
停止位:
? 停止位是按長度來算的,。串行異步通信從計時開始,以單位時間為間隔(一個單位時間就是波特率的倒數(shù)),,依次接受所規(guī)定的數(shù)據(jù)位和奇偶校驗位,,并拼裝成一個字符的并行字節(jié);此后應(yīng)接收到規(guī)定長度的停止位“1”,。所以說,,停止位都是“1”,1.5是它的長度,,即停止位的高電平保持1.5個單位時間長度,。一般來講,停止位有1,,1.5,2個單位時間三種長度,。
2. 幀格式
? 下面我們看下串行協(xié)議的幀格式,,如圖
一個幀由4部分組成,起始位 數(shù)據(jù)位 校驗位 停止位,,正好跟上面的配置一一對應(yīng),,其中,,起始位必須是低電平,停止位必須是高電平,。
至此,,也大致明白串口是怎么回事了。
3. 常見的串口電平標(biāo)準(zhǔn)
? 下面幾種都是串口,,只是電平標(biāo)準(zhǔn)不同,,導(dǎo)致其應(yīng)用場景存在差異,通信協(xié)議和配置都是相同的,,通信原理是相同的,,軟件實現(xiàn)相同,硬件電路存在差異,。
TTL:
接線方式如圖
高電平表示邏輯1,, 低電平表示邏輯零
![1536645105294](assets/1536739271596.png)
RS232和RS485對比
抗干擾性:RS485 接口是采用平衡驅(qū)動器和差分接收器的組合,抗噪聲干擾性好,。RS232 接口使用一根信號線和一根信號返回線而構(gòu)成共地的傳輸形式,,這種共地傳輸容易產(chǎn)生共模干擾。
傳輸距離:RS485 接口的最大傳輸距離標(biāo)準(zhǔn)值為 1200 米(9600bps 時),,實際上可達(dá) 3000 米,。RS232 傳輸距離有限,最大傳輸距離標(biāo)準(zhǔn)值為 50 米,,實際上也只能用在 15 米左右,。
通信能力:RS-485 接口在總線上是允許連接多達(dá)128個收發(fā)器,用戶可以利用單一的 RS-485 接口方便地建立起設(shè)備網(wǎng)絡(luò),。RS-232只允許一對一通信,。
傳輸速率:RS-232傳輸速率較低,在異步傳輸時,,波特率為 20Kbps,。RS-485 的數(shù)據(jù)最高傳輸速率為 10Mbps 。
信號線:RS485 接口組成的半雙工網(wǎng)絡(luò),,一般只需二根信號線,。RS-232 口一般只使用 RXD、TXD,、GND 三條線 ,。
電氣電平值:RS-485的邏輯"1"以兩線間的電壓差為 (2-6) V 表示;邏輯"0"以兩線間的電壓差為-(2-6)V 表 示 ,。在 RS-232-C 中任何一條信號線的電壓均為負(fù)邏輯關(guān)系,。即:邏輯"1",-5- -15V,;邏輯"0 " 5- 15V ,。
4. 芯片如何實現(xiàn)串口功能
?
? 我們知道串口的作用是為CPU和其它設(shè)備之間提供通信,,本質(zhì)上是把數(shù)據(jù)從其他設(shè)備搬移到自身MCU的內(nèi)存中去,如上圖,,MCU為實現(xiàn)串口功能會做如上的模塊劃分,。
GPIO
串口總線狀態(tài),默認(rèn)是高電平,,所以Tx應(yīng)該是上拉輸出,,Rx應(yīng)該是浮空輸入。
移位器
我們知道串口是一位位傳輸?shù)?,所以移位器即可以實現(xiàn)串口的收發(fā),。
數(shù)據(jù)寄存器
用于存儲將要發(fā)送和接收的數(shù)據(jù),其實只要收發(fā)共用一個字節(jié)就足以,。
時鐘
上述的運(yùn)行過程都需要在固定時鐘下才能正確運(yùn)行,,例如波特率。
數(shù)據(jù)由寄存器搬移到內(nèi)存
CPU方式,,由CPU控制數(shù)據(jù)如何在數(shù)據(jù)寄存器和內(nèi)存之間進(jìn)行轉(zhuǎn)移,,例如當(dāng)數(shù)據(jù)寄存器空時,將內(nèi)存轉(zhuǎn)移到數(shù)據(jù)寄存器中,,即發(fā)送過程
DMA方式,,過程同CPU,那為什么還有有DMA呢,?因為數(shù)據(jù)搬移省去CPU的參與,,也就意味著CPU可以去忙其它事情,效率自然就高了,。
狀態(tài)寄存器
我們粗略的思考下,,在整個串口的傳輸過程中,肯定會有各式各樣的狀態(tài),,例如,,收到數(shù)據(jù),數(shù)據(jù)異常,,幀錯誤,,數(shù)據(jù)發(fā)送完畢,數(shù)據(jù)寄存器空了等等,,這些都需要狀態(tài)寄存器存儲,。
再深入思考下,當(dāng)我們需要及時的處理上述狀態(tài)時,,靠CPU輪詢顯然太慢了,,所以肯定需要中斷,再增加一組中斷狀態(tài)寄存器。
配置寄存器
? 上述情況那么多,,代表不同的配置,肯定需要幾組配置寄存器,。例如,,中斷的使能控制等
功能設(shè)計
? 如果明白了原理,那么自然就知道該如何配置一個串口了,,無非就是從芯片手冊中找到相應(yīng)的寄存器進(jìn)行配置而已,。
? 在”串口發(fā)送“例子中,已經(jīng)接觸了串口的發(fā)送功能,,現(xiàn)在我們把這個例子再度深入,,實現(xiàn)串口的接收功能。實現(xiàn)一個回顯功能,,即PC通過串口向GD32寫入數(shù)據(jù),,然后GD32把數(shù)據(jù)原封不動返回給PC。
輪詢方式
VOID DRV_UART1_PollTest(VOID)
{
U8 ch = 0;
while (1)
{
if (USART_GetBitState(USART1, USART_FLAG_RBNE) != RESET)
{
ch = (U8)USART_DataReceive(USART1);
UART1_SendChar(ch);
}
}
}
VOID DRV_UART1_PollInit(VOID)
{
UART1_GpioInit();
UART1_Config();
USART_Enable(USART1, ENABLE);
}
效果如圖
中斷方式
注:中斷優(yōu)先級部分,,我會抽單獨章節(jié)分析,。
必須注意下面這兩個函數(shù)的區(qū)別,
USART_GetBitState(USART1, USART_FLAG_RBNE),; /* 非中斷使用 */
USART_GetIntBitState(USART1, USART_INT_RBNE);/* 中斷內(nèi)使用 */
中斷方式處理代碼如下:
VOID USART1_IRQHandler(VOID)
{
if (USART_GetIntBitState(USART1, USART_INT_RBNE) != RESET)
{
if (gUart1RxCount >= DRV_UART1_BUFLEN)
{
memset(gUart1RxBuf, 0, sizeof(gUart1RxBuf));
gUart1RxCount = 0;
}
gUart1RxBuf[gUart1RxCount] = (U8)USART_DataReceive(USART1);
gUart1RxCount ;
}
if (USART_GetIntBitState(USART1, USART_INT_IDLEF) != RESET)
{
gUart1RxBufFlag ;
}
}
VOID DRV_UART1_InterruptTest(VOID)
{
U8 rxCount = 0;
while (1)
{
if (gUart1RxBufFlag > 0)
{
for (rxCount = 0; rxCount < gUart1RxCount; rxCount )
{
UART1_SendChar(gUart1RxBuf[rxCount]);
}
memset(gUart1RxBuf, 0, sizeof(gUart1RxBuf));
gUart1RxCount = 0;
gUart1RxBufFlag = 0;
}
}
}
VOID DRV_UART1_InterruptInit(VOID)
{
UART1_GpioInit();
UART1_Config();
UART1_NvicConfiguration();
USART_Enable(USART1, ENABLE);
USART_INT_Set(USART1, USART_INT_RBNE, ENABLE);
USART_INT_Set(USART1, USART_INT_IDLEF, ENABLE);
}
DMA方式
? 注:DMA細(xì)節(jié)我會抽單獨章節(jié)分析,,此處只寫一個DMA輪詢方式的例子,。
static VOID UART1_DmaRxConfig(IN U8 *buf, IN U32 len)
{
DMA_InitPara DMA_InitStructure;
DMA_Enable(DMA1_CHANNEL5, DISABLE);
/* USART1 RX DMA1 Channel (triggered by USART1 Rx event) Config */
DMA_DeInit(DMA1_CHANNEL5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (U32) &(USART1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (U32)buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PERIPHERALSRC;
DMA_InitStructure.DMA_BufferSize = len;
DMA_InitStructure.DMA_PeripheralInc = DMA_PERIPHERALINC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEMORYINC_ENABLE;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PERIPHERALDATASIZE_BYTE;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MEMORYDATASIZE_BYTE;
DMA_InitStructure.DMA_Mode = DMA_MODE_NORMAL;
DMA_InitStructure.DMA_Priority = DMA_PRIORITY_VERYHIGH;
DMA_InitStructure.DMA_MTOM = DMA_MEMTOMEM_DISABLE;
DMA_Init(DMA1_CHANNEL5, &DMA_InitStructure);
DMA_Enable(DMA1_CHANNEL5, ENABLE);
}
VOID DRV_UART1_DmaInit(VOID)
{
UART1_GpioInit();
UART1_Config();
RCC_AHBPeriphClock_Enable(RCC_AHBPERIPH_DMA1, ENABLE);
UART1_DmaRxConfig(gUart1RxBuf, DRV_UART1_BUFLEN);
USART_Enable(USART1, ENABLE);
USART_DMA_Enable(USART1, (USART_DMAREQ_TX | USART_DMAREQ_RX), ENABLE);
}
static VOID UART1_DmaSend(IN U8 *buf, IN U32 len)
{
DMA_InitPara DMA_InitStructure;
DMA_Enable(DMA1_CHANNEL4, DISABLE);
/* USART1_Tx_DMA_Channel (triggered by USART1 Tx event) Config */
DMA_DeInit(DMA1_CHANNEL4);
DMA_InitStructure.DMA_PeripheralBaseAddr = (U32) &(USART1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (U32)buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PERIPHERALDST;
DMA_InitStructure.DMA_BufferSize = len;
DMA_InitStructure.DMA_PeripheralInc = DMA_PERIPHERALINC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEMORYINC_ENABLE;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PERIPHERALDATASIZE_BYTE;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MEMORYDATASIZE_BYTE;
DMA_InitStructure.DMA_Mode = DMA_MODE_NORMAL;
DMA_InitStructure.DMA_Priority = DMA_PRIORITY_VERYHIGH;
DMA_InitStructure.DMA_MTOM = DMA_MEMTOMEM_DISABLE;
DMA_Init(DMA1_CHANNEL4, &DMA_InitStructure);
DMA_Enable(DMA1_CHANNEL4, ENABLE);
while (DMA_GetBitState(DMA1_FLAG_TC4) == RESET)
{
}
}
VOID DRV_UART1_DmaTest(VOID)
{
while (1)
{
if (USART_GetBitState(USART1, USART_FLAG_IDLEF) != RESET)
{
UART1_DmaSend(gUart1RxBuf, DRV_UART1_BUFLEN);
memset(gUart1RxBuf, 0, DRV_UART1_BUFLEN);
UART1_DmaRxConfig(gUart1RxBuf, DRV_UART1_BUFLEN);
USART_DataReceive(USART1); /* 清除USART_FLAG_IDLEF */
}
}
}
總結(jié)
? 串口是一種非常常見的通信總線,,必須掌握。如果上面的原理和例子理解了,,我相信用GPIO口虛擬一個窗口并不是什么難事,。
代碼路徑
https://github.com/YaFood/GD32F103/tree/master/TestUART
來源:http://www./content-4-246651.html