/*务必通过ISP通讯,烧录时,选11.0592Mhz进行烧录,否则数据会乱码modbus-rtu 处理过程主机数据接收(超时机制,不定长接收)->数据校验码判断->
*/
#include <STC15F2K60S2.H>
#include "string.h"
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;bit busy=0;//0 表示不忙
bit rxend=0;
#define LENMAX 25 /*最大接收数据长度,根据实际需要而定,不宜过长,*本代码用于modbus-rtu,一般数据长度为8字节*/
uint8_t rxbuf[LENMAX];//用于存储接收数据
uint8_t rxcnt=0;//用于保存当前接收数据长度。最大值=LENMAX,
uint16_t rxcrc; //CRC16校验码//uint8_t err[]={""};
/*蓝桥杯51开发板开机LED和蜂鸣器上电需要初始化,否则会出现蜂鸣器乱响*/
void IOinit(void)
{P0=0xff;P2=P2&0X1F|0X80;P2=P2&0X1F;P0=0x00;P2=P2&0X1F|0XA0;P2=P2&0X1F;
}
/*初始化串口1,可以由STC-ISP软件生成(推荐)*/
void UartInit(void) //9600bps@11.0592MHz
{SCON = 0x50; //8位数据,可变波特率AUXR |= 0x01; //串口1选择定时器2为波特率发生器AUXR |= 0x04; //定时器2时钟为Fosc,即1TT2L = 0xE0; //设定定时初值T2H = 0xFE; //设定定时初值AUXR |= 0x10; //启动定时器2ES=1;//允许串口中断
}/*初始化定时器1,可以由STC-ISP软件生成(推荐)*/
void Timer1Init(void) //1毫秒@11.0592MHz
{AUXR |= 0x40; //定时器时钟1T模式TMOD &= 0x0F; //设置定时器模式TL1 = 0xCD; //设置定时初值TH1 = 0xD4; //设置定时初值TF1 = 0; //清除TF1标志TR1 = 1; //定时器1开始计时ET1 = 1;//开定时器1中断允许
}/*串口1单字符发送函数*/
void sendchar(uint8_t ch)
{/**由于此单片机中的串口收发是由同一个寄存器完成的,一次仅发送一个字符。*因此需要一个忙标志来防止字符还未发送完成就进行下一次发送了。*/while(busy);//忙?等待上一次发送完成,何时busy置零?在触发串口发送中断后busy=1;//设置为忙状态,即将发送数据SBUF = ch;//发送数据
}
/*串口1字符串发送函数*/
void sendstring(char *str)
{while(*str)//字符串结束?等价于while(*str!='\0'){sendchar(*str++);//发送当前字符}
}//RS485通信CRC校验,buf待校验数组,n待校验长度
//返回success表示数据正常,返回error表示数据错误
uint16_t crc16(uint8_t *buf) {uint16_t tmp = 0xffff, ret1 = 0;uint8_t i = 0, j;for (j = 0; j <6; j++) {/*此处的n -- 要校验的位数为n个*/tmp = buf[j] ^ tmp;for (i = 0; i < 8; i++) {/*此处的8 -- 指每一个char类型又8bit,每bit都要处理*/if (tmp & 0x01) {tmp = tmp >> 1;tmp = tmp ^ 0xa001;} else {tmp = tmp >> 1;}}}/*CRC校验后的值*//*将CRC校验的高低位对换位置*/ret1 = tmp >> 8;ret1 = ret1 | (tmp << 8);return ret1;
}
//modbus处理
void modbus_work(void) {uint8_t i = 0;uint16_t crc; //CON低电平,RE高电平,允许发送数据if (rxcnt == 8) { //只响应字节长度8的if (rxbuf[0] == 0x01) { //地址正确crc = ((rxbuf[6] & 0x00ff) << 8) + rxbuf[7];if (crc = crc16(rxbuf)) { //校验码正确if (rxbuf[1] == 0x03) { //03H功能码sendstring("rtu_ok");/**这里返回modbus-rtu的数据给主机,这里面校验码通过了,*还需要有其他的异常处理,如数据域的异常查询**/}else { //收到不支持的功能码sendstring("fun_err");}} else { //校验码错误sendstring("crc_err");}}}
}
/*主函数*/
void main()
{IOinit();UartInit();Timer1Init();EA=1;//开总中断sendstring("--modbus-rtu test!--\r\n");sendstring("--作者:小谦·!--\r\n");sendstring("--请选择HEX显示与发送!--\r\n");while(1){if(rxend)//接收结束{rxend=0;modbus_work();rxcnt=0;} }
}
/*串口1中断服务函数*/
void Uart() interrupt 4 using 1
{if (RI) //接收中断?{RI = 0; //清除RI位,以便下一次因接收进入中断rxbuf[rxcnt++]=SBUF;//保存数据} if (TI)//发送中断?{TI = 0; //清除TI位,以便下一次因发送进入中断busy = 0; //清忙标志}
}
/*定时器1中断服务函数,用于串口1的数据接收结束判断*/
/********************* Timer1中断函数************************/
void timer1_int (void) interrupt 3
{static uint8_t oldcnt,newcnt,count;if(++count==10){count=0;oldcnt=newcnt;newcnt=rxcnt; if((oldcnt==newcnt)&&rxcnt!=0)//发送已结束,且收到了数据{rxend=1;}}
}
实现展示
正确输入查询码后会返回rtu_ok的字符串提示,其余异常数据或返回错提示或不响应。