/*********************************************************/
#define MAIN_Fosc 11059200L //定义主时钟
#include "..\..\STC8Gxxx.h"
/************* 功能说明 **************
请先别修改程序, 直接下载"08-串口1中断收发-C语言-MODBUS协议"里的"UART1.hex"测试, 主频选择11.0592MHZ. 测试正常后再修改移植.
串口1按MODBUS-RTU协议通信. 本例为从机程序, 主机一般是电脑端.
本例程只支持多寄存器读和多寄存器写, 寄存器长度为64个, 别的命令用户可以根据需要按MODBUS-RTU协议自行添加.
本例子数据使用大端模式(与C51一致), CRC16使用小端模式(与PC一致).
默认参数:
串口1设置均为 1位起始位, 8位数据位, 1位停止位, 无校验.
串口1(P3.0 P3.1): 9600bps.
定时器0用于超时计时. 串口每收到一个字节都会重置超时计数, 当串口空闲超过35bit时间时(9600bps对应3.6ms)则接收完成.
用户修改波特率时注意要修改这个超时时间.
本例程只是一个应用例子, 科普MODBUS-RTU协议并不在本例子职责范围, 用户可以上网搜索相关协议文本参考.
本例定义了64个寄存器, 访问地址为0x1000~0x103f.
命令例子:
写入4个寄存器(8个字节):
10 10 1000 0004 08 1234 5678 90AB CDEF 4930
返回:
10 10 10 00 00 04 4B C6
读出4个寄存器:
10 03 1000 0004 4388
返回:
10 03 08 12 34 56 78 90 AB CD EF 3D D5
命令错误返回信息(自定义):
0x90: 功能码错误. 收到了不支持的功能码.
0x91: 命令长度错误.
0x92: 写入或读出寄存器个数或字节数错误.
0x93: 寄存器地址错误.
注意: 收到广播地址0x00时要处理信息, 但不返回应答.
******************************************/
/************* 本地常量声明 **************/
#define RX1_Length 128 /* 接收缓冲长度 */
#define TX1_Length 128 /* 发送缓冲长度 */
/************* 本地变量声明 **************/
u8 xdata RX1_Buffer[RX1_Length]; //接收缓冲
u8 xdata TX1_Buffer[TX1_Length]; //发送缓冲
u8 RX1_cnt; //接收字节计数.
u8 TX1_cnt; //发送字节计数
u8 TX1_number; //要发送的字节数
u8 RX1_TimeOut; //接收超时计时器
bit B_RX1_OK; // 接收数据标志
bit B_TX1_Busy; // 发送忙标志
/************* 本地函数声明 **************/
void UART1_config(u32 brt, u8 timer, u8 io); // brt: 通信波特率, timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1, =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7, =3: 切换到P4.3 P4.4.
u8 Timer0_Config(u8 t, u32 reload); //t=0: reload值是主时钟周期数, t=1: reload值是时间(单位us), 返回0正确, 返回1装载值过大错误.
u16 MODBUS_CRC16(u8 *p, u8 n);
u8 MODBUS_RTU(void);
#define SL_ADDR 0x10 /* 本从机站号地址 */
#define REG_ADDRESS 0x1000 /* 寄存器首地址 */
#define REG_LENGTH 64 /* 寄存器长度 */
u16 xdata modbus_reg[REG_LENGTH]; /* 寄存器地址 */
//========================================================================
// 函数: void main(void)
// 描述: 主函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void main(void)
{
u8 i;
u16 crc;
Timer0_Config(0, MAIN_Fosc / 10000); //t=0: reload值是主时钟周期数, (中断频率, 20000次/秒)
UART1_config(9600UL, 1, 0); // brt: 通信波特率, timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1, =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7, =3: 切换到P4.3 P4.4.
EA = 1;
while (1)
{
if(B_RX1_OK && !B_TX1_Busy) //收到数据, 进行MODBUS-RTU协议解析
{
if(MODBUS_CRC16(RX1_Buffer, RX1_cnt) == 0) //首先判断CRC16是否正确, 不正确则忽略, 不处理也不返回信息
{
if((RX1_Buffer[0] == 0x00) || (RX1_Buffer[0] == SL_ADDR)) //然后判断站号地址是否正确, 或者是否广播地址(不返回信息)
{
if(RX1_cnt > 2) RX1_cnt -= 2; //去掉CRC16校验字节
i = MODBUS_RTU(); //MODBUS-RTU协议解析
if(i != 0) //错误处理
{
TX1_Buffer[0] = SL_ADDR; //站号地址
TX1_Buffer[1] = i; //错误代码
crc = MODBUS_CRC16(TX1_Buffer, 2);
TX1_Buffer[2] = (u8)(crc>>8); //CRC是小端模式
TX1_Buffer[3] = (u8)crc;
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = 4; //要发送的字节数
TI = 1; //启动发送
}
}
}
RX1_cnt = 0;
B_RX1_OK = 0;
}
}
}
/****************************** MODBUS_CRC (shift) *************** past test 06-11-27 *********
计算CRC,调用方式 MODBUS_CRC16(&CRC,8); &CRC为首地址,8为字节数
CRC-16 for MODBUS
CRC16=X16+X15+X2+1
TEST: ---> ABCDEFGHIJ CRC16=0x0BEE 1627T
*/
//========================================================================
// 函数: u16 MODBUS_CRC16(u8 *p, u8 n)
// 描述: 计算CRC16函数.
// 参数: *p: 要计算的数据指针.
// n: 要计算的字节数.
// 返回: CRC16值.
// 版本: V1.0, 2022-3-18 梁工
//========================================================================
u16 MODBUS_CRC16(u8 *p, u8 n)
{
u8 i;
u16 crc16;
crc16 = 0xffff; //预置16位CRC寄存器为0xffff(即全为1)
do
{
crc16 ^= (u16)*p; //把8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器
for(i=0; i<8; i++) //8位数据
{
if(crc16 & 1) crc16 = (crc16 >> 1) ^ 0xA001; //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位,
//再异或多项式0xA001
else crc16 >>= 1; //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位
}
p++;
}while(--n != 0);
return (crc16);
}
/********************* modbus协议 *************************/
/***************************************************************************
写多寄存器
数据: 地址 功能码 寄存地址 寄存器个数 写入字节数 写入数据 CRC16
偏移: 0 1 2 3 4 5 6 7~ 最后2字节
字节: 1 byte 1 byte 2 byte 2 byte 1byte 2*n byte 2 byte
addr 0x10 xxxx xxxx xx xx....xx xxxx
返回
数据: 地址 功能码 寄存地址 寄存器个数 CRC16
偏移: 0 1 2 3 4 5 6 7
字节: 1 byte 1 byte 2 byte 2 byte 2 byte
addr 0x10 xxxx xxxx xxxx
读多寄存器
数据:站号(地址) 功能码 寄存地址 寄存器个数 CRC16
偏移: 0 1 2 3 4 5 6 7
字节: 1 byte 1 byte 2 byte 2 byte 2 byte
addr 0x03 xxxx xxxx xxxx
返回
数据:站号(地址) 功能码 读出字节数 读出数据 CRC16
偏移: 0 1 2 3~ 最后2字节
字节: 1 byte 1 byte 1byte 2*n byte 2 byte
addr 0x03 xx xx....xx xxxx
返回错误代码
数据:站号(地址) 错误码 CRC16
偏移: 0 1 最后2字节
字节: 1 byte 1 byte 2 byte
addr 0x03 xxxx
***************************************************************************/
u8 MODBUS_RTU(void)
{
u8 i,j,k;
u16 reg_addr; //寄存器地址
u8 reg_len; //写入寄存器个数
u16 crc;
if(RX1_Buffer[1] == 0x10) //写多寄存器
{
if(RX1_cnt < 9) return 0x91; //命令长度错误
if((RX1_Buffer[4] != 0) || ((RX1_Buffer[5] *2) != RX1_Buffer[6])) return 0x92; //写入寄存器个数与字节数错误
if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH)) return 0x92; //写入寄存器个数错误
reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3]; //寄存器地址
reg_len = RX1_Buffer[5]; //写入寄存器个数
if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH)) return 0x93; //寄存器地址错误
if(reg_addr < REG_ADDRESS) return 0x93; //寄存器地址错误
if((reg_len*2+7) != RX1_cnt) return 0x91; //命令长度错误
j = reg_addr - REG_ADDRESS; //寄存器数据下标
for(k=7, i=0; i<reg_len; i++,j++)
{
modbus_reg[j] = ((u16)RX1_Buffer[k] << 8) + RX1_Buffer[k+1]; //写入数据, 大端模式
k += 2;
}
if(RX1_Buffer[0] != 0) //非广播地址则应答
{
for(i=0; i<6; i++) TX1_Buffer[i] = RX1_Buffer[i]; //要返回的应答
crc = MODBUS_CRC16(TX1_Buffer, 6);
TX1_Buffer[6] = (u8)(crc>>8); //CRC是小端模式
TX1_Buffer[7] = (u8)crc;
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = 8; //要发送的字节数
TI = 1; //启动发送
}
}
else if(RX1_Buffer[1] == 0x03) //读多寄存器
{
if(RX1_Buffer[0] != 0) //非广播地址则应答
{
if(RX1_cnt != 6) return 0x91; //命令长度错误
if(RX1_Buffer[4] != 0) return 0x92; //读出寄存器个数错误
if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH)) return 0x92; //读出寄存器个数错误
reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3]; //寄存器地址
reg_len = RX1_Buffer[5]; //读出寄存器个数
if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH)) return 0x93; //寄存器地址错误
if(reg_addr < REG_ADDRESS) return 0x93; //寄存器地址错误
j = reg_addr - REG_ADDRESS; //寄存器数据下标
TX1_Buffer[0] = SL_ADDR; //站号地址
TX1_Buffer[1] = 0x03; //读功能码
TX1_Buffer[2] = reg_len*2; //返回字节数
for(k=3, i=0; i<reg_len; i++,j++)
{
TX1_Buffer[k++] = (u8)(modbus_reg[j] >> 8); //数据为大端模式
TX1_Buffer[k++] = (u8)modbus_reg[j];
}
crc = MODBUS_CRC16(TX1_Buffer, k);
TX1_Buffer[k++] = (u8)(crc>>8); //CRC是小端模式
TX1_Buffer[k++] = (u8)crc;
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = k; //要发送的字节数
TI = 1; //启动发送
}
}
else return 0x90; //功能码错误
return 0; //解析正确
}
//========================================================================
// 函数:u8 Timer0_Config(u8 t, u32 reload)
// 描述: timer0初始化函数.
// 参数: t: 重装值类型, 0表示重装的是系统时钟数, 其余值表示重装的是时间(us).
// reload: 重装值.
// 返回: 0: 初始化正确, 1: 重装值过大, 初始化错误.
// 版本: V1.0, 2018-3-5
//========================================================================
u8 Timer0_Config(u8 t, u32 reload) //t=0: reload值是主时钟周期数, t=1: reload值是时间(单位us)
{
TR0 = 0; //停止计数
if(t != 0) reload = (u32)(((float)MAIN_Fosc * (float)reload)/1000000UL); //重装的是时间(us), 计算所需要的系统时钟数.
if(reload >= (65536UL * 12)) return 1; //值过大, 返回错误
if(reload < 65536UL) AUXR |= 0x80; //1T mode
else
{
AUXR &= ~0x80; //12T mode
reload = reload / 12;
}
reload = 65536UL - reload;
TH0 = (u8)(reload >> 8);
TL0 = (u8)(reload);
ET0 = 1; //允许中断
TMOD &= 0xf0;
TMOD |= 0; //工作模式, 0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装, 3: 16位自动重装, 不可屏蔽中断
TR0 = 1; //开始运行
return 0;
}
//========================================================================
// 函数: void timer0_ISR (void) interrupt TIMER0_VECTOR
// 描述: timer0中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2016-5-12
//========================================================================
void timer0_ISR (void) interrupt TIMER0_VECTOR
{
if(RX1_TimeOut != 0)
{
if(--RX1_TimeOut == 0) //超时
{
if(RX1_cnt != 0) //接收有数据
{
B_RX1_OK = 1; //标志已收到数据块
}
}
}
}
//========================================================================
// 函数: SetTimer2Baudraye(u16 dat)
// 描述: 设置Timer2做波特率发生器。
// 参数: dat: Timer2的重装值.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void SetTimer2Baudraye(u16 dat) // 选择波特率, 2: 使用Timer2做波特率, 其它值: 使用Timer1做波特率.
{
AUXR &= ~(1<<4); //Timer stop
AUXR &= ~(1<<3); //Timer2 set As Timer
AUXR |= (1<<2); //Timer2 set as 1T mode
TH2 = (u8)(dat >> 8);
TL2 = (u8)dat;
IE2 &= ~(1<<2); //禁止中断
AUXR |= (1<<4); //Timer run enable
}
//========================================================================
// 函数: void UART1_config(u32 brt, u8 timer, u8 io)
// 描述: UART1初始化函数。
// 参数: brt: 通信波特率.
// timer: 波特率使用的定时器, timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率.
// io: 串口1切换到的IO, io=0: 串口1切换到P3.0 P3.1, =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7, =3: 切换到P4.3 P4.4.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void UART1_config(u32 brt, u8 timer, u8 io) // brt: 通信波特率, timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1, =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7, =3: 切换到P4.3 P4.4.
{
brt = 65536UL - (MAIN_Fosc / 4) / brt;
if(timer == 2) //波特率使用定时器2
{
AUXR |= 0x01; //S1 BRT Use Timer2;
SetTimer2Baudraye((u16)brt);
}
else //波特率使用定时器1
{
TR1 = 0;
AUXR &= ~0x01; //S1 BRT Use Timer1;
AUXR |= (1<<6); //Timer1 set as 1T mode
TMOD &= ~(1<<6); //Timer1 set As Timer
TMOD &= ~0x30; //Timer1_16bitAutoReload;
TH1 = (u8)(brt >> 8);
TL1 = (u8)brt;
ET1 = 0; // 禁止Timer1中断
INT_CLKO &= ~0x02; // Timer1不输出高速时钟
TR1 = 1; // 运行Timer1
}
if(io == 1) {S1_USE_P36P37(); P3n_standard(0xc0);} //切换到 P3.6 P3.7
else if(io == 2) {S1_USE_P16P17(); P1n_standard(0xc0);} //切换到 P1.6 P1.7
else if(io == 3) {S1_USE_P43P44(); P4n_standard(0x18);} //切换到 P4.3 P4.4
else {S1_USE_P30P31(); P3n_standard(0x03);} //切换到 P3.0 P3.1
SCON = (SCON & 0x3f) | (1<<6); // 8位数据, 1位起始位, 1位停止位, 无校验
// PS = 1; //高优先级中断
ES = 1; //允许中断
REN = 1; //允许接收
}
//========================================================================
// 函数: void UART1_ISR (void) interrupt UART1_VECTOR
// 描述: 串口1中断函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void UART1_ISR (void) interrupt UART1_VECTOR
{
if(RI)
{
RI = 0;
if(!B_RX1_OK) //接收缓冲空闲
{
if(RX1_cnt >= RX1_Length) RX1_cnt = 0;
RX1_Buffer[RX1_cnt++] = SBUF;
RX1_TimeOut = 36; //接收超时计时器, 35个位时间
}
}
if(TI)
{
TI = 0;
if(TX1_number != 0) //有数据要发
{
SBUF = TX1_Buffer[TX1_cnt++];
TX1_number--;
}
else B_TX1_Busy = 0;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
/***********************************************************************/
/**********************51单片机与威纶通屏通讯***************************/
/*********************** *****************************/
/************************制作:威纶通技术部*****************************/
/***********************************************************************/
/***********************************************************************
晶振11.0592M
0X1-0X8与LED0-LED7对应,LED分别代表屏相应的位的状态,对应输出为P1口
6X1-6X8最后写入对应的值可以在数码管上显示,例如:6X1写入100数码管显示100,
往6X3里面写入200数码管显示200 ,数码管显示最后一次修改的寄存器的值。
/***********************************************************************/
#include<reg52.h> // 包含52单片机头文件,此头文件中包含52单片机中的资源定义,包括IO口,寄存器等
#include<intrins.h> //nop延时头文件,在延时函数中用到,调用一个_nop_()代表延时1us。
/* 宏定义无符号字符型、整形、长整形 */
#define uchar unsigned char //8位
#define ulint unsigned long //32位
#define uint unsigned int //16位
uchar idata Send_buf[30]; //发送数据数组
uchar idata Receive_buf[30]; //接收数据数组
uint idata disp[8]={0,0,0,0,0,0,0,0}; //字接收显示数组,用于存储6x1-6x8的十六位接收数据
uchar count=0,time=0; //count为收到数据的次数,time应用于发送03代码时,中间的字数量是多少个数值
uchar flag_finish=0,flag_led=0; //是否接收完成位,flag_led暂时不用
uint address_num=0; //接收到字的数值
uint begin_address=0,address_leg=0; //数值字开始位,数值字长度
uchar sign7,sign6,sign5,sign4,sign3,sign2,sign1,sign0; //用于判定发送的8个位,即0x1-0x8的位状态
//* LED亮灭情况表示相应的位状态,LED0-LED7分别对于0x1-0x8*//
sbit LED0=P1^0;
sbit LED1=P1^1;
sbit LED2=P1^2;
sbit LED3=P1^3;
sbit LED4=P1^4;
sbit LED5=P1^5;
sbit LED6=P1^6;
sbit LED7=P1^7;
sbit f3=P3^2;
sbit f4=P3^3;
/**************************数码管显示相关,DuanMa是显示0-9*****************************/
uchar code DuanMa[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};// 显示段码值0~9
/*****16位CRC检验表,低位在前,高位在后*******/
//////////////*高位表*///////////////////
uchar code auchCRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40
} ;
//////////////*低位表*///////////////////
uchar code auchCRCLo[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
0x40
} ;
////////*16位CRC校验函数,查表法*////////////////
uint crc16(uchar *puchMsg,uchar usDataLen)
{
uchar uchCRCHi = 0xFF ;
uchar uchCRCLo = 0xFF ;
uint uIndex ;
while (usDataLen--)
{
uIndex = uchCRCHi ^ *puchMsg++ ;
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;
uchCRCLo = auchCRCLo[uIndex] ;
}
return (((uint)(uchCRCLo) << 8) | uchCRCHi) ;
}
/////////*延时子程序,延迟N us*//////////
void delay_us(int n)
{
int i;
for(i=n;n<0;n--)
_nop_();
}
/////////*延时子程序,延迟N ms*//////////
void delay_ms(int n)
{
int i,j;
for(i=n;i>0;i--)
for(j=141;j>0;j--);
}
//////////数码管显示函数,最多显示五位数,数值仅限于0-9,该函数根据实际情况选择使用////////////////
void Display(uint Num)
{
uint i;
for (i=0;i<5; i++)
{
P2 = 0; //清除显示,防止数字重影
P0 = DuanMa[Num%10]; //显示最低一位数
delay_us(10);
P2 = 0x80>>i; //显示第一位数时点亮第一个数码管,第二位时点亮第二个数码管,以此类推。
Num = Num/10; //将变量除以10处理将显示下一位数
delay_ms(1);
}
}
/*************串口初始化函数***************/
/********设定的参数为9600,N,8,1*********/
void init_uart(void)
{
TH1=0xfa;
TL1=0xfa; // 波特率设为9600
TMOD=0x21; // 使用T1定时器,模式2
PCON=PCON | 0x80; // 启用波特率加强位
SCON=0x50; // 串口模式为2,即8位数据位,停止位为1,无校验
PS=1; // 设定串口中断优先级为最高
TR1=1; // 开始定时
ES=1; // 打开串口中断
EA=1; // 打开总中断
}
/******************发送函数******************/
/***********发送一个字符即一个字节***********/
void send(uchar num)
{
SBUF=num; //把字符放到发送数据缓存区SBUF,num可以是字符也可以是一个字节数据
while(!TI); //当发送完之后,TI自动置位,即TI=1,表示发送完成
TI=0; //当发送完之后TI不会自动置0,所以需手动将其置0,表示还没发送完数据
}
/**********************发送函数***********************/
/***********发送字符串或者一连串的字节数据************/
/****mydata表示要发送的字符串数组,num表示数组长度****/
void send_word(uchar *mydata,uchar num)
{
int i;
for(i=0;i<num;i++)
{
send(*(mydata+i));
}
count=0;
}
/********************中断接收函数**********************/
/****当数据接收完时,进入串口中断将接收的值放入数组****/
void comm() interrupt 4 using 1
{
if(RI) //当接收完一个字节的数据后,RI会自动置位,即RI=1
{
uchar a;
RI=0; //接受中断标志软件清零
a=SBUF; //把缓存在单片机的数据给a
Receive_buf[count]=a; //将接受数据放入预置数组
count++; //数组自动递增,用于存储下一个数据
if(count==8) //当接收完8个MODBUS的功能码之后,自动清0,从新接收(支持的01、05、03、06命令读取或者写入每次都发送8个字节)
{
count=0;
flag_finish=1; //接受完8个MODBUS功能码的标志位
}
}
}
/**************************MODBUS功能码处理***************************/
/********目前只能识别01,03,05,06功能码,而且只能挂载单台***********/
void command(void)
{
// uint begin_address=0,address_leg=0,crc_end;
uint crc_end,legg; //CEC校验值和03功能码发送的位的长度
char hi_type,low_type; //03、06功能码发送字节高低位
// if(Receive_buf[0]==0x01)
// {
if(Receive_buf[1] == 0x01) //01功能码
{
// begin_address=Receive_buf[2]<<8+Receive_buf[3];
// address_leg=Receive_buf[4]<<8+Receive_buf[5];
crc_end=crc16(Receive_buf,6); //校验
if(crc_end==Receive_buf[6]<<8 | Receive_buf[7]) //当校验一致时
{
hi_type=0; //高位状态
low_type=sign0 | sign1 | sign2 | sign3 | sign4 | sign5 | sign6 | sign7; //低位状态
// low_type=~P0;
Send_buf[0]=Receive_buf[0]; //站号
Send_buf[1]=Receive_buf[1]; //功能码
Send_buf[2]=0x02; //字节数
Send_buf[3]=low_type; //低位状态,即LED开关情况
Send_buf[4]=hi_type; //高位状态
crc_end=crc16(Send_buf,5); //校验
Send_buf[5]=crc_end%256; //校验低位
Send_buf[6]=crc_end/256; //校验高位
send_word(Send_buf,7); //发送返回屏
}
}
if(Receive_buf[1] == 0x05) //05功能码
{
begin_address=Receive_buf[3]; //开始地址
crc_end=crc16(Receive_buf,6); //校验
if(crc_end==Receive_buf[6]<<8|Receive_buf[7]) //校验正确时
{
if(Receive_buf[4]==0xff) //当为强制打开时
{
switch(begin_address) //对应的地址
{
case 0x00: LED0=0; sign0=0x01; break; //相应的LED点亮,记录下相应的P0位状态
case 0x01: LED1=0; sign1=0x02; break;
case 0x02: LED2=0; sign2=0x04; break;
case 0x03: LED3=0; sign3=0x08; break;
case 0x04: LED4=0; sign4=0x10; break;
case 0x05: LED5=0; sign5=0x20; break;
case 0x06: LED6=0; sign6=0x40; break;
case 0x07: LED7=0; sign7=0x80; break;
}
}
else //当强制为OFF时
{
// flag_led=0;
switch(begin_address)
{
case 0x00: LED0=1; sign0=0; break; //相应的LED熄灭,记录下相应的P0位状态
case 0x01: LED1=1; sign1=0; break;
case 0x02: LED2=1; sign2=0; break;
case 0x03: LED3=1; sign3=0; break;
case 0x04: LED4=1; sign4=0; break;
case 0x05: LED5=1; sign5=0; break;
case 0x06: LED6=1; sign6=0; break;
case 0x07: LED7=1; sign7=0; break;
}
}
Send_buf[0]=Receive_buf[0]; //站号
Send_buf[1]=Receive_buf[1]; //功能码
Send_buf[2]=Receive_buf[2]; //起始高位
Send_buf[3]=Receive_buf[3]; //起始低位
Send_buf[4]=Receive_buf[4]; //返回状态
Send_buf[5]=Receive_buf[5]; //返回状态
crc_end=crc16(Send_buf,6); //校验
Send_buf[6]=crc_end%256; //校验低位
Send_buf[7]=crc_end/256; //校验高位
send_word(Send_buf,8); //发送返回屏
}
}
if(Receive_buf[1]==0x03) //03功能码
{
crc_end=crc16(Receive_buf,6); //校验
if(crc_end == Receive_buf[6]<<8|Receive_buf[7]) //校验一致
{
begin_address=Receive_buf[2]<<8|Receive_buf[3];
address_leg=Receive_buf[4]<<8 | Receive_buf[5]; //总寄存器长度
hi_type=0;
Send_buf[0]=Receive_buf[0]; //站号
Send_buf[1]=Receive_buf[1]; //功能码
legg=(uint)address_leg*2; //字节数
Send_buf[2]=address_leg*2;; //字节数
for(time=0;time<legg;time++) //发送相应字节
{
if(!(time%2)) //字高位为0
Send_buf[time+3] = disp[time/2+begin_address]/256; //字高字节
else
Send_buf[time+3] = disp[time/2+begin_address]%256; //字低字节
}
crc_end=crc16(Send_buf,legg+3); //校验
Send_buf[legg+3]=crc_end%256; //校验低位
Send_buf[legg+4]=crc_end/256; //校验高位
send_word(Send_buf,legg+5); //返回屏
}
}
if(Receive_buf[1]==0x06) //06功能码
{
begin_address=Receive_buf[2]<<8|Receive_buf[3]; //写入地址
address_num=Receive_buf[4]<<8|Receive_buf[5]; //寄存器值
crc_end=crc16(Receive_buf,6); //校验
if(crc_end==Receive_buf[6]<<8|Receive_buf[7]) //校验正确
{
disp[begin_address]=address_num; //存入数组,给03功能码调用数值
Send_buf[0]=Receive_buf[0]; //站号
Send_buf[1]=Receive_buf[1]; //功能码
Send_buf[2]=Receive_buf[2]; //开始高位
Send_buf[3]=Receive_buf[3]; //开始低位
Send_buf[4]=Receive_buf[4]; //字高位
Send_buf[5]=Receive_buf[5]; //字低位
crc_end=crc16(Send_buf,6); //校验
Send_buf[6]=crc_end%256; //校验低位
Send_buf[7]=crc_end/256; //校验高位
send_word(Send_buf,8); //返回屏
}
}
}
/*------------------ main code following -----------------*/
void main(void)
{
init_uart(); //初始化串口
while(1)
{
Display(address_num); //在开发板数码管上显示相应位的数值,发送或者接受数据会暂停显示(数码管闪烁)
//按钮输入
if(!f3) //判断按钮是否按下
{
delay_ms(2); //延时
while (!f3);
//delay_ms(2);
LED2=!LED2;
if(LED2)
sign2=0x0;
else
sign2=0x4;
}
if(!f4)
{
delay_ms(2);
while (!f4);
//delay_ms(2);
LED3=!LED3;
if(LED3)
sign3=0x0;
else
sign3=0x8;
}
while(flag_finish) //当接收完成时
{
command(); //识别相应的功能码
flag_finish=0; //重新接收
}
}
}