本软件可分为以下五个部分,第一部分是状态设定和位置监测模块,其中发电车的行车和发电状态的设定是由一个船型开关决定的,当开关闭合时,为发电状态,反之为行车状态,步进电机控制油门的开合,但考虑到异常情况,用一个常开型开关实时监测步进电机是否转动到了极限位置;第二部分为LCD液晶显示,主要将串口通信得到的发电机输出电的频率fre、发电机输出电的电流current以及控制步进电机转动快慢的方波频率mtfre这三个参数实时显示在LCD液晶上。第三部分为单片机与发电机上的仪表之间的Modbus-RTU串口通信,通过软件的实时发送和接收可以得到发电机输出电的频率和电流等参数;第四部分为利用定时中断实现频率可调的方波输出,根据发电机的频率与额定频率50HZ的比较来决定输出方波的频率,而方波的频率之所以可以实时变化的原因是通过改变定时器的初值来实现的,改变了定时器的初值,则改变了定时时间的大小,在每一次溢出中断产生时,置反某一个I/O口的电平,从而实现了频率可调的方波输出;第五部分是步进电机的控制模块,这里需要了解步进电机的工作原理以及驱动原理,这个系统采用专用驱动器驱动步进电机进行转动,驱动器上的输入信号包括脉冲信号、方向信号和使能信号,只要将这三种信号按照要求进行输入,驱动器就可以控制步进电机进行正常转动; 整体的软件功能是在几个模块函数的相互联系相互调用的情况下实现的,每一个模块有自己的专用函数,在编写程序时,用特殊的专用的函数名来对各个模块的函数进行命名,这样方便查找和修改。 我觉得大家感兴趣的应该是MODBUS通信,因为用到这种通信的一般是工业仪器仪表,所以我想给大家介绍一下,我在编写这一块时的问题和解决办法。 1. Modbus-RTU串口通信模块通信协议:该模块是整个软件的重点部分,实现发电车上的电力仪表与单片机之间的实时通信,得到系统所要提取的发电机的频率和电流。Modbus协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络和其它设备之间可以通信。发电机上的电力仪表本身规定用的通信协议为RS485接口Modbus-RTU通信协议。当在Modbus网络上通信时,此协议决定了每个控制器必须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行为。如果需要回应,控制器将生成反馈信息并通过Modbus协议发送。 在Modbus协议中,典型的主设备可以为:主机和可编程仪表。典型的从设备主要为:可编程控制器。在系统中,单片机为主设备,发电机上的电力仪表为从设备。主设备可单独和从设备通信,也能以广播方式和所有从设备通信,如果单独通信,从设备返回一消息作为回应,如果是以广播方式查询的,则从设备们不作任何回应。Modbus协议建立了主设备查询的格式:设备(或广播)地址、功能代码、所有要发送的数据、一错误检测域。从设备回应消息也由Modbus协议构成,包括要确认行动的域、任何要返回的数据、和一错误检测域。如果在消息接收过程中发生一错误,或从设备不能执行其命令,从设备将建立一错误消息并把它作为回应发送出去。 (1)查询 查询消息中的功能代码告之被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03是要求从设备读保持寄存器并返回它们的内容。数据段必须包含要告之从设备的信息:从何寄存器开始读及要读的寄存器数量。错误检测域为从设备提供了一种验证消息内容是否正确的方法。 查询数据包结构如下: (2)回应 如果从设备产生一正常的回应,在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包括了从设备收集的数据:像寄存器值或状态。如果有错误发生,功能代码将被修改以用于指出回应消息是错误的,同时数据段包含了描述此错误信息的代码。错误检测域允许主设备确认消息内容是否可用。 回应数据包结构: (3)两种传输方式 控制器能设置为两种传输模式:ASCII或RTU中的任何一种在标准的Modbus网络通信。用户选择想要的模式,包括串口通信参数(波特率、校验方式等),在配置每个控制器的时候,在一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。相对来说,RTU模式传输效率更高,因此,在当前普遍的生产环境中RTU模式获得了广泛应用,而ASCII模式只作为特殊情况下的可选项。 ASCII模式 当控制器设为在Modbus网络上以ASCII(美国标准信息交换代码)模式通信,一个信息中的每8个比特作为2个ASCII字符传输,如数值63H用ASCII方式时,需发送两个字节,即ASCII“6"(0110110)和ASCII”3“(0110011),ASCII字符占用的位数有7位和8位,国际通用7位为多。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。 代码系统 · 十六进制,ASCII字符0...9,A...F · 消息中的每个ASCII字符都是一个十六进制字符组成 每个字节的位 · 1个起始位 · 7个数据位,最小的有效位先发送 · 1个奇偶校验位,无校验则无 1个停止位(有校验时),2个Bit(无校验时) 错误检测域 · LRC(纵向冗长检测) RTU模式 当控制器设为在Modbus网络上以RTU模式通信,在消息中的每个8Bit字节按照原值传送,不做处理,如63H,RTU将直接发送01100011。这种方式的主要优点是:数据帧传送之间没有间隔,相同波特率下传输数据的密度要比ASCII高,传输速度更快。 代码系统 8位二进制,十六进制数0...9,A...F 消息中的每个8位域都是一或两个十六进制字符组成 每个字节的位 1个起始位 8个数据位,最小的有效位先发送 1个奇偶校验位,无校验则无 1个停止位(有校验时),2个Bit(无校验时) (4)数据校验方式 CRC校验 CRC域是两个字节,包含一16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC,并与接收到的CRC域中的值比较,如果两值不同,则有误。 CRC是先调入一值是全“1”的16位寄存器,然后调用一过程将消息中连续的8位字节和当前寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效,起始位和停止位以及奇偶校验位均无效。 CRC产生过程中,每个8位字符都单独和寄存器内容相异或(XOR),结果向最低有效位方向移动,最高有效位以0填充。LSB被提取出来检测,如果LSB为1,寄存器单独和预置的值或一下,如果LSB为0,则不进行。整个过程要重复8次。在最后一位(第8位)完成后,下一个8位字节又单独和寄存器的当前值相异或(XOR)。最终寄存器中的值,是消息中所有的字节都执行之后的CRC值。 CRC添加到消息中时,低字节先加入,然后高字节。 CRC-16错误校验程序如下:报文(此处只涉及数据位,不指起始位、停止位和任选的奇偶校验位)被看作是一个连续的二进制,其最高有效位(MSB)首选发送。报文先与X↑16相乘(左移16位),然后看X↑16+X↑15+X↑2+1除,X↑16+X↑15+X↑2+1可以表示为二进制数11000,0000,0000,0101。整数商位忽略不记,16位余数加入该报文(MSB先发送),成为2个CRC校验字节。余数中的1全部初始化,以免所有的零成为一条报文被接收。经上述处理而含有CRC字节的报文,若无错误,到接收设备后再被同一多项式(X↑16+X↑15+X↑2+1)除,会得到一个零余数(接收设备核验这个CRC字节,并将其与被传送的CRC比较)。全部运算以2为模(无进位)。 习惯于成串发送数据的设备会首选送出字符的最右位(LSB-最低有效位)。而在生成CRC情况下,发送首位应是被除数的最高有效位MSB。由于在运算中不用进位,为便于操作起见,计算CRC时设MSB在最右位。生成多项式的位序也必须反过来,以保持一致。多项式的MSB略去不记,因其只对商有影响而不影响余数。 LRC检验 LRC错误校验用于ASCII模式。这个错误校验是一个8位二进制数,可作为2个ASCII十六进制字节传送。把十六进制字符转换成二进制,加上无循环进位的二进制字符和二进制补码结果生成LRC错误校验(参见图)。这个LRC在接收设备进行核验,并与被传送的LRC进行比较,冒号(:)、回车符号(CR)、换行字符(LF)和置入的其他任何非ASCII十六进制字符在运算时忽略不计。 (5)Modbus协议中功能码定义 Modbus功能码在查询数据包和回应数据包里都只占用一个字节,取值范围是1~127。之所以127以上不能使用,是因为Modbus规定当通信出现异常时,功能码+0X80(十进制128)代表异常状态,因此129~255的取值代表异常码。 Modbus相关公共功能码: 函数说明:在了解了Modbus协议之后,开始编写Modbus-RTU通信函数。本系统所用的PIC18F4520单片机最小系统只有RS232通信接口,电力仪表提供的是串行异步半双工的RS485通信接口,故需用RS232转RS485转接器实现通信。本模块用USART来编写通信函数,且必须将其设置为串行异步半双工的通信方式,相关USART初始化函数如下: //USART模块初始化
void USARTinit(void)
{
TXSTAbits.SYNC=0; //选择异步通信方式
TXSTAbits.TX9=0; //选择8位发送数据格式
//TXSTAbits.TXEN=1; //允许发送,配置为半双工方式
RCSTAbits.SPEN=1; //使能串口
RCSTAbits.RX9=0; //选择8位接收数据模式
//RCSTAbits.CREN=1; //异位模式下,使能接收器
BAUDCONbits.BRG16=1; //使能16位的波特率发生器
TXSTAbits.BRGH=1; //采用高速波特率
SPBRGH=832/256;
SPBRG=832%256; //波特率设置为9600,系统时钟为32MHZ
} 在这个函数模块中,电力仪表要收到来自单片机的查询数据包以后,会返回一个回应数据包,而且由于已知要查的频率变量在仪表的74、75两个寄存器以内,发送数据包一共有8个字节,回应数据包一共有9个字节,本来需要根据两个字节之间的传输时间是否大于3.5个字节传输时间来确定是否为新的数据包,但在已知回应数据包字节数的情况下,软件可以简化为直接判断单片机接收到的回应数据包的字节数是否为9。按照这个思路,我们可以打开单片机UASRT的接收中断,首先,将通信方式配置为发送模式,在单片机将查询数据包发送给电力仪表后,将通信方式配置为接收模式,电力仪表发送给单片机一个字节,则接收中断标志位置1,我们将接收到的字节存储在接收数组里面,通过相关函数将接收数组里面有效的信息提取出来加以使用。 接收中断相关函数如下: void USARTINT(void)
{
RCONbits.IPEN=0; //禁止中断嵌套,禁止中断优先级功能,所以这里不用加上低优先级中断服务函数,但加上问题不大
INTCON|=0XC0; //CPU开中断,开启了总中断GIE和外设中断PEIE,即允许外设中断 IPR1bits.RCIP=0; //设置禁止EUSART接收中断为高优先级
PIR1bits.RCIF=0; //清USART接收中断标志
PIE1bits.RCIE=1; //允许USART接收中断
}
#pragma interrupt PIC18F_HIGH_ISR
void PIC18F_HIGH_ISR(void)
{
if(1==PIR1bits.RCIF) //rx_index为接收数组的长度
{
flag=0;
PIR1bits.RCIF=0; //清除接收中断标志位
if(rx_index<sizeof(rx_buf)) //接收缓冲器尚未用完
{
rx_buf[rx_index]=RCREG;
rx_index++; //将接收到的数据存进接收数组,并递增计数器rx_index
}
if(rx_index==9) //接收到九个字节,说明一帧结束
{ rx_index=0;flag=1;} //接受到完整的一帧数据
} } 该模块发送函数的设计过程为构建有效信息发送数组,根据发送数组计算CRC校验码,最后将有效信息和CRC校验码构造成一个完整的发送数组再一并发送出去。在这个过程中,比较重要的为CRC计算函数,因为有效信息在已知电力仪表相关寄存器信息以后是确定的,但若校验码错误,即使单片机将发送数组发送给电力仪表,电力仪表经过计算以后与单片机发送过来的CRC校验码不相等,电力仪表则判断为发送信息错误,会发送异常码给单片机,这样通信为不成功,故以下着重介绍CRC计算函数: //CRC检验函数 1 unsigned short crc(unsigned char *ptr,unsigned char size) { unsigned short a,b,tmp,CRC16,V; CRC16=0xffff; //CRC寄存器初始值 for (a=0;a<size;a++) //N个字节 { CRC16=*ptr^CRC16; for (b=0;b<8;b++) //8位数据 { tmp=CRC16 & 0x0001; CRC16 =CRC16 >>1; //右移一位 if (tmp) CRC16=CRC16 ^ 0xa001; //异或多项式 } *ptr++; } V = ((CRC16 & 0x00FF) << 8) | ((CRC16 & 0xFF00) >> 8); //高低字节转换,这个时候低字节在高八位,高字节在低八位 return V; } 生成CRC-16校验字节的步骤如下: ①例如一个16位寄存器,所有数位均为1。 ②该16位寄存器的高位字节与开始8位字节进行“异或”运算。运算结果放入这个16位寄存器。 ③把这个16寄存器向右移一位。 ④若向右(标记位)移出的数位是1,则生成多项式10,1000,000,0000,001和这个寄存器进行“异或”运算;若向右移出的数位是0,则返回③。 ⑤重复③和④,直至移出8位。 ⑥另外8位与该十六位寄存器进行“异或”运算。 ⑦重复③~⑥,直至该报文所有字节均与16位寄存器进行“异或”运算,并移位8次。 ⑧这个16位寄存器的内容即2字节CRC错误校验,被加到报文的最高有效位。 另外,在某些非ModBus通信协议中也经常使用CRC16作为校验手段,而且产生了一些CRC16的变种,他们是使用CRC16多项式X↑16+X↑15+X↑2+1,单首次装入的16位寄存器为0000;使用CRC16的反序X↑16+X↑14+X↑1+1,首次装入寄存器值为0000或FFFFH。 Modbus-RTU通信函数如下: //modbus-rtu通信得到输出电频率
float modbus_rtu(void)
{
float a;
cnttxd=rtu_read_hldreg(LOCALADDR,tx_buf,73,2); //得到发送数组的字节数
transmit(tx_buf,cnttxd); //发送,cnttxd为发送数组的长度
while(!flag&&RC4==0); //判断接收数组长度是否达到了要求的长度
if(rtu_data_anlys(rx_buf,9)==1) //数据分析,检验接收到的数据对不对
a=receive(rx_buf); //实时接收到仪表的频率fre
else
a=0;
return a;
} 接口设计:本模块的接口电路已在PIC18F4520单片机最小系统里面集成,值得注意的是本模块虽然使用的是RS232串行通信模块,但是由于仪表的通信接口为RS485,仪表与单片机之间用公—公串口线与RS485转RS232的转接器相连进行通信,故实际中即使RS232串行通讯模块是全双工的,其中的RC6、RC7本可以在同一时间使用,但是在本模块下,必须设置为半双工工作方式。RS232串行通信电路如下: 通信的接线如下: 最后发电机的仪表与单片机实现的通信结果如下:
|