梳理STM32F429之通信傳輸部分---NO.6 RS485 通訊
目錄
一、RS-485 通訊協議簡介
與 CAN 類似, RS-485 是一種工業控制環境中常用的通訊協議,它具有抗干擾能力強、傳輸距離遠的特點。 RS-485 通訊協議由 RS-232 協議改進而來,協議層不變,只是改進了物理層,因而保留了串口通訊協議應用簡單的特點。
RS-485 的物理層
從《CAN—通訊實驗》章節中瞭解到,差分信號線具有很強的干擾能力,特別適合應用於電磁環境複雜的工業控制環境中, RS-485 協議主要是把 RS-232 的信號改進成差分信號,從而大大提高了抗干擾特性,它的通訊網絡示意圖見下圖。
對比 CAN 通訊網絡,可發現它們的網絡結構組成是類似的,每個節點都是由一個通訊控制器和一個收發器組成,在 RS-485 通訊網絡中,節點中的串口控制器使用 RX 與 TX信號線連接到收發器上,而收發器通過差分線連接到網絡總線,串口控制器與收發器之間一般使用 TTL 信號傳輸,收發器與總線則使用差分信號來傳輸。發送數據時,串口控制器的 TX 信號經過收發器轉換成差分信號傳輸到總線上,而接收數據時,收發器把總線上的差分信號轉化成 TTL 信號通過 RX 引腳傳輸到串口控制器中。
RS-485 通訊網絡的最大傳輸距離可達 1200 米,總線上可掛載 128 個通訊節點,而由於 RS-485 網絡只有一對差分信號線,它使用差分信號來表達邏輯,當 AB 兩線間的電壓差爲-6V~-2V 時表示邏輯 1,當電壓差爲+2V~+6V 表示邏輯 0,在同一時刻只能表達一個信號,所以它的通訊是半雙工形式的,它與 RS-232 通訊協議的特性對比見下圖。
RS-485 與 RS-232 的差異只體現在物理層上,它們的協議層是相同的,也是使用串口數據包的形式傳輸數據。而由於 RS-485 具有強大的組網功能,人們在基礎協議之上還制定了 MODBUS 協議,被廣泛應用在工業控制網絡中。此處說的基礎協議是指前面串口章節中講解的,僅封裝了基本數據包格式的協議(基於數據位),而 MODBUS 協議是使用基本數據包組合成通訊幀格式的高層應用協議(基於數據包或字節)。感興趣的讀者可查找MODBUS 協議的相關資料瞭解。
由於 RS-485 與 RS-232 的協議層沒有區別,進行通訊時,我們同樣是使用 STM32 的USART 外設作爲通訊節點中的串口控制器,再外接一個 RS-485 收發器芯片把 USART 外設的 TTL 電平信號轉化成 RS-485 的差分信號即可。
二、RS-485—雙機通訊實驗
本小節演示如何使用 STM32 的 USART 控制器與 MAX485 收發器,在兩個設備之間使用 RS-485 協議進行通訊,本實驗中使用了兩個實驗板,無法像 CAN 實驗那樣使用迴環測試(把 STM32 USART 外設的 TXD 引腳使用杜邦線連接到 RXD 引腳可進行自收發測試,不過這樣的通訊不經過 RS-485 收發器,跟普通 TTL 串口實驗沒有區別),本教程主要以“ USART—485 通訊”工程進行講解。
1、硬件設計
上圖中的是兩個實驗板的硬件連接。在單個實驗板中,作爲串口控制器的 STM32從 USART 外設引出 TX 和 RX 兩個引腳與 RS-485 收發器 MAX485 相連,收發器使用它的A 和 B 引腳連接到 RS-485 總線網絡中。爲了方便使用,我們每個實驗板引出的 A 和 B 之間都連接了 1 個 120 歐的電阻作爲 RS-485 總線的端電阻,所以要注意如果您要把實驗板作爲一個普通節點連接到現有的 RS-485 總線時,是不應添加該電阻的!
由於 485 只能以半雙工的形式工作,所以需要切換狀態, MAX485 芯片中有“ RE”和“ DE”兩個引腳,用於控制 485 芯片的收發工作狀態的,當 RE 引腳爲低電平時, 485 芯片處於接收狀態,當 DE 引腳爲高電平時芯片處於發送狀態。實驗板中使用了 STM32 的PB8 直接連接到這兩個引腳上,所以通過控制 PB8 的輸出電平即可控制 485 的收發狀態。
2、軟件設計
(1)編程要點
- 初始化 485 通訊使用的 USART 外設及相關引腳;
- 編寫控制 MAX485 芯片進行收發數據的函數;
- 編寫測試程序,收發數據。
(2)代碼分析
NO.1 485 硬件相關宏定義我們把 485 硬件相關的配置都以宏的形式定義到 “ bsp_485.h”文件中,見下面的代碼清單。
/*USART 號、時鐘、波特率*/
#define RS485_USART USART2
#define RS485_USART_CLK RCC_APB1Periph_USART2
#define RS485_USART_BAUDRATE 115200
/*RX 引腳*/
#define RS485_USART_RX_GPIO_PORT GPIOD
#define RS485_USART_RX_GPIO_CLK RCC_AHB1Periph_GPIOD
#define RS485_USART_RX_PIN GPIO_Pin_6
#define RS485_USART_RX_AF GPIO_AF_USART2
#define RS485_USART_RX_SOURCE GPIO_PinSource6
/*TX 引腳*/
#define RS485_USART_TX_GPIO_PORT GPIOD
#define RS485_USART_TX_GPIO_CLK RCC_AHB1Periph_GPIOD
#define RS485_USART_TX_PIN GPIO_Pin_5
#define RS485_USART_TX_AF GPIO_AF_USART2
#define RS485_USART_TX_SOURCE GPIO_PinSource5
/*485 收發控制引腳*/
#define RS485_RE_GPIO_PORT GPIOB
#define RS485_RE_GPIO_CLK RCC_AHB1Periph_GPIOB
#define RS485_RE_PIN GPIO_Pin_8
/*中斷相關*/
#define RS485_INT_IRQ USART2_IRQn
#define RS485_IRQHandler USART2_IRQHandler
以上代碼根據硬件連接, 把與 485 通訊使用的 USART 外設號 、引腳號、引腳源以及複用功能映射都以宏封裝起來,並且定義了接收中斷的中斷向量和中斷服務函數,我們通過中斷來獲知接收數據。
NO.2 初始化 485 的 USART 配置
利用上面的宏,編寫 485 的 USART 初始化函數,見下面的代碼清單。
/*
* 函數名: RS485_Config
* 描述 : USART GPIO 配置,工作模式配置
* 輸入 :無
* 輸出 : 無
* 調用 :外部調用
*/
void RS485_Config(void)
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* 配置 USART 時鐘 */
RCC_AHB1PeriphClockCmd(RS485_USART_RX_GPIO_CLK|
RS485_USART_TX_GPIO_CLK|
RS485_RE_GPIO_CLK, ENABLE);
RCC_APB1PeriphClockCmd(RS485_USART_CLK, ENABLE);
/* TX 引腳源*/
GPIO_PinAFConfig(RS485_USART_RX_GPIO_PORT,RS485_USART_RX_SOURCE, RS485_USART_RX_AF);
/* RX 引腳源*/
GPIO_PinAFConfig(RS485_USART_TX_GPIO_PORT,RS485_USART_TX_SOURCE,RS485_USART_TX_AF);
/* USART GPIO 配置 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*TX*/
GPIO_InitStructure.GPIO_Pin = RS485_USART_TX_PIN;
GPIO_Init(RS485_USART_TX_GPIO_PORT, &GPIO_InitStructure);
/*RX */
GPIO_InitStructure.GPIO_Pin = RS485_USART_RX_PIN;
GPIO_Init(RS485_USART_RX_GPIO_PORT, &GPIO_InitStructure);
/* 485 收發控制管腳 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Pin = RS485_RE_PIN ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RS485_RE_GPIO_PORT, &GPIO_InitStructure);
/* USART 模式配置*/
USART_InitStructure.USART_BaudRate = RS485_USART_BAUDRATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(RS485_USART, &USART_InitStructure);
/*使能 USART*/
USART_Cmd(RS485_USART, ENABLE);
/*配置中斷優先級*/
NVIC_Configuration();
/* 使能串口接收中斷 */
USART_ITConfig(RS485_USART, USART_IT_RXNE, ENABLE);
/*控制 485 芯片進入接收模式*/
GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);
}
與所有使用到 GPIO 的外設一樣,都要先把使用到的 GPIO 引腳模式初始化,配置好複用功能,其中用於控制 MAX485 芯片的收發狀態的引腳被初始化成普通推輓輸出模式,以便手動控制它的電平輸出,切換狀態。 485 使用到的 USART 也需要配置好波特率、有效字長、停止位及校驗位等基本參數,在通訊中,兩個 485 節點的串口參數應一致,否則會導致通訊解包錯誤。在實驗中還使能了串口的接收中斷功能,當檢測到新的數據時,進入中斷服務函數中獲取數據。
NO.3 使用中斷接收數據
接下來我們編寫在 USART 中斷服務函數中接收數據的相關過程,見代碼清單,其中的 bsp_RS485_IRQHandler 函數直接被 bsp_stm32f4xx_it.c 文件的 USART 中斷服務函數調用,不在此列出。
//中斷緩存串口數據
#define UART_BUFF_SIZE 1024
volatile uint16_t uart_p = 0;
uint8_t uart_buff[UART_BUFF_SIZE];
void bsp_RS485_IRQHandler(void)
{
if (uart_p<UART_BUFF_SIZE) {
if (USART_GetITStatus(RS485_USART, USART_IT_RXNE) != RESET) {
uart_buff[uart_p] = USART_ReceiveData(RS485_USART);
uart_p++;
USART_ClearITPendingBit(RS485_USART, USART_IT_RXNE);
}
} else {
USART_ClearITPendingBit(RS485_USART, USART_IT_RXNE);
}
}
//獲取接收到的數據和長度
char *get_rebuff(uint16_t *len)
{
*len = uart_p;
return (char *)&uart_buff;
}
//清空緩衝區
void clean_rebuff(void)
{
uint16_t i=UART_BUFF_SIZE+1;
uart_p = 0;
while (i)
uart_buff[--i]=0;
}
這個數據接收過程主要思路是使用了接收緩衝區,當 USART 有新的數據引起中斷時,調用庫函數 USART_ReceiveData 把新數據讀取到緩衝區數組 uart_buff 中,其中 get_rebuff函數可以用於獲緩衝區中有效數據的長度,而 clean_rebuff 函數可以用於對緩衝區整體清 0,這些函數配合使用,實現了簡單的串口接收緩衝機制。這部分串口數據接收的過程跟 485收發器無關,是串口協議通用的。
NO.4 切換收發狀態
在前面我們瞭解到 RS-485 是半雙工通訊協議,發送數據和接收數據需要分時進行,所以需要經常切換收發狀態。而 MAX485 收發器根據其“ RE”和“ DE”引腳的外部電平信號切換收發狀態,所以控制與其相連的 STM32 普通 IO 電平即可控制收尾,爲簡便起見,我們把收發狀態切換定義成了宏。
// 簡單的延時
static void RS485_delay(__IO u32 nCount)
{
for (; nCount != 0; nCount--);
}
/*控制收發引腳*/
//進入接收模式,必須要有延時等待 485 處理完數據
#define RS485_RX_EN() RS485_delay(1000);\
GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN); \
RS485_delay(1000);
//進入發送模式,必須要有延時等待 485 處理完數據
#define RS485_TX_EN() RS485_delay(1000); \
GPIO_SetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);\
RS485_delay(1000);
這兩個宏中,主要是在控制電平輸出前後加了一小段時間延時,這是爲了給 MAX485芯片預留響應時間,因爲 STM32 的引腳狀態電平變換後, MAX485 芯片可能存在響應延時。例如,當 STM32 控制自己的引腳電平輸出高電平(控制成發送狀態),然後立即通過 TX 信號線發送數據給 MAX485 芯片,而 MAX485 芯片由於狀態不能馬上切換,會導致丟失了部分 STM32 傳送過來的數據,造成錯誤。
NO.5 發送數據
STM32 使用 485 發送數據的過程也與普通的 USART 發送數據過程差不多,我們定義了一個 RS485_SendByte 函數來發送一個字節的數據內容,見代碼清單。
/***************** 發送一個字符 **********************/
//使用單字節數據發送前要使能發送引腳,發送後要使能接收引腳。
void RS485_SendByte( uint8_t ch )
{
/* 發送一個字節數據到 USART1 */
USART_SendData(RS485_USART,ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(RS485_USART, USART_FLAG_TXE) == RESET);
}
上述代碼中就是直接調用了 STM32 庫函數 USART_SendData 把要發送的數據寫入到USART 的數據寄存器,然後檢查標誌位等待發送完成。在調用 RS485_SendByte 函數前,需要先使用前面提到的切換收發狀態宏,把MAX485 切換到發送模式, STM32 發出的數據才能正常傳輸到 485 網絡總線上,當發送完數據的時候,應重新把 MAX485 切換回接收模式,以便獲取網絡總線上的數據。
(3)main 函數
最後我們來閱讀 main 函數,瞭解整個通訊過程,見代碼清單。這個 main 函數的整體設計思路是,實驗板檢測自身的按鍵狀態,若按鍵被按下,則通過 485 發送 256 個測試數據到網絡總線上,若自身接收到總線上的 256 個數據,則把這些數據作爲調試信息打印到電腦端。所以,如果把這樣的程序分別應用到 485 總線上的兩個通訊節點時,就可以通過按鍵控制互相發送數據了。
/**
* @brief 主函數
* @param 無
* @retval 無
*/
int main(void)
{
char *pbuf;
uint16_t len;
LED_GPIO_Config();
/*初始化 USART1*/
Debug_USART_Config();
/*初始化 485 使用的串口,使用中斷模式接收*/
RS485_Config();
LED_BLUE;
Key_GPIO_Config();
printf("\r\n 歡迎使用 STM32 F429 開發板。 \r\n");
printf("\r\n F429 485 通訊實驗例程\r\n");
printf("\r\n 實驗步驟: \r\n");
printf("\r\n 1.使用導線連接好兩個 485 通訊設備\r\n");
printf("\r\n 2.使用跳線帽連接好: 3V3<--->CAN/485_3V3,485-RX--PD5,485-TX--PD6 \r\n");
printf("\r\n 3.若使用兩個開發板進行實驗,給兩個開發板都下載本程序即可。 \r\n");
printf("\r\n 4.準備好後,按下其中一個開發板的 KEY1 鍵,會使用 485 向外發送 0-255 的數字 \r\n");
printf("\r\n 5.若開發板的 485 接收到 256 個字節數據,會把數據以 16 進制形式打印出來。 \r\n");
while (1) {
/*按一次按鍵發送一次數據*/
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON) {
uint16_t i;
LED_BLUE;
//切換到發送狀態
RS485_TX_EN();
for (i=0; i<=0xff; i++) {
RS485_SendByte(i); //發送數據
}
/*加短暫延時,保證 485 發送數據完畢*/
Delay(0xFFF);
RS485_RX_EN();//切換回接收狀態
LED_GREEN;
printf("\r\n 發送數據成功! \r\n"); //使用調試串口打印調試信息到終端
} else {
LED_BLUE;
pbuf = get_rebuff(&len);
if (len>=256) {
LED_GREEN;
printf("\r\n 接收到長度爲%d 的數據\r\n",len);
RS485_DEBUG_ARRAY((uint8_t*)pbuf,len);
clean_rebuff();
}
}
}
}
在 main 函數中,首先初始化了 LED、按鍵以及調試使用的串口,再調用前面分析的RS485_Config 函數初始化了 RS-485 通訊使用的串口工作模式。
初始化後 485 就進入了接收模式,當接收到數據的時候會進入中斷並把數據存儲到接收緩衝數組中,我們在 main 函數的 while 循環中(else 部分)調用 get_rebuff 來查看該緩衝區的狀態,若接收到 256 個數據就把這些數據通過調試串口打印到電腦端,然後清空緩衝區。
在 while 循環中,還檢測了按鍵的狀態,若按鍵被按下,就把 MAX485 芯片切換到發送狀態並調用 RS485_SendByte 函數發送測試數據 0x00-0xFF,發送完畢後切換回接收狀態以檢測總線的數據。
3、下載驗證
下載驗證這個 485 通訊實驗需要您有兩個實驗板,操作步驟如下:
- 按照“硬件設計”小節中的圖例連接兩個板子的 485 總線;
- 使用跳線帽連接 : 485_TX<--->PD6、 485_RX<--->PD5、 3V3<--->CAN/485_3V3 ;
- 用 USB 線使實驗板“ USB TO UART”接口跟電腦連接起來,在電腦端打開串口調試助手,編譯本章配套的程序,並給兩個板子都下載該程序,然後復位。
- 復位後在串口調試助手應看到 485 測試的調試信息,按一下其中一個實驗板上的KEY1 按鍵,另一個實驗板會接收到報文,在串口調試助手可以看到相應的發送和接收的信息。