基于51单片机的Modbus从机通信系统

本文展示了一个基于51单片机的Modbus从机通信系统实现代码,主要功能包括:

  1. 通信协议处理

    • 实现了Modbus RTU协议的01-06、10功能码解析,包括线圈/寄存器读写操作
    • 包含CRC16校验计算和错误响应机制
  2. 硬件控制

    • 通过P1、P2口控制继电器和蜂鸣器
    • 支持手动/自动模式切换
    • 提供32个位变量和32个寄存器变量空间
  3. 数据存储

    • 使用片内EEPROM存储关键数据
    • 实现扇区擦除、字节读写等Flash操作函数
  4. 系统架构

    • 采用串口中断+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(); //保存数值

}

}

相关推荐
elseif1231 小时前
【C++】vector 详细版
开发语言·c++·算法
codingPower1 小时前
JAVA后端安全进阶:基于HMAC-SHA256+Nonce+Timestamp的API防重放攻击方案
java·开发语言·spring boot·安全
暗冰ཏོ1 小时前
Go 语言从入门到后端项目实战完整指南
开发语言·后端·golang·go·go语言
Xin_ye100861 小时前
C# 零基础到精通教程 - 第十七章:前端集成——Blazor 基础
开发语言·c#
LDR0061 小时前
LDR6020:多 Type‑C 端口角色管理与外设上电顺序的智慧核心
c语言·开发语言·云计算
小杍随笔2 小时前
【Rust 工具链管理完全指南:rustup toolchain 命令实战详解】
开发语言·后端·rust
五月君_2 小时前
放弃 Python,Kimi 用 TS + Node.js 重写了一个 Kimi Code
开发语言·python·node.js
Cloud_Shy6182 小时前
解读《Effective Python 3rd Edition》:从练气到老魔
开发语言·python
雨辰AI2 小时前
MySQL 迁移至达梦 DM9 完整改造指南|99% SQL 零改动
java·开发语言·数据库·sql·mysql·政务