谈谈单片机串口/Modbus 485通信对eeprom数据修正

单位:浙江省江山江汇电气有限公司
作者:郑贤亨 2024年05月26日

   在一些仪器控制仪表中,经常会用到各参数的设定、存储以及各种设备间的通信。今天,也谈谈单片机串口/Modbus 485通信对eeprom数据修正。
	类似这种数据的通信与存储,一般情况都会以串口为主,扩展出RS485接口,也可扩展TCP、wifi接口,只不过走的物理层不同而已,其数据流是一样的,接收主机的解析函是一样的。
	本文以数显温湿度控制器主板为例,主控STC8G1K08(20PIN)可以说明小而全,另外外接RS485接口SN75176芯片。
	进入正题,首先要进行Modbus 通信,主软件中需添加crc_com.c/crc_com.h

#include “STC8Gxxx.h”
#include “crc_com.h”

// CRC-16/MODBUS x16+x15+x2+1 0x8005

/* CRC16计算函数,ptr-数据指针,len-数据长度,返回值-计算出的CRC16数值 */

unsigned int GetCRC16(unsigned char *ptr, unsigned char len)

{
unsigned int index;

unsigned char crch = 0xFF; //高CRC字节

unsigned char crcl = 0xFF; //低CRC字节

unsigned char code TabH[] = { //CRC高位字节值表

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  

} ;

unsigned char code TabL[] = { //CRC低位字节值表

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  

} ;

while (len–) //计算指定长度的CRC

{

index = crch ^ *ptr++;

crch = crcl ^ TabH[index];

crcl = TabL[index];

}

return ((crch<<8) | crcl);

}

添加crc.h
#ifndef _crc_com_H
#define _crc_com_H

#include “STC8Gxxx.h”

unsigned int GetCRC16(unsigned char *ptr, unsigned char len);

#endif
以上都是为Modbus 485通信查表校验用的。
并添加官方的EEProm所用的操作函数:
/***********************************************

地址温湿度设定数据的存储
***********************************************/
void IapIdle()
{
IAP_CONTR = 0; // 关闭 IAP功能
IAP_CMD = 0; // 清除命令寄存器
IAP_TRIG = 0; // 清除触发寄存器
IAP_ADDRH = 0x80; // 将地址设置到非 IAP区域
IAP_ADDRL = 0;
}

char IapRead(int addr)
{
char dat;
IAP_CONTR = 0x80; // 使能 IAP
IAP_TPS = 12; // 设置擦除等待参数 12MHz
IAP_CMD = 1; // 设置 IAP 读命令
IAP_ADDRL = addr; // 设置 IAP 低地址
IAP_ADDRH = addr >> 8; // 设置 IAP 高地址
IAP_TRIG = 0x5a; // 写触发命令 (0x5a)
IAP_TRIG = 0xa5; // 写触发命令 (0xa5)
nop();
dat = IAP_DATA; // 读 IAP 数据
IapIdle();
return dat;
}

void IapProgram(int addr, char dat)
{
IAP_CONTR = 0x80; // 使能 IAP
IAP_TPS = 12; // 设置擦除等待参数 12MHz
IAP_CMD = 2; // 设置 IAP 写命令
IAP_ADDRL = addr; // 设置 IAP 低地址
IAP_ADDRH = addr >> 8; // 设置 IAP 高地址
IAP_DATA = dat; // 写 IAP 数据
IAP_TRIG = 0x5a; // 写触发命令 (0x5a)
IAP_TRIG = 0xa5; // 写触发命令 (0xa5)
nop();
IapIdle(); // 关闭 IAP 功?
}

void IapErase(int addr)
{
IAP_CONTR = 0x80; // 使能 IAP
IAP_TPS = 12; // 设置擦除等待参数 12MHz
IAP_CMD = 3; // 设置 IAP 擦除命令
IAP_ADDRL = addr; // 设置 IAP 低地址
IAP_ADDRH = addr >> 8; // 设置 IAP 高地址
IAP_TRIG = 0x5a; // 写触发命令 (0x5a)
IAP_TRIG = 0xa5; // 写触发命令 (0xa5)
nop();
IapIdle(); // 关闭 IAP 功?
}

以上这些只是移植官网程序序毕竟人家是权威。
在主函数当中,在例只举三种参数的配置示例:
1:接收主的地址 RS485通信址addcode 存入EEprom:0x0404
2:温度设定参数savewd 存入EEprom:0x0400
3:湿度设定参数savesd 存入EEprom:0x0402
接下看一下主程序启动前操作:

savewd=IapRead(0x0400);
savesd=IapRead(0x0402);
addcode= IapRead(0x0404);
 if(savewd>99||savewd<=0)
{
	savewd=10;
}
IapErase(0x0400); 
	IapProgram(0x0400, savewd);
 if(savesd>99||savesd<=0)
{
	savesd=75;
	//IapErase(0x0402); 
}
IapProgram(0x0402, savesd);
	 if(addcode>101||addcode<=0)
{
	addcode=1;
	//IapErase(0x0402); 
}
IapProgram(0x0404, addcode);

分别从EEPROM的相应存储空间中读要数据供主程使用,如果数据值越界分别给赋初值,并执行初值或正确的设定值写到对应的地址空间中。
如果主机按键操作修改的各设定值:
if(!KEYIN)//此时按建S1被按下了要进入设置页面
{

		Delay20ms();
			
			if(!KEYIN)
			{
							
				while(!KEYIN);
				savewd=IapRead(0x0400);    
				savewd++;
			
				if(savewd>=100){savewd=0;}
		
			}
		}
		if(!KEYIN)//此时按建S1被按下了要进入设置页面
		{
		Delay20ms();
			
			if(!KEYIN)
			{

				while(!KEYIN);
				savewd=IapRead(0x0400); 
				savewd--;
				if(savewd<=0){savewd=99;}
		
			}
		}



else
{page=0;}
IapErase(0x0400);
//指定地址扇区擦除
IapProgram(0x0400, savewd);
//存入被用户修正过的设定值 :修正以前应从指定地址读出 //savewd=IapRead(0x0400);
IapProgram(0x0402, savesd);
//存入被用户修正过的设定值 :修正以前应从指定地址读出 //savesd=IapRead(0x0402);
IapProgram(0x0404, addcode);
//这里的关键在于第二个0x0402 0404地址就不要执行指定地址扇区擦除,那样会把以前存入的数据也改掉的!!!!!
以上为进入菜单以及退出菜单需对修改后的数据进行存入相应设定参数。
再一次回过头来讲一讲单片机来自主机RS485通信数据的接收:
串口设备初始化自不必说,主要讲一下串口中断接收数据函数和数据分析函数。
//========================================================================
// 函数: void UART1_int (void) interrupt UART1_VECTOR
// 描述: 串口1中断函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2016-4-28
// 备注:
//========================================================================
void UART1_int (void) interrupt UART1_VECTOR
{
static unsigned char num=0;
if(RI)
{
RI = 0;
Rxd_Buffer[num]=SBUF;
if(Rxd_Buffer[0]==addcode)
{
num++;
if(num>=RX_Lenth)
{num=0;flag=1;}
}
//if(++RX_cnt>=RX_Lenth) RX_cnt=0;
}

if(TI)
{
	TI = 0;
	B_TX1_Busy = 0;
}

}
这里配置了一个RX_Lenth=8字节的静态数据接收空间,收到的首址数据为址址值就可以把全部的数据进行接收。

void Data_analysis() //接收到数据分析
{
unsigned int j;
if(flag==1)
{ //Rxd_Buffer[0]=0;//保证每接收一次发一次
ES=0;
flag=0;
j=GetCRC16(Rxd_Buffer,6);
if((Rxd_Buffer[6]*256+Rxd_Buffer[7])==j)

{
		m=3;
	}
	else
	{m=0;}
	ES=1;
	if(m==3)
	{
	savewd=Rxd_Buffer[3];
	savesd=Rxd_Buffer[4];
	addcode=Rxd_Buffer[5];
	if(savewd>99||savewd<=0)
		{
			savewd=10;
		}
	IapErase(0x0400); 
	IapProgram(0x0400, savewd);
 if(savesd>99||savesd<=0)
		{
			savesd=80;
       	//IapErase(0x0402); 
		}
IapProgram(0x0402, savesd);

	DRE=1;
Uart1_TxByte('A');
Uart1_TxByte('d');
Uart1_TxByte('d');
Uart1_TxByte(':');
Uart1_TxByte(addcode/1000 + '0');
Uart1_TxByte(addcode%1000/100 + '0');
Uart1_TxByte(addcode%100/10 + '0');
Uart1_TxByte(addcode%10 + '0');
Uart1_TxByte(' ');
Uart1_TxByte(' ');


Uart1_TxByte('W');
Uart1_TxByte('D');
Uart1_TxByte(':');
Uart1_TxByte('0');
Uart1_TxByte('2');
Uart1_TxByte('=');				//发送ADC读数
Uart1_TxByte(wd_adj/1000 + '0');
Uart1_TxByte(wd_adj%1000/100 + '0');
Uart1_TxByte(wd_adj%100/10 + '0');
Uart1_TxByte(wd_adj%10 + '0');
Uart1_TxByte(' ');
Uart1_TxByte(' ');
Uart1_TxByte('S');
Uart1_TxByte('D');
Uart1_TxByte(':');
Uart1_TxByte('0');
Uart1_TxByte('1');
Uart1_TxByte('=');				//发送ADC读数
Uart1_TxByte(sd_adj/1000 + '0');
Uart1_TxByte(sd_adj%1000/100 + '0');
Uart1_TxByte(sd_adj%100/10 + '0');
Uart1_TxByte(sd_adj%10 + '0');
Uart1_TxByte(' ');
Uart1_TxByte(' ');
DRE=0;
	}
	if(m==0)
	{
	Uart1_TxByte('e');
	Uart1_TxByte('r');
	Uart1_TxByte('r');
	Uart1_TxByte('!');
	Uart1_TxByte('\n');
	}

}
}

数据分析函数主要可以根据Modbus各种功能码进行进一步的功能解析,本文为以示例为主定义如下数据结构:
addcode xx xx bgsavewd bgsavesd bgaddcode CRCL CRCH
例: 01 (地址) 03(任意,以Modbus需要为主) 03(任意,以Modbus需要为主)20(重置温度设定值得32度)57(重置湿度设定值87)07(重置该主机的通信地址)3b (CRCL) d0 (CRCH) 注意以上数据需用16进制数.
在上述解析函数中,解析正确通过传递都参数,返回修正后主机新地址和该机的温度湿度参数。我在试验时是有两个串口的设备的,一个是USB转485进入设备的485接口com6,另一个是STC的下载工具串口接口com5。
在这里插入图片描述
用带CRC的发送指令收发区如图:
在这里插入图片描述
在另外一接收窗口同样也能收到数据(这个窗口发送时,后两字节需要CRC计算器算出后两字节CRC码):两个接收区也能收到相同数据。
在这里插入图片描述

在这里插入图片描述
下位机(主控制器的)2菜单为接收到被修改后的温度设定数据:命令每4个字节20即32
在这里插入图片描述
下位机(主控制器的)3菜单为接收到被修改后的湿度设定数据:命令每5个字节57即5*16+7=87

下位机(主控制器的)4菜单为接收到被修改后的本机通信新地址
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/79799a47a9d946f9ab61f2a214d54fbc.png

并且实现了掉电不丢数据的存储。

参考资料《新概念51单片机C语言教程》郭天祥
《STC8G1K08中文参考手册》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值