標(biāo)準(zhǔn)庫(kù)3.5實(shí)現(xiàn):
《嵌入式-STM32開(kāi)發(fā)指南》第二部分 基礎(chǔ)篇 - 第6章串口通信
6.1串口簡(jiǎn)介
通用同步異步收發(fā)器(USART)提供了一種靈活的方法與使用工業(yè)標(biāo)準(zhǔn)NRZ異步串行數(shù)據(jù)格式的外部設(shè)備之間進(jìn)行全雙工數(shù)據(jù)交換撞鹉。USART利用分?jǐn)?shù)波特率發(fā)生器提供寬范圍的波特率選擇疆导。它支持同步單向通信和半雙工單線通信龄恋,也支持LIN(局部互連網(wǎng))不狮,智能卡協(xié)議和IrDA(紅外數(shù)據(jù)組織)SIR ENDEC規(guī)范,以及調(diào)制解調(diào)器(CTS/RTS)操作沉桌。它還允許多處理器通信敌土。使用多緩沖器配置的DMA方式瞳遍,可以實(shí)現(xiàn)高速數(shù)據(jù)通信压彭。圖一也就我們熟悉的串口通通信標(biāo)準(zhǔn)睦优。
如圖2所示,串口通過(guò)三個(gè)引腳與其他設(shè)備連接在一起壮不。任何USART雙向通信至少需要兩個(gè)腳:接收數(shù)據(jù)輸入(RX)和發(fā)送數(shù)據(jù)輸出(TX)汗盘。
? RX:接收數(shù)據(jù)串行輸入。通過(guò)采樣技術(shù)來(lái)區(qū)別數(shù)據(jù)和噪音询一,從而恢復(fù)數(shù)據(jù)隐孽。
? TX :發(fā)送數(shù)據(jù)輸出。當(dāng)發(fā)送器被禁止時(shí)家凯,輸出引腳恢復(fù)到它的I/O端口配置缓醋。當(dāng)發(fā)送器被激活,并且不發(fā)送數(shù)據(jù)時(shí)绊诲,TX引腳處于高電平。在單線和智能卡模式里褪贵,此I/O 口被同時(shí)用于數(shù)據(jù)的發(fā)送和接收掂之。
● 總線在發(fā)送或接收前應(yīng)處于空閑狀態(tài) ;
● 一個(gè)起始位 脆丁;
● 一個(gè)數(shù)據(jù)字(8或9位)世舰,最低有效位在前;
● 1或2個(gè)的停止位槽卫,由此表明數(shù)據(jù)幀的結(jié)束跟压;
● 使用分?jǐn)?shù)波特率發(fā)生器—— 12位整數(shù)和4位小數(shù)的表示方法。歼培;
● 一個(gè)狀態(tài)寄存器(USART_SR) 茸塞;
● 數(shù)據(jù)寄存器(USART_DR) ;
● 一個(gè)波特率寄存器(USART_BRR)查剖,12位的整數(shù)和4位小數(shù) 钾虐;
● 一個(gè)智能卡模式下的保護(hù)時(shí)間寄存器(USART_GTPR) ;
● IrDA_RDI: IrDA模式下的數(shù)據(jù)輸入笋庄;
● IrDA_TDO: IrDA模式下的數(shù)據(jù)輸出效扫;
● nCTS: 清除發(fā)送,若是高電平直砂,在當(dāng)前數(shù)據(jù)傳輸結(jié)束時(shí)阻斷下一次的數(shù)據(jù)發(fā)送菌仁;
● nRTS: 發(fā)送請(qǐng)求,若是低電平静暂,表明USART準(zhǔn)備好接收數(shù)據(jù)掘托。
異步串行通信以字符為單位,即一個(gè)字符一個(gè)字符地傳送 籍嘹。
串口外設(shè)的架構(gòu)圖(見(jiàn)圖 4)看起來(lái)十分復(fù)雜闪盔,實(shí)際上對(duì)于軟件開(kāi)發(fā)人員來(lái)說(shuō),我們只需要大概了解串口發(fā)送的過(guò)程即可辱士。從下至上泪掀,我們看到串口外設(shè)主要由三個(gè)部分組成,分別是波特率控制颂碘、收發(fā)控制和數(shù)據(jù)存儲(chǔ)轉(zhuǎn)移异赫。
? 波特率控制
波特率,即每秒傳輸?shù)亩M(jìn)制位數(shù)头岔,用 b/s (bps)表示塔拳,通過(guò)對(duì)時(shí)鐘的控制可以改變波特率。在配置波特率時(shí)峡竣,我們向波特比率寄存器 USART_BRR 寫(xiě)入?yún)?shù)靠抑,修改了串口時(shí)鐘 的 分 頻值USARTDIV 。 USART_BRR 寄存器包括兩部分适掰,分別是 DIV_Mantissa(USARTDIV 的整數(shù)部分)和 DIV_Fraction(USARTDIV 的小數(shù))部分颂碧,最終,計(jì)算公式為 USARTDIV=DIV_Mantissa+(DIV_Fraction/16)类浪。
USARTDIV 是對(duì)串口外設(shè)的時(shí)鐘源進(jìn)行分頻的载城,對(duì)于 USART1,由于它掛載在 APB2總線上费就,所以它的時(shí)鐘源為 f PCLK2 诉瓦;而 USART2、3 掛載在 APB1 上,時(shí)鐘源則為 fPCLK1睬澡,串口的時(shí)鐘源經(jīng)過(guò) USARTDIV 分頻后分別輸出作為發(fā)送器時(shí)鐘及接收器時(shí)鐘固额,控制發(fā)送和接收的時(shí)序。
? 收發(fā)控制
圍繞著發(fā)送器和接收器控制部分猴贰,有好多個(gè)寄存器 :CR1对雪、CR2、CR3 和 SR米绕,即USART 的三個(gè)控制寄存器(Control Register)及一個(gè)狀態(tài)寄存器(Status Register)瑟捣。通過(guò)向寄存器寫(xiě)入 各種控制參數(shù)來(lái)控制發(fā)送和接收,如奇偶校驗(yàn)位栅干、停止位等迈套,還包括對(duì)USART 中斷的控制 ;串口的狀態(tài)在任何時(shí)候都可以從狀態(tài)寄存器中查詢(xún)得到碱鳞。其中停止位的配置如圖3所示桑李。
發(fā)送配置步驟:
1 通過(guò)在USART_CR1寄存器上置位UE位來(lái)激活USART
2.編程USART_CR1的M位來(lái)定義字長(zhǎng)。
3.在USART_CR2中編程停止位的位數(shù)窿给。
4.如果采用多緩沖器通信贵白,配置USART_CR3中的DMA使能位(DMAT)。按多緩沖器通信中的描述配置DMA寄存器崩泡。
5.利用USART_BRR寄存器選擇要求的波特率禁荒。
6.設(shè)置USART_CR1中的TE位,發(fā)送一個(gè)空閑幀作為第一次數(shù)據(jù)發(fā)送角撞。
7.把要發(fā)送的數(shù)據(jù)寫(xiě)進(jìn)USART_DR寄存器(此動(dòng)作清除TXE位)呛伴。在只有一個(gè)緩沖器的情況下,對(duì)每個(gè)待發(fā)送的數(shù)據(jù)重復(fù)步驟7谒所。
8.在USART_DR寄存器中寫(xiě)入最后一個(gè)數(shù)據(jù)字后热康,要等待TC=1,它表示最后一個(gè)數(shù)據(jù)幀的傳輸結(jié)束劣领。當(dāng)需要關(guān)閉USART或需要進(jìn)入停機(jī)模式之前姐军,需要確認(rèn)傳輸結(jié)束,避免破壞最后一次傳輸剖踊。
接收配置步驟:
- 將USART_CR1寄存器的UE置1來(lái)激活USART庶弃。
2.編程USART_CR1的M位定義字長(zhǎng)
3.在USART_CR2中編寫(xiě)停止位的個(gè)數(shù)
4.如果需多緩沖器通信,選擇USART_CR3中的DMA使能位(DMAR)德澈。按多緩沖器通信所要求的配置DMA寄存器。
5.利用波特率寄存器USART_BRR選擇希望的波特率固惯。
6.設(shè)置USART_CR1的RE位梆造。激活接收器,使它開(kāi)始尋找起始位。
? 數(shù)據(jù)存儲(chǔ)轉(zhuǎn)移
收發(fā)控制器根據(jù)我們的寄存器配置镇辉,對(duì)數(shù)據(jù)存儲(chǔ)轉(zhuǎn)移部分的移位寄存器進(jìn)行控制屡穗。當(dāng)我們需要發(fā)送數(shù)據(jù)時(shí),內(nèi)核或 DMA 外設(shè)(一種數(shù)據(jù)傳輸方式忽肛,在后面介紹)把數(shù)據(jù)從內(nèi)存(變量)寫(xiě)入到發(fā)送數(shù)據(jù)寄存器 TDR 后村砂,發(fā)送控制器將適時(shí)地自動(dòng)把數(shù)據(jù)從 TDR 加載到發(fā)送移位寄存器,然后通過(guò)串口線 Tx屹逛,把數(shù)據(jù)一位一位地發(fā)送出去础废,當(dāng)數(shù)據(jù)從 TDR轉(zhuǎn)移到移位寄存器時(shí),會(huì)產(chǎn)生發(fā)送寄存器 TDR 已空事件 TXE罕模,當(dāng)數(shù)據(jù)從移位寄存器全部發(fā)送出去時(shí)评腺,會(huì)產(chǎn)生數(shù)據(jù)發(fā)送完成事件 TC,這些事件可以在狀態(tài)寄存器中查詢(xún)到淑掌。而接收數(shù)據(jù)則是一個(gè)逆過(guò)程蒿讥,數(shù)據(jù)從串口線 Rx 一位一位地輸入到接收移位寄存器,然后自動(dòng)地轉(zhuǎn)移到接收數(shù)據(jù)寄存器 RDR抛腕,最后用內(nèi)核指令或 DMA 讀取到內(nèi)存(變量)中芋绸。
以上對(duì)串口通信進(jìn)行了簡(jiǎn)單介紹,為了方便各位讀者朋友更好的理解担敌,在這里筆者將引入一個(gè)新的思想--系統(tǒng)分層思想摔敛。既然各位對(duì)著有意于嵌入式,那么必須得有對(duì)整個(gè)系統(tǒng)的架構(gòu)要有一定的認(rèn)知柄错。
6.2 STM32Cube生成工程
1.設(shè)置RCC
設(shè)置高速外部時(shí)鐘HSE舷夺,選擇外部時(shí)鐘源。
2.時(shí)鐘配置
筆者的板子使用的外部晶振為8MHz售貌,選擇外部時(shí)鐘HSE 8MHz 给猾,PLL鎖相環(huán)9倍頻后為72MHz,系統(tǒng)時(shí)鐘來(lái)源選擇為PLL颂跨,設(shè)置APB2分頻器為 /1敢伸,這時(shí)候定時(shí)器的時(shí)鐘頻率為72Mhz。本文筆者使用的定時(shí)器是USART1恒削,USART1掛在APB2上池颈,不同的USART掛在不同總線上的。
【注】APB1上面連接的是低速外設(shè)钓丰,包括電源接口躯砰、備份接口、CAN携丁、USB琢歇、I2C1、I2C2、USART2李茫、USART3揭保、UART4、UART5魄宏、SPI2秸侣、SP3等;而APB2上面連接的是高速外設(shè)宠互,包括UART1味榛、SPI1、Timer1名秀、ADC1励负、ADC2、ADC3匕得、所有的普通I/O口(PA-PE)继榆、第二功能I/O(AFIO)口等。
3.串口配置
點(diǎn)擊USATR1汁掠,設(shè)置MODE為異步通信(Asynchronous) 略吨,波特率為115200 Bits/s。傳輸數(shù)據(jù)長(zhǎng)度為8 Bit考阱。奇偶檢驗(yàn)無(wú)翠忠,停止位1 ,接收和發(fā)送都使能乞榨。
GPIO引腳設(shè)置 USART1_RX/USART_TX秽之,默認(rèn)即可。
NVIC Settings 一欄使能接收中斷吃既,人默認(rèn)沒(méi)打開(kāi)考榨。
6.3串口收發(fā)代碼講解
6.3.1串口發(fā)送代碼實(shí)現(xiàn)與講解(printf重定向)
我們先看如何實(shí)現(xiàn)的,再講解具體的代碼鹦倚。先實(shí)現(xiàn)printf重定向函數(shù)河质,在main.c中添加如下函數(shù)。
/**
* @brief 重定向c庫(kù)函數(shù)printf到USARTx
* @retval None
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/**
* @brief 重定向c庫(kù)函數(shù)getchar,scanf到USARTx
* @retval None
*/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
在main()函數(shù)主循環(huán)中添加以下代碼:
printf("USART1 Test!\n");
HAL_Delay(1000);//這里的延時(shí)表示1s震叙。不清楚的請(qǐng)看滴答定時(shí)器的內(nèi)容
好了掀鹅,這就是串口發(fā)送代碼實(shí)現(xiàn)。另外媒楼,筆者在此給出輸出格式的說(shuō)明乐尊,請(qǐng)讀者朋友參考。
表1 輸出格式說(shuō)明
格式 | 說(shuō)明 |
---|---|
%d | 按照十進(jìn)制整型數(shù)打印 |
%6d | 按照十進(jìn)制整型數(shù)打印划址,至少6個(gè)字符寬 |
%f | 按照浮點(diǎn)數(shù)打印 |
%6f | 按照浮點(diǎn)數(shù)打印科吭,至少6個(gè)字符寬 |
%.2f | 按照浮點(diǎn)數(shù)打印昏滴,小數(shù)點(diǎn)后有2位小數(shù) |
%6.2f | 按照浮點(diǎn)數(shù)打印猴鲫,至少6個(gè)字符寬对人,小數(shù)點(diǎn)后有2位小數(shù) |
%x | 按照十六進(jìn)制打印 |
%c | 打印字符 |
%s | 打印字符串 |
完整代碼請(qǐng)查看配套程序,另外還需添加Use MicroLIB以便支持printf拂共。具體設(shè)置參看本節(jié)后文的小貼士部分牺弄。
好了,我們來(lái)總結(jié)下串口發(fā)送的流程:
1.初始化硬件宜狐,時(shí)鐘势告;
2.USART 的GPIO初始化,USART參數(shù)初始化抚恒;
3.重定向printf
4.打印輸出
這里只講解串口的參數(shù)初始化咱台,代碼如下:
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
這是STM32cudeMX自動(dòng)生成的代碼,我們要注意UART_HandleTypeDef結(jié)構(gòu)體俭驮,這個(gè)結(jié)構(gòu)體就是用來(lái)配串口參數(shù)的回溺,原型如下:
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance; /*!< UART registers base address */
UART_InitTypeDef Init; /*!< UART communication parameters */
uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
uint16_t TxXferSize; /*!< UART Tx Transfer size */
__IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
uint16_t RxXferSize; /*!< UART Rx Transfer size */
__IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */
DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_UART_StateTypeDef gState; /*!< UART state information related to global Handle management
and also related to Tx operations.
This parameter can be a value of @ref HAL_UART_StateTypeDef */
__IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx operations.
This parameter can be a value of @ref HAL_UART_StateTypeDef */
__IO uint32_t ErrorCode; /*!< UART Error code */
} UART_HandleTypeDef;
這個(gè)結(jié)構(gòu)體很簡(jiǎn)單,也有英文注釋?zhuān)P者就不在贅述了混萝。當(dāng)然啦遗遵,除了使用普通方式發(fā)送,還可使用中斷方式和DMA方式發(fā)送數(shù)據(jù)逸嘀。這里中斷發(fā)送數(shù)據(jù)就不講了车要,下面會(huì)將中斷接收,有興趣的朋友請(qǐng)自行去看參考手冊(cè)自行實(shí)現(xiàn)崭倘,DMA方式會(huì)在將軍誒DMA的時(shí)候講解翼岁。
HAL_UART_Transmit_IT();串口中斷模式發(fā)送
HAL_UART_Transmit_DMA();串口DMA模式發(fā)送
6.3.2串口接收代碼實(shí)現(xiàn)與講解(中斷方式)
和串口發(fā)送數(shù)據(jù)一樣,先看如何實(shí)現(xiàn)的司光,然后再進(jìn)行代碼講解琅坡,
在main.c中添加下列定義:
#define RXBUFFERSIZE 256 //最大接收字節(jié)數(shù)
char TxBuffer[RXBUFFERSIZE]; //發(fā)送緩沖
uint8_t RxBuffer; //接收中斷緩沖
uint8_t Uart1_Rx_Cnt = 0; //接收緩沖計(jì)數(shù)
在main()主函數(shù)中,調(diào)用一次接收中斷函數(shù)
HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
在main.c下方添加中斷回調(diào)函數(shù)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
if(Uart1_Rx_Cnt >= 255) //溢出判斷
{
Uart1_Rx_Cnt = 0;
memset(TxBuffer,0x00,sizeof(TxBuffer));
HAL_UART_Transmit(&huart1, (uint8_t *)"數(shù)據(jù)溢出", 10,0xFFFF);
}
else
{
TxBuffer[Uart1_Rx_Cnt++] = RxBuffer; //接收數(shù)據(jù)轉(zhuǎn)存
if((TxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(TxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判斷結(jié)束位
{
HAL_UART_Transmit(&huart1, (uint8_t *)&TxBuffer, Uart1_Rx_Cnt,0xFFFF); //將收**加粗樣式**到的信息發(fā)送出去
while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//檢測(cè)UART發(fā)送結(jié)束
Uart1_Rx_Cnt = 0;
memset(TxBuffer,0x00,sizeof(TxBuffer)); //清空數(shù)組
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1); //開(kāi)啟接收中斷
}
好了飘庄,要添加的代碼就這些了脑蠕,下面講解如何實(shí)現(xiàn)串口中斷接收的。先看串口接收的編程流程:
1.硬件初始化跪削,時(shí)鐘初始化谴仙;
2.串口GPIO初始化,串口參數(shù)配置碾盐;
3.在main()函數(shù)中使能中斷接收晃跺;
4.編寫(xiě)HAL_UART_RxCpltCallback中斷回調(diào)函數(shù),處理接收的數(shù)據(jù)毫玖,
【注】中斷接收函數(shù)只能觸發(fā)一次接收中斷掀虎,所以我們需要在中斷回調(diào)函數(shù)中再次調(diào)用中斷接收函數(shù)凌盯。這里可以對(duì)比下標(biāo)準(zhǔn)庫(kù)的流程。
6.3.4實(shí)驗(yàn)現(xiàn)象
? 串口發(fā)送
將程序編譯好下載到板子中烹玉,打開(kāi)串口助手驰怎,按下圖設(shè)置相應(yīng)參數(shù),按下板子的復(fù)位按鍵二打,在接收區(qū)可以看到如下信息县忌。
? 串口接收
將程序編譯好下載到板子中,打開(kāi)串口助手继效,按下圖設(shè)置相應(yīng)參數(shù)症杏,按下板子的復(fù)位按鍵,在接收區(qū)可以看到如下信息瑞信。
小貼士:printf 函數(shù)重定向
要想 printf() 函數(shù)工作的話厉颤,我們需要把 printf() 重新定向到串口中。重定向是指用戶(hù)可以自己重寫(xiě) C 的庫(kù)函數(shù)凡简,當(dāng)連接器檢查到用戶(hù)編寫(xiě)了與 C 庫(kù)函數(shù)相同名字的函數(shù)時(shí)逼友,優(yōu)先采用用戶(hù)編寫(xiě)的函數(shù),這樣用戶(hù)就可以實(shí)現(xiàn)對(duì)庫(kù)的修改了潘鲫。
為了實(shí)現(xiàn)重定向 printf() 函數(shù)翁逞,我們需要重寫(xiě) fputc() 這個(gè) C 標(biāo)準(zhǔn)庫(kù)函數(shù),因?yàn)?printf()在 C 標(biāo)準(zhǔn)庫(kù)函數(shù)中實(shí)質(zhì)是一個(gè)宏溉仑,最終是調(diào)用了 fputc() 這個(gè)函數(shù)挖函。
重定向的這部分工作, 由main.c 文件中的 fputc(int ch, FILE *f) 這個(gè)函數(shù)來(lái)完成浊竟。重定向時(shí)怨喘,我們把 fputc( ) 的形參 ch,作為串口將要發(fā)送的數(shù)據(jù)振定,也就是說(shuō)必怜,當(dāng)使用 printf( ) 時(shí),它先調(diào)用這個(gè) fputc( ) 函數(shù)后频,然后使用 ST 庫(kù)的串口發(fā)送函數(shù) USART_SendData()梳庆,把數(shù)據(jù)轉(zhuǎn)移到發(fā)送數(shù)據(jù)寄存器 TDR,觸發(fā)我們的串口向 PC 發(fā)送一個(gè)相應(yīng)的數(shù)據(jù)卑惜。調(diào) 用 完 USART_SendData( ) 后 膏执, 要 使 用 while (USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET) 語(yǔ)句不停地檢查串口發(fā)送是否完成的標(biāo)志位TC,一直檢測(cè)到標(biāo)志為“完成”露久,才進(jìn)入下一步的操作更米,避免出錯(cuò)。在這段 while 循環(huán)檢測(cè)的延時(shí)中毫痕,串口外設(shè)已經(jīng)由發(fā)送控制器以及根 據(jù)我們的配置把數(shù)據(jù)從移位寄存器一位一位地通過(guò)串口線 Tx 發(fā)送出去了征峦。
【注意】printf函數(shù)在“stdio.h”頭文件里迟几,使用該函數(shù)必須引用“stdio.h”庫(kù), 還
要在編譯器中設(shè)置一個(gè)選項(xiàng) Use MicroLIB (使用微庫(kù))。設(shè)置方式如下:
單擊Project栏笆,選擇option選項(xiàng)类腮,再選擇Target 勾選Use MicroLIB 即可。
代碼獲取方式
1.關(guān)注公眾號(hào)[嵌入式實(shí)驗(yàn)樓]
2.在公眾號(hào)回復(fù)關(guān)鍵詞[STM32F1]獲取資料
歡迎訪問(wèn)我的網(wǎng)站: