一、基礎知識
本節主要寫一下通信的一些基礎知識,簡單過一遍,防忘。
1. 通信基本知識
(1)數據傳送方式
分類:串行和並行。
(2)數據通信方向
分類:全雙工、半雙工和單工。
(3)數據同步方式
分類:同步和異步。
(4)通信速率
比特率(Bitrate):每秒鍾傳輸的二進制位數,單位為比特每秒(bit/s)。
波特率(Baudrate):表示每秒鍾傳輸的碼元個數,單位為波特(B),沒有每秒!(常用的波特率:4800、9600、19200、38400、43000、56000、57600、115200)
碼元:舉個例子,當我們用一個二進制數(0或1)表示信息時,0或1為一個碼元,它只有一個位數;當用兩個二進制數(00,01,10,11)表示信息時,譬如01,就是一個碼元,它有兩個位數。
2. 串口通信協議
(1)物理層
物理層:規定通訊系統中具有機械、電子功能部分的特性,確保原始數據在物理媒體的傳輸。其實就是硬件部分。
RS-232標准
RS232標准串口主要用於工業設備直接通信。電平轉換芯片一般有MAX3232,SP3232。
TTL電平標准是5V表示邏輯1,0V表示邏輯0;而為了增加串口通信的遠距離傳輸以及抗干擾能力,RS-232使用-15V表示邏輯1,+15V表示邏輯0。
信號通過DB9串口連接器傳輸入設備,使用的是RS-232標准電平,因此還有電平轉換芯片將其轉為TTL電平。
USB轉串口
USB轉串口主要用於設備跟電腦通信,電平轉換芯片一般有CH340等,所以電腦要安裝CH340電平轉換芯片的驅動程序(之后我們會用到)。
原生串口轉串口
原生的串口通信主要是控制器跟串口的設備或者傳感器通信,不需要經過電平轉換芯片來轉換電平,直接用TTL電平通信。
(2)協議層
串口通信一般是以幀格式傳輸數據,即一幀一幀傳輸,每幀包含有起始信號、數據信息、停止信息,可能還有校驗信息。
串口數據包的組成:
- 起始位:1個邏輯0表示。
- 有效數據:在起始位后緊接着的就是有效數據,有效數據的長度常被約定為5、 6 、 7 或 8 位長。
- 校驗位:用於數據的抗干擾性。
五種校驗方法:
奇校驗(odd,有效數據和校驗位中“ 1”的個數為奇數)
偶校驗(even,有效數據和校驗位中“ 1”的個數為偶數)
0校驗(space,校驗位總為0)
1校驗(mark,校驗位總為1)
無校驗(noparity,不包含校驗位)
- 結束位:由 0.5、 1、 1.5 或 2 個邏輯1的數據位表示。
二、USART串口通信詳解
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)為通用同步異步收發器,支持同步單向通信和半雙工單線通信。還有一種“閹割版”——UART(Universal Asynchronous Receiver/Transmitter),去掉了同步,為異步收發器。
【以下內容來自STM32中文參考手冊25章通用同步異步收發器(USART)】USART 滿足外部設備對工業標准NRZ異步串行數據格式的要求,並且使用了小數波特率發生器,可以提供多種波特率,使得它的應用更加廣泛。USART支持同步單向通信和半雙工單線通信;還支持局域互連網絡 LIN、智能卡(SmartCard)協議與lrDA(紅外線數據協會) SIR ENDEC規范。USART支持使用DMA,可實現高速數據通信。
1. USART功能框圖
本節內容可參考STM32中文參考手冊25.3節USART功能概述。我們將框圖分為4個部分進行講解。
(1)引腳
- TX:發送數據輸出
- RX:接收數據串行輸入
- SCLK(位於最右邊):發送器時鍾輸出,僅同步通信時使用
- nRTS:請求發送(Request To Send)
- nCTS:允許發送(Clear To Send)
- SW_RX:數據接收引腳,屬於內部引腳。
引腳對應的編號如下:
(2)數據寄存器
數據寄存器(USART_DR)只有低9位有效,實際上它包含一個發送數據寄存器USART_TDR和一個接收數據寄存器USART_RDR。TDR和RDR都是介於系統總線和移位寄存器之間。這里比較特別:一個地址對應了兩個物理內存。
當進行發送操作時,往USART_DR寫入數據會自動存儲在 TDR內,然后把內容轉移到發送移位寄存器,最后通過模塊發送到TX引腳;
當進行讀取操作時,信息從RX引腳進入,通過模塊后存入接受移位寄存器,然后把內容轉移到RDR內,最后USART_DR提取RDR數據。
(3)控制器
本部分內容可參考STM32中文參考手冊25.6節USART寄存器描述。
USART有專門控制發送的發送器、控制接收的接收器,還有喚醒單元、中斷控制等。
控制寄存器1(USART_CR1)的TE位負責使能發送器,發送器就會“叫醒”發送移位寄存器。
控制寄存器1(USART_CR1)的RX位負責使能接收器,接收器就會“叫醒”接收移位寄存器。
控制寄存器1的TXEIE或RXNEIE置1可以產生中斷。
控制寄存器1其他位:TXE:發送移位寄存器為空,發送單個字節時使用。TC:發送完成,發送多個字節數據時候使用。TXIE:發送完成中斷使能。
控制寄存器1的M位設置的是字長,該位定義了數據字的長度,由軟件對其設置和清零。設置0:一個起始位,8個數據位;設置1:一個起始位,9個數據位。之前已經說過,數據寄存器(USART_DR)只有低9位有效,並且第9位數據是否有效要取決於控制寄存器1的M位設置。
控制寄存器2(USART_CR2)的STOP[1:0]位用於設置停止位,可選0.5個、1個、1.5個、2個停止位。默認使用1個停止位。2個停止位適用於正常USART模式、單線模式和調制解調器模式。0.5和1.5個停止位用於智能卡模式。
(4)波特率
通過波特率寄存器(USART_BRR)可設置波特率。由於計算出的分頻因子有小數,因此寄存器分為兩個部分:
DIV_Mantissa[11:0]:USARTDIV的整數部分,這12位定義了USART分頻器除法因子(USARTDIV)的整數部分。
DIV_Fraction[3:0]:USARTDIV的小數部分,這4位定義了USART分頻器除法因子(USARTDIV)的小數部分。
波特比率的計算公式為:
fCK:串口的時鍾,需要注意不同USART有不同AHB時鍾頻率。
USARTDIV:分頻器除法因子,為無符號的定點數,最后為寫入寄存器的值。
例如設置波特率為115200的計算過程:
使用十進制小數轉換為二進制小數的方法進行。
2. USART的結構體定義和相關庫函數
本節內容位於頭文件stm32f10x_usart.h中。
USART的初始化結構體定義:
typedef struct
{
//波特率設置
uint32_t USART_BaudRate;
//字長設置
uint16_t USART_WordLength;
//停止位設置
uint16_t USART_StopBits;
//校驗位設置
uint16_t USART_Parity;
//USART模式設置
uint16_t USART_Mode;
//硬件流控制選擇
uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;
USART的相關常用且重要庫函數(可查看庫函數手冊):
void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
3. 配置USART的流程
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 1. 開啟USART時鍾和對應的引腳時鍾
// USART1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 2. 配置USART引腳
// USART1-TX發送 GPIOA.9 復用推挽模式
GPIO_InitStructure.GPIO_Pin = USART1_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStructure);
// USART1-RX接收 GPIOA.10 浮空輸入模式
GPIO_InitStructure.GPIO_Pin = USART1_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStructure);
// 3. 配置USART1串口參數
USART_InitStructure.USART_BaudRate = USART_BAUDRATE; // 3.1 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 3.2 配置數據字長
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 3.3 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 3.4 配置校驗位
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 3.5 配置工作模式
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 3.6 配置硬件控制流
USART_Init(USART1, &USART_InitStructure); // 4. 初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//USART中斷使能
USART_Cmd(USART1, ENABLE); // 5. 使能串口
}
三、實例:單片機向PC機(上位機)發送數據
注意,電腦需要安裝CH340驅動和串口調試助手工具,並在工具中設置好波特率、校驗位等參數。單片機要用USB(注意是接到單片機USB232接口)連接電腦而不是用STLINK!!!
1. 發送一個字節
void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
USART_SendData(pUSARTx, data); //發送數據
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); //等待發送完成
}
在main函數中進行調用:
USART_Config();
USART_SendByte(USART1, 0x34);
若選擇16進制顯示,則串口軟件會顯示34。否則會按照ASCII碼轉換為相應的字符。
2. 發送兩個字節
這里需要說明一下,由於一次只能發送一個字節(4位),所以需要把數據源分段發送。
void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data)
{
uint8_t temp_h, temp_l;
temp_h = (data & 0xFF00) >> 8; //截取高4位
temp_l = data & 0x00FF; //截取低4位
USART_SendData(pUSARTx, temp_h);
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
USART_SendData(pUSARTx, temp_l);
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
3. 串口發送8位數據的數組
void USART_SendArray(USART_TypeDef* pUSARTx, uint8_t* Array, uint8_t num)
{
uint8_t i;
for( i = 0; i < num; i++)
{
USART_SendData(pUSARTx, Array[i]);
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
}
4. 串口發送字符串
中文字符也行。
void USART_SendStr(USART_TypeDef* pUSARTx, uint8_t* str)
{
while( *(str) != '\0')
{
USART_SendData(pUSARTx, *(str++));
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
}
5. 重定向printf函數輸出到串口
注意需要在魔法棒Target勾選Use MicroLIB,然后重新編譯一次。
MicroLib是對標准C庫進行了高度優化之后的庫,供MDK默認使用,相比之下,MicroLIB的代碼更少,資源占用更少。
//重定向c庫函數printf到串口,重定向后可使用printf、putchar函數
int fputc(int ch, FILE *f)
{
/* 發送一個字節數據到串口 */
USART_SendData(USART1, (uint8_t)ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
在main函數中調用以下語句時會輸出到串口軟件:
printf("我永遠喜歡伊蕾娜!!!\n");
putchar('A');
四、實例:單片機從PC機(上位機)接收數據
1. 重定向scanf函數輸入到串口
//重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar函數
int fgetc(FILE *f)
{
/* 等待串口輸入數據 */
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
2. 使用中斷服務函數
//stm32f10x_it.c
void USART1_IRQHandler(void)
{
uint8_t a;
if( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET ) //如果接收寄存器接收到數據
{
a = USART_ReceiveData(USART1);
USART_SendData(USART1, a); //將接收的數據發送
}
}
需要配置NVIC:
void NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
在main函數調用scanf函數,PC輸入數據時,單片機會回復同樣的數據給PC。
3. 上位機控制LED
功能實現:輸入1時實現LED0反轉,輸入2時實現LED1反轉,輸入其他向上位機提示輸入有誤。部分代碼如下:
int main(void)
{
uint8_t ch;
USART_Config();
LED_Init();
while(1)
{
ch = getchar();
printf("你選擇了%c燈!\n", ch);
switch(ch)
{
case '1':
LED0 = !LED0;
break;
case '2':
LED1 = !LED1;
break;
default:
printf("輸入有誤!請重新輸入!\n");
break;
}
}
}