本文展示了一个基于51单片机的Modbus从机通信系统实现代码,主要功能包括:
-
通信协议处理:
- 实现了Modbus RTU协议的01-06、10功能码解析,包括线圈/寄存器读写操作
- 包含CRC16校验计算和错误响应机制
-
硬件控制:
- 通过P1、P2口控制继电器和蜂鸣器
- 支持手动/自动模式切换
- 提供32个位变量和32个寄存器变量空间
-
数据存储:
- 使用片内EEPROM存储关键数据
- 实现扇区擦除、字节读写等Flash操作函数
-
系统架构:
- 采用串口中断+T0定时器实现通信超时检测
- 定义清晰的数据结构区(位寻址/字寻址)
- 包含初始化、数据保存等系统函数
该代码实现了完整的Modbus从站功能,适用于工业控制场景,具有结构清晰、功能完备的特点,可直接应用于51系列单片机开发。
#include <REG52.H>
#include <intrins.H>
#define uint unsigned int
#define uchar unsigned char
typedef unsigned int u16;
typedef unsigned char u8;
typedef unsigned long u32;
extern u8 idata sendBuf75; //定义发送数组,最大允许接收16个字,32个字节
extern u8 idata receBuf75; //定义接收数组,最大允许接收16个字,32个字节写入
extern u8 *ps;
extern u8 *pr;
extern u8 rece_index;
extern u8 Reciver_bit;
sbit relay=P2^0;
sbit bee=P2^1; //main h
u16 xdata Word32; //定义字寻址区,32个short型 ,有符号型
u8 xdata Bit32; // 定义位寻址区,10-31内部位,0-9输出口
u8 xdata inputBit32;
u16 *pw=Word;
u16 *pb=Bit;
u16 sendlength; //Modbus_slave_c51_2011_10_09.c
void AnalyzeRecieve();
void send_comm();
void Recirve_01();
void Recirve_02();
void Recirve_03();
void Recirve_04();
void Recirve_05();
void Recirve_06();
void Recirve_10();
void errorsend(u8 func,u8 type);
u16 CalcCrc(unsigned char *chData,unsigned short uNo); //Modbus_slave_c51_2011_10_09.H
u8 Reciver_Buf;
u8 rece_index=0;
u8 idata sendBuf75; //定义发送数组,最大允许发送32个字,64个字节
u8 idata receBuf75; //定义接收数组,最大允许接收32个字,64个字节写入
u8 *ps=sendBuf;
u8 *pr=receBuf;
u8 comm_END;
u8 comm_time_out;
u8 Reciver_bit=0;
u16 R_ISP_L;
u16 R_ISP_H; // Main.c
//#define nop() //定义一个空函数
/***************************************************************
* 定义ISP/IAP操作命令,控制寄存器地址:ISP_CMD E5H *
* 1、0x00: 待机命令,ISP无操作; *
* 2、0x01: 对用户的应用程序FLASH区及数据FLASH区字节读 *
* 3、0x02: 对用户的应用程序FLASH区及数据FLASH区字节编程 *
* 4、0x03: 对用户的应用程序FLASH区及数据FLASH区字节擦除 *
***************************************************************/
#define RDCommand 0x01
#define PRGCommand 0x02
#define ERASECommand 0x03
#define waittime 0x01 //定义CPU的等待时间 SAVE DATA.c
void ISP_enable(void);
void ISP_disable(void);
void go_ISP(void);
void sectorerase(u16 addr);
void write_ISP(u16 st_addr,u8 w_data);
u8 read_ISP(u16 rd_addr); //SAVE DATA.H
/***************************************/
void AnalyzeRecieve() //分析并生成响应报文
{
u16 _crc;
u8 a1,a2;
_crc=CalcCrc(receBuf,(rece_index-2)); //计算校验CRC
a1=_crc&0xff; //CRC低字节
a2=(_crc>>8)&0xff; //CRC高字节
if(a1==*(pr+(rece_index-2))&&a2==*(pr+rece_index-1)) //校验正确
{
switch(*(pr+1))
{
case 0x01:Recirve_01();break; //读位状态
case 0x02:Recirve_02();break; //读输入位状态
case 0x03:Recirve_03();break; //读寄存器
case 0x04:Recirve_04();break; //读输入寄存器
case 0x05:Recirve_05();break; //写单个位状态
case 0x06:Recirve_06();break; //写单个寄存器
case 0x10:Recirve_10();break; //写多个寄存器
default:errorsend(*(pr+1),0x04);break; //不支持的功能码
}
}
else //返回校验错误代码
{
switch(*(pr+1))
{
case 0x01:errorsend(0x01,0x08);break;
case 0x02:errorsend(0x02,0x08);break;
case 0x03:errorsend(0x03,0x08);break;
case 0x04:errorsend(0x04,0x08);break;
case 0x05:errorsend(0x05,0x08);break;
case 0x06:errorsend(0x06,0x08);break;
case 0x10:errorsend(0x10,0x08);break;
}
}
}
/***********从机响应函数***********************/
void send_comm( )
{
u8 i;
ES=0; //先关闭串口中断,以免产生通信再次中断
for(i=0;i<sendlength;i++)
{
SBUF=*(ps+i); //发送响应报文
while(!TI); //等待报文结束
TI=0;
}
ES=1; //发送完成,打开串口中断
}
/*分析01功能码报文,产生响应报文*/
/***************************************/
void Recirve_01()
{
u16 startadd;
u16 bit_num;
u8 startaddH,startaddL;
u8 bit_numH,bit_numL;
u16 i,j;
u16 aakj;
startaddH=*(pr+2);
startaddL=*(pr+3);
bit_numH=*(pr+4);
bit_numL=*(pr+5);
startadd=(startaddH<<8)+startaddL; //要返回的起始地址
bit_num=(bit_numH<<8)+bit_numL; //要读的字节数量,单位是位
if((startadd+bit_num)>=32) //最多允许32个位,从第4个位开始读
{
errorsend(0x01,0x02); //响应寄存器数量超出范围
}
else
{
*(ps+0)=0x01; //站号
*(ps+1)=0x01; //功能码
if((bit_num%8)==0)
*(ps+2)=(bit_num)/8; //要返回的字节数
else
*(ps+2)=((bit_num)/8)+1; //不能整除8的时候要多返回一个字节
for(i=0;i<*(ps+2);i++)
{
*(ps+3+i)=0; //先清零复位
for(j=0;j<8;j++) //每8个位状态组成一个字节返回
{
*(ps+3+i)=(u8)((*(pb+startadd+i*8+j)&0x01)<<j)+*(ps+3+i); //低位在前,高位在后
}
}
aakj=CalcCrc(sendBuf,(*(ps+2)+3)); //CRC校验
*(ps+3+*(ps+2))=(u8)(aakj&0xff); //CRC低字节
*(ps+4+*(ps+2))=(u8)((aakj>>8)&0xff); //CRC高字节
sendlength=*(ps+2)+5;
}
}
/*分析02功能码报文,产生响应报文*/
/***************************************/
void Recirve_02()
{
u16 startadd;
u16 bit_num;
u8 startaddH,startaddL;
u8 bit_numH,bit_numL;
u16 i,j;
u16 aakj;
startaddH=*(pr+2);
startaddL=*(pr+3);
bit_numH=*(pr+4);
bit_numL=*(pr+5);
startadd=(startaddH<<8)+startaddL; //要返回的起始地址
bit_num=(bit_numH<<8)+bit_numL; //要读的字节数量,单位是位
if((startadd+bit_num)>=32||startadd>3) //本案例中只有4个输入位可供读
{
errorsend(0x01,0x02); //响应寄存器数量超出范围
}
else
{
for(i=2;i<6;i++)
inputBiti-2=(~(P3>>i))&0x01; //先读出输入口的状态
for(i=4;i<32;i++)
inputBiti=0; //没有位状态,清零
*(ps+0)=0x01; //站号
*(ps+1)=0x02; //功能码
if((bit_num%8)==0)
*(ps+2)=(bit_num)/8; //要返回的字节数
else
*(ps+2)=((bit_num)/8)+1; //不能整除8的时候要多返回一个字节
for(i=0;i<*(ps+2);i++)
{
*(ps+3+i)=0; //先清零复位
for(j=0;j<8;j++) //每8个位状态组成一个字节返回
{
*(ps+3+i)=(u8)((inputBitstartadd+i\*8+j&0x01)<<j)+*(ps+3+i); //低位在前,高位在后
}
}
aakj=CalcCrc(sendBuf,(*(ps+2)+3)); //CRC校验
*(ps+3+*(ps+2))=(u8)(aakj&0xff); //CRC低字节
*(ps+4+*(ps+2))=(u8)((aakj>>8)&0xff); //CRC高字节
sendlength=*(ps+2)+5;
}
}
/***************************************/
/*分析03功能码报文,产生响应报文*/
void Recirve_03()
{
u16 startadd;
u16 length;
u8 startaddH,startaddL;
u8 lengthH,lengthL;
u16 i;
u16 aakj;
startaddH=*(pr+2);
startaddL=*(pr+3);
lengthH=*(pr+4);
lengthL=*(pr+5);
startadd=(startaddH<<8)+startaddL; //要返回的起始地址
length=(lengthH<<8)+lengthL; //要读的字节数量
if((startadd+length)>32) //最多只能返回32个寄存器,64个字节,注意返回的长度不能超过发送数组长度,否则会溢出导致错误
{
errorsend(0x03,0x02); //响应寄存器数量超出范围
}
else
{
*(ps+0)=0x01; //站号
*(ps+1)=0x03; //功能码
*(ps+2)=length*2; //要返回的字节数是请求报文的第五个字节*2
for(i=0;i<length;i++)
{
*(ps+3+i*2)=((*(pw+startadd+i))>>8)&0xff; //返回寄存器值的高字节
*(ps+4+i*2)=(*(pw+startadd+i))&0xff; //返回寄存器值得低字节
}
aakj=CalcCrc(sendBuf,(length*2)+3); //CRC校验
*(ps+3+length*2)=(u8)(aakj&0xff); //CRC低字节
*(ps+4+length*2)=(u8)((aakj>>8)&0xff); //CRC高字节
sendlength=(length*2)+5;
}
}
/*分析04功能码报文,产生响应报文*/
/*这边返回的是输入口的寄存器值*/
/***************************************/
/***************************************/
void Recirve_04()
{
u16 startadd;
u16 bit_num;
u8 startaddH,startaddL;
u8 bit_numH,bit_numL;
u16 i,j;
u16 aakj;
startaddH=*(pr+2);
startaddL=*(pr+3);
bit_numH=*(pr+4);
bit_numL=*(pr+5);
startadd=(startaddH<<8)+startaddL; //要返回的起始地址
bit_num=(bit_numH<<8)+bit_numL; //要读的字节数量,单位是位
if((startadd+bit_num)>=32||startadd>1)//本案例中只有4个输入位1个寄存器可供读
{
errorsend(0x01,0x02); //响应寄存器数量超出范围
}
else
{
for(i=2;i<6;i++)
inputBiti-2=(~(P3>>i))&0x01; //先读出输入口的状态
for(i=4;i<32;i++)
inputBiti=0; //没有位状态,清零
*(ps+0)=0x01; //站号
*(ps+1)=0x04; //功能码
*(ps+2)=bit_num*2;
for(i=0;i<*(ps+2);i++)
{
*(ps+3+i)=0; //先清零复位
for(j=0;j<8;j++) //每8个位状态组成一个字节返回
{
*(ps+3+i)=(u8)((inputBitstartadd+i\*8+j&0x01)<<j)+*(ps+3+i); //低位在前,高位在后
}
}
aakj=CalcCrc(sendBuf,(*(ps+2)+3)); //CRC校验
*(ps+3+*(ps+2))=(u8)(aakj&0xff); //CRC低字节
*(ps+4+*(ps+2))=(u8)((aakj>>8)&0xff); //CRC高字节
sendlength=*(ps+2)+5;
}
}
/*分析05功能码报文,产生响应报文*/
/***************************************/
void Recirve_05()
{
u16 startadd;
u8 startaddH,startaddL;
u8 bit_valueH,bit_valueL;
u16 aakj;
startaddH=*(pr+2);
startaddL=*(pr+3);
bit_valueH=*(pr+4);
bit_valueL=*(pr+5);
startadd=(startaddH<<8)+startaddL; //要写入的地址
if(startadd>=32)
{
errorsend(0x01,0x02); //响应寄存器数量超出范围
}
else
{
if(bit_valueH==0xff&&bit_valueL==0x00) //置位线圈
*(pb+startadd)=1;
if(bit_valueH==0x00&&bit_valueL==0x00) //复位线圈
*(pb+startadd)=0;
*(ps+0)=0x01; //站号
*(ps+1)=0x05; //功能码
*(ps+2)=startaddH; //地址高字节
*(ps+3)=startaddL; //地址低字节
*(ps+4)=bit_valueH; //地址高字节
*(ps+5)=bit_valueL; //地址低字节
aakj=CalcCrc(sendBuf,6); //CRC校验
*(ps+6)=(u8)(aakj&0xff); //CRC低字节
*(ps+7)=(u8)((aakj>>8)&0xff); //CRC高字节
sendlength=8;
}
}
/***************************************/
/*分析06功能码报文,产生响应报文*/
void Recirve_06()
{
u16 startadd;
u16 wdata_06;
u8 startaddH,startaddL;
u8 wdataH_06,wdataL_06;
u16 aakj;
startaddH=*(pr+2);
startaddL=*(pr+3);
wdataH_06=*(pr+4);
wdataL_06=*(pr+5);
startadd=(startaddH<<8)+startaddL; //要写入的起始地址
wdata_06=(wdataH_06<<8)+wdataL_06; //要写入的数值
if(startadd>32) //寄存器地址超出范围
errorsend(0x06,0x02); //响应寄存器数量超出范围
else if(wdata_06<-32768||wdata_06>32767)
errorsend(0x06,0x03); //响应数据错误
else
{
*(pw+startadd)=wdata_06; //将数值写入寄存器
*(ps+0)=0x01; //站号
*(ps+1)=0x06; //功能码
*(ps+2)=startaddH; //返回地址高字节
*(ps+3)=startaddL; //返回地址低字节
*(ps+4)=(u8)(((*(pw+startadd))>>8)&0xff); //返回寄存器值高字节
*(ps+5)=(u8)(*(pw+startadd)&0xff); //返回寄存器值低字节
aakj=CalcCrc(sendBuf,6); //CRC校验
*(ps+6)=(u8)(aakj&0xff); //CRC低字节
*(ps+7)=(u8)((aakj>>8)&0xff); //CRC高字节
sendlength=8; //返回8个字节长度
}
}
/***************************************/
/*分析10功能码报文,产生响应报文*/
void Recirve_10()
{
u16 startadd;
u16 register_num;
u8 startaddH,startaddL;
u8 register_numH,register_numL;
u8 length;
u16 i;
u16 aakj;
startaddH=*(pr+2);
startaddL=*(pr+3);
register_numH=*(pr+4);
register_numL=*(pr+5);
startadd=(startaddH<<8)+startaddL; //要返回的起始地址
register_num=(register_numH<<8)+register_numL; //寄存器数量
length=*(pr+6); //要写的字节数量
if((startadd+(length/2))>32) //最多允许写32个寄存器
{
errorsend(0x10,0x02); //响应寄存器数量超出范围
}
else
{
for(i=0;i<(length/2);i++) //将值写入寄存器
{
*(pw+startadd+i)=(*(pr+7+i*2)<<8)+*(pr+8+i*2)&0xff;
}
*(ps+0)=0x01; //站号
*(ps+1)=0x10; //功能码
*(ps+2)=startaddH; //返回地址高位
*(ps+3)=startaddL; //返回地址低位
*(ps+4)=register_numH;
*(ps+5)=register_numL;
aakj=CalcCrc(sendBuf,6); //CRC校验
*(ps+6)=(u8)(aakj&0xff); //CRC低字节
*(ps+7)=(u8)((aakj>>8)&0xff); //CRC高字节
sendlength=8;
}
}
/***************************************/
/*错误返回*/
void errorsend(u8 func,u8 type)
{
u16 _crc;
u8 crcH,crcL;
*(ps+0)=0x01; //返回站号
switch(type)
{
case 0x08:
*(ps+1)=0x80+func; //返回错误功能码
*(ps+2)=0x08; //返回错误代码,08:CRC校验错误
break;
case 0x01:
*(ps+1)=0x80+func; //返回错误功能码
*(ps+2)=0x01; //返回错误代码,01:功能码错误
break;
case 0x02:
*(ps+1)=0x80+func; //返回错误功能码
*(ps+2)=0x02; //返回错误代码,02:地址错误
break;
case 0x03:
*(ps+1)=0x80+func; //返回错误功能码
*(ps+2)=0x03; //返回错误代码,03:数据错误
break;
case 0x04:
*(ps+1)=0x80+func; //返回错误功能码
*(ps+2)=0x04; //返回错误代码,04:不支持的功能码
break;
}
_crc=CalcCrc(sendBuf,3);
crcH=(u8)((_crc>>8)&0xff);
crcL=(u8)(_crc&0xff);
*(ps+3)=crcL; //校验低字节
*(ps+4)=crcH; //校验高字节
sendlength=5;
}
/*************************************************
crc16校验码计算函数,计算算法:
1、设置crc校验寄存器的初值为0xffff;
2、将报文的第一个字节与crc寄存器的低字节异或,结果存入crc寄存器
3、判断crc的第一位是否为1,如果是1,crc右移1位后和0xa001异或,如果为0,则再移1位;
4、重复步骤3,直到完成8个位;
5、重复步骤2、3、4直到完成所有字节
6、返回计算结果
***********************************************/
u16 CalcCrc(unsigned char *chData,unsigned short uNo)
{
u16 crc=0xffff;
u16 i,j;
for(i=0;i<uNo;i++)
{
crc^=chDatai;
for(j=0;j<8;j++)
{
if(crc&1)
{
crc>>=1;
crc^=0xA001;
}
else
crc>>=1;
}
}
return (crc);
}
/************************************************/
void ISP_enable(void)
{
// ISP_CONTR=ISP_CONTR&0X18; //初始化SP/IAP控制寄存器;
ISP_CONTR=waittime; //写入硬件延时,注意这边是"|"运算
ISP_CONTR=ISP_CONTR|0x80; //打开ISPEN,运行写入,注意是"|"运算
}
/*关闭ISP/IAP功能*/
void ISP_disable(void)
{
ISP_CONTR=ISP_CONTR&0x00; //关闭ISPEN,运行写入,注意是"&"运算
ISP_TRIG=0x00; //清空ISP命令控制触发器
ISP_CMD=0x00;
// EA=1; //打开中断
}
/*建立公用触发函数*/
void go_ISP(void)
{
ISP_TRIG=0x46; //触发ISP/IAP命令字节1,(固定)
ISP_TRIG=0xB9; //触发ISP/IAP命令字节2,(固定)
nop(); //执行一个空函数
}
/*扇区擦除函数*/
void sectorerase(u16 addr)
{
ISP_ADDRH=(u8)(addr>>8); /*取地址的高位*/
ISP_ADDRL=(u8)(addr&0xff); //地址的低位
// EA=0; //关闭总中断
ISP_enable(); //打开ISP/IAP功能
ISP_CMD=ERASECommand; //从新给ISP命令寄存器赋值,这边赋的是0x03,表示擦除
go_ISP(); //执行触发命令,将擦除命令写入
ISP_disable(); //关闭ISP功能
}
/*写数据函数(单个字节写入)*/
void write_ISP(u16 st_addr,u8 w_data)
{
ISP_DATA=w_data; //将要写入的数据存入ISP FLASH数据寄存区
ISP_ADDRH=(u8)(st_addr>>8); /*取地址的高位*/
ISP_ADDRL=(u8)(st_addr&0xff); //地址的低位
// EA=0; //关闭总中断
ISP_enable(); //打开ISP/IAP功能
ISP_CMD=PRGCommand; //从新给ISP命令寄存器赋值,这边赋的是0x02,表示写入
go_ISP(); //触发写入
ISP_disable(); //关闭ISP功能
}
/*读数据函数(读单个字节)*/
u8 read_ISP(u16 rd_addr)
{
ISP_ADDRH=(u8)(rd_addr>>8); /*取地址的高位*/
ISP_ADDRL=(u8)(rd_addr&0x00ff); //地址的低位
ISP_CMD=ISP_CMD&0xF8; // 清空ISP命令寄存器的第三位
// EA=0; //关闭总中断
ISP_enable(); //打开ISP/IAP功能
ISP_CMD=RDCommand; //从新给ISP命令寄存器赋值,这边赋的是0x01,表示读出
go_ISP(); //触发写入
ISP_disable(); //关闭ISP功能
return (ISP_DATA); //返回读出的数据
}
/************************************************/
/*初始化函数*/
void init()
{
int i;
TMOD=0X21; //设定定时器1的工作方式为2,及8位初值自动重装的8位定时器,用于产生波特率,T0工作方式1,用于判断通信帧结束
TH1=0XFD; //设置定时器的初值为0xfd,是按照9600的波特率计算出来的数值
TL1=0XFD; //定时器高低位数值一样,方式2下,定时器自动将高位的数值装入低位,所以设定的时间要一致
TH0=(65536-11111)/256; //T0设定为1ms定时器, 用于判断通信帧结束,同时可用于其他的定时作用
TL0=(65536-11111)%256; //T0低位
TR1=1; //启动T1定时器
REN=1; //运行串口接收数据,REN=1允许串口接收,REN=0禁止串口接收数据
SM0=0; //设置串口工作方式为1,即10位异步通信,1起始位,8数据位,1停止位
SM1=1; //同上,REN、SM0、SM1同属于SCON寄存器,但是SCON寄存器允许位寻址
EA=1; //开总中断
ES=1; //开串口中断
ET0=1; //开定时器中断
TR0=1; //启动T0定时器
P1=0xff; //复位所有输出
relay=1;
bee=1;
for(i=0;i<32;i++) //复位所有
{
*(pb+i)=0;
*(pw+i)=0;
inputBiti=0;
}
for(i=0;i<32;i++) //读出存在内部E2PROM内的值
{
R_ISP_L=read_ISP(0x2000+i*2); //读出数据低位
R_ISP_H=read_ISP(0x2000+i*2+1); //读出数据高位
*(pw+i)=((R_ISP_H<<8)&0xff00)+(R_ISP_L&0Xff); //组合数据
}
}
/*********************************************************************************
手动调试函数,通过在触摸屏上操作相对应的位来控制单片机的手动输出
相对应的位说明:
0x0: 手动自动切换位,0:自动,1:手动
0x1-0x10;输出口1-10;
***********************************************************************************/
void adj()
{
u8 i,j=0; //j要有初值=0;
for(i=1;i<9;i++)
{
j=(u8)((*(pb+i)&0x01)<<(i-1))+j;
}
P1=~j;
relay=(~(*(pb+9)))&0x01;
bee=(~(*(pb+10)))&0x01;
}
/**********************************************************************************
将数值保存在内部E2PROM中
0x11:在触摸屏上的保存按钮
************************************************************************************/
void saveData()
{
u8 i;
sectorerase(0x2000); //擦除扇区,这边写入的是第一扇区,在擦除是整个扇区被擦除
for(i=0;i<32;i++)
{
write_ISP((0X2000+i*2),(*(pw+i)&0xff)); //保存低8位数据
write_ISP((0x2000+i*2+1),((*(pw+i)>>8)&0xff)); //保存高8位数据
}
*(pb+11)=0; //保存按妞复位
}
/*串口中断服务*/
void ser() interrupt 4 //串口中断的序号是4
{
RI=0; //产生串口中断时RI被硬件置1,在串口中断服务程序中需要用软件清0;
comm_END=4; //9600的波特率下,等待3.5个字节需要约4ms的时间
Reciver_bit=1; //产生一次中断置一次位
Reciver_Buf=SBUF; //将接收到的数据赋给一个变量;
receBufrece_index++=Reciver_Buf; //将接收到的数据存入接收数组
}
/*T0中断服务程序*/
void comm_stop() interrupt 1
{
u8 i;
TH0=(65536-11111)/256; //重装T0
TL0=(65536-11111)%256; //T0低位
if(Reciver_bit&&comm_END!=0)
comm_END--;
if(comm_END==0)
{
Reciver_bit=0; //帧接收,对标志位清零
if((*(pr+0)==0x01||*(pr+0)==0x00)&&rece_index>=8) //判断是否为本站地址或者是广播地址,有效报文的字节数量最少是8个字节
{
AnalyzeRecieve(); //分析并生成响应报文
if(*(pr+0)==0x01) //广播模式下不用返回
send_comm(); //发送响应报文
}
comm_END=4; //重新赋延时初值
rece_index=0; //数组长度清零
for(i=0;i<75;i++) //清空接收数组
receBufi=0;
Reciver_Buf=0;
}
}
/*主函数*/
/****************************************/
void main()
{
init(); //执行初始化程序
while(1) //执行运行程序
{
if(*(pb+0)) adj(); //手动调试
if(*(pb+11)) saveData(); //保存数值
}
}