打印
[应用相关]

[原创]STM32F的232口简易MODBUS-RTU通讯程序

[复制链接]
38306|178
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
sunke9|  楼主 | 2008-6-27 21:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
/*****************************************************
          modbus-rtu 通讯规约
通讯方式:rs-485 半双功
校验方式:crc16
停止位:2位
编写:孙可
编程思路:
    1.串口中断允许自动接收总线上的信息,当接收的
    字节后超过3.5个字节时间没有新的字节认为本次
    接收完成,接收完成标志置1;如果接收完成标志已
    经置1又有数据进来则丢弃新来的数据。
    2.串口接收数据的处理, 当接收完成标志置1进入
    接收数据处理, (1)首先判断接收的第一位数据与
    本机地址是否相同,如果不相同清空接收缓存不发
    送任何信息; (2)接收的第一位数据与本机地址相
    同,则对接收缓存中的数据进行crc16校验,如果接
    收的校验位与本校验结果不相同清空接收缓存不发
    送任何信息;
    (3)如果crc16校验正确则根据数据串中的命令码进
    行相应的处理。
******************************************************/
#include "modbus.h"
u8 Com0_id = 0x05;//本机串口0的通讯地址
u8 Uart0_rev_buff[100];//com0串口接收缓冲区
u8 Uart0_send_buff[100];//com0串口发送缓冲区
vu8 Uart0_rev_count;
vs8 Uart0_send_counter = 0;
vu8 Uart0_rev_comflag;
vu8 Crc_counter = 0;//com0校验计数器
vu8 *Uart0_send_pointer = Uart0_send_buff;//com0串口发送指针
vu16 Mkgz_bz = 0;//模块故障标志1:输入异常,2:过压,3:欠压,4:过温
vu16 Out_current = 50;//输出电流
vu16 Out_voltage = 240;//输出电压
vu16 Mkzt_bz = 0;//模块状态标志
vu16 OutX_current = 1000;//输出限流
vu16 Jc_voltage = 2530;//均充电压
vu16 Fc_voltage = 2400;//浮充电压
vu16 user_day = 1825;//使用天数

void Delay(vu32 nCount);
unsigned short getCRC16(volatile unsigned char *ptr,unsigned char len) ; 
void mov_data(u8 a[100],u8 b[100],u8 c);
void Modbus_Function_3(void); 
void Modbus_Function_6(void);       
/***************************************
函数名称:crc16校验
函数功能:crc16校验
函数输入:字节指针*ptr,数据长度len
函数返回:双字节crc
函数编写:孙可
编写日期:2008年6月9日
函数版本:v0.2
****************************************/
unsigned short getCRC16(volatile unsigned char *ptr,unsigned char len) 

    unsigned char i; 
    unsigned short crc = 0xFFFF; 
    if(len==0) 
    {
        len = 1;
    } 
    while(len--)  
    {   
        crc ^= *ptr; 
        for(i=0; i<8; i++)  
     { 
            if(crc&1) 
         { 
                crc >>= 1;  
                crc ^= 0xA001; 
         }  
         else 
      {
                crc >>= 1;
         } 
        }         
        ptr++; 
    } 
    return(crc); 

/***************************************
    块数据复制数据函数
功能:把数组a的c个数据复制到数组b中
输入:指针a,指针b,数据个数c
返回:无
编写:孙可
编写日期:2008年3月28日
版本:v0.1
****************************************/
void mov_data(u8 a[100],u8 b[100],u8 c)
{
    u8 i;
    for(i=c; i>0; i--)
    {
            a = b;
    }
}
/////////////////////////////////////////////////////////////////////// 
void Modbus_Function_3(void) 

    u16 tempdress = 0;
    u8 i = 3; 
    u16 crcresult;         
    tempdress = (Uart0_rev_buff[2] << 8) + Uart0_rev_buff[3]; 
    if((tempdress >= 0x0120) & (tempdress + Uart0_rev_buff[5] < 0x0132))
    {
        Uart0_send_buff[0] = Com0_id;
        Uart0_send_buff[1] = 0x03; 
        Uart0_send_buff[2] = 2 * Uart0_rev_buff[5]; 
        Uart0_send_counter = 2 * Uart0_rev_buff[5] + 3;
        switch(tempdress)
     {
            case 0x0120:
      {
                Uart0_send_buff = Mkgz_bz & 0xff;
          i++;
                Uart0_send_buff = (Mkgz_bz >> 8) & 0xff;
             i++; 
            }//后面不放break的目的是继续往下执行
            case 0x0122:
      {
                Uart0_send_buff = Out_voltage & 0xff;
          i++;
                Uart0_send_buff = (Out_voltage >> 8) & 0xff;
             i++; 
      }
            case 0x0124:
      {
                Uart0_send_buff = Out_current & 0xff;
          i++;
                Uart0_send_buff = (Out_current >> 8) & 0xff;
             i++; 
      }
            case 0x0126:
      {
                Uart0_send_buff = Mkzt_bz & 0xff;
          i++;
                Uart0_send_buff = (Mkzt_bz >> 8) & 0xff;
             i++; 
      }
            case 0x0128://这个地址是备用的里面的数据没有意义
      {
                Uart0_send_buff = 0x00;
          i++;
                Uart0_send_buff = 0x00;
             i++; 
      }
            case 0x012A:
      {
                Uart0_send_buff = OutX_current & 0xff;
          i++;
                Uart0_send_buff = (OutX_current >> 8) & 0xff;
          i++;
      }
            case 0x012C:
      {
                Uart0_send_buff = Jc_voltage & 0xff;
          i++;
                Uart0_send_buff = (Jc_voltage >> 8) & 0xff;
          i++;
      }
            case 0x012E:
      {
                Uart0_send_buff = Fc_voltage & 0xff;
          i++;
                Uart0_send_buff = (Fc_voltage >> 8) & 0xff;
          i++;
      }
            case 0x0130:
      {
                Uart0_send_buff = 0x00;
          i++;
                Uart0_send_buff = 0x00;
          i++;
      }
     } 
        //UCSRB |= (1<<TXCIE)|(1<<TXEN);//发送、发送中断允许
        crcresult = getCRC16(Uart0_send_buff,Uart0_send_counter); 
        Uart0_send_buff[Uart0_send_counter] = crcresult & 0xff; 
        Uart0_send_buff[Uart0_send_counter+1] = (crcresult >> 8) & 0xff; 
        Uart0_send_counter = Uart0_send_counter+2; 
        Uart0_send_pointer = Uart0_send_buff;
                USART_SendData(USART1, *Uart0_send_pointer++);
                USART_ITConfig(USART1, USART_IT_TXE, ENABLE);                
    }

/////////////////////////////////////////////////////////////
void Modbus_Function_6(void) 

    u16 tempdress = 0;
    u8 tx_flat = 0;
    u16 crcresult;
    tempdress = (Uart0_rev_buff[2]<<8) + Uart0_rev_buff[3];
    switch(tempdress)
    {
        case 0x0126:
     {
            Mkzt_bz = (Uart0_rev_buff[4]<<8) + Uart0_rev_buff[5];
            if(user_day > 0)
      {
                tx_flat = 1;
      }
        }break;
        case 0x012A:
     {
            OutX_current = (Uart0_rev_buff[4]<<8) + Uart0_rev_buff[5];
            if(user_day > 0)
      {
                tx_flat = 1;
      }
        }break;
        case 0x012C:
     {
            Jc_voltage = (Uart0_rev_buff[4]<<8) + Uart0_rev_buff[5];
            if(user_day > 0)
      {
                tx_flat = 1;
      }
        }break;
        case 0x012E:
     {
            Fc_voltage = (Uart0_rev_buff[4]<<8) + Uart0_rev_buff[5];
            if(user_day > 0)
      {
                tx_flat = 1;
      }
        }break;
        case 0x01EE:
     {
            user_day = (Uart0_rev_buff[4]<<8) + Uart0_rev_buff[5];
            tx_flat = 1;
            //eeprom_write_word (&user_day_eep,user_day);
        }break;
        default: //命令码无效不应答
                { 
                    tx_flat = 0;
                } 
    }
    if(tx_flat == 1)
    {
        Uart0_send_buff[0] = Com0_id;
        Uart0_send_buff[1] = 0x06; 
        Uart0_send_buff[2] = Uart0_rev_buff[2]; 
        Uart0_send_buff[3] = Uart0_rev_buff[3]; 
        Uart0_send_buff[4] = Uart0_rev_buff[4]; 
        Uart0_send_buff[5] = Uart0_rev_buff[5]; 
        Uart0_send_counter = 6;
        //UCSRB |= (1<<TXCIE)|(1<<TXEN);//发送、发送中断允许
        crcresult = getCRC16(Uart0_send_buff,Uart0_send_counter); 
        Uart0_send_buff[Uart0_send_counter] = crcresult & 0xff; 
        Uart0_send_buff[Uart0_send_counter+1] = (crcresult >> 8) & 0xff; 
        Uart0_send_counter = Uart0_send_counter+2; 
        Uart0_send_pointer = Uart0_send_buff;
            USART_SendData(USART1, *Uart0_send_pointer++);
                USART_ITConfig(USART1, USART_IT_TXE, ENABLE);                
    }

///////////////////////////////////////////////////////////// 
void Com0_Communication(void) 

    s8 i =0;        
    if(Uart0_rev_comflag == 1)//接收完成标志=1处理,否则退出
    {
        if(Uart0_rev_buff[0] == Com0_id)//地址错误不应答
     {
            unsigned short crcresult; 
            unsigned char temp[2]; 
            crcresult = getCRC16(Uart0_rev_buff,Crc_counter-2); 
            temp[1] = crcresult & 0xff; 
            temp[0] = (crcresult >> 8) & 0xff; 
            if((Uart0_rev_buff[Crc_counter-1] == temp[0])&&(Uart0_rev_buff[Crc_counter-2] == temp[1]))//crc校验错误不应答 
         { 
                //SETBIT(PORTC,PC6); 
                Delay(1);
                    switch(Uart0_rev_buff[1]) 
                 { 
                            case 0x03: 
                      {
                        if(user_day > 0)
            {
                            Modbus_Function_3(); 
                  }      
           }
                            break;  
                            case 0x06: 
                         { 
                        Modbus_Function_6(); 
           }
                            break;             
                    }      
      }
     }
        Uart0_rev_comflag = 0;
        for(i = 100;i > -1;i--) 
     { 
                    Uart0_rev_buff = 0;
     } 
    } 
                 

/*******************************************************************************
* Function Name  : Delay
* Description    : Inserts a delay time.
* Input          : nCount: specifies the delay time length.
* Output         : None
* Return         : None
*******************************************************************************/
void Delay(vu32 nCount)
{
  for(; nCount != 0; nCount--);
}
////////////////////////////////////////////////////////////////////////// 
完整的通讯规约和工程传不上来,有需要的朋友留下EMAIL,我发给你.
评论
王吱吱 2022-1-13 15:51 回复TA
WangZZ0403@163.com 
沙发
xwj| | 2008-6-27 21:47 | 只看该作者

呵呵,不错,给我发一份!

xwjfile@21cn.com

使用特权

评论回复
板凳
xxqarm| | 2008-6-27 22:17 | 只看该作者

给我发一份好吗?谢谢!

pulkey@163.com

使用特权

评论回复
地板
yjwpm| | 2008-6-28 08:30 | 只看该作者

呵呵,不错,给我发一份!

wangpeiyu2004@163.com
 

使用特权

评论回复
5
c51avr| | 2008-6-28 08:32 | 只看该作者

谢谢

c51avr@163.com 谢谢啊

使用特权

评论回复
6
rayzhong| | 2008-6-28 09:12 | 只看该作者

谢谢

好东西,给我一份行吗?谢谢!!
rayzhongr@126.com

使用特权

评论回复
7
sunke9|  楼主 | 2008-6-28 09:29 | 只看该作者

已经给楼上5位发了邮件,请查收。

        [通讯规约]
本模块采用ModBus RTU 通讯规约,具体如下:
通讯数据的类型及格式:
信息传输为异步方式,并以字节为单位。每个字节由8位二进制数组成。主机和从机之间传递的通讯信息是10位的字格式:
字格式(串行数据)    10位二进制
起始位    1位
数据位    8位
奇偶校验位    无奇偶校验位
停止位    1位
桢格式:
序号    1    2    3    4    5    6
定义    模块地址    功能代码    起始地址    数据长度    数据内容    校验码
字节数    1    1    2    2    n    2
★ 注:1、本文件中后缀为“H”的数据为16进制数据,如1AH;后缀为“B”的为二进制数据,如11111111B;无后缀的均为10进制数据。
       2、每字节数据的顺序是低位在前高位在后,桢数据的顺序是高字节在前低字节在后,校验码低字节在前高字节在后。
(一)、通讯信息传输过程:
1.0 时间间隔:
通讯波特率:9600。监控器和模块的接收一直开着,模块在接收完所有字节后判断是否与本机地址相同,相同则置接收完毕标志等待处理,否则重新初始化串口计数器开始接收。
1.1 地址码:
    地址码是每次通讯信息帧的第一字节(8位),从5到13。这个字节表明由用户设置地址的从机将接收由主机发送来的信息。每个从机都必须有唯一的地址码,并且只有符合地址码的从机才能响应回送信息。当从机回送信息时,回送数据均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机返回的地址码表明回送的从机地址。相应的地址码表明该信息来自于何处。
1.2 功能码:
   功能码是每次通讯信息帧传送的第二个字节。ModBus通讯规约可定义的功能码为1到127。GKDM-21直流电源模块仅用到其中的一部分功能码。作为主机请求发送,通过功能码告诉从机应执行什么动作。作为从机响应,从机返回的功能码与从主机发送来的功能码一样,并表明从机已响应主机并且已进行相关的操作。
  表1 MODBUS部分功能码
功能码    定  义    操  作
03H    读多路寄存器    读取一个或多个寄存器的数据
06H    写一路寄存器    把一个数据写入指定寄存器
1.3 数据区:
数据区包括需要由从机返送何种信息或执行什么动作。这些信息可以是数据、参考地址等。
1.4错误校验码(CRC校验):
主机或从机可用校验码进行判别接收信息是否正确。由于电子噪声或一些其它干扰,信息在传输过程中有时会发生错误,错误校验码(CRC)可以检验主机或从机在通讯数据传送过程中的信息是否有误,错误的数据可以放弃(无论是发送还是接收),这样增加了系统的安全和效率。
MODBUS通讯协议的CRC(冗余循环码)包含2个字节,即16位二进制数。CRC码由发送设备(主机)计算,放置于发送信息帧的尾部(CRC低字节在前)。接收信息的设备(从机)再重新计算接收到信息的CRC,比较计算得到的CRC是否与接收到的相符,如果两者不相符,则表明出错。
在进行CRC计算时只用8个数据位,而起始位及停止位,如有奇偶校验位也包括奇偶校验位,都不参与CRC计算。
CRC码的计算方法是: 
计算CRC码的步骤为:
(1).预置16位寄存器为十六进制FFFF(即全为1)。称此寄存器为CRC寄存器;
(2).把第一个8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器;
(3).把CRC寄存器的内容右移一位(朝低位),用0填补最高位,检查最低位(注意:这时的最低位指移位前的最低位,不是移位后的最低位);
(4).如果最低位为0:重复第3步(再次移位)
如果最低位为1:CRC寄存器与多项式A001H(1010000000000001B)进行异或;
(5).重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;
(6).重复步骤2到步骤5,进行下一个8位数据的处理;
(7).最后得到的CRC寄存器即为CRC码。
(二)、功能码简介
2.1  功能码“03”:读多路寄存器
    模块中,功能码“03”能够访问的寄存器地址为0120H—0131H,最大数据长度为18。
例如:主机要读取地址为05,起始地址为0120H的9个从机寄存器数据。
主机发送的报文格式:
主机发送    字节数    发送的信息    备  注
从机地址    1    05H    发送至地址为05的从机
功能码    1    03H    读取寄存器
起始地址    2    0120H    起始地址为0120H
数据长度    2    0009H    读取9个寄存器(共18个字节)
CRC码    2    84H 7EH    由主机计算得到CRC码(高位在后)


从机响应返回的报文格式
从机响应    字节数    返回的信息    备  注
从机地址    1    05H    来自从机05
功能码    1    03H    读取寄存器
读取字节数量    1    12H    9个寄存器共18个字节
模块故障    2    XXXXH    地址为0120H、0121H内存的内容
输出电压    2    XXXXH    地址为0122H、0123H内存的内容
输出电流    2    XXXXH    地址为0124H、0125H内存的内容
模块状态    2    XXXXH    地址为0126H、0127H内存的内容
备用    2    XXXXH    地址为0128H、0129H内存的内容
输出限流    2    XXXXH    地址为012AH、012BH内存的内容
均充电压    2    XXXXH    地址为012CH、012DH内存的内容
浮充电压    2    XXXXH    地址为012EH、012FH内存的内容
备用    2    XXXXH    地址为0130H、0131H内存的内容
CRC码    2    XXXXH    由从机计算得到CRC码
0120H为模块故障寄存器的低字节,0121H为模块故障寄存器的高字节。
2.2  功能码“6”:写一路寄存器
    模块中,功能码“06”能够访问的输入寄存器地址为0126H—012FH。
例如:主机要向地址为05的模块,起始地址为012CH的寄存器写入数据。
主机发送的报文格式:
主机发送    字节数    发送的信息    备  注
从机地址    1    05H    发送至地址为05的从机
功能码    1    06H    写入一路寄存器
起始地址    2    012CH    起始地址为012CH
均充电压    2    XXXXH    地址为012CH、012DH内存的内容
CRC码    2    XXXXH    由主机计算得到CRC码
从机响应返回的报文格式:与主机发送的报文格式及数据内容完全相同。
模块寄存器信息表
保持寄存器内容均为二进制16位无符号整数数值
地址    表示参数或状态    数值计算方法或含义    备注
0120H    模块故障    0输入异常 1过压 2欠压 3过温 4输出过流过压    只读
0122H    输出电压    输出电压值=寄存器内容值(V)    只读
0124H    输出电流    输出电流值=寄存器内容值/10(A)    只读
0126H    模块状态    0充电方式 2开关机    读|写
0128H    备用        读|写
012AH    输出限流    输出限流值=寄存器内容值/100(A)    读|写
012CH    均充电压    均充电压值=寄存器内容值/10(V)    读|写
012EH    浮充电压    浮充电压值=寄存器内容值/10(V)    读|写


模块状态内容:0126H见下表,0127H为备用。
位    位定义    状态    意义
0    充电方式    0    浮充
        1    均充
2    开关机    0    开机
        1    关机
1、3~7    备用        
故障状态内容:0120H见下表,0121H为备用。
位    位定义    状态    意义
1    输入异常    0    无
        1    故障
2    输出过压    0    无
        1    故障
4    过温    0    无
        1    故障
0、3、5~7    备用        
(三)、通讯错误信息及数据的处理:
   模块从主机接收到的信息如有CRC码或非**能码错误,将不予理会 。

使用特权

评论回复
8
keer_zu| | 2008-6-28 11:09 | 只看该作者

给一份吧,学习学习!

zkq1905@sina.com

使用特权

评论回复
9
kelecola| | 2008-6-28 14:08 | 只看该作者

给我也发一份吧,多谢

kelecola@gmail.com

使用特权

评论回复
10
c51avr| | 2008-6-28 17:39 | 只看该作者

谢谢

谢谢楼主,共同进步

使用特权

评论回复
11
yemingxp| | 2008-6-28 21:45 | 只看该作者

.

楼主,在群里看到的,麻烦发我一份,谢谢

yemingxp$163.com ,请将$换成@

使用特权

评论回复
12
sunke9|  楼主 | 2008-6-30 08:45 | 只看该作者

已经给楼上3位发了邮件,请查收。

欢迎提出宝贵的意见。

使用特权

评论回复
13
zoe2003| | 2008-6-30 09:13 | 只看该作者

最近也在学MODBUS RTC及TCP协议,学习!

 给我也发一份吧,多谢!
 anyeliuxing_2003@163.com

使用特权

评论回复
14
zxs2000| | 2008-6-30 09:34 | 只看该作者

希望sunke9给发一份,谢了!zousheng_2008@126.com

希望sunke9给发一份,谢了!zousheng_2008@126.com

使用特权

评论回复
15
ma9801| | 2008-6-30 09:49 | 只看该作者

谢谢,请给我一份

谢谢,请给我发一份
mazufu2001@126.com

使用特权

评论回复
16
sunke9|  楼主 | 2008-6-30 10:22 | 只看该作者

也发给楼上3为了

请看完了,来发表一点感想.

使用特权

评论回复
17
ma9801| | 2008-6-30 11:04 | 只看该作者

哈哈,通讯规约也一起看看

使用特权

评论回复
18
sunke9|  楼主 | 2008-6-30 11:34 | 只看该作者

忘了说了

这个程序的完整工程和通讯规约在EDN我的博客里都提供下载.
http://blog.**/sunke9/

使用特权

评论回复
19
hansliu888| | 2008-6-30 12:10 | 只看该作者

请您也给我发STM32F的MODBUS-RTU通讯程序

请您也给我发STM32F的232口简易MODBUS-RTU通讯程序,Hansliu888@163.com;
Hans.Liu@UPMRaflatac.com
    多谢!

使用特权

评论回复
20
iweikang| | 2008-7-1 13:11 | 只看该作者

iweikang@hotmail.com

iweikang@hotmail.com,楼主,THANKS!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

48

主题

349

帖子

1

粉丝