wangkun2046
发表于 2008-6-27 21:41
[原创]STM32F的232口简易MODBUS-RTU通讯程序
/*****************************************************<br /> modbus-rtu 通讯规约<br />通讯方式:rs-485 半双功<br />校验方式:crc16<br />停止位:2位<br />编写:孙可<br />编程思路:<br /> 1.串口中断允许自动接收总线上的信息,当接收的<br /> 字节后超过3.5个字节时间没有新的字节认为本次<br /> 接收完成,接收完成标志置1;如果接收完成标志已<br /> 经置1又有数据进来则丢弃新来的数据。<br /> 2.串口接收数据的处理, 当接收完成标志置1进入<br /> 接收数据处理, (1)首先判断接收的第一位数据与<br /> 本机地址是否相同,如果不相同清空接收缓存不发<br /> 送任何信息; (2)接收的第一位数据与本机地址相<br /> 同,则对接收缓存中的数据进行crc16校验,如果接<br /> 收的校验位与本校验结果不相同清空接收缓存不发<br /> 送任何信息;<br /> (3)如果crc16校验正确则根据数据串中的命令码进<br /> 行相应的处理。<br />******************************************************/<br />#include "modbus.h"<br />u8 Com0_id = 0x05;//本机串口0的通讯地址<br />u8 Uart0_rev_buff;//com0串口接收缓冲区<br />u8 Uart0_send_buff;//com0串口发送缓冲区<br />vu8 Uart0_rev_count;<br />vs8 Uart0_send_counter = 0;<br />vu8 Uart0_rev_comflag;<br />vu8 Crc_counter = 0;//com0校验计数器<br />vu8 *Uart0_send_pointer = Uart0_send_buff;//com0串口发送指针<br />vu16 Mkgz_bz = 0;//模块故障标志1:输入异常,2:过压,3:欠压,4:过温<br />vu16 Out_current = 50;//输出电流<br />vu16 Out_voltage = 240;//输出电压<br />vu16 Mkzt_bz = 0;//模块状态标志<br />vu16 OutX_current = 1000;//输出限流<br />vu16 Jc_voltage = 2530;//均充电压<br />vu16 Fc_voltage = 2400;//浮充电压<br />vu16 user_day = 1825;//使用天数<br /><br />void Delay(vu32 nCount);<br />unsigned short getCRC16(volatile unsigned char *ptr,unsigned char len) ; <br />void mov_data(u8 a,u8 b,u8 c);<br />void Modbus_Function_3(void); <br />void Modbus_Function_6(void); <br />/***************************************<br />函数名称:crc16校验<br />函数功能:crc16校验<br />函数输入:字节指针*ptr,数据长度len<br />函数返回:双字节crc<br />函数编写:孙可<br />编写日期:2008年6月9日<br />函数版本:v0.2<br />****************************************/<br />unsigned short getCRC16(volatile unsigned char *ptr,unsigned char len) <br />{ <br /> unsigned char i; <br /> unsigned short crc = 0xFFFF; <br /> if(len==0) <br /> {<br /> len = 1;<br /> } <br /> while(len--) <br /> { <br /> crc ^= *ptr; <br /> for(i=0; i<8; i++) <br /> { <br /> if(crc&1) <br /> { <br /> crc >>= 1; <br /> crc ^= 0xA001; <br /> } <br /> else <br /> {<br /> crc >>= 1;<br /> } <br /> } <br /> ptr++; <br /> } <br /> return(crc); <br />} <br />/***************************************<br /> 块数据复制数据函数<br />功能:把数组a的c个数据复制到数组b中<br />输入:指针a,指针b,数据个数c<br />返回:无<br />编写:孙可<br />编写日期:2008年3月28日<br />版本:v0.1<br />****************************************/<br />void mov_data(u8 a,u8 b,u8 c)<br />{<br /> u8 i;<br /> for(i=c; i>0; i--)<br /> {<br /> a = b;<br /> }<br />}<br />/////////////////////////////////////////////////////////////////////// <br />void Modbus_Function_3(void) <br />{ <br /> u16 tempdress = 0;<br /> u8 i = 3; <br /> u16 crcresult; <br /> tempdress = (Uart0_rev_buff << 8) + Uart0_rev_buff; <br /> if((tempdress >= 0x0120) & (tempdress + Uart0_rev_buff < 0x0132))<br /> {<br /> Uart0_send_buff = Com0_id;<br /> Uart0_send_buff = 0x03; <br /> Uart0_send_buff = 2 * Uart0_rev_buff; <br /> Uart0_send_counter = 2 * Uart0_rev_buff + 3;<br /> switch(tempdress)<br /> {<br /> case 0x0120:<br /> {<br /> Uart0_send_buff = Mkgz_bz & 0xff;<br /> i++;<br /> Uart0_send_buff = (Mkgz_bz >> 8) & 0xff;<br /> i++; <br /> }//后面不放break的目的是继续往下执行<br /> case 0x0122:<br /> {<br /> Uart0_send_buff = Out_voltage & 0xff;<br /> i++;<br /> Uart0_send_buff = (Out_voltage >> 8) & 0xff;<br /> i++; <br /> }<br /> case 0x0124:<br /> {<br /> Uart0_send_buff = Out_current & 0xff;<br /> i++;<br /> Uart0_send_buff = (Out_current >> 8) & 0xff;<br /> i++; <br /> }<br /> case 0x0126:<br /> {<br /> Uart0_send_buff = Mkzt_bz & 0xff;<br /> i++;<br /> Uart0_send_buff = (Mkzt_bz >> 8) & 0xff;<br /> i++; <br /> }<br /> case 0x0128://这个地址是备用的里面的数据没有意义<br /> {<br /> Uart0_send_buff = 0x00;<br /> i++;<br /> Uart0_send_buff = 0x00;<br /> i++; <br /> }<br /> case 0x012A:<br /> {<br /> Uart0_send_buff = OutX_current & 0xff;<br /> i++;<br /> Uart0_send_buff = (OutX_current >> 8) & 0xff;<br /> i++;<br /> }<br /> case 0x012C:<br /> {<br /> Uart0_send_buff = Jc_voltage & 0xff;<br /> i++;<br /> Uart0_send_buff = (Jc_voltage >> 8) & 0xff;<br /> i++;<br /> }<br /> case 0x012E:<br /> {<br /> Uart0_send_buff = Fc_voltage & 0xff;<br /> i++;<br /> Uart0_send_buff = (Fc_voltage >> 8) & 0xff;<br /> i++;<br /> }<br /> case 0x0130:<br /> {<br /> Uart0_send_buff = 0x00;<br /> i++;<br /> Uart0_send_buff = 0x00;<br /> i++;<br /> }<br /> } <br /> //UCSRB |= (1<<TXCIE)|(1<<TXEN);//发送、发送中断允许<br /> crcresult = getCRC16(Uart0_send_buff,Uart0_send_counter); <br /> Uart0_send_buff = crcresult & 0xff; <br /> Uart0_send_buff = (crcresult >> 8) & 0xff; <br /> Uart0_send_counter = Uart0_send_counter+2; <br /> Uart0_send_pointer = Uart0_send_buff;<br /> USART_SendData(USART1, *Uart0_send_pointer++);<br /> USART_ITConfig(USART1, USART_IT_TXE, ENABLE); <br /> }<br />} <br />/////////////////////////////////////////////////////////////<br />void Modbus_Function_6(void) <br />{ <br /> u16 tempdress = 0;<br /> u8 tx_flat = 0;<br /> u16 crcresult;<br /> tempdress = (Uart0_rev_buff<<8) + Uart0_rev_buff;<br /> switch(tempdress)<br /> {<br /> case 0x0126:<br /> {<br /> Mkzt_bz = (Uart0_rev_buff<<8) + Uart0_rev_buff;<br /> if(user_day > 0)<br /> {<br /> tx_flat = 1;<br /> }<br /> }break;<br /> case 0x012A:<br /> {<br /> OutX_current = (Uart0_rev_buff<<8) + Uart0_rev_buff;<br /> if(user_day > 0)<br /> {<br /> tx_flat = 1;<br /> }<br /> }break;<br /> case 0x012C:<br /> {<br /> Jc_voltage = (Uart0_rev_buff<<8) + Uart0_rev_buff;<br /> if(user_day > 0)<br /> {<br /> tx_flat = 1;<br /> }<br /> }break;<br /> case 0x012E:<br /> {<br /> Fc_voltage = (Uart0_rev_buff<<8) + Uart0_rev_buff;<br /> if(user_day > 0)<br /> {<br /> tx_flat = 1;<br /> }<br /> }break;<br /> case 0x01EE:<br /> {<br /> user_day = (Uart0_rev_buff<<8) + Uart0_rev_buff;<br /> tx_flat = 1;<br /> //eeprom_write_word (&user_day_eep,user_day);<br /> }break;<br /> default: //命令码无效不应答<br /> { <br /> tx_flat = 0;<br /> } <br /> }<br /> if(tx_flat == 1)<br /> {<br /> Uart0_send_buff = Com0_id;<br /> Uart0_send_buff = 0x06; <br /> Uart0_send_buff = Uart0_rev_buff; <br /> Uart0_send_buff = Uart0_rev_buff; <br /> Uart0_send_buff = Uart0_rev_buff; <br /> Uart0_send_buff = Uart0_rev_buff; <br /> Uart0_send_counter = 6;<br /> //UCSRB |= (1<<TXCIE)|(1<<TXEN);//发送、发送中断允许<br /> crcresult = getCRC16(Uart0_send_buff,Uart0_send_counter); <br /> Uart0_send_buff = crcresult & 0xff; <br /> Uart0_send_buff = (crcresult >> 8) & 0xff; <br /> Uart0_send_counter = Uart0_send_counter+2; <br /> Uart0_send_pointer = Uart0_send_buff;<br /> USART_SendData(USART1, *Uart0_send_pointer++);<br /> USART_ITConfig(USART1, USART_IT_TXE, ENABLE); <br /> }<br />} <br />///////////////////////////////////////////////////////////// <br />void Com0_Communication(void) <br />{ <br /> s8 i =0; <br /> if(Uart0_rev_comflag == 1)//接收完成标志=1处理,否则退出<br /> {<br /> if(Uart0_rev_buff == Com0_id)//地址错误不应答<br /> {<br /> unsigned short crcresult; <br /> unsigned char temp; <br /> crcresult = getCRC16(Uart0_rev_buff,Crc_counter-2); <br /> temp = crcresult & 0xff; <br /> temp = (crcresult >> 8) & 0xff; <br /> if((Uart0_rev_buff == temp)&&(Uart0_rev_buff == temp))//crc校验错误不应答 <br /> { <br /> //SETBIT(PORTC,PC6); <br /> Delay(1);<br /> switch(Uart0_rev_buff) <br /> { <br /> case 0x03: <br /> {<br /> if(user_day > 0)<br /> {<br /> Modbus_Function_3(); <br /> } <br /> }<br /> break; <br /> case 0x06: <br /> { <br /> Modbus_Function_6(); <br /> }<br /> break; <br /> } <br /> }<br /> }<br /> Uart0_rev_comflag = 0;<br /> for(i = 100;i > -1;i--) <br /> { <br /> Uart0_rev_buff = 0;<br /> } <br /> } <br /> <br />} <br />/*******************************************************************************<br />* Function Name : Delay<br />* Description : Inserts a delay time.<br />* Input : nCount: specifies the delay time length.<br />* Output : None<br />* Return : None<br />*******************************************************************************/<br />void Delay(vu32 nCount)<br />{<br /> for(; nCount != 0; nCount--);<br />}<br />////////////////////////////////////////////////////////////////////////// <br />完整的通讯规约和工程传不上来,有需要的朋友留下EMAIL,我发给你.<br />
ye12
发表于 2008-6-27 21:47
呵呵,不错,给我发一份!
xwjfile@21cn.com
shen001
发表于 2008-6-27 22:17
给我发一份好吗?谢谢!
pulkey@163.com
caibapaint
发表于 2008-6-28 08:30
呵呵,不错,给我发一份!
wangpeiyu2004@163.com<br /> <br />
shenlao
发表于 2008-6-28 08:32
谢谢
c51avr@163.com 谢谢啊
xi19871208
发表于 2008-6-28 09:12
谢谢
好东西,给我一份行吗?谢谢!!<br />rayzhongr@126.com
killhill
发表于 2008-6-28 09:29
已经给楼上5位发了邮件,请查收。
[通讯规约]<br />本模块采用ModBus RTU 通讯规约,具体如下:<br />通讯数据的类型及格式:<br />信息传输为异步方式,并以字节为单位。每个字节由8位二进制数组成。主机和从机之间传递的通讯信息是10位的字格式:<br />字格式(串行数据) 10位二进制<br />起始位 1位<br />数据位 8位<br />奇偶校验位 无奇偶校验位<br />停止位 1位<br />桢格式:<br />序号 1 2 3 4 5 6<br />定义 模块地址 功能代码 起始地址 数据长度 数据内容 校验码<br />字节数 1 1 2 2 n 2<br />★ 注:1、本文件中后缀为“H”的数据为16进制数据,如1AH;后缀为“B”的为二进制数据,如11111111B;无后缀的均为10进制数据。<br /> 2、每字节数据的顺序是低位在前高位在后,桢数据的顺序是高字节在前低字节在后,校验码低字节在前高字节在后。<br />(一)、通讯信息传输过程:<br />1.0 时间间隔:<br />通讯波特率:9600。监控器和模块的接收一直开着,模块在接收完所有字节后判断是否与本机地址相同,相同则置接收完毕标志等待处理,否则重新初始化串口计数器开始接收。<br />1.1 地址码:<br /> 地址码是每次通讯信息帧的第一字节(8位),从5到13。这个字节表明由用户设置地址的从机将接收由主机发送来的信息。每个从机都必须有唯一的地址码,并且只有符合地址码的从机才能响应回送信息。当从机回送信息时,回送数据均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机返回的地址码表明回送的从机地址。相应的地址码表明该信息来自于何处。<br />1.2 功能码:<br /> 功能码是每次通讯信息帧传送的第二个字节。ModBus通讯规约可定义的功能码为1到127。GKDM-21直流电源模块仅用到其中的一部分功能码。作为主机请求发送,通过功能码告诉从机应执行什么动作。作为从机响应,从机返回的功能码与从主机发送来的功能码一样,并表明从机已响应主机并且已进行相关的操作。<br /> 表1 MODBUS部分功能码<br />功能码 定 义 操 作<br />03H 读多路寄存器 读取一个或多个寄存器的数据<br />06H 写一路寄存器 把一个数据写入指定寄存器<br />1.3 数据区:<br />数据区包括需要由从机返送何种信息或执行什么动作。这些信息可以是数据、参考地址等。<br />1.4错误校验码(CRC校验):<br />主机或从机可用校验码进行判别接收信息是否正确。由于电子噪声或一些其它干扰,信息在传输过程中有时会发生错误,错误校验码(CRC)可以检验主机或从机在通讯数据传送过程中的信息是否有误,错误的数据可以放弃(无论是发送还是接收),这样增加了系统的安全和效率。<br />MODBUS通讯协议的CRC(冗余循环码)包含2个字节,即16位二进制数。CRC码由发送设备(主机)计算,放置于发送信息帧的尾部(CRC低字节在前)。接收信息的设备(从机)再重新计算接收到信息的CRC,比较计算得到的CRC是否与接收到的相符,如果两者不相符,则表明出错。<br />在进行CRC计算时只用8个数据位,而起始位及停止位,如有奇偶校验位也包括奇偶校验位,都不参与CRC计算。<br />CRC码的计算方法是: <br />计算CRC码的步骤为:<br />(1).预置16位寄存器为十六进制FFFF(即全为1)。称此寄存器为CRC寄存器;<br />(2).把第一个8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器;<br />(3).把CRC寄存器的内容右移一位(朝低位),用0填补最高位,检查最低位(注意:这时的最低位指移位前的最低位,不是移位后的最低位);<br />(4).如果最低位为0:重复第3步(再次移位)<br />如果最低位为1:CRC寄存器与多项式A001H(1010000000000001B)进行异或;<br />(5).重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;<br />(6).重复步骤2到步骤5,进行下一个8位数据的处理;<br />(7).最后得到的CRC寄存器即为CRC码。<br />(二)、功能码简介<br />2.1 功能码“03”:读多路寄存器<br /> 模块中,功能码“03”能够访问的寄存器地址为0120H—0131H,最大数据长度为18。<br />例如:主机要读取地址为05,起始地址为0120H的9个从机寄存器数据。<br />主机发送的报文格式:<br />主机发送 字节数 发送的信息 备 注<br />从机地址 1 05H 发送至地址为05的从机<br />功能码 1 03H 读取寄存器<br />起始地址 2 0120H 起始地址为0120H<br />数据长度 2 0009H 读取9个寄存器(共18个字节)<br />CRC码 2 84H 7EH 由主机计算得到CRC码(高位在后)<br /><br /><br />从机响应返回的报文格式<br />从机响应 字节数 返回的信息 备 注<br />从机地址 1 05H 来自从机05<br />功能码 1 03H 读取寄存器<br />读取字节数量 1 12H 9个寄存器共18个字节<br />模块故障 2 XXXXH 地址为0120H、0121H内存的内容<br />输出电压 2 XXXXH 地址为0122H、0123H内存的内容<br />输出电流 2 XXXXH 地址为0124H、0125H内存的内容<br />模块状态 2 XXXXH 地址为0126H、0127H内存的内容<br />备用 2 XXXXH 地址为0128H、0129H内存的内容<br />输出限流 2 XXXXH 地址为012AH、012BH内存的内容<br />均充电压 2 XXXXH 地址为012CH、012DH内存的内容<br />浮充电压 2 XXXXH 地址为012EH、012FH内存的内容<br />备用 2 XXXXH 地址为0130H、0131H内存的内容<br />CRC码 2 XXXXH 由从机计算得到CRC码<br />0120H为模块故障寄存器的低字节,0121H为模块故障寄存器的高字节。<br />2.2 功能码“6”:写一路寄存器<br /> 模块中,功能码“06”能够访问的输入寄存器地址为0126H—012FH。<br />例如:主机要向地址为05的模块,起始地址为012CH的寄存器写入数据。<br />主机发送的报文格式:<br />主机发送 字节数 发送的信息 备 注<br />从机地址 1 05H 发送至地址为05的从机<br />功能码 1 06H 写入一路寄存器<br />起始地址 2 012CH 起始地址为012CH<br />均充电压 2 XXXXH 地址为012CH、012DH内存的内容<br />CRC码 2 XXXXH 由主机计算得到CRC码<br />从机响应返回的报文格式:与主机发送的报文格式及数据内容完全相同。<br />模块寄存器信息表<br />保持寄存器内容均为二进制16位无符号整数数值<br />地址 表示参数或状态 数值计算方法或含义 备注<br />0120H 模块故障 0输入异常 1过压 2欠压 3过温 4输出过流过压 只读<br />0122H 输出电压 输出电压值=寄存器内容值(V) 只读<br />0124H 输出电流 输出电流值=寄存器内容值/10(A) 只读<br />0126H 模块状态 0充电方式 2开关机 读|写<br />0128H 备用 读|写<br />012AH 输出限流 输出限流值=寄存器内容值/100(A) 读|写<br />012CH 均充电压 均充电压值=寄存器内容值/10(V) 读|写<br />012EH 浮充电压 浮充电压值=寄存器内容值/10(V) 读|写<br /><br /><br />模块状态内容:0126H见下表,0127H为备用。<br />位 位定义 状态 意义<br />0 充电方式 0 浮充<br /> 1 均充<br />2 开关机 0 开机<br /> 1 关机<br />1、3~7 备用 <br />故障状态内容:0120H见下表,0121H为备用。<br />位 位定义 状态 意义<br />1 输入异常 0 无<br /> 1 故障<br />2 输出过压 0 无<br /> 1 故障<br />4 过温 0 无<br /> 1 故障<br />0、3、5~7 备用 <br />(三)、通讯错误信息及数据的处理:<br /> 模块从主机接收到的信息如有CRC码或非法功能码错误,将不予理会 。<br />
lin62485145
发表于 2008-6-28 11:09
给一份吧,学习学习!
zkq1905@sina.com
风过尘扬
发表于 2008-6-28 14:08
给我也发一份吧,多谢
kelecola@gmail.com
tianquan
发表于 2008-6-28 17:39
谢谢
谢谢楼主,共同进步
wangshenglin
发表于 2008-6-28 21:45
.
楼主,在群里看到的,麻烦发我一份,谢谢<br /><br />yemingxp$163.com ,请将$换成@
sealcold
发表于 2008-6-30 08:45
已经给楼上3位发了邮件,请查收。
欢迎提出宝贵的意见。
ambition
发表于 2008-6-30 09:13
最近也在学MODBUS RTC及TCP协议,学习!
给我也发一份吧,多谢!<br /> anyeliuxing_2003@163.com
johnychoi
发表于 2008-6-30 09:34
希望sunke9给发一份,谢了!zousheng_2008@126.com
希望sunke9给发一份,谢了!zousheng_2008@126.com
dhuazhang
发表于 2008-6-30 09:49
谢谢,请给我一份
谢谢,请给我发一份<br />mazufu2001@126.com
baobeidudu
发表于 2008-6-30 10:22
也发给楼上3为了
请看完了,来发表一点感想.
leoxmxiong
发表于 2008-6-30 11:04
哈哈,通讯规约也一起看看
maiwenjian
发表于 2008-6-30 11:34
忘了说了
这个程序的完整工程和通讯规约在EDN我的博客里都提供下载.<br />http://blog.ednchina.com/sunke9/
wmj0402
发表于 2008-6-30 12:10
请您也给我发STM32F的MODBUS-RTU通讯程序
请您也给我发STM32F的232口简易MODBUS-RTU通讯程序,Hansliu888@163.com;<br />Hans.Liu@UPMRaflatac.com<br /> 多谢!
cclccl985
发表于 2008-7-1 10:47
请看完了,来发表一点感想
首先感谢你很及时地就把你的程序发到我的邮箱里的了。<br /> 今天我看了看你的程序及协议,有几个问题想请教。我的QQ:358954666.<br />