You are on page 1of 12

https://www.twblogs.

net/a/5b8cd4122b7177188335e7a9

RS485 通信基礎理論與 STM32 測試

原創 口袋里的超超 2018-09-03 14:26

1.優劣
優勢:RS485 的可靠傳輸距離遠,接線簡單成爲了相對於 RS232 的最大優勢。

不足:RS485 總線是一種常規的通信總線,它不能夠做總線的自動仲裁,也就是不能夠同時發送數據以
避免總線競爭,所以整個系統的通信效率必然較低,數據冗餘量較大,對於速度要求高的應用場所不適
應用 RS485 總線。同時由於 RS485 總線上通常只有一臺主機,所以這種總線方式是典型的集中—分散型
控制系統。一旦主機出現故障,會使整個系統的通信限於癱瘓狀態,因此做好主機的在線備份是一個重
要措施。

2. 硬件層協議
通訊協議主要是實現兩個設備之間的數據交換功能,通訊協議分硬件層協議和軟件層協議。
硬件層協議決定數據如何傳輸問題,比如要在設備 1 向設備 2 發送 0x63,0x63 的二進制數
爲 0110 0011,這 8 個二進制數從設備 1 傳輸到設備 2,涉及到 1 怎麼傳,0 怎麼傳的問題,
這就是硬件層要解決的問題。
硬件層協議目前比較多見的有 RS-232、RS-485、SPI、IIC 等。RS-232 規定,線上的電壓
爲 x 伏都表示傳輸的是 0,y 伏傳輸的則是 1。再者,比如要選擇多少條線傳輸數據,選擇什
麼材質的線傳輸輸入,這些也屬於硬件層協議約束的。

3.RS-485 通訊協議
MCU 管腳輸出 TTL 電平,TTL 電平的意思是,當 MCU 管腳輸出 0 電平時,一般情況下電壓
是 0V,當 MCU 管腳輸出 1 電平時,電壓是 5V。因 TTL 電平的是由一條信號線,一條地線
產生,信號線上的干擾信號會跟隨有效信號傳送到接收端,使得有效信號受到干擾,485 通
訊實際上是把 MCU 出來的 TTL 電平通過硬件層的一個轉換器芯片進行轉換:
把 MCU 出來的一條的 TTL 信號經過芯片轉換爲兩根線(線 A、線 B)上的信號。當 MCU 給轉
換器輸入低 TTL 電平時,轉換器會使得 B 的電壓比 A 的電壓高,反之,A 的電壓比 B 的電壓
高。
485 協議規約兩條電平線上差值爲多少表示 0 或者 1,電壓是通過儀表可以測量得到的,所
以說 RS-485 是硬件層協議。
485 協議的接收端可能是另一個 MCU,MCU 管腳也只接受 TTL 電平,轉換芯片過來的是兩
條線的電壓,所以需要對此兩條線差分電壓轉換爲 TTL 電平。

把 TTL 轉爲 485,實質是一個集成芯片,其間無任何程序代碼,純粹硬件邏輯。同理,將
485 電平轉爲 TTL 也是如此。現在很多芯片把接收和轉換都集成到一塊 IC,注意,轉換器和
接收器依舊是沒有同時工作的,常見的轉換芯片是 MAX485。
可以這樣理解,硬件層協議是公路,路的目的是爲了讓車輛能夠過去。

4.半雙工通訊
首先了解什麼是單工通訊,單工通訊是指數據只能朝着一個方向傳輸的通訊方式。而半雙工
通訊則是指對於通訊兩端,不能同時相對方法發送數據,必須錯開時間段發送。

RS-485 的通訊線只有 2 條,且這兩條通訊線在一次傳輸中都需要用到,因此 485 只可實現


半雙工通訊。485 實現半雙工通訊,會遇到一個問題,MCU1 向 MCU2 發數據時,並不知道
線上是否正傳來 MCU2 數據,因爲沒有其他線可用來判斷對方的收發狀態,那麼可能也會導
致數據衝突。因此,RS-485 要實現半雙工通訊,就需要上層的軟件協議加以規約,也就是
做到”不能你想發數據就發數據”。可以理解,軟件層協議就好像交通規則,它能讓數據有
序傳輸。

5.基本電路
三種常用電路如下:

5.1 基本的 RS485 電路

上圖是最基本的 RS485 電路,R/D 爲低電平時,發送禁止,接收有效,R/D 爲高電


平時,則發送有效,接收截止。上拉電阻 R7 和下拉電阻 R8,用於保證無連接的
SP485R 芯片處於空閒狀態,提供網絡失效保護,提高 RS485 節點與網絡的可靠性,
R7,R8,R9 這三個電阻,需要根據實際應用改變大小,特別是使用 120 歐或更小
的終端電阻時,R9 就不需要了,此時 R7,R8 使用 680 歐電阻。正常情況下,一
般 R7=R8=4.7K,R9 不要。

圖中鉗位於 6.8V 的管 V4,V5,V6,都是爲了保護 RS485 總線的,避免受外界干


擾,也可以選擇集成的總線保護原件。另外圖中的 L1,L2,C1,C2 爲可選安裝
原件,用於提高電路的 EMI 性能.

5.2 帶隔離的 RS485 電路


根本原理與基本電路的原理相似。使用 DC-DC 器件可以產生 1 組與微處理器電路
完全隔離的電源輸出,用於向 RS485 收發器提供+5V 電源。電路中的光耦器件速
率會影響 RS485 電路的通信速率。上圖中選用了 NEC 的光耦 PS2501,受其影響,
該電路的通訊速率控制在 19200bps 下。

5.3 自動切換電路

上圖中,TX,RX 引腳均需要上拉電阻,這一點特別重要。
接收:默認沒有數據時,TX 爲高電平,三極管導通,RE 爲低電平使能,RO 收數
據有效,MAX485 爲接收態。
發送:發送數據 1 時,TX 爲高電平時,三極管導通,DE 爲低電平,此時收發器處
於接收狀態,驅動器就變成了高阻態,也就是發送端與 A\B 斷開了,此時 A\B 之
間的電壓就取決於 A\B 的上下拉電阻了,A 爲高電平、B 爲低電平,也就成爲了邏
輯 1 了。
發送數據 0 時,TX 爲低電平,三極管截止,DE 爲高電平,驅動器使能,
此時正好 DI 是接地的,也就是低電平,驅動器也就會驅動輸出 B 爲 1,A 爲 0,也
就是所謂的邏輯 0 了。
理解自收發的作用,關鍵是要理解 RE 和 DE 的作用,尤其是 DE 爲 0 時,驅
動器與 A\B 之間就是高阻態,也就是斷開狀態,而且 A\B 都要有上下拉電阻。然
後就有了邏輯 0-1 之間的切換了。所以很巧妙,但是這裏也有一個很明顯的
bug,也就是隻適用於“半雙工”,如果是全雙工,就不行了,因爲 TX 爲 1 時,
接收使能,此時從機如果回覆數據,那麼也就亂了。
基本原理了解了,除了使用三極管實現,還可以使用施密特觸發器,也就是所
謂的“非”門,來顯現,如下圖所示:

基本原理與三極管相同,TX 爲 1 時,經過施密特觸發器進行“非”運算,DE 爲
0,則接收使能,驅動器呈高阻態,此時 A\B 的電平就是上下拉電阻的電平,也就
是邏輯 1。TX 爲 0 時,DE 爲 1,發送使能,由於 DI 接地,也就是 0,A\B 輸出也
是 0.

6.SP3485 內部結構圖:
圖中:
A、B 總線接口,用於連接 485 總線。RO 是接收輸出端,DI 是發送數據收入端,RE 是接收
使能信號(低電平有效),DE 是發送使能信號(高電平有效)。

SP3485 硬件連接:

注意:
R55 和 R56 是兩個偏置電阻,用來保證總線空閒時,AB 之間的電壓差都會大約 200mV,
避免總線空閒時壓差不定邏輯混亂。

7. RS485 串口編程
7.1 編程思路

使用 RS485 實現兩個 MCU 之間的通信,把接收到的數據通過串口助手顯示在超級終端上。


首先對 Usart1 和 Usart2 進行初始化,Usart1 負責與串口助手通信,Usart2 與 RS485 連接
進行兩個 MCU 之間的通信。然後編寫發送和接收函數,接收函數在 Usart2 的中斷服務函數
中實現。最後把接收到的數據和必要的提示信息發送到超級終端上顯示。

7.2 功能模塊代碼

① 串口初始化

void Uart1_Init(void)
{
//USART1 初始化
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //開啓
GPIOA 時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //開啓
USART1 時鐘

//串口 1 對應引腳複用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9
複用爲 USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10
複用爲 USART1

//USART1 端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
//GPIOA9,GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //複用功

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度
50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推輓複
用輸出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化
PA9,PA10

//USART1 端口配置
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_HardwareFlowControl =
USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx |
USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);

USART_Cmd(USART1, ENABLE); //使能串口 1

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //開啓相關中斷

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

void Uart2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);

//串口 2 對應引腳複用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//USART2 端口配置
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_HardwareFlowControl =
USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);

USART_Cmd(USART2, ENABLE);

USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

//Usart2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

② 接收數據

void USART2_IRQHandler(void)
{
static u32 rx_i=0;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除標誌位
rx_buf[rx_i++] = USART_ReceiveData(USART2); //rx_buf 是在
main.c 定義的全局變量
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
rx_flag = 1;
}
③RS485 初始化

(SP3485 的 RE,DE 引腳與 MCU 的 PG8 引腳相連接)

void Rs485_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOG, &GPIO_InitStruct);

// RS485_TX_EN = 0; //默認爲接收模式
}

④ 主函數

int main(void)
{
char *tx_buf = "I believe I can fly!";
u8 len;
Led_Init();
Key_Init();
Systick_Init();
Uart1_Init();
Uart2_Init();
Rs485_Init();

printf("Usart test succeeded!\r\n");


while(1)
{
if(!KEY0) //KEY1 按鍵按下
{
delay_ms(10); //消抖動
if(!KEY0)
{
while(!KEY0);
RS485_TX_EN = 1; //發送模式,RS485_TX_EN 是自定義的一個宏,
即對 PG8 進行置位復位
len = strlen(tx_buf);
while(len--)
{
USART_SendData(USART2, *tx_buf++);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) ==
RESET);
}
printf("Send data succeeded!\r\n"); //printf 函數已經重定

}
}
if(!KEY1) //KEY1 按鍵按下
{
delay_ms(10); //消抖動
if(!KEY1) //等待按鍵鬆開
{
while(!KEY1);
RS485_TX_EN = 0; //接收模式
if(rx_flag)
{
rx_flag = 0; //清除標誌
printf("Receive data: %s\r\n", rx_buf);
}
}
}
}
}

You might also like