MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。[^1]
帧头、地址信息、数据长度、数据块、检验码、帧尾
1A1B——1————len——……——ab cd——A1 B1
在这里我把地址信息设为每个MCU的ID,检验码为计算得到的CRC校验码
数组、结构体
Typedef struct{
U8 head[2];
U8 id;//保存设备地址
U16 lenth;
U8 data[64];//保存接收到的数据
U8 jiaoyan[2];
U8 end[2];
}XXX;
——循环
我这里还没上系统,所以开的是时间片
串口中断+DMA 、空闲中断 、 定时器的溢出中断
——状态机
由自己定义的数据解析方式
请求:功能码+数据
正常响应:操作码+数据
异常响应:差错码+异常码
对串行链路通信来说,MODBUS PDU=256-服务器地址(1字节)-CRC(2字节)=253字节
从而:
RS232/RS485 ADU=253字节+服务器地址(1byte)+CRC(2字节)=256字节。
TCP MODBUS ADU=249字节+MBAP(7字节)=256字节。
离散量输入:按键、热释电
线圈:LED、BEEP、继电器
输入寄存器:光照、噪声、空气质量
保持寄存器:DHT11、OLED、W25Q
公共功能码、用户自定义功能码、保留功能码
03 功能码:
请求 —— 响应
请求:03 00 00 00 03
响应:03 06 00 37 00 50 ….
生成 CRC 的过程为:
#include "modbus.h"
#include "rs485.h"
#include "stdio.h"
#include "crc_16_tab.h"
#include "crc_8_tab.h"
MODBUS modbus_slave={.id=0x01,.rxcount=0,.rxover=0,.txcount=0};
uint8_t Get_ID(void)
{
u8 id=0;
u8 *p = (u8 *)0x1FFFF7E8;
id = CRC_8_Tab(p,12);
return id;
}
void Modbus_Config(uint32_t brr)
{
u8 ID;
RS485_Config(brr);
TIM3_Config(72,4000); //>3.6 --4ms
ID = Get_ID();
modbus_slave.id = ID;
printf("ID=%d\r\n",modbus_slave.id);
}
void TIM3_Config(u16 psc,u16 arr)
{
NVIC_InitTypeDef NVIC_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3时钟使能
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频因子 -- 用于输入捕获
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
TIM_TimeBaseInitStruct.TIM_Period = arr-1; //重装载值
TIM_TimeBaseInitStruct.TIM_Prescaler = psc -1; //分频值
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct); //初始化TIM3时基单元
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//更新中断
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;//TIM3中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //中断通道使能
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;//次级
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;//占先
NVIC_Init(&NVIC_InitStruct);//初始化 NVIC 寄存器
TIM_Cmd(TIM3,DISABLE);//失能TIM3
}
void USART3_IRQHandler(void)
{
if(USART_GetITStatus(USART3,USART_IT_RXNE))
{
USART_ClearFlag(USART3,USART_FLAG_RXNE);
modbus_slave.rxbuff[modbus_slave.rxcount++] = USART_ReceiveData(USART3);
if(modbus_slave.rxcount ==1)
{
TIM_Cmd(TIM3,ENABLE);//启动TIM3
}
TIM_SetCounter(TIM3,0);//计数器清0
}
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update))
{
TIM_ClearFlag(TIM3,TIM_FLAG_Update);
TIM_Cmd(TIM3,DISABLE);//失能TIM3
modbus_slave.rxover = 1;
}
}
void Modbus_EvenPoll(void)
{
u8 i=0;
u16 rx_crc =0; //存放接收到的CRC的值
u16 current_crc = 0;//存放计算的CRC的值
if(modbus_slave.rxover==0)return;//未接收完成
if(modbus_slave.rxcount<4)goto MODBUS_REEOR;//解决TIM bug问题
//测试
for(i=0;i<modbus_slave.rxcount;i++)
{
printf("%x\t",modbus_slave.rxbuff[i]);
}
printf("\r\n");
//判断CRC
rx_crc = (modbus_slave.rxbuff[modbus_slave.rxcount-1]<<8) | modbus_slave.rxbuff[modbus_slave.rxcount-2];
//计算的CRC的值
current_crc = CRC_16_Tab(modbus_slave.rxbuff,modbus_slave.rxcount-2);
if(current_crc != rx_crc)goto MODBUS_REEOR;
//判断ID -- 是否是广播地址 和 本身地址
if((modbus_slave.rxbuff[0] != modbus_slave.id)&&(modbus_slave.rxbuff[0] != 0x00))goto MODBUS_REEOR;
//功能码
switch(modbus_slave.rxbuff[1])
{
case 0x00:break;
case 0x01:break;//读线圈
case 0x02:break;//读输入离散量
case 0x03:Modbus_Funtion03();break;
default:Modbus_FunctionError(modbus_slave.rxbuff[1],0x01);break;//错误
}
//发送数据
RS485_SendData(modbus_slave.txbuff,modbus_slave.txcount);
MODBUS_REEOR:
modbus_slave.txcount = 0;
modbus_slave.rxcount = 0;
modbus_slave.rxover = 0;
}
void Modbus_Function00(void)
{
u16 tx_crc = 0;
//设备ID
modbus_slave.txbuff[modbus_slave.txcount++] = modbus_slave.id;
//功能码
modbus_slave.txbuff[modbus_slave.txcount++] = 0x00;
//数据码
modbus_slave.txbuff[modbus_slave.txcount++] = 0x00;
//crc校验
tx_crc =CRC_16_Tab(modbus_slave.txbuff,modbus_slave.txcount);
//将crc值存放发送缓冲区
modbus_slave.txbuff[modbus_slave.txcount++] = (tx_crc & 0xff);
modbus_slave.txbuff[modbus_slave.txcount++] = (tx_crc & 0xff00)>>8;
}
void Modbus_FunctionError(u16 error_code,u16 abnormal)
{
u16 tx_crc =0;
//设备id
modbus_slave.txbuff[modbus_slave.txcount++] = modbus_slave.rxbuff[0];
//差错码
modbus_slave.txbuff[modbus_slave.txcount++] = error_code | 0x80;
//异常码
modbus_slave.txbuff[modbus_slave.txcount++] = abnormal;
//CRC校验
//计算CRC
tx_crc = CRC_16_Tab(modbus_slave.txbuff,modbus_slave.txcount);
//将CRC校验值写入发送缓存区
modbus_slave.txbuff[modbus_slave.txcount++] = tx_crc & 0xff;//低位
modbus_slave.txbuff[modbus_slave.txcount++] = (tx_crc & 0xff00)>>8;//高位
}
u16 hold_reg[6]={0x10,0x20,0x30,0x40,0x50,0x60};
void Modbus_Funtion03(void)
{
u16 tx_crc = 0;
u16 register_count = 0;//保存寄存器的数量
u16 register_addr = 0;//寄存器的起始地址
//寄存器数量
register_count = (modbus_slave.rxbuff[4]<<8) | modbus_slave.rxbuff[5];
//寄存器的起始地址
register_addr = (modbus_slave.rxbuff[2]<<8) | modbus_slave.rxbuff[3];
//判断寄存器的数量是否在0x01 ~ 0x7D之间
if(register_count <0x01 || register_count>0x7D)
{
Modbus_FunctionError(0x03,0x03);
}
//寄存器的起始地址 起始地址+寄存器的数量
if(register_addr>6 || (register_addr +register_count)>6)
{
Modbus_FunctionError(0x03,0x02);
}
//响应
//设备ID
modbus_slave.txbuff[modbus_slave.txcount++] = modbus_slave.rxbuff[0];
//功能码
modbus_slave.txbuff[modbus_slave.txcount++] = 0x03;
//字节数
modbus_slave.txbuff[modbus_slave.txcount++] = 2*register_count;
//寄存器的数值
for(u8 i=0;i<register_count;i++)
{
modbus_slave.txbuff[modbus_slave.txcount++] = (hold_reg[register_addr+i]&0xff00)>>8;//高位
modbus_slave.txbuff[modbus_slave.txcount++] = hold_reg[register_addr+i]&0xff;//低位
}
//计算CRC
tx_crc = CRC_16_Tab(modbus_slave.txbuff,modbus_slave.txcount);
//将CRC值存放发送缓存区
modbus_slave.txbuff[modbus_slave.txcount++] = (tx_crc & 0xff);
modbus_slave.txbuff[modbus_slave.txcount++] = (tx_crc & 0xff00)>>8;
}
void RS485_SendData(u8 *tx_buff,u8 lenth)
{
//设置为发送模式
RS485_RE = RS485_ModeTx;
for(u8 i=0;i<lenth;i++)
{
//发送数据
USART3->DR = tx_buff[i];
//判断数据是否发送完成
while(USART_GetFlagStatus(USART3,USART_FLAG_TC)!=SET);//等待发送结束
}
//配置为接收模式
RS485_RE = RS485_ModeRx;
}
extern float temp,hum;
//更新数据
void Updata_Data(void)
{
hold_reg[0] = temp;
hold_reg[1] = hum;
}
本篇接上一篇RS485上实现的,加油
上一篇 基于STM32f103——RS458通信
上上篇 基于STM32f103c8t6的红外接收发送
[参考文档]:modbus协议