自写简易版从机Modbus
看这篇文章之前要对Modbus协议要有一个简单的了解,本篇文章以STM32单片机为例写一个简易版的从机Modbus.
Modbus通信机制需要单片机两个外设资源:串口和定时器。
设一个向上计数的定时器,计数周期为3.5个字符的时间。3.5个字符时间如何计算请参考这个https://zhidao.baidu.com/question/2266066387336737428.html
其实这个时间设长一点也没关系,比如设个50ms,100ms甚至是1s,如果设为1s,主机的Modbus发送两帧数据的间隔就不能低于1s,看完从机的具体实现就会明白为什么了。
用Stm32CubeIDE配置一个50ms中断的定时器
再配置串口
串口中断回调函数如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { /* Prevent unused argument(s) compilation warning */ UNUSED(huart); /* NOTE: This function Should not be modified, when the callback is needed, the HAL_UART_TxCpltCallback could be implemented in the user file */ if(huart->Instance == huart1.Instance) { Rx_Buf[RxCount++] = aRx1Buffer;//把接收到的数据存入接收缓存数组中 __HAL_TIM_SET_COUNTER(&htim7,0);//只要有数据进来就清空定时器计数器,如果没有新的数据进来即从此刻开始计时,50ms后进入定时器中断回调函数,此时意味着接收完一帧数据。 HAL_TIM_Base_Start_IT(&htim7);//启动定时器 HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRx1Buffer, 1); //再开启接收串口中断 } }
在定时器中断回调函数如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == htim7.Instance) { SLAVE_RS485_SEND_MODE;//485切换成发送模式,不再接受新的串口中断 HAL_TIM_Base_Stop_IT(&htim7);//停止定时器 MB_Parse_Data();// 提取数据帧,进行解析数据帧 MB_Analyze_Execute();//对接收到的数据进行分析并执行 RxCount = 0;//清空接收计数 SLAVE_RS485_RECEIVE_MODE;//数据处理完毕后,再重新接收串口中断 } }
串口引脚接了485芯片,SLAVE_RS485_SEND_MODE和SLAVE_RS485_RECEIVE_MODE就是普通的IO口,用于控制485芯片发送与接收模式
#define SLAVE_RS485_SEND_MODE HAL_GPIO_WritePin(USART1_EN_GPIO_Port, USART1_EN_Pin, GPIO_PIN_SET) #define SLAVE_RS485_RECEIVE_MODE HAL_GPIO_WritePin(USART1_EN_GPIO_Port, USART1_EN_Pin, GPIO_PIN_RESET)
接下来看提取数据帧函数
/* 提取数据帧,进行解析数据帧 */ void MB_Parse_Data() { PduData.Code = Rx_Buf[1]; // 功能码 PduData.Addr = ((Rx_Buf[2]<<8) | Rx_Buf[3]);// 寄存器起始地址 PduData.Num = ((Rx_Buf[4]<<8) | Rx_Buf[5]);// 数量(Coil,Input,Holding Reg,Input Reg) PduData._CRC = MB_CRC16((uint8_t*)&Rx_Buf,RxCount-2); // CRC校验码 PduData.byteNums = Rx_Buf[6]; // 获得字节数 }
PduData是一个结构体如下:
/* 类型定义 ------------------------------------------------------------------*/ typedef struct { __IO uint8_t Code ; // 功能码 __IO uint8_t byteNums; // 字节数 __IO uint16_t Addr ; // 操作内存的起始地址 __IO uint16_t Num; // 寄存器或者线圈的数量 __IO uint16_t _CRC; // CRC校验码 __IO uint8_t *ValueReg; // 10H功能码的数据 }PDUData_TypeDef;
MB_CRC16校验函数请参考:https://www.cnblogs.com/lizhiqiang0204/p/12122928.html
接下来看最重要的对接收到的数据进行分析并执行,我们以简单的写线圈寄存器为例
/** * 函数功能: 对接收到的数据进行分析并执行 * 输入参数: 无 * 返 回 值: 异常码或0x00 * 说 明: 判断功能码,验证地址是否正确.数值内容是否溢出,数据没错误就发送响应信号 */ uint8_t MB_Analyze_Execute(void ) { uint16_t ExCode = EX_CODE_NONE; uint16_t tem_crc; if(PduData._CRC !=((Rx_Buf[RxCount-1])<<8 | Rx_Buf[RxCount-2])) { /* Modbus异常响应 */ ExCode = EX_CODE_02H; // 异常码02H return ExCode; } /* 根据功能码分别做判断 */ switch(PduData.Code) { /* ---- 01H 02H 读取离散量输入(Coil Input)---------------------- */ case FUN_CODE_01H: break; case FUN_CODE_02H: break; case FUN_CODE_03H: break; case FUN_CODE_04H: break; case FUN_CODE_05H: /* 写入一个线圈值 */ if(PduData.Num == 0xFF00) { usCoilBuf[PduData.Addr] = 1;//把这个PduData.Addr地址的线圈寄存器写1 } else { usCoilBuf[PduData.Addr] = 0;//把这个PduData.Addr地址的线圈寄存器写0 } MB_SendRX();//把接收到的数据原封不动的发送出去(即应答) break; } /* 数据帧没有异常 */ return ExCode; // EX_CODE_NONE }
uint8_t usCoilBuf[100] ;//定义100个线圈寄存器 uint16_t usRegInputBuf[100] ;//定义100个输入寄存器 uint16_t usRegHoldingBuf[100] ;//定义100个保持寄存器 __IO uint8_t Rx_Buf[256]; // 接收缓存,最大256字节 __IO uint8_t Tx_Buf[256]; // 发送缓存,最大256字节 __IO uint16_t RxCount = 0; // 接收字符计数
数据分析执行函数MB_Analyze_Execute除了验证校验位,还应该对寄存器的地址进行检查是否越位。上面的执行函数MB_Analyze_Execute只是对功能码05H写单个线圈寄存器进行回应操作,接下来分别补充10H写多个保持寄存器和04H读多个输入寄存器的回应操作。
case FUN_CODE_10H://写入多个保持寄存器 for(int i = 0;i < PduData.Num;i++) { usRegHoldingBuf[PduData.Addr + i] = (Rx_Buf[i*2+7] << 8) | (Rx_Buf[i*2+8] << 0); } //响应写多个保持寄存器 Tx_Buf[0] = Rx_Buf[0];//从机地址 Tx_Buf[1] = Rx_Buf[1];//功能码 Tx_Buf[2] = Rx_Buf[2];//起始地址高位 Tx_Buf[3] = Rx_Buf[3];//起始地址低位 Tx_Buf[4] = Rx_Buf[4];//数量高位 Tx_Buf[5] = Rx_Buf[5];//数量低位 tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,6); // CRC校验码 Tx_Buf[6] = (uint8_t)tem_crc;//CRC高位 Tx_Buf[7] = (uint8_t)(tem_crc >> 8);//CRC低位 HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, 8, HAL_MAX_DELAY); break;
case FUN_CODE_04H://写多个输入寄存器 Tx_Buf[0] = Rx_Buf[0];//从机地址 Tx_Buf[1] = PduData.Code;//功能码 Tx_Buf[2] = PduData.Num * 2;//发送字节数 for(uint8_t i = 0; i <PduData.Num;i++) { //把对应地址的输入寄存器写入发送缓冲数组中 Tx_Buf[i*2+3] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 8); Tx_Buf[i*2+4] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3); // CRC校验码 Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc; Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8);//填写校验位 HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY);//串口发送 break;

/* * cus_modbus.c * * Created on: Dec 25, 2019 * Author: LiZhiqiang */ #include "stm32g0xx_hal.h" #include "cus_modbus.h" #include "usart.h" uint16_t usDiscreteInputStart ; uint8_t usDiscreteInputBuf[DISCRETE_INPUT_NDISCRETES/8] ; uint16_t usCoilStart ; uint8_t usCoilBuf[COIL_NCOILS/8] ; uint16_t usRegInputStart ; uint16_t usRegInputBuf[REG_INPUT_NREGS] ; uint16_t usRegHoldingStart ; uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] ; __IO uint8_t Rx_Buf[256]; // 接收缓存,最大256字节 __IO uint8_t Tx_Buf[256]; // 发送缓存,最大256字节 __IO uint8_t tmp_Rx_Buf; // 临时接收缓存 __IO uint16_t RxCount = 0; // 接收字符计数 __IO uint8_t Addr_Slave = 3;//从机地址 PDUData_TypeDef PduData; // CRC 高位字节值表 static const uint8_t auchCRCHi[] = { 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 } ; // CRC 低位字节值表 static const uint8_t auchCRCLo[] = { 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 }; void MB_10H_WR_NReg(uint16_t* _AddrOffset,uint16_t _RegNum , uint8_t* _Datebuf); //把接受到的数据再回复出去 void MB_SendRX() { HAL_UART_Transmit(&huart1, (uint8_t*)&Rx_Buf, RxCount, HAL_MAX_DELAY); } /* 函数体 --------------------------------------------------------------------*/ /** * 函数功能: Modbus CRC16 校验计算函数 * 输入参数: pushMsg:待计算的数据首地址,usDataLen:数据长度 * 返 回 值: CRC16 计算结果 * 说 明: 计算结果是高位在前,需要转换才能发送 */ uint16_t MB_CRC16(uint8_t *_pushMsg,uint8_t _usDataLen) { uint8_t uchCRCHi = 0xFF; uint8_t uchCRCLo = 0xFF; uint16_t uIndex; while(_usDataLen--) { uIndex = uchCRCLo ^ *_pushMsg++; uchCRCLo = uchCRCHi^auchCRCHi[uIndex]; uchCRCHi = auchCRCLo[uIndex]; } return (uchCRCHi<<8|uchCRCLo); } /* 提取数据帧,进行解析数据帧 */ void MB_Parse_Data() { PduData.Code = Rx_Buf[1]; // 功能码 PduData.Addr = ((Rx_Buf[2]<<8) | Rx_Buf[3]);// 寄存器起始地址 PduData.Num = ((Rx_Buf[4]<<8) | Rx_Buf[5]);// 数量(Coil,Input,Holding Reg,Input Reg) PduData._CRC = MB_CRC16((uint8_t*)&Rx_Buf,RxCount-2); // CRC校验码 PduData.byteNums = Rx_Buf[6]; // 获得字节数 PduData.ValueReg = (uint8_t*)&Rx_Buf[7]; // 寄存器值起始地址 PduData.PtrCoilOffset = PduData.PtrCoilbase + PduData.Addr; // 离散量的内存起始地址 PduData.PtrHoldingOffset = PduData.PtrHoldingbase + PduData.Addr; // 保持寄存器的起始地址 } /** * 函数功能: 对接收到的数据进行分析并执行 * 输入参数: 无 * 返 回 值: 异常码或0x00 * 说 明: 判断功能码,验证地址是否正确.数值内容是否溢出,数据没错误就发送响应信号 */ uint8_t MB_Analyze_Execute(void ) { uint16_t ExCode = EX_CODE_NONE; uint16_t tem_crc; MB_Parse_Data(); /* 校验功能码 */ if( IS_NOT_FUNCODE(PduData.Code) ) // 不支持的功能码 { /* Modbus异常响应 */ ExCode = EX_CODE_01H; // 异常码01H return ExCode; } if(PduData._CRC !=((Rx_Buf[RxCount-1])<<8 | Rx_Buf[RxCount-2])) { /* Modbus异常响应 */ ExCode = EX_CODE_02H; // 异常码02H return ExCode; } /* 根据功能码分别做判断 */ switch(PduData.Code) { case FUN_CODE_01H://读线圈寄存器 Tx_Buf[0] = Rx_Buf[0];//从机地址 Tx_Buf[1] = PduData.Code;//功能码 if(PduData.Num % 8 == 0)//如果读取线圈的数量是8的整数倍,则返回字节数Tx_Buf[2] = PduData.Num / 8 Tx_Buf[2] = PduData.Num / 8; else//如果不是8的整数倍,则加一 Tx_Buf[2] = PduData.Num / 8 + 1; for(int i = 0; i < Tx_Buf[2];i++) { Tx_Buf[3 + i] = (usCoilBuf[PduData.Addr+7 + i*8] << 7) | (usCoilBuf[PduData.Addr +6 +i*8] << 6) |(usCoilBuf[PduData.Addr+5 + i*8] << 5) | (usCoilBuf[PduData.Addr +4 +i*8] << 4) |(usCoilBuf[PduData.Addr+3 + i*8] << 3) | (usCoilBuf[PduData.Addr +2 +i*8] << 2) |(usCoilBuf[PduData.Addr+1 + i*8] << 1) | (usCoilBuf[PduData.Addr +0 +i*8] << 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,Tx_Buf[2]+3); // CRC校验码 Tx_Buf[Tx_Buf[2]+3] = (uint8_t)tem_crc; Tx_Buf[Tx_Buf[2]+4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, Tx_Buf[2] +5, HAL_MAX_DELAY); break; case FUN_CODE_02H://读离散输入寄存器,其实读离散输入寄存器和读线圈寄存器类似 Tx_Buf[0] = Rx_Buf[0];//从机地址 Tx_Buf[1] = PduData.Code;//功能码 if(PduData.Num % 8 == 0)//如果读取线圈的数量是8的整数倍,则返回字节数Tx_Buf[2] = PduData.Num / 8 Tx_Buf[2] = PduData.Num / 8; else//如果不是8的整数倍,则加一 Tx_Buf[2] = PduData.Num / 8 + 1; for(int i = 0; i < Tx_Buf[2];i++) { Tx_Buf[3 + i] = (usDiscreteInputBuf[PduData.Addr+7 + i*8] << 7) | (usDiscreteInputBuf[PduData.Addr +6 +i*8] << 6) |(usDiscreteInputBuf[PduData.Addr+5 + i*8] << 5) | (usDiscreteInputBuf[PduData.Addr +4 +i*8] << 4) |(usDiscreteInputBuf[PduData.Addr+3 + i*8] << 3) | (usDiscreteInputBuf[PduData.Addr +2 +i*8] << 2) |(usDiscreteInputBuf[PduData.Addr+1 + i*8] << 1) | (usDiscreteInputBuf[PduData.Addr +0 +i*8] << 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,Tx_Buf[2]+3); // CRC校验码 Tx_Buf[Tx_Buf[2]+3] = (uint8_t)tem_crc; Tx_Buf[Tx_Buf[2]+4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, Tx_Buf[2] +5, HAL_MAX_DELAY); break; case FUN_CODE_03H://响应读保持寄存器 Tx_Buf[0] = Rx_Buf[0]; Tx_Buf[1] = PduData.Code; Tx_Buf[2] = PduData.Num * 2; for(uint8_t i = 0; i <PduData.Num;i++) { Tx_Buf[i*2+3] = (uint8_t)(usRegHoldingBuf[PduData.Addr+i] >> 8); Tx_Buf[i*2+4] = (uint8_t)(usRegHoldingBuf[PduData.Addr+i] >> 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3); // CRC校验码 Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc; Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY); break; case FUN_CODE_04H://响应读输入寄存器 Tx_Buf[0] = Rx_Buf[0]; Tx_Buf[1] = PduData.Code; Tx_Buf[2] = PduData.Num * 2; for(uint8_t i = 0; i <PduData.Num;i++) { Tx_Buf[i*2+3] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 8); Tx_Buf[i*2+4] = (uint8_t)(usRegInputBuf[PduData.Addr+i] >> 0); } tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,PduData.Num * 2 +3); // CRC校验码 Tx_Buf[PduData.Num * 2 +3] = (uint8_t)tem_crc; Tx_Buf[PduData.Num * 2 +4] = (uint8_t)(tem_crc >> 8); HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, PduData.Num * 2 +5, HAL_MAX_DELAY); break; case FUN_CODE_05H: /* 写入一个线圈值 */ if(PduData.Num == 0xFF00) { usCoilBuf[PduData.Addr] = 1; } else { usCoilBuf[PduData.Addr] = 0; } MB_SendRX();//返回发送的指令作为响应 break; case FUN_CODE_06H://写单个保持寄存器 usRegHoldingBuf[PduData.Addr] = (Rx_Buf[4] << 8) | (Rx_Buf[5] << 0); MB_SendRX();//返回发送的指令作为响应 break; case FUN_CODE_10H://写入多个保持寄存器 for(int i = 0;i < PduData.Num;i++) { usRegHoldingBuf[PduData.Addr + i] = (Rx_Buf[i*2+7] << 8) | (Rx_Buf[i*2+8] << 0); } //响应写多个保持寄存器 Tx_Buf[0] = Rx_Buf[0];//从机地址 Tx_Buf[1] = Rx_Buf[1];//功能码 Tx_Buf[2] = Rx_Buf[2];//起始地址高位 Tx_Buf[3] = Rx_Buf[3];//起始地址低位 Tx_Buf[4] = Rx_Buf[4];//数量高位 Tx_Buf[5] = Rx_Buf[5];//数量低位 tem_crc = MB_CRC16((uint8_t*)&Tx_Buf,6); // CRC校验码 Tx_Buf[6] = (uint8_t)tem_crc;//CRC高位 Tx_Buf[7] = (uint8_t)(tem_crc >> 8);//CRC低位 HAL_UART_Transmit(&huart1, (uint8_t*)&Tx_Buf, 8, HAL_MAX_DELAY); break; } /* 数据帧没有异常 */ return ExCode; // EX_CODE_NONE } /** * 函数功能: 写,读N个寄存器 * 输入参数: _AddrOffset:偏移地址,_RegNum:寄存器数量,_Datebuf:数据指针 * 返 回 值: 异常码:04H或NONE * 说 明: 在_AddrOffset所指向的空间里写入_RegNum*2个数据,并且读取验证是否写入成功 */ void MB_10H_WR_NReg(uint16_t* _AddrOffset,uint16_t _RegNum , uint8_t* _Datebuf) { uint16_t i = 0; uint16_t Value = 0; for(i=0;i<_RegNum;i++) { Value = (uint16_t)((*_Datebuf<<8 ) | (*(_Datebuf+1))); *_AddrOffset++ = Value ; _Datebuf+=2; } MB_SendRX(); }

/* * cus_modbus.h * * Created on: Dec 24, 2019 * Author: LiZhiqiang */ #ifndef INC_CUS_MODBUS_H_ #define INC_CUS_MODBUS_H_ #include "stm32g0xx_hal.h" #include "cmsis_os.h" /* 类型定义 ------------------------------------------------------------------*/ typedef struct { __IO uint8_t Code ; // 功能码 __IO uint8_t byteNums; // 字节数 __IO uint16_t Addr ; // 操作内存的起始地址 __IO uint16_t Num; // 寄存器或者线圈的数量 __IO uint16_t _CRC; // CRC校验码 __IO uint8_t *ValueReg; // 10H功能码的数据 __IO uint8_t *PtrCoilbase; // Coil和Input内存首地址 __IO uint8_t *PtrCoilOffset; // Coil和Input偏移内存首地址 __IO uint16_t *PtrHoldingbase; // HoldingReg内存首地址 __IO uint16_t *PtrHoldingOffset;// HoldingReg内存首地址 }PDUData_TypeDef; /* 宏定义 --------------------------------------------------------------------*/ #define MB_SLAVEADDR 0x0001 #define MB_ALLSLAVEADDR 0x00FF #define FUN_CODE_01H 0x01 // 功能码01H #define FUN_CODE_02H 0x02 // 功能码02H #define FUN_CODE_03H 0x03 // 功能码03H #define FUN_CODE_04H 0x04 // 功能码04H #define FUN_CODE_05H 0x05 // 功能码05H #define FUN_CODE_06H 0x06 // 功能码06H #define FUN_CODE_10H 0x10 // 功能码10H /* 本例程所支持的功能码,需要添加新功能码还需要在.c文件里面添加 */ #define IS_NOT_FUNCODE(code) (!((code == FUN_CODE_01H)||\ (code == FUN_CODE_02H)||\ (code == FUN_CODE_03H)||\ (code == FUN_CODE_04H)||\ (code == FUN_CODE_05H)||\ (code == FUN_CODE_06H)||\ (code == FUN_CODE_10H))) #define EX_CODE_NONE 0x00 // 异常码 无异常 #define EX_CODE_01H 0x01 // 异常码 #define EX_CODE_02H 0x02 // 异常码 校验错误 #define EX_CODE_03H 0x03 // 异常码 #define EX_CODE_04H 0x04 // 异常码 /* ----------------------- modbus reg lengh Defines ------------------------------------------*/ /* ----------------------- modbus 各个寄存器数据长度,允许用户调用的数据----------------------*/ #define DISCRETE_INPUT_START 1 #define DISCRETE_INPUT_NDISCRETES 96 #define COIL_START 1 #define COIL_NCOILS 96 #define REG_INPUT_START 1 #define REG_INPUT_NREGS 100 #define REG_HOLDING_START 1 #define REG_HOLDING_NREGS 100 /* ----------------------- modbus Static variables defines------------------------------------*/ extern uint16_t usDiscreteInputStart ; extern uint8_t usDiscreteInputBuf[DISCRETE_INPUT_NDISCRETES/8] ; extern uint16_t usCoilStart ; extern uint8_t usCoilBuf[COIL_NCOILS/8] ; extern uint16_t usRegInputStart ; extern uint16_t usRegInputBuf[REG_INPUT_NREGS] ; extern uint16_t usRegHoldingStart ; extern uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] ; extern __IO uint8_t Addr_Slave; extern __IO uint8_t Rx_Buf[256]; // 接收缓存,最大256字节 extern __IO uint8_t Tx_Buf[256]; // 接收缓存,最大256字节 extern __IO uint8_t tmp_Rx_Buf; // 接收缓存 extern __IO uint16_t RxCount; // 接收字符计数 extern PDUData_TypeDef PduData; void MB_Parse_Data(); uint8_t MB_Analyze_Execute(void ); #endif /* INC_CUS_MODBUS_H_ */
设定从机地址的话,只需要在定时器回调函数中限制一下即可

/* USER CODE BEGIN 1 */ __IO uint8_t Addr_Slave = 3;//设定从机地址 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == htim7.Instance) { SLAVE_RS485_SEND_MODE;//485切换成发送模式,不再接受新的串口中断 HAL_TIM_Base_Stop_IT(&htim7);//停止定时器 if(Rx_Buf[0] == Addr_Slave)//只处理本从机的命令 { MB_Analyze_Execute();//对接收到的数据进行分析并执行 } RxCount = 0;//清空接收计数 SLAVE_RS485_RECEIVE_MODE;//数据处理完毕后,再重新接收串口中断 } } /* USER CODE END 1 */
modbus主机代码如下:

/* * modbus_master.c * * Created on: Apr 14, 2022 * Author: MingYi-LZQ */ #include "main.h" #include "modbus_master.h" // CRC 高位字节值表 static const uint8_t MasterCRCHi[] = { 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 } ; // CRC 低位字节值表 static const uint8_t MasterCRCLo[] = { 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 }; /** * 函数功能: Modbus CRC16 校验计算函数 * 输入参数: pushMsg:待计算的数据首地址,usDataLen:数据长度 * 返 回 值: CRC16 计算结果 * 说 明: 计算结果是高位在前,需要转换才能发送 */ uint16_t MB_Master_CRC16(uint8_t *_pushMsg,uint8_t _usDataLen) { uint8_t uchCRCHi = 0xFF; uint8_t uchCRCLo = 0xFF; uint16_t uIndex; while(_usDataLen--) { uIndex = uchCRCLo ^ *_pushMsg++; uchCRCLo = uchCRCHi^MasterCRCHi[uIndex]; uchCRCHi = MasterCRCLo[uIndex]; } return (uchCRCHi<<8|uchCRCLo); } /** * 函数功能: 读单个线圈状态(CoilStatue) * 输入参数: _addr:从站地址,_reg:寄存器地址,num:读线圈数量 * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void ReadCoil_01H(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t num) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x01; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(num >> 8); /* 线圈(bit)个数 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(num); /* 线圈(bit)个数 低字节 */ crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ } /** * 函数功能: 写单个线圈状态(CoilStatue) * 输入参数: _addr:从站地址,_reg:寄存器地址,_sta:待写入的线圈状态(0,1) * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void WriteCoil_05H(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t _sta) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x05; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_sta >> 8); /* 线圈(bit)个数 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_sta); /* 线圈(bit)个数 低字节 */ crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ } /** * 函数功能: 写多个线圈状态(CoilStatue) * 输入参数: _addr:从站地址,_reg:寄存器地址,_length:待写入的线圈数量, * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void WriteCoil_0FH(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t _length, uint16_t _data) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x0F; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length >> 8); /* 数据长度 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length); /* 数据长度 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length); /* 数据字节数 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_data); /* 数据1*/ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_data >> 8); /* 数据2*/ crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ } /** * 函数功能: 写单个保持寄存器(HoldingRegister) * 输入参数: _addr:从站地址,_reg:寄存器地址,_data:待写入的寄存器数据 * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void WriteHoldingReg_06H(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t _data) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x06; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); ; /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_data >> 8); /* 寄存器(16bits)个数 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_data); /* 低字节 */ crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ } /** * 函数功能: 写多个保持寄存器(HoldingRegister) * 输入参数: _addr:从站地址,_reg:寄存器地址,_length写入数据长度,_data[]:待写入的寄存器数据 * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void WriteHoldingReg_10H(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t _length, uint16_t data[256]) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x10; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); ; /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length >> 8); ; /* 数据长度 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length); /* 数据长度 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length * 2); /* 数据字节数 */ for (int i = 0; i < _length; i++)//写入_length长度的数据 { uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(data[i] >> 8); /* 寄存器(16bits)个数 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(data[i]); /* 低字节 */ } crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ } /** * 函数功能: 写多个保持寄存器(HoldingRegister) * 输入参数: _addr:从站地址,_reg:寄存器地址,_length写入数据长度,_data[]:待写入的寄存器数据 * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void WriteHoldingReg_10H_short(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t _length, int16_t data[256]) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x10; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); ; /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length >> 8); ; /* 数据长度 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length); /* 数据长度 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_length * 2); /* 数据字节数 */ for (int i = 0; i < _length; i++)//写入_length长度的数据 { uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(data[i] >> 8); /* 寄存器(16bits)个数 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(data[i]); /* 低字节 */ } crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ } /** * 函数功能: 读保持寄存器(HoldingRegister) * 输入参数: _addr:从站地址,_reg:寄存器地址,_num:待读取的寄存器数量 * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void ReadHoldingReg_03H(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t _num) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x03; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); ; /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_num >> 8); /* 寄存器(16bits)个数 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_num); /* 低字节 */ crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ } /** * 函数功能: 读输入寄存器(InputRegister) * 输入参数: _addr:从站地址,_reg:寄存器地址,_num:待读取的寄存器数量 * 返 回 值: 无 * 说 明: 填充数据发送缓存区,然后发送 */ void ReadInputReg_04H(UartTimerTypeDef *uart_timer,uint8_t _addr, uint16_t _reg, uint16_t _num) { uint16_t crc = 0; uart_timer->TxCount = 0; uart_timer->Tx_Buf[uart_timer->TxCount ++] = _addr; /* 从站地址 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = 0x04; /* 功能码 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg >> 8); ; /* 寄存器地址 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_reg); /* 寄存器地址 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_num >> 8); /* 寄存器(16bits)个数 高字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(_num); /* 低字节 */ crc = MB_Master_CRC16(uart_timer->Tx_Buf, uart_timer->TxCount ); uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)crc; /* crc 低字节 */ uart_timer->Tx_Buf[uart_timer->TxCount ++] = (uint8_t)(crc >> 8); /* crc 高字节 */ }
把填充好的Tx_Buf通过串口发送出去即可,主机代码里的UartTimerTypeDef是自定义的结构体,不用此结构体直接用数组也可以,UartTimerTypeDef定义如下
typedef struct _UartTimerTypeDef { uint8_t id_rx; uint8_t id_tx; uint8_t IsRcFinish; uint8_t IsResponse; uint8_t aRxBuffer; uint8_t LengthRx; uint8_t RxCount; uint8_t TxCount; uint8_t DataLength; TIM_HandleTypeDef *timer; UART_HandleTypeDef *uart; uint8_t Rx_Buf[256]; uint8_t Tx_Buf[256]; }UartTimerTypeDef;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」