STM32C8TX实现Modbus_rtu从机

本实验使用Stm32c8t6实现Modbus_rtu从机

主机为串口调试助手,通过串口调试助手来与单片机实现数据交互.

Modbus协议

Modbus协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。它已经成为一通用工业标准.

Modbus协议格式

modbus信息帧所允许的最大长度为256个字节。所以数据的最大长度是252个字节

一个数据帧可以分为:设备码、功能码、数据码、校验码

常用的指令码(功能码)

    主设备发送读取0x03指令 
    0x00          0x03           0x0000             0x0000        0x0000
    (设备地址)  (指令码)  (从设备起始地址)    (读取个数)    (校验)
    从设备回复读取0x03指令
    0x00          0x03           0x00                0x0000         0x0000
    (设备地址)  (指令码)  (返回的字节个数)    (返回的数据)    (校验)

    主设备发送单个写入0x06指令
    0x00          0x06           0x0000              0x0000        0x0000
    (设备地址)  (指令码)  (从设备起始地址)    (写入数据)    (校验)
    从设备回复写入0x06指令
    0x00          0x06           0x0000             0x0000         0x0000
    (设备地址)  (指令码)  (从设备起始地址)    (修改数量)     (校验)

    主设备发送多个写入0x10指令
    0x00          0x10           0x0000             0x0000       0x00           0x0000           0x0000
    (设备地址)  (指令码)  (从设备起始地址)    (从哪里写)  (写入个数)  (写入数据)  (校验)
    从设备回复写入0x10指令
    0x00          0x10           0x0000             0x0000         0x0000
    (设备地址)  (指令码)  (从设备起始地址)    (修改数量)     (校验)

代码部分

本次实验使用Modbus_RTU协议,单片机做从,串口调试助手做主

首先使能串口,定时器等外设

串口USART1:
/**
*@brief     串口初始化函数
*@retval    无
*/
void Serial_UsartInit(void)
{
    //开启GPIO和串口时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    //GPIO配置
    GPIO_InitTypeDef serialgpio;
    serialgpio.GPIO_Mode = GPIO_Mode_AF_PP;
    serialgpio.GPIO_Pin = GPIO_Pin_9;//串口发送
    serialgpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&serialgpio);
    
    serialgpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    serialgpio.GPIO_Pin = GPIO_Pin_10;//串口接受
    GPIO_Init(GPIOA,&serialgpio);
    //串口配置
    USART_InitTypeDef serialconfig;
    serialconfig.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    serialconfig.USART_BaudRate = 9600;
    serialconfig.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    serialconfig.USART_Parity = USART_Parity_No;
    serialconfig.USART_StopBits = USART_StopBits_1;
    serialconfig.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1,&serialconfig);
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    //串口中断配置
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    NVIC_InitTypeDef serialnvic;
    serialnvic.NVIC_IRQChannelPreemptionPriority = 1;
    serialnvic.NVIC_IRQChannelSubPriority = 1;
    serialnvic.NVIC_IRQChannelCmd = ENABLE;
    serialnvic.NVIC_IRQChannel = USART1_IRQn;
    NVIC_Init(&serialnvic);
    
    USART_Cmd(USART1,ENABLE);
}

/**
*@brief     串口发送数据函数
*@retval    无
*/
void Serial_SendByte(uint8_t byte)
{
    USART_SendData(USART1,byte);
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}
定时器TIM3: 
/**
*@brief     定时器初始化函数
*@retval    无
*/
void Modbus_TimeIRQInit(void)
{
    //开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    //gpio配置
    GPIO_InitTypeDef timgpio;
    timgpio.GPIO_Mode = GPIO_Mode_AF_PP;
    timgpio.GPIO_Pin = GPIO_Pin_0;
    timgpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&timgpio);
    
    TIM_InternalClockConfig(TIM3);
    //定时器配置
    TIM_TimeBaseInitTypeDef timconfig;
    timconfig.TIM_Prescaler = 9;
    timconfig.TIM_CounterMode = TIM_CounterMode_Up;
    timconfig.TIM_Period = 7199;
    timconfig.TIM_ClockDivision = TIM_CKD_DIV1;
    timconfig.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3,&timconfig);
    
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
    //中断配置
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef timnvic;
    timnvic.NVIC_IRQChannelPreemptionPriority = 1;
    timnvic.NVIC_IRQChannelSubPriority = 1;
    timnvic.NVIC_IRQChannelCmd = ENABLE;
    timnvic.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_Init(&timnvic);  

    TIM_Cmd(TIM3,ENABLE);
    
}
 MODBUS相关代码
//定义一个结构体用来储存ModBus的数据
typedef struct
{
    uint8_t dev_addr;        //本设备从机地址
    uint8_t rece_buf[64];    //接受缓冲区
    uint8_t rece_cnt;        //接受计数器
    uint8_t send_buf[64];    //发送缓冲区
    uint8_t rece_flag;       //接受标志位
    uint8_t timout;          //数据持续时间
    uint8_t timrun;          //定时器开始计数标志
}Modbus_t;
//定义一个数组用于Modbus读写
uint16_t Reg[]={0x0001,0x0002,0x0003,0x0004,0x0005,0x0006,0x0007,0x0008,
                0x0009,0x0010,0x0011,0x0012,0x0013,0x0014,0x0015,0x0016};

/**
*@brief     Modbus初始化函数
*@retval    无
*/
void Modbus_Init(void)
{
    modbusdata.dev_addr = 0x02;
    modbusdata.timrun = 0;
}

/**
*@brief     CRC校验函数
*@retval    CRC校验数
*/
uint16_t Modbus_CRC16(uint8_t *ch,uint8_t len)
{
	uint16_t crc = 0xffff,code = 0xa001;
	char i,j;
	for(i = 0;i< len;i++){
		crc ^= ch[i]; 
		for(j = 0;j < 8;j++){
			if(crc&1){
				crc>>=1;
				crc^=code;
			}else{
				crc>>=1;
			}
		}
	}	
	return crc;  
}

/**
*@brief     Modbus事件函数
*@retval    错误码
*/
uint8_t Modbus_Event(void)
{
    uint16_t crc,rccrc;
    if(modbusdata.rece_flag == 0) //判断数据接受是否完成
    {
        return ERR_FlAG;    //宏定义ERR_FLAG = 0x01
    }
    //获取CRC校验
    crc = Modbus_CRC16(&modbusdata.rece_buf[0],modbusdata.rece_cnt-2);
    //获取数据帧的CRC校验
    rccrc = modbusdata.rece_buf[modbusdata.rece_cnt-1];
    rccrc <<=8;
    rccrc |= modbusdata.rece_buf[modbusdata.rece_cnt-2];
    //对比CRC校验
    if(crc == rccrc)        
    {
        //判断头码
        if(modbusdata.rece_buf[0] == modbusdata.dev_addr)
        {
            //索引功能码
            switch(modbusdata.rece_buf[1])
            {
                case 0x03:Modbus_ReadCMD();break;
                case 0x06:Modbus_WriteBitCMD();break;
                case 0x10:Modbus_WriteDataCMD();break;
                default:Serial_SendByte(0x44);break;
            }
        }
        else
        {
            modbusdata.rece_cnt = 0;
            modbusdata.rece_flag = 0;            
            return ERR_CRC; //宏定义ERR_CRC = 0x03
        }
    }
    else
    {
        modbusdata.rece_cnt = 0;
        modbusdata.rece_flag = 0;        
        return ERR_ADDR;//宏定义ERR_ADDR = 0x04
    }
    modbusdata.rece_cnt = 0;
    modbusdata.rece_flag = 0; 
    return 0x05; 
}

/********************************************************
************各个功能码的指令格式在上面有*******************
********************************************************/

/**
*@brief     Modbus功能0x03函数
*@retval    无
*/
void Modbus_ReadCMD(void)
{
    uint8_t i,j;
    uint16_t regaddr,reglen,r_crc;
    //获取 要读取寄存器的首地址(16位)
    regaddr = modbusdata.rece_buf[2];
    regaddr <<=8;
    regaddr |= modbusdata.rece_buf[3];
    //获取 要读取寄存器的数量(16位)
    reglen = modbusdata.rece_buf[4];
    reglen <<=8;
    reglen |= modbusdata.rece_buf[5];
    //从机回复数据
    i=0;
    modbusdata.send_buf[i++] = modbusdata.dev_addr;
    modbusdata.send_buf[i++] = 0x03;
    modbusdata.send_buf[i++] = (reglen*2)%256;
    for(j=0;j<reglen;j++)
    {
        modbusdata.send_buf[i++] = Reg[j]>>8;
        modbusdata.send_buf[i++] = Reg[j];
    }
    r_crc = Modbus_CRC16(modbusdata.send_buf,i);
    modbusdata.send_buf[i++] = r_crc>>8;
    modbusdata.send_buf[i++] = r_crc;
    //从机发送回复数据
    for(j=0;j<i;j++)
    {
        Serial_SendByte(modbusdata.send_buf[j]);
    }
}

/**
*@brief     Modbus功能0x06函数
*@retval    无
*/
void Modbus_WriteBitCMD(void)
{
    uint16_t regaddr,w_data,w_crc;
    uint16_t i,j;
    //获取 要写入寄存器的起始地址(16位)
    regaddr = modbusdata.rece_buf[2];
    regaddr <<=8;
    regaddr |= modbusdata.rece_buf[3];
    //获取 要写入寄存器的数据(16位)
    w_data = modbusdata.rece_buf[4];
    w_data <<=8;
    w_data |= modbusdata.rece_buf[5];
    Reg[regaddr] = w_data;
    //从机回复数据
    i=0;
    modbusdata.send_buf[i++] = modbusdata.dev_addr;
    modbusdata.send_buf[i++] = 0x06;
    modbusdata.send_buf[i++] = regaddr>>8;
    modbusdata.send_buf[i++] = regaddr;
    modbusdata.send_buf[i++] = w_data>>8;
    modbusdata.send_buf[i++] = w_data; 
    w_crc = Modbus_CRC16(modbusdata.send_buf,i);
    modbusdata.send_buf[i++] = w_crc>>8;
    modbusdata.send_buf[i++] = w_crc; 
    //从机发送回复数据
    for(j=0;j<i;j++)
    {
        Serial_SendByte(modbusdata.send_buf[j]);
    }    
}

/**
*@brief     Modbus功能0x10函数
*@retval    无
*/
void Modbus_WriteDataCMD(void)
{
    uint16_t regaddr,reglen,w_crc;
    uint16_t i,j;
    //获取 要写入寄存器的起始地址(16位)
    regaddr = modbusdata.rece_buf[2];
    regaddr <<=8;
    regaddr |= modbusdata.rece_buf[3];
    //获取 从那个寄存器开始写入(16位)
    reglen = modbusdata.rece_buf[4];
    reglen <<=8;
    reglen |= modbusdata.rece_buf[5];
    //获取 要写入寄存器的数据 
    for(i=0;i<reglen;i++)
    {
        Reg[reglen+i] = modbusdata.rece_buf[7+i*2];
        Reg[reglen+i] <<=8;
        Reg[reglen+i] |= modbusdata.rece_buf[8+i*2];
    }
    //从机回复数据
    modbusdata.send_buf[0] = modbusdata.dev_addr;
    modbusdata.send_buf[1] = 0x10;
    modbusdata.send_buf[2] = regaddr>>8;
    modbusdata.send_buf[3] = regaddr;
    modbusdata.send_buf[4] = reglen>>8;
    modbusdata.send_buf[5] = reglen;
    w_crc = Modbus_CRC16(modbusdata.send_buf,6);
    modbusdata.send_buf[6] = w_crc>>8;
    modbusdata.send_buf[7] = w_crc;  
    //从机发送回复数据  
    for(j=0;j<8;j++)
    {
        Serial_SendByte(modbusdata.send_buf[j]);
    }
}


中断配置:
/**********************************************
//中断配置时,串口的中断优先级必须大于定时器的优先级,
//否则会出问题,目前我暂且没弄明白是哪里的问题,
//知道的朋友可以评论区告诉我
**********************************************/
void USART1_IRQHandler(void)
{
    uint8_t data;
    if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
    {
        //接受下发数据
        data = USART_ReceiveData(USART1);
        if(modbusdata.rece_flag == 1)
        {
            return;
        }
        //将下发数据储存至接受缓冲区
        modbusdata.rece_buf[modbusdata.rece_cnt++] = data;
        modbusdata.timout = 0;
        //接受一个数据后打开定时器 因为modbus rtu标准协议规定:每帧数据间隔至少为3.5个字符时间
        if(modbusdata.rece_cnt == 1)
        {
            modbusdata.timrun = 1;
        }
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);
    }
}

void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)
    {
        //判断是否打开定时器
        if(modbusdata.timrun != 0)
        {
            modbusdata.timout++;
            if(modbusdata.timout >= 8)
            {
                modbusdata.timrun = 0;
                //接收数据完毕
                modbusdata.rece_flag = 1;
            }
        }
        TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
    }    
}
主函数:
#include "stm32f10x.h"                  // Device header
#include "UsartModbusRTU.h"

typedef struct
{
    uint8_t error;
}system_t;

extern Modbus_t modbusdata;
system_t sys_data;

int main(void)
{
    Serial_UsartInit();
    Modbus_Init();
    Modbus_TimeIRQInit();

	while (1)
	{
        //Modbus事件轮询并且接受错误码
		sys_data.error = Modbus_Event();
        if(sys_data.error != 0x05)
        {
            switch(sys_data.error) 
            {
                //如果错误码为ERR_ADDR:表示地址错误
                case ERR_ADDR:Serial_SendByte(0x03);break;
                //如果错误码为ERR_CRC:表示校验错误
                case ERR_CRC:Serial_SendByte(0x04);break;
                default:break;                
            }
        }
	}
}

测试结果

串口助手发送  0x03功能码

串口助手发送 0x10功能码

再次发送 0x03可以看到数据已经改变

串口助手发送  0x06功能码 但是地址给个错误地址

可以看到错误码0x04表示地址错误

在此完结谢谢大家

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值