在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

18.3 單片機(jī) Modbus 多機(jī)通信程序設(shè)計(jì)

給從機(jī)下發(fā)不同的指令,從機(jī)去執(zhí)行不同的操作,這個(gè)就是判斷一下功能碼即可,和我們前邊學(xué)的實(shí)用串口例程是類(lèi)似的。多機(jī)通信,無(wú)非就是添加了一個(gè)設(shè)備地址判斷而已,難度也不大。我們找了一個(gè) Modbus 調(diào)試精靈,通過(guò)設(shè)置設(shè)備地址,讀寫(xiě)寄存器的地址以及數(shù)值數(shù)量等參數(shù),可以直接替代串口調(diào)試助手,比較方便的下發(fā)多個(gè)字節(jié)的數(shù)據(jù),如圖18-7所示。我們先來(lái)就圖中的設(shè)置和數(shù)據(jù)來(lái)對(duì) Modbus 做進(jìn)一步的分析,圖中的數(shù)據(jù)來(lái)自于調(diào)試精靈與我們接下來(lái)要講的例程之間的交互。

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

圖18-7 Modbus 調(diào)試精靈

如圖,我們的 USB 轉(zhuǎn) RS485 模塊虛擬出的是 COM5,波特率9600,無(wú)校驗(yàn)位,數(shù)據(jù)位是8位,1位停止位,設(shè)備地址假設(shè)為1。

寫(xiě)寄存器的時(shí)候,如果我們要把01寫(xiě)到一個(gè)地址是0000的寄存器地址里,點(diǎn)一下“寫(xiě)入”,就會(huì)出現(xiàn)發(fā)送指令:01 06 00 00 00 01 48 0A。我們來(lái)分析一下這幀數(shù)據(jù),其中01是設(shè)備地址,06是功能碼,代表寫(xiě)寄存器這個(gè)功能,后邊跟00 00表示的是要寫(xiě)入的寄存器的地址,00 01就是要寫(xiě)入的數(shù)據(jù),48 0A就是 CRC 校驗(yàn)碼,這是軟件自動(dòng)算出來(lái)的。而根據(jù) Modbus 協(xié)議,當(dāng)寫(xiě)寄存器的時(shí)候,從機(jī)成功完成該指令的操作后,會(huì)把主機(jī)發(fā)送的指令直接返回,我們的調(diào)試精靈會(huì)接收到這樣一幀數(shù)據(jù):01 06 00 00 00 01 48 0A。

假如我們現(xiàn)在要從寄存器地址0002開(kāi)始讀取寄存器,并且讀取的數(shù)量是2個(gè)。點(diǎn)一下“讀出”,就會(huì)出現(xiàn)發(fā)送指令:01 03 00 02 00 02 65 CB。其中01是設(shè)備地址,03是功能碼,代表讀寄存器這個(gè)功能,00 02就是讀寄存器的起始地址,后一個(gè)00 02就是要讀取2個(gè)寄存器的數(shù)值,65 CB就是 CRC 校驗(yàn)。而接收到的數(shù)據(jù)是:01 03 04 00 00 00 00 FA 33。其中01是設(shè)備地址,03是功能碼,04代表的是后邊讀到的數(shù)據(jù)字節(jié)數(shù)是4個(gè),00 00 00 00分別是地址00 02和00 03的寄存器內(nèi)部的數(shù)據(jù),而 FA 33 就是 CRC 校驗(yàn)了。

似乎越來(lái)越明朗了,所謂的 Modbus 通信協(xié)議,無(wú)非就是主機(jī)下發(fā)了不同的指令,從機(jī)根據(jù)指令的判斷來(lái)執(zhí)行不同的操作而已。由于我們的開(kāi)發(fā)板沒(méi)有 Modbus 功能碼那么多相應(yīng)的功能,我們?cè)诔绦蛑卸x了一個(gè)數(shù)組 regGroup[5],相當(dāng)于5個(gè)寄存器,此外又定義了第6個(gè)寄存器,控制蜂鳴器,通過(guò)下發(fā)不同的指令我們改變寄存器組的數(shù)據(jù)或者改變蜂鳴器的開(kāi)關(guān)狀態(tài)。在 Modbus 協(xié)議里寄存器的地址和數(shù)值都是16位的,即2個(gè)字節(jié),我們默認(rèn)高字節(jié)是 0x00,低字節(jié)就是數(shù)組 regGroup 對(duì)應(yīng)的值。其中地址 0x0000 到 0x0004 對(duì)應(yīng)的就是 regGroup數(shù)組中的元素,我們寫(xiě)入的同時(shí)把數(shù)字又顯示到 1602 液晶上,而 0x0005 這個(gè)地址,寫(xiě)入 0x00,蜂鳴器就不響,寫(xiě)入任何其它數(shù)值,蜂鳴器就報(bào)警。我們單片機(jī)的主要工作也就是解析串口接收的數(shù)據(jù)執(zhí)行不同操作。 /*Lcd1602.c 文件程序源代碼***/ (此處省略,可參考之前章節(jié)的代碼) /****RS485.c 文件程序源代碼*****/ (此處省略,可參考之前章節(jié)的代碼) /****CRC16.c 文件程序源代碼****/

/* CRC16 計(jì)算函數(shù),ptr-數(shù)據(jù)指針,len-數(shù)據(jù)長(zhǎng)度,返回值-計(jì)算出的 CRC16 數(shù)值 */
unsigned int GetCRC16(unsigned char *ptr, unsigned char len){
    unsigned int index;
    unsigned char crch = 0xFF; //高 CRC 字節(jié)
    unsigned char crcl = 0xFF; //低 CRC 字節(jié)
    unsigned char code TabH[] = { //CRC 高位字節(jié)值表
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
    } ;
    unsigned char code TabL[] = { //CRC 低位字節(jié)值表
        0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
        0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
        0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
        0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
        0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
        0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
        0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
        0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
        0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
        0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
        0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
        0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
        0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
        0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
        0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
        0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
        0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
        0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
        0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
        0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
        0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
        0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
        0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
        0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
        0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
        0x43, 0x83, 0x41, 0x81, 0x80, 0x40
    } ;

    while (len--){ //計(jì)算指定長(zhǎng)度的 CRC
        index = crch ^ *ptr++;
        crch = crcl ^ TabH[index];
        crcl = TabL[index];
    }
    return ((crch<<8) | crcl);
}

關(guān)于 CRC 校驗(yàn)的算法,如果不是專(zhuān)門(mén)學(xué)習(xí)校驗(yàn)算法本身,大家可以不去研究這個(gè)程序的細(xì)節(jié),直接使用現(xiàn)成的函數(shù)即可。 /*****main.c 文件程序源代碼**/

#include <reg52.h>
sbit BUZZ = P1^6;
bit flagBuzzOn = 0; //蜂鳴器啟動(dòng)標(biāo)志
unsigned char T0RH = 0; //T0 重載值的高字節(jié)
unsigned char T0RL = 0; //T0 重載值的低字節(jié)
unsigned char regGroup[5]; //Modbus 寄存器組,地址為 0x00~0x04

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);
extern unsigned int GetCRC16(unsigned char *ptr, unsigned char len);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);

void main(){
    EA = 1; //開(kāi)總中斷
    ConfigTimer0(1); //配置 T0 定時(shí) 1ms
    ConfigUART(9600); //配置波特率為 9600
    InitLcd1602(); //初始化液晶

    while (1){
        UartDriver(); //調(diào)用串口驅(qū)動(dòng)
    }
}
/* 串口動(dòng)作函數(shù),根據(jù)接收到的命令幀執(zhí)行響應(yīng)的動(dòng)作
buf-接收到的命令幀指針,len-命令幀長(zhǎng)度 */
void UartAction(unsigned char *buf, unsigned char len){
    unsigned char i;
    unsigned char cnt;
    unsigned char str[4];
    unsigned int crc;
    unsigned char crch, crcl;
    /* 本例中的本機(jī)地址設(shè)定為 0x01,
    如數(shù)據(jù)幀中的地址字節(jié)與本機(jī)地址不符,
    則直接退出,即丟棄本幀數(shù)據(jù)不做任何處理 */
    if (buf[0] != 0x01){
        return;
    }
    //地址相符時(shí),再對(duì)本幀數(shù)據(jù)進(jìn)行校驗(yàn)
    crc = GetCRC16(buf, len-2); //計(jì)算 CRC 校驗(yàn)值
    crch = crc >> 8;
    crcl = crc & 0xFF;
    if ((buf[len-2]!=crch) || (buf[len-1]!=crcl)){
        return; //如 CRC 校驗(yàn)不符時(shí)直接退出
    }
    //地址和校驗(yàn)字均相符后,解析功能碼,執(zhí)行相關(guān)操作
    switch (buf[1]){
        case 0x03: //讀取一個(gè)或連續(xù)的寄存器
            if ((buf[2]==0x00) && (buf[3]<=0x05)){ //只支持 0x0000~0x0005
                if (buf[3] <= 0x04){
                    i = buf[3]; //提取寄存器地址
                    cnt = buf[5]; //提取待讀取的寄存器數(shù)量
                    buf[2] = cnt*2; //讀取數(shù)據(jù)的字節(jié)數(shù),為寄存器數(shù)*2
                    len = 3; //幀前部已有地址、功能碼、字節(jié)數(shù)共 3 個(gè)字節(jié)
                    while (cnt--){
                        buf[len++] = 0x00; //寄存器高字節(jié)補(bǔ) 0
                        buf[len++] = regGroup[i++]; //寄存器低字節(jié)
                    }
                }else{ //地址 0x05 為蜂鳴器狀態(tài)
                    buf[2] = 2; //讀取數(shù)據(jù)的字節(jié)數(shù)
                    buf[3] = 0x00;
                    buf[4] = flagBuzzOn;
                    len = 5;
                }
                break;
            }else{ //寄存器地址不被支持時(shí),返回錯(cuò)誤碼
                buf[1] = 0x83; //功能碼最高位置 1
                buf[2] = 0x02; //設(shè)置異常碼為 02-無(wú)效地址
                len = 3;
                break;
            }

        case 0x06: //寫(xiě)入單個(gè)寄存器
            if ((buf[2]==0x00) && (buf[3]<=0x05)){ //只支持 0x0000~0x0005
               if (buf[3] <= 0x04){
                    i = buf[3]; //提取寄存器地址
                    regGroup[i] = buf[5]; //保存寄存器數(shù)據(jù)
                    cnt = regGroup[i] >> 4; //顯示到液晶上
                    if (cnt >= 0xA){
                        str[0] = cnt - 0xA + 'A';
                    }else{
                        str[0] = cnt + '0';
                    }
                    cnt = regGroup[i] & 0x0F;
                    if (cnt >= 0xA){
                        str[1] = cnt - 0xA + 'A';
                    }else{
                        str[1] = cnt + '0';
                    }
                    str[2] = '\0';
                    LcdShowStr(i*3, 0, str);
                }else{ //地址 0x05 為蜂鳴器狀態(tài)
                    flagBuzzOn = (bit)buf[5]; //寄存器值轉(zhuǎn)為蜂鳴器的開(kāi)關(guān)
                }
                len -= 2; //長(zhǎng)度-2 以重新計(jì)算 CRC 并返回原幀
                break;
            }else{ //寄存器地址不被支持時(shí),返回錯(cuò)誤碼
                buf[1] = 0x86; //功能碼最高位置 1
                buf[2] = 0x02; //設(shè)置異常碼為 02-無(wú)效地址
                len = 3;
                break;
            }

        default: //其它不支持的功能碼
            buf[1] |= 0x80; //功能碼最高位置 1
            buf[2] = 0x01; //設(shè)置異常碼為 01-無(wú)效功能
            len = 3;
            break;
    }
    crc = GetCRC16(buf, len); //計(jì)算返回幀的 CRC 校驗(yàn)值
    buf[len++] = crc >> 8; //CRC 高字節(jié)
    buf[len++] = crc & 0xFF; //CRC 低字節(jié)
    UartWrite(buf, len); //發(fā)送返回幀
}
/* 配置并啟動(dòng) T0,ms-T0 定時(shí)時(shí)間 */
void ConfigTimer0(unsigned int ms){
    unsigned long tmp; //臨時(shí)變量
    tmp = 11059200 / 12; //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000; //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp; //計(jì)算定時(shí)器重載值
    tmp = tmp + 33; //補(bǔ)償中斷響應(yīng)延時(shí)造成的誤差
    T0RH = (unsigned char)(tmp>>8); //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0; //清零 T0 的控制位
    TMOD |= 0x01; //配置 T0 為模式 1
    TH0 = T0RH; //加載 T0 重載值
    TL0 = T0RL;
    ET0 = 1; //使能 T0 中斷
    TR0 = 1; //啟動(dòng) T0
}
/* T0 中斷服務(wù)函數(shù),執(zhí)行串口接收監(jiān)控和蜂鳴器驅(qū)動(dòng) */
void InterruptTimer0() interrupt 1{
    TH0 = T0RH; //重新加載重載值
    TL0 = T0RL;
    if (flagBuzzOn){ //執(zhí)行蜂鳴器鳴叫或關(guān)閉
        BUZZ = ~BUZZ;
    }else{
        BUZZ = 1;
    }
    UartRxMonitor(1); //串口接收監(jiān)控
}

大家可以看到負(fù)責(zé)解析協(xié)議的 UartAction 函數(shù)很長(zhǎng),因?yàn)閰f(xié)議解析本來(lái)就是一件很繁瑣的事情。我們的例程僅解析執(zhí)行了兩個(gè)功能命令,就已經(jīng)有近百行程序了,如果你需要解析更多的功能命令的話,那么建議把每個(gè)功能都做一個(gè)函數(shù),然后在相應(yīng)的 case 分支里調(diào)用即可,這樣就不會(huì)使單個(gè)函數(shù)過(guò)于龐大而難以維護(hù)。