現(xiàn)RS485通信:從硬件電路到軟件調(diào)試全解析)
簡(jiǎn)介面向嵌入式開(kāi)發(fā)者與自動(dòng)化工程師提供STM32F103實(shí)現(xiàn)RS485通信的完整工程源碼包。工程基于標(biāo)準(zhǔn)外設(shè)庫(kù)實(shí)現(xiàn)了UART初始化、波特率與數(shù)據(jù)位配置、485方向引腳DE/RE自動(dòng)切換、發(fā)送完成與接收中斷處理并包含超時(shí)/溢出等錯(cuò)誤檢測(cè)邏輯讀者可據(jù)此掌握RS485半雙工通信的收發(fā)時(shí)序控制方法。資源共122個(gè)文件以C源碼、H頭文件、啟動(dòng)匯編、Keil工程配置為主附帶可直接燒錄的hex文件、PDF與doc說(shuō)明文檔、清除編譯中間文件的bat腳本資源包大小約826KB。已有5507人學(xué)習(xí)。壓縮包內(nèi)目錄結(jié)構(gòu)清晰演示了結(jié)合MAX485芯片從硬件連接到軟件配置、再到串口助手調(diào)試的完整流程并提示終端電阻匹配、總線沖突避免、多節(jié)點(diǎn)地址識(shí)別等工程注意事項(xiàng)適合競(jìng)賽備賽、課程設(shè)計(jì)和實(shí)際項(xiàng)目快速移植。 不知道你有沒(méi)有經(jīng)歷過(guò)這種情況在車間里調(diào)一臺(tái)伺服驅(qū)動(dòng)器電腦和PLC都在總線上你的STM32開(kāi)發(fā)板插上去數(shù)據(jù)發(fā)不出去偶爾收到一堆亂碼甚至整個(gè)總線都被“拉死”。把波特率改低、把線重新接一遍還是不行。折騰一晚上最后發(fā)現(xiàn)是方向控制引腳沒(méi)拉對(duì)或者A、B兩根線接反了。這種場(chǎng)景在工業(yè)現(xiàn)場(chǎng)太常見(jiàn)了而它背后的主角就是RS485。RS485這些年幾乎成了工業(yè)通信的默認(rèn)選項(xiàng)我的STM32項(xiàng)目里十個(gè)有八個(gè)都要用到它從Modbus儀表、伺服驅(qū)動(dòng)器、變頻器到溫濕度傳感器、電表、條碼槍只要是長(zhǎng)距離、多節(jié)點(diǎn)、抗干擾要求高的場(chǎng)景基本都是485上場(chǎng)。這篇就圍繞STM32如何實(shí)現(xiàn)485通信來(lái)寫從硬件電路選型到軟件收發(fā)狀態(tài)機(jī)再到現(xiàn)場(chǎng)調(diào)試最容易踩的坑一次說(shuō)清。1. RS485為什么這么多項(xiàng)目在用設(shè)計(jì)前要想清楚什么1.1 485相比232、422、I2C、SPI到底強(qiáng)在哪先說(shuō)個(gè)最容易混淆的點(diǎn)RS485不是一種像SPI、I2C那樣的完整通信協(xié)議它只是物理層標(biāo)準(zhǔn)。你跑在RS485上的還是UART那套數(shù)據(jù)幀格式波特率、起始位、數(shù)據(jù)位、停止位這些和串口一模一樣。所以你在STM32上做485本質(zhì)就是調(diào)一個(gè)UART外設(shè)再額外控制一下收發(fā)方向。那為什么不用RS232主要差在三點(diǎn)。第一是距離RS232一般15米就到頭了RS485在低速情況下能到1200米工廠車間幾十米上百米的布線非常常見(jiàn)。第二是多節(jié)點(diǎn)RS232天生只能點(diǎn)對(duì)點(diǎn)RS485通過(guò)總線掛載標(biāo)準(zhǔn)負(fù)載下能掛32個(gè)節(jié)點(diǎn)。第三是抗干擾RS485用差分信號(hào)傳輸兩根線A和B互為參考外界噪聲同時(shí)疊加到兩根線上接收端只判斷A-B的電壓差共模干擾被抵消掉這在變頻器、電機(jī)旁邊尤其重要。和I2C、SPI這類板級(jí)總線比485的優(yōu)勢(shì)在于它把物理層放到了外部收發(fā)器芯片上電平是差分信號(hào)不是單端電平所以能長(zhǎng)距離傳輸。SPI的SCLK、MISO、MOSI三根線跑到半米開(kāi)外就各種丟數(shù)據(jù)485上百米還能穩(wěn)定運(yùn)行。說(shuō)到底選485不是因?yàn)樗膮f(xié)議有多先進(jìn)而是它在工業(yè)現(xiàn)場(chǎng)這種惡劣條件下用最少的線兩根和最便宜的成本實(shí)現(xiàn)了可靠的長(zhǎng)距離多點(diǎn)通信。這就是它幾十年還沒(méi)被淘汰的根本原因。1.2 半雙工的方向控制是整個(gè)設(shè)計(jì)的核心矛盾485總線是半雙工工作方式同一時(shí)刻總線上只能有一個(gè)節(jié)點(diǎn)在發(fā)送其他節(jié)點(diǎn)都在接收。如果兩個(gè)節(jié)點(diǎn)同時(shí)拉高發(fā)送總線就會(huì)沖突數(shù)據(jù)全亂。這就是為什么每個(gè)485節(jié)點(diǎn)都要有一個(gè)方向控制的概念。收發(fā)器芯片內(nèi)部其實(shí)有兩個(gè)放大器一個(gè)把總線上的差分信號(hào)轉(zhuǎn)換為單端信號(hào)送給STM32的RX引腳另一個(gè)把STM32的TX引腳的信號(hào)轉(zhuǎn)換為差分信號(hào)輸出到總線。這兩個(gè)放大器由芯片上的DE引腳和RE引腳控制。DE是發(fā)送使能高電平有效RE是接收使能低電平有效。很多芯片像MAX485、SP3485內(nèi)部已經(jīng)把DE和RE邏輯反相處理好了所以你可以把DE和RE直接短接成一個(gè)引腳拉高就是發(fā)送模式拉低就是接收模式。這里有一個(gè)設(shè)計(jì)方案的選擇可以用GPIO軟件控制方向也可以用帶自動(dòng)方向檢測(cè)功能的收發(fā)器比如MAX13487來(lái)自動(dòng)切換。自動(dòng)方向芯片看起來(lái)省事少占一個(gè)GPIO但在高速、長(zhǎng)線、總線節(jié)點(diǎn)多的場(chǎng)景下方向切換時(shí)序不完全受控偶爾會(huì)出莫名其妙的丟幀。我自己的習(xí)慣是用GPIO手動(dòng)控制方向代碼只多兩行但每一個(gè)時(shí)序細(xì)節(jié)都在自己手里排查問(wèn)題的時(shí)候心里有底。后面所有代碼和電路都按手動(dòng)GPIO控制方向的方案來(lái)講。2. 硬件電路這么接才不容易翻車2.1 收發(fā)器芯片選型與外圍電路做STM32項(xiàng)目時(shí)收發(fā)器芯片一般從MAX485、SP3485、MAX3485里選。STM32是3.3V供電所以最推薦SP3485或者M(jìn)AX3485它們是3.3V供電版本RO和DI電平可以直接和STM32的GPIO對(duì)接不需要電平轉(zhuǎn)換電路。MAX485是5V供電的如果系統(tǒng)里本來(lái)就有5V電源也能用但RO輸出高電平是5V直接進(jìn)STM32的RX引腳會(huì)有風(fēng)險(xiǎn)最好串聯(lián)電阻或者用分壓處理。節(jié)約成本沒(méi)問(wèn)題但穩(wěn)定性優(yōu)先的話選3.3V版本最省心。典型的外圍電路這樣接STM32的TX引腳接收發(fā)器的DIRX引腳接收發(fā)器的RO。收發(fā)器的RE和DE短接后接一個(gè)STM32的空閑GPIO比如PB0。A和B兩根線引出到接線端子A接雙絞線的一根B接另一根。在A和B之間加一個(gè)120歐終端電阻。在A線上加一個(gè)上拉電阻到3.3VB線上加一個(gè)下拉電阻到GND一般用1k到10k。這個(gè)偏置電阻的作用是保證總線空閑時(shí)A-B的電壓差不在不確定區(qū)否則接收端會(huì)輸出隨機(jī)電平產(chǎn)生亂碼。A、B線上各串一個(gè)TVS管到GND保護(hù)收發(fā)器芯片不被靜電或浪涌打壞。終端電阻和偏置電阻的取舍值得展開(kāi)說(shuō)一下。120歐終端電阻是為了匹配雙絞線的特性阻抗減少信號(hào)反射。但它只應(yīng)該加在總線物理線路的兩端不是每個(gè)節(jié)點(diǎn)都加。如果你的STM32節(jié)點(diǎn)正好位于總線末端那就在你的板子上加如果在中間就別加加了反而會(huì)增大負(fù)載拉低信號(hào)幅度。一個(gè)典型的Modbus總線最遠(yuǎn)端的兩個(gè)設(shè)備各加一個(gè)120歐就對(duì)了。偏置電阻很多人會(huì)忽略但它恰恰是發(fā)數(shù)據(jù)正??臻e時(shí)亂碼這類問(wèn)題的關(guān)鍵。阻值選多大取決于總線上掛了多少個(gè)節(jié)點(diǎn)因?yàn)槊總€(gè)節(jié)點(diǎn)的收發(fā)器都有輸入阻抗會(huì)分壓。標(biāo)準(zhǔn)做法是讓總線上所有偏置電阻并聯(lián)后在A-B之間形成的靜態(tài)壓差大于200mV。按經(jīng)驗(yàn)一個(gè)節(jié)點(diǎn)典型偏置用10k到20k也能工作但如果總線長(zhǎng)、節(jié)點(diǎn)多建議用1k到4.7k更穩(wěn)。我通常直接用2k效果穩(wěn)定。2.2 與STM32的接線及共地問(wèn)題接線端子這里有個(gè)特別容易踩的坑RS485連接必須A對(duì)A、B對(duì)B不能像RS232那樣交叉。很多人第一次用485轉(zhuǎn)USB工具接STM32板子順手按232的習(xí)慣把A接B、B接A結(jié)果數(shù)據(jù)完全不通還以為是波特率問(wèn)題。用萬(wàn)用表量一下波形就明白了A、B接反時(shí)波形是反相的接收到的數(shù)據(jù)全是亂碼。共地問(wèn)題更要重視。RS485雖然是差分傳輸?shù)珒蓚€(gè)通信節(jié)點(diǎn)之間仍然需要一個(gè)共同的參考地。工業(yè)現(xiàn)場(chǎng)經(jīng)常出現(xiàn)的情況是兩個(gè)設(shè)備分別接了不同的開(kāi)關(guān)電源GND之間壓差達(dá)到幾伏甚至幾十伏這時(shí)候A、B線上的共模電壓會(huì)超過(guò)收發(fā)器的承受范圍輕則通信不正常重則直接燒掉收發(fā)器芯片。所以你的STM32板子和對(duì)端設(shè)備之間除了A、B兩根線還必須拉一根GND線把兩個(gè)系統(tǒng)的地兜在一起。記住這句話485隔離的是設(shè)備之間的信號(hào)干擾不是地電位地線該接還得接。3. 軟件部分狀態(tài)機(jī)、時(shí)序、代碼實(shí)現(xiàn)3.1 用CubeMX配置USART和方向控制引腳軟件部分用STM32的HAL庫(kù)來(lái)做。CubeMX里配置很簡(jiǎn)單以USART1為例打開(kāi)USART1模式選Asynchronous異步波特率按你的從機(jī)設(shè)備要求來(lái)一般Modbus設(shè)備默認(rèn)9600或1152008位數(shù)據(jù)、無(wú)校驗(yàn)、1位停止位8N1是標(biāo)準(zhǔn)配置。使能USART1全局中斷。PB0配置為GPIO_Output初始電平設(shè)為低電平。這里的關(guān)鍵是初始狀態(tài)必須是低電平也就是接收模式。如果初始化為高電平STM32一上電就會(huì)讓收發(fā)器進(jìn)入發(fā)送模式把總線占住其他節(jié)點(diǎn)全被拉死這就是常見(jiàn)的一種總線異常現(xiàn)象的根源。生成工程后在main.c里把方向控制引腳的設(shè)置寫成宏定義方便后面調(diào)用。3.2 發(fā)送邏輯先拉方向再發(fā)數(shù)據(jù)發(fā)完再拉回來(lái)發(fā)送的過(guò)程很簡(jiǎn)單但順序絕對(duì)錯(cuò)不得先拉高方向引腳、再發(fā)數(shù)據(jù)、等發(fā)送徹底完成、最后拉低方向引腳。只要順序錯(cuò)一步就會(huì)出現(xiàn)最后一字節(jié)被截?cái)嗷蚩偩€一直被占用的問(wèn)題?;A(chǔ)的阻塞發(fā)送函數(shù)這樣寫#define RS485_DE_HIGH() HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET) #define RS485_DE_LOW() HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET) void RS485_Send_Data(uint8_t *data, uint16_t len) { RS485_DE_HIGH(); HAL_UART_Transmit(huart1, data, len, 100); HAL_Delay(1); RS485_DE_LOW(); }HAL_UART_Transmit是阻塞發(fā)送內(nèi)部會(huì)等待發(fā)送完成標(biāo)志TCTransmission Complete也就是最后一個(gè)字節(jié)已經(jīng)從移位寄存器里完全挪出去了。但為什么發(fā)完之后還要加個(gè)HAL_Delay(1)再拉低方向因?yàn)槲覍?shí)際測(cè)試下來(lái)雖然TC已經(jīng)置位但在高速主頻下UART外設(shè)和GPIO之間還有一點(diǎn)硬件動(dòng)作余量個(gè)別芯片在發(fā)送完成后立即切換方向時(shí)最后一個(gè)字節(jié)的停止位仍然會(huì)被切掉。保險(xiǎn)起見(jiàn)加一兩個(gè)字節(jié)的發(fā)送時(shí)間再切方向。波特率9600時(shí)一個(gè)字節(jié)約1ms115200時(shí)約0.1msDelay(1)完全夠用。如果對(duì)實(shí)時(shí)性要求高你完全可以用DWT計(jì)數(shù)器做一個(gè)微秒級(jí)延時(shí)去掉這個(gè)1ms但大多數(shù)場(chǎng)景沒(méi)必要。如果你用的是中斷發(fā)送或DMA發(fā)送那就不能在函數(shù)尾部拉低方向因?yàn)閿?shù)據(jù)可能還在DMA搬運(yùn)中函數(shù)已經(jīng)返回了。這時(shí)候要在HAL_UART_TxCpltCallback發(fā)送完成回調(diào)里拉低方向。這是新手最容易搞錯(cuò)的地方必須單獨(dú)強(qiáng)調(diào)。3.3 接收邏輯中斷接收加空閑判斷湊成一幀接收部分用中斷方式循環(huán)接收再用串口空閑中斷IDLE來(lái)判定一幀數(shù)據(jù)結(jié)束。它的原理是485總線上一幀數(shù)據(jù)內(nèi)部的字節(jié)間隔很短總線空閑狀態(tài)超過(guò)一個(gè)閾值時(shí)UART外設(shè)會(huì)產(chǎn)生IDLE中斷。這樣你不需要知道這一幀有多長(zhǎng)只要總線上安靜了就認(rèn)為當(dāng)前這一幀已經(jīng)收完。先定義幾個(gè)緩沖區(qū)變量uint8_t rx_buffer[256]; volatile uint16_t rx_len 0; volatile uint8_t frame_ready 0; uint8_t rx_byte;接著在初始化里啟動(dòng)接收HAL_UART_Receive_IT(huart1, rx_byte, 1);然后實(shí)現(xiàn)接收完成回調(diào)和中斷服務(wù)函數(shù)void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { if (rx_len sizeof(rx_buffer)) { rx_buffer[rx_len] rx_byte; } else { rx_len 0; } HAL_UART_Receive_IT(huart1, rx_byte, 1); } } void USART1_IRQHandler(void) { HAL_UART_IRQHandler(huart1); if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart1); if (rx_len 0) { frame_ready 1; } rx_len 0; } }接收流程是這樣的每來(lái)一個(gè)字節(jié)中斷一次RxCpltCallback里把字節(jié)放進(jìn)rx_buffer同時(shí)重新開(kāi)啟單字節(jié)接收。當(dāng)總線上靜默一個(gè)字節(jié)多的時(shí)間UART外設(shè)產(chǎn)生IDLE中斷此時(shí)rx_len里攢著的數(shù)據(jù)就是一整幀置frame_ready標(biāo)志主循環(huán)檢測(cè)到這個(gè)標(biāo)志就處理數(shù)據(jù)。這里有一個(gè)HAL庫(kù)的坑必須提__HAL_UART_CLEAR_IDLEFLAG這個(gè)宏在不同的HAL庫(kù)版本里行為不一致。保險(xiǎn)的做法是像下面這樣手動(dòng)清標(biāo)志volatile uint32_t dummy; dummy huart1.Instance-SR; dummy huart1.Instance-DR;讀SR再讀DR是清UART各種標(biāo)志最原始也最可靠的方式。遇到IDLE中斷一直觸發(fā)或者清了之后再也不觸發(fā)的怪問(wèn)題換成這段代碼基本就解決了。如果是做Modbus RTU協(xié)議更規(guī)范的辦法是用3.5個(gè)字符時(shí)間窗口來(lái)判斷幀結(jié)束邏輯比IDLE中斷稍微復(fù)雜一點(diǎn)但容錯(cuò)性更好。可以先跑通中斷加IDLE的方式再按協(xié)議需求優(yōu)化。3.4 主循環(huán)里的幀處理與應(yīng)答邏輯主循環(huán)的結(jié)構(gòu)保持簡(jiǎn)單只處理標(biāo)志位while (1) { if (frame_ready) { frame_ready 0; // 在這里解析接收到的幀數(shù)據(jù) // 如果是Modbus請(qǐng)求則組幀回復(fù) // 調(diào)用RS485_Send_Data()發(fā)送應(yīng)答 } // 其他業(yè)務(wù)代碼 }如果這個(gè)STM32節(jié)點(diǎn)要做Modbus從站你可以在解析到正確地址和功能碼后調(diào)用前面寫的RS485_Send_Data把響應(yīng)幀發(fā)出去。要注意的是回復(fù)時(shí)也要遵守先拉高方向、再發(fā)數(shù)據(jù)、發(fā)完拉低的順序和普通發(fā)送完全一樣不存在特殊處理。4. 實(shí)際調(diào)試中的問(wèn)題、解決和速查4.1 三個(gè)最讓人頭大的調(diào)試現(xiàn)象及真實(shí)解決過(guò)程現(xiàn)象一STM32發(fā)送出去對(duì)端485轉(zhuǎn)USB工具收不到任何數(shù)據(jù)但直接測(cè)STM32的TX引腳有波形。這個(gè)問(wèn)題我遇到不下五次萬(wàn)用表量一下波形就能定位。如果STM32的TX有波形說(shuō)明問(wèn)題出在方向切換或A/B接線。用示波器看方向控制引腳的波形如果發(fā)送期間方向引腳始終是低電平說(shuō)明軟件壓根沒(méi)進(jìn)發(fā)送函數(shù)或者GPIO映射錯(cuò)了。如果方向引腳在高電平期間同時(shí)看A、B兩線上有輸出那就是A/B線序錯(cuò)了對(duì)調(diào)一下就通。現(xiàn)象二接收端能收到數(shù)據(jù)但是亂碼或者第一個(gè)字節(jié)丟失、最后一字節(jié)被截?cái)?。亂碼先查波特率Emphasis這個(gè)是最常見(jiàn)的。波特率對(duì)不上時(shí)收到的字節(jié)幾乎全是錯(cuò)位的十六進(jìn)制值。排除波特率后再查共地線。如果你的STM32板和對(duì)端設(shè)備用兩個(gè)獨(dú)立的開(kāi)關(guān)電源供電先把GND連通試一下。最后一字節(jié)被截?cái)嗷揪褪欠较蚯袚Q太早把HAL_Delay(1)加上去或者改用TC標(biāo)志加延時(shí)一般就好了。現(xiàn)象三STM32能給從機(jī)發(fā)送命令從機(jī)也執(zhí)行了動(dòng)作但從不回復(fù)數(shù)據(jù)。這種情況說(shuō)明發(fā)送鏈路是通的問(wèn)題出在接收鏈路或者從機(jī)根本沒(méi)收到正確的幀。先用485轉(zhuǎn)USB工具掛到總線上人為給從機(jī)發(fā)一幀測(cè)試指令看從機(jī)有沒(méi)有響應(yīng)。如果有響應(yīng)說(shuō)明從機(jī)沒(méi)問(wèn)題問(wèn)題回到STM32的接收側(cè)。檢查STM32有沒(méi)有進(jìn)接收中斷用調(diào)試器在HAL_UART_RxCpltCallback里打斷點(diǎn)如果斷點(diǎn)不進(jìn)檢查中斷優(yōu)先級(jí)和NVIC配置如果斷點(diǎn)進(jìn)去了但frame_ready始終不置位那就是IDLE中斷沒(méi)有產(chǎn)生換成讀SR加DR的清標(biāo)志方式再試。4.2 總線異常恢復(fù)與穩(wěn)定性設(shè)計(jì)485總線上有一個(gè)很揪心的問(wèn)題是某個(gè)節(jié)點(diǎn)發(fā)完數(shù)據(jù)后忘了把方向引腳拉回低電平導(dǎo)致該節(jié)點(diǎn)一直占住總線其他所有節(jié)點(diǎn)全都無(wú)法通信。這種情況表現(xiàn)為整個(gè)系統(tǒng)全線癱瘓而且很難定位是哪臺(tái)設(shè)備出了問(wèn)題因?yàn)榭偩€已經(jīng)被拉死了。在STM32的代碼里我自己習(xí)慣加一道保護(hù)邏輯。每次新數(shù)據(jù)發(fā)送之前先強(qiáng)制把方向引腳拉低再拉高這樣即使上一次發(fā)送過(guò)程被異常中斷、方向引腳停留在高電平下一輪發(fā)送也能先從接收態(tài)重新走一遍相當(dāng)于把總線狀態(tài)恢復(fù)了一遍。再配一個(gè)超時(shí)重發(fā)機(jī)制發(fā)送一幀命令后如果在規(guī)定時(shí)間內(nèi)沒(méi)收到應(yīng)答就重新初始化一次UART或者把GPIO重新寫一遍強(qiáng)制釋放總線。這種軟復(fù)位不需要重啟單片機(jī)但對(duì)現(xiàn)場(chǎng)穩(wěn)定性提升巨大。還有一個(gè)上電保護(hù)STM32復(fù)位期間GPIO引腳默認(rèn)是浮空輸入狀態(tài)方向控制引腳如果沒(méi)有下拉電阻外部電平不確定收發(fā)器有可能在上電瞬間進(jìn)入發(fā)送模式。處理辦法有兩個(gè)硬件上一是在DE/RE引腳對(duì)地加一個(gè)10k下拉電阻二是軟件上在main函數(shù)一開(kāi)始就立即執(zhí)行RS485_DE_LOW()把方向鎖定在接收狀態(tài)。兩種都做最穩(wěn)。4.3 常見(jiàn)問(wèn)題速查表我把這幾年調(diào)試485遇到的問(wèn)題整理成一張速查表方便你在現(xiàn)場(chǎng)對(duì)照排查現(xiàn)象可能原因解決方案上電就亂碼DE/RE初始電平不確定方向引腳加下拉電阻初始化拉低最后一字節(jié)被截?cái)喟l(fā)送完立即切方向等TC標(biāo)志加延時(shí)再拉低收到大量亂碼波特率不匹配核對(duì)兩端波特率A/B接反A對(duì)B、B對(duì)AA接A、B接B不要交叉通信距離一遠(yuǎn)就丟包終端電阻缺失、線徑太細(xì)總線兩端各加120歐終端換屏蔽雙絞線某節(jié)點(diǎn)接入后總線癱瘓節(jié)點(diǎn)方向引腳卡在發(fā)送態(tài)檢查該節(jié)點(diǎn)軟件方向邏輯從機(jī)不回復(fù)地址錯(cuò)、幀格式錯(cuò)、從機(jī)本身問(wèn)題用485轉(zhuǎn)USB工具單獨(dú)調(diào)試從機(jī)IDLE中斷反復(fù)觸發(fā)清標(biāo)志方式不對(duì)讀SR再讀DR清除標(biāo)志DMA發(fā)送不完整發(fā)送未完成就切方向在DMA發(fā)送完成回調(diào)中拉低方向供電不同地導(dǎo)致通信異常節(jié)點(diǎn)之間地電位差大節(jié)點(diǎn)間連接GND線最后分享一點(diǎn)我自己調(diào)485的習(xí)慣只要能手動(dòng)控制方向就絕不依賴自動(dòng)方向芯片代碼多兩行但現(xiàn)場(chǎng)少一堆坑。每一幀收發(fā)結(jié)束之后都讓電路回到接收態(tài)這是整個(gè)485穩(wěn)定通信的基本原則。總線空閑的時(shí)候永遠(yuǎn)不要讓它懸空上拉下拉電阻和終端電阻這些幾毛錢的電路該加就得加不然省下來(lái)的成本都會(huì)變成現(xiàn)場(chǎng)的調(diào)試時(shí)間。還有一個(gè)小技巧調(diào)試485最痛苦的是不知道總線上到底在傳什么所以我會(huì)在STM32板子上額外留一路空閑的UART接一個(gè)藍(lán)牙模塊或者直接接一根TTL轉(zhuǎn)USB線把收發(fā)數(shù)據(jù)實(shí)時(shí)打印到電腦上人就能看見(jiàn)總線數(shù)據(jù)流排查起來(lái)效率翻倍。這套做法在我做過(guò)的伺服控制、Modbus數(shù)據(jù)采集、多設(shè)備聯(lián)動(dòng)項(xiàng)目里一直都很管用。本文還有配套的精品資源點(diǎn)擊獲取