常用的串口pin
STM32的串口是基礎通信方式, 每個型號都帶多組串口, 一般都使用默認的組, 可以參考芯片的datasheet, 去看pinout and pin definitions,
stm32f103c8t6
這是48pin的芯片, 提供3組串口, 注意USART1是APB2, USART2和3都是PBA1. 各組串口的pin腳為
USART2 | USART1 | USART3 | |
---|---|---|---|
總線 | APB1 | APB2 | APB1 |
TX | PA2 | PA9 | PB10 |
RX | PA3 | PA10 | PB11 |
CTS | PA0 | PA11 | PB13 |
RTS | PA1 | PA12 | PB14 |
CK | PA4 | PA8 | PB12 |
可以同時使用三組UART: USART1(PA9, PA10), USART2(PA2, PA3), USART3(PB10, PB11)
stm32f401ccu6
USART1和USART6是APB2, USART2是APB1
USART2 | USART1 | USART6 | |
---|---|---|---|
總線 | APB1 | APB2 | APB2 |
TX | PA2 | PA9 / PB6 | PA11 |
RX | PA3 | PA10 / PB7 | PA12 |
CTS | PA0 | PA11 | PB13 |
RTS | PA1 | PA12 | PB14 |
CK | PA4 | PA8 | PC8 |
可以同時使用三組UART: USART1(PA9, PA10)或(PB6, PB7), USART2(PA2, PA3), USART6(PA11, PA12)
串口相關的中斷
Interrupt Mode
===============
In Interrupt Mode, the USART communication can be managed by 8 interrupt sources and 10 pending bits:
Pending Bits:
-------------
1. USART_IT_TXE : to indicate the status of the transmit buffer register
2. USART_IT_RXNE : to indicate the status of the receive buffer register
3. USART_IT_TC : to indicate the status of the transmit operation
4. USART_IT_IDLE : to indicate the status of the Idle Line
5. USART_IT_CTS : to indicate the status of the nCTS input
6. USART_IT_LBD : to indicate the status of the LIN break detection
7. USART_IT_NE : to indicate if a noise error occur
8. USART_IT_FE : to indicate if a frame error occur
9. USART_IT_PE : to indicate if a parity error occur
10. USART_IT_ORE : to indicate if an Overrun error occur
Interrupt Source:
-----------------
1. USART_IT_TXE : specifies the interrupt source for the Tx buffer empty interrupt.
2. USART_IT_RXNE : specifies the interrupt source for the Rx buffer not empty interrupt.
3. USART_IT_TC : specifies the interrupt source for the Transmit complete interrupt.
4. USART_IT_IDLE : specifies the interrupt source for the Idle Line interrupt.
5. USART_IT_CTS : specifies the interrupt source for the CTS interrupt.
6. USART_IT_LBD : specifies the interrupt source for the LIN break detection interrupt.
7. USART_IT_PE : specifies the interrupt source for the parity error interrupt.
8. USART_IT_ERR : specifies the interrupt source for the errors interrupt.
@note Some parameters are coded in order to use them as interrupt source or as pending bits.
In this Mode it is advised to use the following functions:
- void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
- ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
- void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
pending bits是一種防止中斷丟失的機制, 當Mask bits為1的時候中斷請求並不會發出, 而是將pending bits置為1, 當mask bits變為0時中斷會發出, 然后pending位被清0.
串口通信編程
串口通信就是TX發送和RX接收. 其中TX在絕大多數場合可以直接按字節發送, 需要額外處理的是RX.
串口通信的常見問題
理想的通信方式是發送->等待響應->返回響應
, TX之后等待RX響應, 而且響應是完整發送的, 但是實際使用中, RX接收有多種特殊情況
響應太長將緩沖區寫滿
緩沖區一般會設置為u8[128], 或者u8[256], 對於大部分消息是夠的, 對於更大的返回, 如果會造成緩沖溢出的, 建議
- 如果是累計的多個返回, 最好改進接收響應完成狀態的判斷機制, 盡量分段處理
- 對於無法分段的特殊情況, 需要保留最新的內容, 將緩沖設計成FIFO的環形結構, 后接收的消息可以覆蓋掉最早的內容.
非請求產生的響應
有兩種情況, 一種是在設備開機階段自檢和初始化產生的內容, 這些內容可以通過在上位機設置足夠長的delay, 把這些內容忽略掉; 第二種就是在正常工作中, 隨時出現的通知信息, 這種情況就不能使用發送->等待
的處理方式了, 因為RX和TX完全異步. 體現在代碼中, 就是TX之后不等待RX的結果. 在TX之后只需要必要的delay, 例如20ms, 以避免和下一條TX連在一起.
在處理接收時, 可以使用IDLE中斷, 也可以使用定時器判斷.
- 如果每次響應到達時基本都是完整的(中間的間隔不會超過1個字節的傳輸耗時), 就可以使用IDLE中斷, 這樣實現最簡單
- 如果不能滿足上一條的條件, 使用IDLE中斷就會有問題, 一個響應可能會被拆成好幾份, 導致后期處理難度增大. 這時候可以用一個單獨的定時器做延時, 判斷響應是否接收完整. 延時設置到10ms - 40ms, 對於大部分串口設備的返回都可以完整收集, 又不至於累積太長的響應. 在定時器中斷時將緩沖中的整個響應取出處理.
因請求產生的響應, 響應等待時間可能較長(幾十毫秒到幾百毫秒)
首先, 如果串口設備帶回顯, 要將回顯先關閉. 回顯是為了方便手工調試, 但是在程序中, 會引起不必要的麻煩. 因為命令通過TX輸出之后RX就會立即收到回顯, 但是真正的響應要過一陣子回來, 在程序處理中很可能就把回顯當成是響應, 而把真正的響應丟了. 雖然可以將等待響應的定時器設置得長一點, 但是中間的空檔期長, 出錯的概率也越大. 對響應的接收是通過RXNE中斷將接收到的字節寫入緩沖實現的, 和前面的處理方式一樣, 可以通過定時器延時, 在請求發送后, 設置一個超時時間然后阻塞進程, 在進程中循環判斷響應的接收情況. 在串口的中斷處理中, 每次收到RXNE中斷后都重置並啟用定時器延時20ms, 直至超過這個間隔無響應, 在定時器中斷中將響應完整狀態置位, 在進程中收集到響應.
串口通信常見實現方式
一般通過以下的步驟實現串口通信
1. 實現Buffer工具方法
#ifndef __BUFFER_H_
#define __BUFFER_H_
#include "stm32f10x.h"
typedef struct
{
u8* buf;
u16 size;
u16 front;
u16 rear;
} BufferTypeDef;
typedef struct
{
u8 size;
u8 length;
u8* data;
} BufferClip;
void Buffer_Reset(BufferTypeDef* buff);
u16 Buffer_Length(BufferTypeDef* buff);
u8 Buffer_Push(BufferTypeDef* buff, u8 data);
u8 Buffer_Pop(BufferTypeDef* buff, u8* data);
u8 Buffer_Pop_All(BufferTypeDef* buff, BufferClip* clip);
void Buffer_Print(BufferTypeDef* buff);
void Buffer_Print_Hex(BufferTypeDef* buff);
void Buffer_Print_All(BufferTypeDef* buff);
void Buffer_Clip_Print(BufferClip* clip);
void Buffer_Clip_Print_Hex(BufferClip* clip);
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "buffer.h"
void Buffer_Reset(BufferTypeDef* buff)
{
buff->front = 0;
buff->rear = 0;
}
u16 Buffer_Length(BufferTypeDef* buff)
{
if (buff->rear >= buff->front) {
return buff->rear - buff->front;
} else {
return (buff->size - buff->front) + (buff->rear - 0);
}
}
u8 Buffer_Push(BufferTypeDef* buff, u8 data)
{
buff->buf[buff->rear] = data;
buff->rear++;
if (buff->rear >= buff->size) {
buff->rear = 0;
}
if (buff->front == buff->rear) {
buff->front = (buff->front + 1) % buff->size;
return NULL;
} else {
return !NULL;
}
}
u8 Buffer_Pop(BufferTypeDef* buff, u8* data)
{
if (buff->front == buff->rear) return NULL;
*data = buff->buf[buff->front];
buff->front = (buff->front + 1) % buff->size;
return !NULL;
}
u8 Buffer_Pop_All(BufferTypeDef* buff, BufferClip* clip)
{
if (buff->front == buff->rear) return NULL;
memset(clip->data, 0x00, clip->size * sizeof(u8));
clip->length = 0;
if (buff->front > buff->rear) {
while (buff->front < buff->size && clip->length <= clip->size) {
*(clip->data + clip->length++) = buff->buf[buff->front++];
}
if (buff->front == buff->size) {
buff->front = 0;
}
}
while (buff->front < buff->rear && clip->length <= clip->size) {
*(clip->data + clip->length++) = buff->buf[buff->front++];
}
return !NULL;
}
void Buffer_Print(BufferTypeDef* buff)
{
printf("BUFF:[%03d,%03d)",buff->front, buff->rear);
if (buff->front == buff->rear) {
// print nothing;
} else if (buff->front < buff->rear) {
for(int i=buff->front; i < buff->rear; i++) {
printf("%c", buff->buf[i]);
}
} else {
for(int i = buff->front; i < buff->size; i++) {
printf("%c", buff->buf[i]);
}
for(int i = 0; i < buff->rear; i++) {
printf("%c", buff->buf[i]);
}
}
printf("\r\n");
}
void Buffer_Print_Hex(BufferTypeDef* buff)
{
printf("BUFF:[%03d,%03d)",buff->front, buff->rear);
if (buff->front == buff->rear) {
// print nothing;
} else if (buff->front < buff->rear) {
for(int i=buff->front; i<buff->rear; i++) {
printf("%02X ", buff->buf[i]);
}
} else {
for(int i=buff->front; i < buff->size; i++) {
printf("%02X ", buff->buf[i]);
}
for(int i=0; i<buff->rear; i++) {
printf("%02X ", buff->buf[i]);
}
}
printf("\r\n");
}
void Buffer_Print_All(BufferTypeDef* buff)
{
printf("BUFF:[%d,%d)",buff->front, buff->rear);
for(int i=0; i < buff->size; i++) {
printf("%c", buff->buf[i]);
}
printf("\r\n");
}
void Buffer_Clip_Print(BufferClip* clip)
{
printf("CLIP:[%03d]", clip->length);
for(int i = 0; i < clip->length; i++) {
printf("%c", clip->data[i]);
}
printf("\r\n");
}
void Buffer_Clip_Print_Hex(BufferClip* clip)
{
printf("CLIP:[%03d]", clip->length);
for(int i = 0; i < clip->length; i++) {
printf("%02X ", clip->data[i]);
}
printf("\r\n");
}
2. 初始化UART端口: 使能GPIO, UART, NVIC
BufferTypeDef RFID_RX_BUF;
u8 RFID_RX_BUF_BUFF[RFID_BUF_SIZE] = {0x00};
BufferClip RFID_RX_CLIP;
u8 RFID_RX_CLIP_DATA[UINT8_MAX] = {0x00};
u8 RFID_RX_STATE = 0;
void RFID_Init(void)
{
RFID_RX_BUF.buf = RFID_RX_BUF_BUFF;
RFID_RX_BUF.size = RFID_BUF_SIZE;
RFID_RX_CLIP.data = RFID_RX_CLIP_DATA;
RFID_RX_CLIP.size = UINT8_MAX;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RFID_RCC, ENABLE);
// GPIO for TX
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = RFID_TX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RFID_TX_GPIO, &GPIO_InitStructure);
// GPIO for RX
GPIO_InitStructure.GPIO_Pin = RFID_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RFID_RX_GPIO, &GPIO_InitStructure);
// NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// USART
USART_DeInit(RFID_USART);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(RFID_USART, &USART_InitStructure);
USART_ClearFlag(RFID_USART, USART_FLAG_CTS);
USART_Cmd(RFID_USART, ENABLE);
USART_ITConfig(RFID_USART, USART_IT_RXNE, ENABLE);
printf("## RFID Initialized ##\r\n");
}
3. 實現中斷處理方法接收消息
一個是串口的RXNE中斷, 用於接收每個字節; 另一個是TIMx的計時中斷, 用於標記響應接收完成
void USART3_IRQHandler(void)
{
u8 rev_byte;
u32 clear;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {
rev_byte = USART_ReceiveData(USART3);
Buffer_Push(&RFID_RX_BUF, rev_byte);
// Reset the TIM2 counter and enable it
TIM_SetCounter(TIM2, 0);
TIM_Cmd(TIM2, ENABLE);
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
}
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
printf("RFID_RX_STATE++\r\n");
RFID_RX_STATE++;
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_Cmd(TIM2, DISABLE);
}
4. 實現消息發送
下面這個例子, 在收到消息后, 調用 RFID_Handle_Message()處理響應
void RFID_Send_String(const u8* data, u16 length)
{
printf("RFID CMD: ");
for (u16 i = 0; i < length; i++) {
printf("%02X ", *(data + i));
USART_SendData(RFID_USART, *(data + i));
while(USART_GetFlagStatus(RFID_USART, USART_FLAG_TXE) == RESET) { // Wait till sent
;// Do nothing
}
}
printf(">> Sent\r\n");
}
bool RFID_Send_Cmd(const u8* cmd, u16 length)
{
RFID_Send_String(cmd, length);
// Delay 50ms to avoid being joinned by other commands
Systick_Delay_ms(50);
u8 waittime = 10;
while (waittime--) {
if(RFID_RX_STATE > 0) {
printf("RFID_RX_STATE %d\r\n", RFID_RX_STATE);
if (Buffer_Pop_All(&RFID_RX_BUF, &RFID_RX_CLIP) != NULL) {
Buffer_Clip_Print_Hex(&RFID_RX_CLIP);
RFID_Handle_Message();
}
RFID_RX_STATE--;
}
Systick_Delay_ms(50);
}
return true;
}
下面這個例子, 直接在參數中指定期望的響應結果, 只需要返回對比的結果
u8 ESP8266_Send_Cmd2(char *cmd, char *ack, char *ack2, u16 waittime)
{
ESP8266_Send_String((u8 *)cmd);
Systick_Delay_ms(50);
// Make sure waittime is set
if (waittime < 10) waittime = 10;
while (waittime--) {
if(ESP_RX_STATE > 0) {
printf("ESP_RX_STATE %d\r\n", ESP_RX_STATE);
ESP_RX_STATE--;
if (Buffer_Pop_All(&ESP_RX_BUF, &ESP_RX_CLIP) != NULL) {
Buffer_Clip_Print(&ESP_RX_CLIP);
if(strstr((char *)(ESP_RX_CLIP.data), ack) != NULL) {
printf("return success\r\n\n");
return ACK_SUCCESS;
}
if (strlen(ack2) > 0) {
if(strstr((char *)(ESP_RX_CLIP.data), ack2) != NULL) {
printf("return success\r\n\n");
return ACK_SUCCESS;
}
}
}
}
Systick_Delay_ms(20);
}
printf("return defeat\r\n\n");
return ACK_DEFEAT;
}
參考
- https://geekilyinteresting.wordpress.com/tag/usart1_irqhandler/
- http://www.disca.upv.es/aperles/arm_cortex_m3/curset/STM32F4xx_DSP_StdPeriph_Lib_V1.0.1/html/group___u_s_a_r_t___group9.html
- https://www.mikrocontroller.net/attachment/274908/Code.txt
- https://github.com/PX4/PX4-Flow/blob/master/src/modules/flow/usart.c
- https://hackaday.com/2020/10/22/stm32-clones-the-good-the-bad-and-the-ugly/
- https://hackaday.com/2021/01/20/blue-pill-vs-black-pill-transitioning-from-stm32f103-to-stm32f411/