鍍金池/ 教程/ 數(shù)據(jù)庫/ 18.1 單片機(jī) RS485 通信接口、控制線、原理圖及程序?qū)嵗?/span>
18. RS485 通信與 Modbus 協(xié)議
17.5 A/D 差分輸入信號
15.8 C 語言復(fù)合數(shù)據(jù)類型(結(jié)構(gòu)體,共用體,枚舉類型)
16.3 NEC 協(xié)議紅外遙控器
13.1 單片機(jī)通信時序解析
14.4 單片機(jī) EEPROM 單字節(jié)讀寫操作時序
13.3 多個 .c 文件的初步認(rèn)識
18.2 Modbus 通信協(xié)議介紹
15.1 BCD 碼介紹
18.3 單片機(jī) Modbus 多機(jī)通信程序設(shè)計
18.1 單片機(jī) RS485 通信接口、控制線、原理圖及程序?qū)嵗?/span>
15. 實時時鐘 DS1302
14.7 單片機(jī) I2C 和 EEPROM 的綜合編程
17. 模數(shù)轉(zhuǎn)換與數(shù)模轉(zhuǎn)換
16.2 紅外遙控通信原理
13.2 1602 液晶整屏移動程序
17.6 D/A 輸出
17.7 單片機(jī)信號發(fā)生器程序
16.4 溫度傳感器 DS18B20
14.6 單片機(jī)EEPROM的頁寫入
13.4 單片機(jī)計算器程序設(shè)計[詳細(xì)]
17.2 A/D(模數(shù)轉(zhuǎn)換)的主要指標(biāo)
17.4 PCF8591 應(yīng)用程序
17.1 A/D 和 D/A 的基本概念
17.3 PCF8591硬件接口(電路圖引腳圖)
14.3 單片機(jī) EEPROM 簡介
13.5 單片機(jī)串口通信原理和控制程序
15.5 DS1302 寄存器介紹
15.2 單片機(jī) SPI 通信接口
15.6 DS1302 通信時序介紹
14.5 單片機(jī) EEPROM 多字節(jié)讀寫操作時序
16. 紅外通信與 DS18B20 溫度傳感器
14.1 單片機(jī) I2C 時序介紹
15.3 實時時鐘芯片 DS1302 介紹
15.9 單片機(jī)電子時鐘程序設(shè)計
16.1 紅外光的基本原理
15.4 DS1302 的硬件信息
15.7 DS1302 的 BURST 模式
14.2 單片機(jī) I2C 尋址模式
14. 單片機(jī) I2C 總線與 EEPROM
13. 單片機(jī) 1602 液晶與串口的應(yīng)用實例

18.1 單片機(jī) RS485 通信接口、控制線、原理圖及程序?qū)嵗?/h1>

RS232 標(biāo)準(zhǔn)是誕生于 RS485 之前的,但是 RS232 有幾處不足的地方:

  1. 接口的信號電平值較高,達(dá)到十幾 V,使用不當(dāng)容易損壞接口芯片,電平標(biāo)準(zhǔn)也與 TTL 電平不兼容。
  2. 傳輸速率有局限,不可以過高,一般到一兩百千比特每秒(Kb/s)就到極限了。
  3. 接口使用信號線和 GND 與其它設(shè)備形成共地模式的通信,這種共地模式傳輸容易產(chǎn)生干擾,并且抗干擾性能也比較弱。
  4. 傳輸距離有限,最多只能通信幾十米。
  5. 通信的時候只能兩點之間進(jìn)行通信,不能夠?qū)崿F(xiàn)多機(jī)聯(lián)網(wǎng)通信。

針對 RS232 接口的不足,就不斷出現(xiàn)了一些新的接口標(biāo)準(zhǔn),RS485 就是其中之一,它具備以下的特點:

  1. 采用差分信號。我們在講 A/D 的時候,講過差分信號輸入的概念,同時也介紹了差分輸入的好處,最大的優(yōu)勢是可以抑制共模干擾。尤其當(dāng)工業(yè)現(xiàn)場環(huán)境比較復(fù)雜,干擾比較多時,采用差分方式可以有效的提高通信可靠性。RS485 采用兩根通信線,通常用 A 和 B 或者 D+ 和 D- 來表示。邏輯“1”以兩線之間的電壓差為 +(0.2~6)V 表示,邏輯“0”以兩線間的電壓差為 -(0.2~6)V 來表示,是一種典型的差分通信。
  2. RS485 通信速率快,最大傳輸速度可以達(dá)到 10 Mb/s 以上。
  3. RS485 內(nèi)部的物理結(jié)構(gòu),采用的是平衡驅(qū)動器和差分接收器的組合,抗干擾能力也大大增加。
  4. 傳輸距離最遠(yuǎn)可以達(dá)到1200米左右,但是它的傳輸速率和傳輸距離是成反比的,只有在 100 Kb/s 以下的傳輸速度,才能達(dá)到最大的通信距離,如果需要傳輸更遠(yuǎn)距離可以使用中繼。
  5. 可以在總線上進(jìn)行聯(lián)網(wǎng)實現(xiàn)多機(jī)通信,總線上允許掛多個收發(fā)器,從現(xiàn)有的 RS485 芯片來看,有可以掛32、64、128、256等不同個設(shè)備的驅(qū)動器。
  6. RS485 的接口非常簡單,與 RS232 所使用的 MAX232 是類似的,只需要一個 RS485 轉(zhuǎn)換器,就可以直接與單片機(jī)的 UART 串口連接起來,并且使用完全相同的異步串行通信協(xié)議。但是由于 RS485 是差分通信,因此接收數(shù)據(jù)和發(fā)送數(shù)據(jù)是不能同時進(jìn)行的,也就是說它是一種半雙工通信。那我們?nèi)绾闻袛嗍裁磿r候發(fā)送,什么時候接收呢?

RS485 轉(zhuǎn)換芯片很多,這節(jié)課我們以典型的 MAX485 為例講解 RS485 通信,如圖18-1所示。

http://wiki.jikexueyuan.com/project/mcu-tutorial-three/images/55.png" alt="" />

圖18-1 MAX485 硬件接口

MAX485 是美信(Maxim)推出的一款常用 RS485 轉(zhuǎn)換器。其中5腳和8腳是電源引腳;6腳和7腳就是 RS485 通信中的 A 和 B 兩個引腳;1腳和4腳分別接到單片機(jī)的 RXD 和 TXD 引腳上,直接使用單片機(jī) UART 進(jìn)行數(shù)據(jù)接收和發(fā)送;2腳和3腳是方向引腳,其中2腳是低電平使能接收器,3腳是高電平使能輸出驅(qū)動器,我們把這兩個引腳連到一起,平時不發(fā)送數(shù)據(jù)的時候,保持這兩個引腳是低電平,讓 MAX485 處于接收狀態(tài),當(dāng)需要發(fā)送數(shù)據(jù)的時候,把這個引腳拉高,發(fā)送數(shù)據(jù),發(fā)送完畢后再拉低這個引腳就可以了。為了提高 RS485 的抗干擾能力,需要在靠近 MAX485 的 A 和 B 引腳之間并接一個電阻,這個電阻阻值從100歐到 1 K 都是可以。

在這里我們還要介紹一下如何使用 KST-51 單片機(jī)開發(fā)板進(jìn)行外圍擴(kuò)展實驗。我們的開發(fā)板只能把基本的功能給同學(xué)們做出來提供實驗練習(xí),但是同學(xué)們學(xué)習(xí)的腳步不應(yīng)該停留在這個實驗板上。如果想進(jìn)行更多的實驗,就可以通過單片機(jī)開發(fā)板的擴(kuò)展接口進(jìn)行擴(kuò)展實驗。大家可以看到藍(lán)綠色的單片機(jī)座周圍有32個插針,這32個插針就是把單片機(jī)的32個 IO 引腳全部都引出來了。在原理圖上體現(xiàn)出來的就是 J4、J5、J6、J7 這4個器件,如圖18-2所示。

http://wiki.jikexueyuan.com/project/mcu-tutorial-three/images/56.png" alt="" />

圖18-2 單片機(jī)擴(kuò)展接口

這32個 IO 口中并不是所有的都可以用來對外擴(kuò)展,其中既作為數(shù)據(jù)輸出,又可以作為數(shù)據(jù)輸入的引腳是不可以用的,比如 P3.2、P3.4、P3.6 引腳,這三個引腳是不可用的。比如 P3.2 這個引腳,如果我們用來擴(kuò)展,發(fā)送的信號如果和 DS18B20 的時序吻合,會導(dǎo)致 DS18B20 拉低引腳,影響通信。除這3個 IO 口以外的其它29個,都可以使用杜邦線接上插針,擴(kuò)展出來使用。當(dāng)然了,如果把當(dāng)前的 IO 口應(yīng)用于擴(kuò)展功能了,板子上的相應(yīng)功能就實現(xiàn)不了了,也就是說需要擴(kuò)展功能和板載功能之間二選一。

在進(jìn)行 RS485 實驗中,我們通信用的引腳必須是 P3.0 和 P3.1,此外還有一個方向控制引腳,我們使用杜邦線將其連接到 P1.7 上去。RS485 的另外一端,大家可以使用一個 USB 轉(zhuǎn) RS485 模塊,用雙絞線把開發(fā)板和模塊上的 A 和 B 分別對應(yīng)連起來,USB 那頭插入電腦,然后就可以進(jìn)行通信了。

學(xué)習(xí)了第13章實用的串口通信方法和程序后,做這種串口通信的方法就很簡單了,基本是一致的。我們使用實用串口通信例程的思路,做了一個簡單的程序,通過串口調(diào)試助手下發(fā)任意個字符,單片機(jī)接收到后在末尾添加“回車+換行”符后再送回,在調(diào)試助手上重新顯示出來,先把程序貼出來。

程序中需要注意的一點是:因為平常都是將 MAX485 設(shè)置為接收狀態(tài),只有在發(fā)送數(shù)據(jù)的時候才將 MAX485 改為發(fā)送狀態(tài),所以在 UartWrite()函數(shù)開頭將 MAX485 方向引腳拉高,函數(shù)退出前再拉低。但是這里有一個細(xì)節(jié),就是單片機(jī)的發(fā)送和接收中斷產(chǎn)生的時刻都是在停止位的一半上,也就是說每當(dāng)停止位傳送了一半的時候,RI 或 TI 就已經(jīng)置位并且馬上進(jìn)入中斷(如果中斷使能的話)函數(shù)了,接收的時候自然不會存在問題,但發(fā)送的時候就不一樣了:當(dāng)緊接著向 SBUF 寫入一個字節(jié)數(shù)據(jù)時,UART 硬件會在完成上一個停止位的發(fā)送后,再開始新字節(jié)的發(fā)送,但如果此時不是繼續(xù)發(fā)送下一個字節(jié),而是已經(jīng)發(fā)送完畢了,要停止發(fā)送并將 MAX485 方向引腳拉低以使 MAX485 重新處于接收狀態(tài)時就有問題了,因為這時候最后的這個停止位實際只發(fā)送了一半,還沒有完全完成,所以就有了 UartWrite()函數(shù)內(nèi) DelayX10us(5)這個操作,這是人為的增加了 50 us 的延時,這 50 us 的時間正好讓剩下的一半停止位完成,那么這個時間自然就是由通信波特率決定的了,為波特率周期的一半。 /****RS485.c 文件程序源代碼*****/

#include <reg52.h>
#include <intrins.h>

sbit RS485_DIR = P1^7; //RS485 方向選擇引腳
bit flagFrame = 0; //幀接收完成標(biāo)志,即接收到一幀新數(shù)據(jù)
bit flagTxd = 0; //單字節(jié)發(fā)送完成標(biāo)志,用來替代 TXD 中斷標(biāo)志位
unsigned char cntRxd = 0; //接收字節(jié)計數(shù)器
unsigned char pdata bufRxd[64]; //接收字節(jié)緩沖區(qū)

extern void UartAction(unsigned char *buf, unsigned char len);

/* 串口配置函數(shù),baud-通信波特率 */
void ConfigUART(unsigned int baud){
    RS485_DIR = 0; //RS485 設(shè)置為接收方向
    SCON = 0x50; //配置串口為模式 1
    TMOD &= 0x0F; //清零 T1 的控制位
    TMOD |= 0x20; //配置 T1 為模式 2
    TH1 = 256 - (11059200/12/32)/baud; //計算 T1 重載值
    TL1 = TH1; //初值等于重載值
    ET1 = 0; //禁止 T1 中斷
    ES = 1; //使能串口中斷
    TR1 = 1; //啟動 T1
}
/* 軟件延時函數(shù),延時時間(t*10)us */
void DelayX10us(unsigned char t){
    do {
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
    } while (--t);
}
/* 串口數(shù)據(jù)寫入,即串口發(fā)送函數(shù),buf-待發(fā)送數(shù)據(jù)的指針,len-指定的發(fā)送長度 */
void UartWrite(unsigned char *buf, unsigned char len){
    RS485_DIR = 1; //RS485 設(shè)置為發(fā)送

    while (len--){ //循環(huán)發(fā)送所有字節(jié)
        flagTxd = 0; //清零發(fā)送標(biāo)志
        SBUF = *buf++; //發(fā)送一個字節(jié)數(shù)據(jù)
        while (!flagTxd); //等待該字節(jié)發(fā)送完成
    }
    DelayX10us(5); //等待最后的停止位完成,延時時間由波特率決定
    RS485_DIR = 0; //RS485 設(shè)置為接收
}
/* 串口數(shù)據(jù)讀取函數(shù),buf-接收指針,len-指定的讀取長度,返回值-實際讀到的長度 */
unsigned char UartRead(unsigned char *buf, unsigned char len){
    unsigned char i;
    //指定讀取長度大于實際接收到的數(shù)據(jù)長度時,
    //讀取長度設(shè)置為實際接收到的數(shù)據(jù)長度
    if (len > cntRxd){
        len = cntRxd;
    }
    for (i=0; i<len; i++){ //拷貝接收到的數(shù)據(jù)到接收指針上
        *buf++ = bufRxd[i];
    }
    cntRxd = 0; //接收計數(shù)器清零
    return len; //返回實際讀取長度
}
/* 串口接收監(jiān)控,由空閑時間判定幀結(jié)束,需在定時中斷中調(diào)用,ms-定時間隔 */
void UartRxMonitor(unsigned char ms){
    static unsigned char cntbkp = 0;
    static unsigned char idletmr = 0;

    if (cntRxd > 0){ //接收計數(shù)器大于零時,監(jiān)控總線空閑時間
        if (cntbkp != cntRxd){ //接收計數(shù)器改變,即剛接收到數(shù)據(jù)時,清零空閑計時
            cntbkp = cntRxd;
            idletmr = 0;
        }else{ //接收計數(shù)器未改變,即總線空閑時,累積空閑時間
            if (idletmr < 30){ //空閑計時小于 30ms 時,持續(xù)累加
                idletmr += ms;
                if (idletmr >= 30){ //空閑時間達(dá)到 30ms 時,即判定為一幀接收完畢
                    flagFrame = 1; //設(shè)置幀接收完成標(biāo)志
                }
            }
        }
    }else{
        cntbkp = 0;
    }
}
/* 串口驅(qū)動函數(shù),監(jiān)測數(shù)據(jù)幀的接收,調(diào)度功能函數(shù),需在主循環(huán)中調(diào)用 */
void UartDriver(){
    unsigned char len;
    unsigned char pdata buf[40];

    if (flagFrame){ //有命令到達(dá)時,讀取處理該命令
        flagFrame = 0;
        len = UartRead(buf, sizeof(buf)-2); //將接收到的命令讀取到緩沖區(qū)中
        UartAction(buf, len); //傳遞數(shù)據(jù)幀,調(diào)用動作執(zhí)行函數(shù)
    }
}
/* 串口中斷服務(wù)函數(shù) */
void InterruptUART() interrupt 4{
    if (RI){ //接收到新字節(jié)
        RI = 0; //清零接收中斷標(biāo)志位
        //接收緩沖區(qū)尚未用完時,保存接收字節(jié),并遞增計數(shù)器
        if (cntRxd < sizeof(bufRxd)){
            bufRxd[cntRxd++] = SBUF;
        }
    }
    if (TI){ //字節(jié)發(fā)送完畢
        TI = 0; //清零發(fā)送中斷標(biāo)志位
        flagTxd = 1; //設(shè)置字節(jié)發(fā)送完成標(biāo)志
    }
}

/*****main.c 文件程序源代碼**/

#include <reg52.h>

unsigned char T0RH = 0; //T0 重載值的高字節(jié)
unsigned char T0RL = 0; //T0 重載值的低字節(jié)

void ConfigTimer0(unsigned int ms);
extern void UartDriver();
extern void ConfigUART(unsigned int baud);
extern void UartRxMonitor(unsigned char ms);
extern void UartWrite(unsigned char *buf, unsigned char len);

void main(){
    EA = 1; //開總中斷
    ConfigTimer0(1); //配置 T0 定時 1ms
    ConfigUART(9600); //配置波特率為 9600

    while (1){
        UartDriver(); //調(diào)用串口驅(qū)動
    }
}
/* 串口動作函數(shù),根據(jù)接收到的命令幀執(zhí)行響應(yīng)的動作
buf-接收到的命令幀指針,len-命令幀長度 */
void UartAction(unsigned char *buf, unsigned char len){
    //在接收到的數(shù)據(jù)幀后添加換車換行符后發(fā)回
    buf[len++] = '\r';
    buf[len++] = '\n';
    UartWrite(buf, len);
}
/* 配置并啟動 T0,ms-T0 定時時間 */
void ConfigTimer0(unsigned int ms){
    unsigned long tmp; //臨時變量
    tmp = 11059200 / 12; //定時器計數(shù)頻率
    tmp = (tmp * ms) / 1000; //計算所需的計數(shù)值
    tmp = 65536 - tmp; //計算定時器重載值
    tmp = tmp + 33; //補(bǔ)償中斷響應(yīng)延時造成的誤差
    T0RH = (unsigned char)(tmp>>8); //定時器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0; //清零 T0 的控制位
    TMOD |= 0x01; //配置 T0 為模式 1
    TH0 = T0RH; //加載 T0 重載值
    TL0 = T0RL;
    ET0 = 1; //使能 T0 中斷
    TR0 = 1; //啟動 T0
}
/* T0 中斷服務(wù)函數(shù),執(zhí)行串口接收監(jiān)控 */
void InterruptTimer0() interrupt 1{
    TH0 = T0RH; //重新加載重載值
    TL0 = T0RL;
    UartRxMonitor(1); //串口接收監(jiān)控
}

現(xiàn)在看這種串口程序,是不是感覺很簡單了呢?串口通信程序我們反反復(fù)復(fù)的使用,加上隨著學(xué)習(xí)的模塊越來越多,實踐的越來越多,原先感覺很復(fù)雜的東西,現(xiàn)在就會感到簡單了。從設(shè)備管理器里可以查看所有的 COM 口號,我們下載程序用的是 COM4,而 USB 轉(zhuǎn) RS485 虛擬的是 COM5,通信的時候我們用的是 COM5 口,如圖18-3所示。

http://wiki.jikexueyuan.com/project/mcu-tutorial-three/images/57.png" alt="" />

圖18-3 RS485 通信試驗設(shè)置和結(jié)果