使用STM8L DMA 串口空闲中断 使用RS485(sp485EE)的微型modbus HOLD寄存器程序
自己测试是好用的
//****************************************************************************************
/*
miniModuBusSavle 是基于ModbusRTU协议的微型slave端程序(目前只支持HOLD寄存器读写模式)
使用一个装满指针的结构体来保存数据,模拟HOLD型寄存器。
对数据的写入和读取全通过指针直接操作
利用指针可以将分散的数据集中起来管理,方便形成连续的地址,在与上位机进行数据通信时更加方便
*/
//****************************************************************************************
//**********************************************************
//构建一个由指针组成的结构体,类型均为16bit或32bit
//例如 uint16_t *pSET_V_CO2;//计算得到SET_V
//**********************************************************
struct PARA_REG{
//构建一个由指针组成的结构体,类型均为16bit或32bit
//例如 uint16_t *pSET_V_CO2;//计算得到SET_V
};
//**********************************************************
//声明相关变量
//**********************************************************
uint8_t CRC_TYPE;//CRC16校验类型 高位在前或地位在前CRC_TYPE_HL;CRC_TYPE_LH;
uint16_t CRC; //CRC校验计算结果
uint8_t SENT_COUNT; //发送数据长度
uint8_t MODBUS_ADD = 0XAA; //设备地址
struct PARA_REG PARA_REG_STRUCT;//声明一个结构体实例
uint8_t ERRCOD;//错误数据
//**********************************************************
//程序声明
//**********************************************************
uint16_t CRC16(uint8_t *BUF,uint8_t LENGTH); //CRC16计算程序
uint8_t CHACK_CRC(uint8_t *BUF,uint8_t BUFLENGTH); //接收数据crc校验程序
uint8_t CHACK_ADDR(uint8_t *BUF); //检查设备地址是否匹配,返回ERROR_COD,具体见 .H文件定义
uint8_t Function_MODBUS(uint8_t *rec_buff,uint8_t BUFLENGTH); //modbus操作选择程序,接收数据后进入此程序进行地址校验,crc校验,正确后分配工作如,读,写等
uint8_t READ_REG(uint8_t * rec_buff); //读取寄存器
uint8_t WRITE_REG(uint8_t * rec_buf); //写寄存器
void MODBUS_POINTER_INIT(void); //数据结构体指针初始化
void MODBUS_POINTER_INIT(void)
{
//结构体内指针指向内容初始化
// PARA_REG_STRUCT.pSET_V_CO2 = &SET_V_CO2; //计算得到SET_V_co2
}
//****************************************************
//检查设备地址是否匹配
//*BUF 接收的数组
//****************************************************
uint8_t CHACK_ADDR(uint8_t *BUF)
{
if(BUF[0] == MODBUS_ADD)return ADDRESS_MATCHED; else return ADDRESS_UNMATCH;
}
//****************************************************
//CRC16计算函数
//*BUF 要计算的数组 LENGTH 数据长度
//****************************************************
uint16_t CRC16(uint8_t *BUF,uint8_t LENGTH)
{
uint16_t i,j,tmp,CRC;
CRC=0XFFFF;
for(i=0;i<LENGTH;i++)
{
CRC = BUF[i]^CRC;
for(j=0;j<8;j++)
{
tmp=CRC&0x0001;
CRC=CRC>>1;
if(tmp) CRC=CRC^0xA001;
}
}
return CRC;
}
//****************************************************
//接收数据crc校验程序
//*BUF 接收的缓冲数组 BUFLENGTH接收的数据长度
//****************************************************
uint8_t CHACK_CRC(uint8_t *BUF,uint8_t BUFLENGTH)
{
//由于市面上有两种CRC校验方法,是高低位互换的,所以进行两种校准,并判断上位机使用的是哪种顺序
uint16_t RX_CRC_A,RX_CRC_B;
CRC = CRC16(BUF,BUFLENGTH-2);
RX_CRC_A = (BUF[BUFLENGTH-2])<<8 | BUF[BUFLENGTH-1];
RX_CRC_B = BUF[BUFLENGTH-1]*256 + BUF[BUFLENGTH-2];
if(CRC == RX_CRC_A)
{
CRC_TYPE = CRC_TYPE_HL;
return 1;
}
else if(CRC == RX_CRC_B)
{
CRC_TYPE = CRC_TYPE_LH;
return 1;
}
else
{
return CRC_UNMATCH;
}
}
//*****************************************************************************************
//modbus操作选择程序,接收数据后进入此程序进行地址校验,crc校验,正确后分配工作如,读,写等
//*rec_buff 缓冲数组 BUFLENGTH接收到的数据长度
//*****************************************************************************************
uint8_t Function_MODBUS(uint8_t *rec_buff,uint8_t BUFLENGTH)
{
ERRCOD = CHACK_ADDR(rec_buff);
if(ERRCOD == ADDRESS_UNMATCH) return ERRCOD;
ERRCOD = CHACK_CRC(rec_buff,BUFLENGTH);
if(ERRCOD == CRC_UNMATCH) return ERRCOD;
switch(rec_buff[1]) // 功能码索引
{
case 1: // 01功能码:读取线圈(输出)状态 读取一组逻辑线圈的当前状态(ON/OFF)
//read_coil();
ERRCOD = ERRCOD_UNSUPPORT_COMD;
break;
case 2: //02功能码:读取输入状态 读取一组开关输入的当前状态(ON/OFF)
//read_input_bit();
ERRCOD = ERRCOD_UNSUPPORT_COMD;
return ERRCOD;
break;
case 3: //03功能码:读取保持型寄存器 在一个或多个保持寄存器中读取当前二进制值
ERRCOD =READ_REG(rec_buff);
break;
case 4: //04功能码:读取输入寄存器 在一个或多个输入寄存器中读取当前二进制值
ERRCOD =READ_REG(rec_buff);
break;
case 5: //05功能码 :强制(写)单线圈(输出)状态 强制(写)一个逻辑线圈通断状态(ON/OFF)
//force_coil_bit();
ERRCOD = ERRCOD_UNSUPPORT_COMD;
break;
case 6: //06功能码:强制(写)单寄存器 把二进制写入一个保持寄存器
ERRCOD = WRITE_REG(rec_buff);
break;
case 15:
//force_coil_mul();
ERRCOD = ERRCOD_UNSUPPORT_COMD;
break;
case 16: //16功能码:强制(写)多寄存器 把二进制值写入一串连续的保持寄存器
ERRCOD = WRITE_REG(rec_buff);
break;
default:
ERRCOD = ERRCOD_UNSUPPORT_COMD;
break;
}
if(ERRCOD != NOERROR)
{
rec_buff[1] = rec_buff[1] | 0X80;
rec_buff[2] = ERRCOD; //有错误
DMA_USART_SEND(rec_buff,5);
}
return ERRCOD;
}
//******************************************************
//对应modbus功能号03,04 批量读寄存器
//rec_buf接收到的指令 send_data需要发送的指令
//******************************************************
uint8_t READ_REG(uint8_t * rec_buff)
{
uint16_t begin_add = 0;
uint16_t data_num = 0;
uint16_t **piont;
uint16_t send_CRC;
uint8_t send_num;
int16_t i;
uint8_t ERRCOD = NOERROR;
//本程序中寄存器都是 int或uint型,占用2个字节,但是在modbus中地址一位是一个word也就是2个字节。
begin_add = rec_buff[3];//地址1字节
data_num = rec_buff[5];//需要读取的字节数
if((begin_add+data_num) >REG_SIZE)return ERRCOD_REG_ADDRESS_ERROR; //REG_ADDRESS ERROR
send_num = 5 + data_num*2; // 5个固定字节+数据个数 addr1 + fun1 + num1 +【data】+ crc2?
rec_buff[2] = data_num*2;//字节数
piont = (uint16_t **)&PARA_REG_STRUCT; //将结构体转换为字符数组,便于后面的循环读取或写入
for(i=0;i<data_num;i++)
{
rec_buff[3+i*2] = (uint8_t)(**(piont + begin_add +i)>>8);
rec_buff[3+i*2+1] = (uint8_t)(**(piont + begin_add +i));
}
send_CRC = CRC16(rec_buff, send_num-2);
if(CRC_TYPE == CRC_TYPE_HL)
{
rec_buff[send_num-2] = (uint8_t)(send_CRC >> 8);
rec_buff[send_num -1] = (uint8_t)send_CRC;
}
else
{
rec_buff[send_num-1] = send_CRC >> 8;
rec_buff[send_num -2] = send_CRC;
}
SENT_COUNT = send_num;
DMA_USART_SEND(rec_buff,SENT_COUNT);
return ERRCOD;
}
//*************************************************************
//对应modbus功能号06和16,单个和批量写寄存器
//rec_buf接收到的指令 send_data需要发送的指令
//*************************************************************
uint8_t WRITE_REG(uint8_t * rec_buf)
{
uint8_t fun_code,begin_add,data_num;//功能码,开始地址,数据长度
uint16_t send_num;//发送数据长度
uint16_t **piont;
uint16_t send_CRC;
int16_t i;
uint8_t ERRCOD = NOERROR;
fun_code = rec_buf[1]; //获取功能码
begin_add = rec_buf[3];
piont = (uint16_t **)&PARA_REG_STRUCT; //将结构体转换为字符数组,便于后面的循环读取或写入
if(fun_code == 6)//写单个寄存器,返回指令与接收的指令完全一样
{
if((begin_add)>=REG_SIZE) return ERRCOD_REG_ADDRESS_ERROR;
**(piont+begin_add) = rec_buf[4]<<8 + rec_buf[5];//寄存器高位+寄存器低位写入;
}
else if(fun_code == 16)//写多个寄存器
{
data_num = rec_buf[5];
if((begin_add+data_num)>REG_SIZE) return ERRCOD_REG_ADDRESS_ERROR;
for(i=0;i<data_num;i++)
{
**(piont+begin_add+i) = rec_buf[7+i*2]*256 + rec_buf[7+i*2+1];
}
}
send_num = 8;//写单个寄存器的响应数据个数是固定的为 8;
send_CRC = CRC16(rec_buf, send_num-2);//计算CRC 并自动匹配两种CRC16 高地位配置
if(CRC_TYPE_HL)
{
rec_buf[send_num-2] = (uint8_t)(send_CRC >> 8);
rec_buf[send_num -1] = (uint8_t)send_CRC;
}
else
{
rec_buf[send_num-1] = send_CRC >> 8;
rec_buf[send_num -2] = send_CRC;
}
SENT_COUNT = send_num;
DMA_USART_SEND(rec_buf,SENT_COUNT);
return ERRCOD;
}
//**************************************************
//软启动modbus 过程
//**************************************************
void SOFT_START_MODBUS_PROCESS(uint8_t *rec_buff,uint8_t BUFLENGTH)
{
if(TX_DMA_SOFTSTART_FLG && TX_DMA_READY_FLG)//如果在串口IDLE中断中DMA处于忙状态则进入主循环中配制DMA
{
TX_DMA_READY_FLG = FALSE;
TX_DMA_SOFTSTART_FLG = FALSE;
Function_MODBUS(uint8_t *rec_buff,uint8_t BUFLENGTH);
DMA_START_RX();//重新开启串口DMA接收模式
}
}
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
stm8L的定义是1个字符空闲约10个数据位,modbus定义是1.5个字符约15个数据位左右会认为数据不可用。
如果线路正常的话不会出现超过10个数据位的断开。
modbus协议规定 两组数据至少间隔 3.5个字符的时间。
所以如果间隔大于1.5小于3.5会抛弃,大于3.5是下一组数据。这样有影响的时间只是 1个字符与1.5个字符的差异,但是对于正常线路这种情况不会发生。
再就是一般主机都有重发机制。
一周热门 更多>