找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4228|回复: 1
打印 上一主题 下一主题
收起左侧

基于RS485的Modbus通信示例

[复制链接]
跳转到指定楼层
楼主
ID:164629 发表于 2017-2-18 16:33 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式


#include <reg52.h>

sbit BUZZ = P1^6;

bit flagBuzzOn = 0;   //蜂鸣器启动标志
unsigned char T0RH = 0;  //T0重载值的高字节
unsigned char T0RL = 0;  //T0重载值的低字节
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;            //开总中断
    ConfigTimer0(1);   //配置T0定时1ms
    ConfigUART(9600);  //配置波特率为9600
    InitLcd1602();     //初始化液晶
   
    while (1)
    {
        UartDriver();  //调用串口驱动
    }
}
/* 串口动作函数,根据接收到的命令帧执行响应的动作
   buf-接收到的命令帧指针,len-命令帧长度 */
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;
   
    if (buf[0] != 0x01) //本例中的本机地址设定为0x01,
    {                   //如数据帧中的地址字节与本机地址不符,
        return;         //则直接退出,即丢弃本帧数据不做任何处理
    }
    //地址相符时,再对本帧数据进行校验
    crc = GetCRC16(buf, len-2);  //计算CRC校验值
    crch = crc >> 8;
    crcl = crc & 0xFF;
    if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))
    {
        return;   //如CRC校验不符时直接退出
    }
    //地址和校验字均相符后,解析功能码,执行相关操作
    switch (buf[1])
    {
        case 0x03:  //读取一个或连续的寄存器
            if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持0x0000~0x0005
            {
                if (buf[3] <= 0x04)
                {
                    i = buf[3];      //提取寄存器地址
                    cnt = buf[5];    //提取待读取的寄存器数量
                    buf[2] = cnt*2;  //读取数据的字节数,为寄存器数*2
                    len = 3;         //帧前部已有地址、功能码、字节数共3个字节
                    while (cnt--)
                    {
                        buf[len++] = 0x00;          //寄存器高字节补0
                        buf[len++] = regGroup[i++]; //寄存器低字节
                    }
                }
                else  //地址0x05为蜂鸣器状态
                {
                    buf[2] = 2;  //读取数据的字节数
                    buf[3] = 0x00;
                    buf[4] = flagBuzzOn;
                    len = 5;
                }
                break;
            }
            else  //寄存器地址不被支持时,返回错误码
            {
                buf[1] = 0x83;  //功能码最高位置1
                buf[2] = 0x02;  //设置异常码为02-无效地址
                len = 3;
                break;
            }
            
        case 0x06:  //写入单个寄存器
            if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持0x0000~0x0005
            {
                if (buf[3] <= 0x04)
                {
                    i = buf[3];             //提取寄存器地址
                    regGroup[i] = buf[5];   //保存寄存器数据
                    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为蜂鸣器状态
                {
                    flagBuzzOn = (bit)buf[5]; //寄存器值转为蜂鸣器的开关
                }
                len -= 2; //长度-2以重新计算CRC并返回原帧
                break;
            }
            else  //寄存器地址不被支持时,返回错误码
            {
                buf[1] = 0x86;  //功能码最高位置1
                buf[2] = 0x02;  //设置异常码为02-无效地址
                len = 3;
                break;
            }
            
        default:  //其它不支持的功能码
            buf[1] |= 0x80;  //功能码最高位置1
            buf[2] = 0x01;   //设置异常码为01-无效功能
            len = 3;
            break;
    }
    crc = GetCRC16(buf, len); //计算返回帧的CRC校验值
    buf[len++] = crc >> 8;    //CRC高字节
    buf[len++] = crc & 0xFF;  //CRC低字节
    UartWrite(buf, len);      //发送返回帧
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  //临时变量
   
    tmp = 11059200 / 12;      //定时器计数频率
    tmp = (tmp * ms) / 1000;  //计算所需的计数值
    tmp = 65536 - tmp;        //计算定时器重载值
    tmp = tmp + 33;           //补偿中断响应延时造成的误差
    T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0为模式1
    TH0 = T0RH;     //加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
}
/* T0中断服务函数,执行串口接收监控和蜂鸣器驱动 */
void InterruptTimer0() interrupt 1
{
    TH0 = T0RH;  //重新加载重载值
    TL0 = T0RL;
    if (flagBuzzOn)  //执行蜂鸣器鸣叫或关闭
        BUZZ = ~BUZZ;
    else
        BUZZ = 1;
    UartRxMonitor(1);  //串口接收监控
}

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏3 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:366088 发表于 2018-7-6 16:24 | 只看该作者
程序有残缺吧,有些函数被写出来了但是没有调用
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表