STM32-Modbus主机实现-正点原子精英板

STM32-Modbus主机实现-正点原子精英板

实现方案

使用网上大神的开源Modbus主机框架:
链接: 点击跳转.
链接: 源码地址.
融合参考了另一位大神在openedv论坛开源的Modbus主机代码:
链接: 点击跳转.
得到本次的Modbus主机代码。
移植到正点原子STM32F1精英板,可结合我发表的
链接: 正点原子精英板移植freemodbus-v1.6.
可实现Modbus主从机,当然,得使用两个485串口。

最终实现效果

正常通讯:
完全正常通讯通讯出错重复发送命令:
完成错误通讯

完整工程下载

链接: 点击下载.

移植过程

  1. 首先下载解压Modbus主机框架
    文件目录
    解压后得到以上文件
  2. 在mb_port.c 内添加各个函数的具体实现
/**
 * 	@brief  MODBUS串口初始化接口
 * 	@param	baud:串口波特率
 * 	@param 	parity:奇偶校验位设置	
 * 	@return	NONE
 * 	@note	需要根据使用MCU进行移植
 */
void mb_port_uartInit(uint32_t baud,uint8_t parity);
/**
 * 	@brief  串口TX\RX使能接口
 * 	@param	txen:0-关闭tx中断	1-打开tx中断
 * 	@param 	rxen:0-关闭rx中断	1-打开rx中断	
 * 	@return	NONE
 * 	@note	需要根据使用MCU进行移植
 */
void mb_port_uartEnable(uint8_t txen,uint8_t rxen);
/**
 * 	@brief  串口发送一个byte
 * 	@param	ch:要发送的byte	
 * 	@return	NONE
 * 	@note	需要根据使用MCU进行移植
 */
void mb_port_putchar(uint8_t ch);
/**
 * 	@brief  串口读取一个byte
 * 	@param	ch:存放读取一个byte的指针	
 * 	@return	NONE
 * 	@note	需要根据使用MCU进行移植
 */
void mb_port_getchar(uint8_t *ch);
/**
 * 	@brief  定时器初始化接口
 * 	@param	baud:串口波特率,根据波特率生成3.5T的定时
 * 	@return	NONE
 * 	@note	需要根据使用MCU进行移植
 */
void mb_port_timerInit(uint32_t baud);
/**
 * 	@brief  定时器使能
 * 	@return	NONE
 * 	@note	定时器要清0重新计数
 */
void mb_port_timerEnable(void);
/**
 * 	@brief  定时器关闭
 * 	@return	NONE
 * 	@note	定时器要清0重新计数
 */
void mb_port_timerDisable(void);

/**
 * 	@brief  定时器计数清0
 * 	@return	NONE
 * 	@note	定时器计数清0重新计数
 */
void mb_port_timerReset(void);

代码较为简单,不做过多介绍,这里我添加了一个mb_port_timerReset 计数清零函数,在mb_host.c 内用到,贴一下mb_port.c总的代码:
为了方便移植修改,使用了一些宏定义。

#include "mb_include.h"
//主机485发送/接收控制端定义 
#define MD_MASTER_TX_EN_CLK_FUN         RCC_APB2PeriphClockCmd
#define MD_MASTER_TX_EN_CLK             RCC_APB2Periph_GPIOD
#define MD_MASTER_TX_EN_PORT            GPIOD
#define MD_MASTER_TX_EN_PIN             GPIO_Pin_7

//主机485串口定义
#define MD_MASTER_USART                     USART2
#define MD_MASTER_USART_CLK_FUN             RCC_APB1PeriphClockCmd
#define MD_MASTER_USART_CLK                 RCC_APB1Periph_USART2
#define MD_MASTER_USART_IRQn                  USART2_IRQn
#define MD_MASTER_USART_IRQHandler          USART2_IRQHandler
//主机485串口TX RX引脚定义
#define MD_MASTER_TRX_GPIO_CLK      RCC_APB2Periph_GPIOA
#define MD_MASTER_TRX_GPIO_CLK_FUN  RCC_APB2PeriphClockCmd
#define MD_MASTER_TRX_GPIO_PORT     GPIOA
#define MD_MASTER_RX_PIN            GPIO_Pin_3
//#define MD_MASTER_RX_SOURCE         GPIO_PinSource7
#define MD_MASTER_TX_PIN            GPIO_Pin_2
//#define MD_MASTER_TX_SOURCE         GPIO_PinSource6
//主机使用的定时器定义
#define MD_MASTER_TIM               TIM4
#define MD_MASTER_TIM_CLK           RCC_APB1Periph_TIM4
#define MD_MASTER_TIM_CLK_FUN				RCC_APB1PeriphClockCmd
#define MD_MASTER_TIM_IRQn          TIM4_IRQn
#define MD_MASTER_TIM_IRQHandler    TIM4_IRQHandler

#define RS485_Frame_Distance  10 //数据帧最小间隔(ms),超过此时间则认为是下一帧

void mb_port_uartInit(uint32_t baud,uint8_t parity)
{
	/*串口部分初始化*/
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

  //使能USART,GPIOA
  MD_MASTER_TRX_GPIO_CLK_FUN(MD_MASTER_TRX_GPIO_CLK , ENABLE);
	MD_MASTER_USART_CLK_FUN(MD_MASTER_USART_CLK , ENABLE);
  //GPIOA9 USART1_Tx
  GPIO_InitStructure.GPIO_Pin = MD_MASTER_TX_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;             //推挽输出
  GPIO_Init(MD_MASTER_TRX_GPIO_PORT, &GPIO_InitStructure);
  //GPIOA.10 USART1_Rx
  GPIO_InitStructure.GPIO_Pin = MD_MASTER_RX_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;       //浮动输入
  GPIO_Init(MD_MASTER_TRX_GPIO_PORT, &GPIO_InitStructure);

  USART_InitStructure.USART_BaudRate = baud;            //只修改波特率
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  //串口初始化
  USART_Init(MD_MASTER_USART, &USART_InitStructure);
  //使能USART
  USART_Cmd(MD_MASTER_USART, ENABLE);
  

  
  //设定USART1 中断优先级
  NVIC_InitStructure.NVIC_IRQChannel = MD_MASTER_USART_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
  //最后配置485发送和接收模式
  MD_MASTER_TX_EN_CLK_FUN(MD_MASTER_TX_EN_CLK, ENABLE);
  //GPIOG.9
  GPIO_InitStructure.GPIO_Pin = MD_MASTER_TX_EN_PIN; 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(MD_MASTER_TX_EN_PORT, &GPIO_InitStructure); 
}

void mb_port_uartEnable(uint8_t txen,uint8_t rxen)
{
	if(txen)
	{
    //使能发送完成中断
    USART_ITConfig(MD_MASTER_USART, USART_IT_TC, ENABLE);	
		//MAX485操作 高电平为发送模式
    GPIO_SetBits(MD_MASTER_TX_EN_PORT,MD_MASTER_TX_EN_PIN);		
	}
	else
	{
    //禁止发送完成中断
    USART_ITConfig(MD_MASTER_USART, USART_IT_TC, DISABLE);
		//MAX485操作 低电平为接收模式
    GPIO_ResetBits(MD_MASTER_TX_EN_PORT,MD_MASTER_TX_EN_PIN);
	}
	
	if(rxen)
	{
		//使能接收和接收中断
    USART_ITConfig(MD_MASTER_USART, USART_IT_RXNE, ENABLE);
		//MAX485操作 低电平为接收模式
    GPIO_ResetBits(MD_MASTER_TX_EN_PORT,MD_MASTER_TX_EN_PIN);
	}
	else
	{
		USART_ITConfig(MD_MASTER_USART, USART_IT_RXNE, DISABLE); 
		//MAX485操作 高电平为发送模式
    GPIO_SetBits(MD_MASTER_TX_EN_PORT,MD_MASTER_TX_EN_PIN);
	}		
}
void mb_port_putchar(uint8_t ch)
{
//	huart1.Instance->DR = ch;  //直接操作寄存器比HAL封装的更高效
	  //发送数据
		USART_SendData(MD_MASTER_USART, ch);
		while(USART_GetFlagStatus(MD_MASTER_USART,USART_FLAG_TXE) == RESET){};//等待发送完成
}

void mb_port_getchar(uint8_t *ch)
{
//	*ch= (uint8_t)(huart1.Instance->DR & (uint8_t)0x00FF);	
	  *ch = (uint8_t)(USART_ReceiveData(MD_MASTER_USART));
}

void mb_port_timerInit(uint32_t baud)
{
	/*定时器部分初始化*/	
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  NVIC_InitTypeDef NVIC_InitStructure;

//  uint16_t PrescalerValue = 0;
  
  //使能定时器4时钟
  MD_MASTER_TIM_CLK_FUN(MD_MASTER_TIM_CLK, ENABLE);
  
  //定时器时间基配置说明
  //HCLK为72MHz,APB1经过2分频为36MHz
  //MD_MASTER_TIM的时钟倍频后为72MHz(硬件自动倍频,达到最大)
  //MD_MASTER_TIM的分频系数为3599,时间基频率为72 / (1 + Prescaler) = 20KHz,基准为50us
  //TIM最大计数值为usTim1Timerout50u
	//  PrescalerValue = (uint16_t) (SystemCoreClock / 20000) - 1; 
  //定时器1初始化
	/* If baudrate > 19200 then we should use the fixed timer values
	 * t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
	 */
	if(baud>19200)   //波特率大于19200固定使用1800作为3.5T
	{
		  TIM_TimeBaseStructure.TIM_Period = 35;
	}
	else   //其他波特率的需要根据计算
	{
	/* The timer reload value for a character is given by:
	 *
	 * ChTimeValue = Ticks_per_1s / ( baud / 11 )
	 *             = 11 * Ticks_per_1s / baud
	 *             = 220000 / baud
	 * The reload for t3.5 is 1.5 times this value and similary
	 * for t3.5.
	 */
		TIM_TimeBaseStructure.TIM_Period = (uint32_t)(( 7UL * 220000UL ) / ( 2UL * baud ));
	
	}
//	TIM_TimeBaseStructure.TIM_Period = RS485_Frame_Distance*10;
  TIM_TimeBaseStructure.TIM_Prescaler =(uint16_t) (SystemCoreClock / 20000) - 1;//20KHZ
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseInit(MD_MASTER_TIM, &TIM_TimeBaseStructure);
  //预装载使能
  TIM_ARRPreloadConfig(MD_MASTER_TIM, ENABLE);
  
  //定时器4中断优先级
  NVIC_InitStructure.NVIC_IRQChannel = MD_MASTER_TIM_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
  //清除溢出中断标志位
  TIM_ClearITPendingBit(MD_MASTER_TIM,TIM_IT_Update);
  //定时器4溢出中断关闭
  TIM_ITConfig(MD_MASTER_TIM, TIM_IT_Update, DISABLE);
  //定时器4禁能
  TIM_Cmd(MD_MASTER_TIM,  DISABLE);
}

void mb_port_timerEnable()
{
  TIM_ClearITPendingBit(MD_MASTER_TIM, TIM_IT_Update);
  TIM_ITConfig(MD_MASTER_TIM, TIM_IT_Update, ENABLE);
  //设定定时器4的初始值
  TIM_SetCounter(MD_MASTER_TIM,0x0000); 
  //定时器4启动
  TIM_Cmd(MD_MASTER_TIM, ENABLE);
}

void mb_port_timerDisable()
{
	TIM_ClearITPendingBit(MD_MASTER_TIM, TIM_IT_Update);
  TIM_ITConfig(MD_MASTER_TIM, TIM_IT_Update, DISABLE);
  TIM_SetCounter(MD_MASTER_TIM,0x0000); 
  //关闭定时器4
  TIM_Cmd(MD_MASTER_TIM, DISABLE);
}
void mb_port_timerReset(void)
{
	TIM_SetCounter(MD_MASTER_TIM,0x0000);
}
//串口中断服务函数
void MD_MASTER_USART_IRQHandler()
{
  //发生接收中断
  if(USART_GetITStatus(MD_MASTER_USART, USART_IT_RXNE) == SET)
  {
    //清除中断标志位    
    USART_ClearITPendingBit(MD_MASTER_USART, USART_IT_RXNE);   
		mbh_uartRxIsr();
  }
  
  //发生完成中断
  if(USART_GetITStatus(MD_MASTER_USART, USART_IT_TC) == SET)
  {
    //清除中断标志
    USART_ClearITPendingBit(MD_MASTER_USART, USART_IT_TC);
		mbh_uartTxIsr();
  }	
}

//定时器中断服务函数
void MD_MASTER_TIM_IRQHandler()
{
  if (TIM_GetITStatus(MD_MASTER_TIM, TIM_IT_Update) != RESET)
  {
    //清除定时器T4溢出中断标志位
    TIM_ClearITPendingBit(MD_MASTER_TIM, TIM_IT_Update);

    mbh_timer3T5Isr();
  }
}
  1. mb_hook.c内需要添加各个功能码回调处理函数,以及错误处理
/**
 * 	@brief  MODBUS主机模式下接收到从机回复不同功能码的回调处理
 * 	@param	add:从机的地址
 * 	@param 	data:接收到的从机发来的数据指针
 *  @param 	datalen:接收到的从机发来的数据长度
 * 	@return	NONE
 * 	@note	rec01\02\03……等数字代表功能码
 */
void mbh_hook_rec01(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec02(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec03(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec04(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec05(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec06(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec15(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec16(uint8_t add,uint8_t *data,uint8_t datalen);
/**
 * 	@brief  MODBUS主机读写从机超过最大错误次数回调
 *	@param	add:从机的地址
 *	@param  cmd:功能码
 * 	@return	NONE
 * 	@note	
 */
void mbh_hook_timesErr(uint8_t add,uint8_t cmd);

这里参考openedv论坛那位大哥的代码,编写各个函数的实现逻辑,需要注意的是freemodbus的开关与线圈是用位来表示的,这位老哥的是单个数组元素表示一个位,为了与freemodbus兼容,这里也改成单个位表示,一个字节表示8位。

//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE/8 ] = {0x11,0xEF};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE/8 ] = {0x00,0x00};

为了与freemodbus兼容参考freemodbus源码内对数组单个位操作的函数,并移植到这里,mb_hook.c全部代码:

#include "mb_include.h"
这里使用了宏定义来方便修改主机使用的数据,方便主从机移植。
/***************主机寄存器宏定义***************/
#define M_CoilsRegBuf       ucRegCoilsBuf
#define M_DiscreteRegBuf    ucRegDiscreteBuf
#define M_HoldingRegBuf     usRegHoldingBuf
#define M_InputRegBuf       usRegInputBuf
/*********************************************/

uint16_t  SaveStartAddr = 0;  //数据保存起始地址
uint16_t  DataORLenth = 24; //数据长度 在写单个线圈寄存器时代表值
uint8_t MD_MASTER_ComErr = 8; //0代表通讯正常

#define BITS_UINT8_T      8U
/*
ucByteBuf:位存储的缓冲区。必须是2个字节。

usBitOffset: 位设置的起始地址,第一个位的偏移为0。

ucNBits:  需要修改的位的数量。该值必须小于8。

ucValue: 位的新值。在usBitOffset中的第一位的值是ucValues的最低有效位。
*/
/* ----------------------- Start implementation -----------------------------*/
void MBSetBits( uint8_t * ucByteBuf, uint16_t usBitOffset, uint8_t ucNBits,uint8_t ucValue )
{
    uint16_t          usWordBuf;
    uint16_t          usMask;
    uint16_t          usByteOffset;
    uint16_t          usNPreBits;
    uint16_t          usValue = ucValue;

//    assert( ucNBits <= 8 );
//    assert( ( size_t )BITS_UINT8_T == sizeof( uint8_t ) * 8 );

    /* Calculate byte offset for first byte containing the bit values starting
     * at usBitOffset. */
    usByteOffset = ( uint16_t )( ( usBitOffset ) / BITS_UINT8_T );

    /* How many bits precede our bits to set. */
    usNPreBits = ( uint16_t )( usBitOffset - usByteOffset * BITS_UINT8_T );

    /* Move bit field into position over bits to set */
    usValue <<= usNPreBits;

    /* Prepare a mask for setting the new bits. */
    usMask = ( uint16_t )( ( 1 << ( uint16_t ) ucNBits ) - 1 );
    usMask <<= usBitOffset - usByteOffset * BITS_UINT8_T;

    /* copy bits into temporary storage. */
    usWordBuf = ucByteBuf[usByteOffset];
    usWordBuf |= ucByteBuf[usByteOffset + 1] << BITS_UINT8_T;

    /* Zero out bit field bits and then or value bits into them. */
    usWordBuf = ( uint16_t )( ( usWordBuf & ( ~usMask ) ) | usValue );

    /* move bits back into storage */
    ucByteBuf[usByteOffset] = ( uint8_t )( usWordBuf & 0xFF );
    ucByteBuf[usByteOffset + 1] = ( uint8_t )( usWordBuf >> BITS_UINT8_T );
}
/*
ucByteBuf:位存储的缓冲区。必须是2个字节。

usBitOffset: 位设置的起始地址,第一个位的偏移为0。

ucNBits:  需要修改的位的数量。该值必须小于8。
*/
uint8_t MBGetBits( uint8_t * ucByteBuf, uint16_t usBitOffset, uint8_t ucNBits )
{
    uint16_t          usWordBuf;
    uint16_t          usMask;
    uint16_t          usByteOffset;
    uint16_t          usNPreBits;

    /* Calculate byte offset for first byte containing the bit values starting
     * at usBitOffset. */
    usByteOffset = ( uint16_t )( ( usBitOffset ) / BITS_UINT8_T );

    /* How many bits precede our bits to set. */
    usNPreBits = ( uint16_t )( usBitOffset - usByteOffset * BITS_UINT8_T );

    /* Prepare a mask for setting the new bits. */
    usMask = ( uint16_t )( ( 1 << ( uint16_t ) ucNBits ) - 1 );

    /* copy bits into temporary storage. */
    usWordBuf = ucByteBuf[usByteOffset];
    usWordBuf |= ucByteBuf[usByteOffset + 1] << BITS_UINT8_T;

    /* throw away unneeded bits. */
    usWordBuf >>= usNPreBits;

    /* mask away bits above the requested bitfield. */
    usWordBuf &= usMask;

    return ( uint8_t ) usWordBuf;
}


//data[0] 返回的字节数 data[1]~[X]数据
void mbh_hook_rec01(uint8_t add, uint8_t *data, uint8_t datalen)
{
//    uint16_t i;
		//寄存器个数
		int16_t Coils = DataORLenth;
		//寄存器偏移量
		uint16_t BitOffset=SaveStartAddr;
    if ((SaveStartAddr + DataORLenth) <= REG_COILS_SIZE) //寄存器地址+数量在范围内
    {
//        for (i = 0; i < DataORLenth; i++)
//        {
//            M_CoilsRegBuf[SaveStartAddr + i] = data[1 + i / 8] & 0x01; //低位先发送
//            data[1 + i / 8] >>= 1;
//        }
				data++;
        while( Coils > 0 )
        {
          MBSetBits( M_CoilsRegBuf, BitOffset,
                        ( uint8_t )( Coils > 8 ? 8 : Coils ),
                        *data++ );
          Coils -= 8;
					BitOffset +=8;
        }
        MD_MASTER_ComErr = 0;
    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}
void mbh_hook_rec02(uint8_t add, uint8_t *data, uint8_t datalen)
{
//    uint16_t i;
		//寄存器个数
		int16_t Coils = DataORLenth;
		//寄存器偏移量
		uint16_t BitOffset=SaveStartAddr;
    if ((SaveStartAddr + DataORLenth) <= REG_DISCRETE_SIZE) //寄存器地址+数量在范围内
    {
//        for (i = 0; i < DataORLenth; i++)
//        {
//            M_DiscreteRegBuf[SaveStartAddr + i] = data[1 + i / 8] & 0x01; //低位先发送
//            data[1 + i / 8] >>= 1;
//        }
				data++;
        while( Coils > 0 )
        {
          MBSetBits( M_DiscreteRegBuf, BitOffset,
                        ( uint8_t )( Coils > 8 ? 8 : Coils ),
                        *data++ );
          Coils -= 8;
					BitOffset +=8;
        }
        MD_MASTER_ComErr = 0;
    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}
void mbh_hook_rec03(uint8_t add, uint8_t *data, uint8_t datalen)
{
    uint8_t i;
    uint8_t RegNum;
    RegNum = data[0] / 2; //获取字节数
    if ((SaveStartAddr + RegNum) < 1000) //寄存器地址+数量在范围内
    {
        for (i = 0; i < RegNum; i++)
        {
            M_HoldingRegBuf[SaveStartAddr + i] = data[1 + i * 2];    /高8位
            M_HoldingRegBuf[SaveStartAddr + i] = data[2 + i * 2] + (M_HoldingRegBuf[SaveStartAddr + i] << 8); // 低8位+高8位
        }

        MD_MASTER_ComErr = 0;
    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}
void mbh_hook_rec04(uint8_t add, uint8_t *data, uint8_t datalen)
{
    uint8_t i;
    uint8_t RegNum;
    RegNum = data[0] / 2; //获取字节数
    if ((SaveStartAddr + RegNum) < 1000) //寄存器地址+数量在范围内
    {
        for (i = 0; i < RegNum; i++)
        {
            M_InputRegBuf[SaveStartAddr + i] = data[1 + i * 2];    /高8位
            M_InputRegBuf[SaveStartAddr + i] = data[2 + i * 2] + (M_InputRegBuf[SaveStartAddr + i] << 8); // 低8位+高8位
        }

        MD_MASTER_ComErr = 0;
    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}
void mbh_hook_rec05(uint8_t add, uint8_t *data, uint8_t datalen)
{
    uint16_t i;
    i = DataORLenth;
    if ((i > 0 && data[2] == 0XFF && data[3] == 0X00) || (i == 0 && data[2] == 0X00 && data[3] == 0X00))
    {
        MD_MASTER_ComErr = 0;

    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}
void mbh_hook_rec06(uint8_t add, uint8_t *data, uint8_t datalen)
{
    uint16_t i; //数据返回校验用
    i = (((uint16_t)data[2]) << 8) | data[3]; //获取寄存器值
    if (i == M_HoldingRegBuf[SaveStartAddr])
    {
        MD_MASTER_ComErr = 0;
    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}
void mbh_hook_rec15(uint8_t add, uint8_t *data, uint8_t datalen)
{
    uint16_t i;//数据返回校验用
    i = (((uint16_t)data[2]) << 8) | data[3]; //获取寄存器数量
    if (i == DataORLenth)
    {
        MD_MASTER_ComErr = 0;
    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}
void mbh_hook_rec16(uint8_t add, uint8_t *data, uint8_t datalen)
{
    uint16_t i;//数据返回校验用
    i = (((uint16_t)data[2]) << 8) | ((data[3])); //获取寄存器数量
    if (i == DataORLenth)
    {
         MD_MASTER_ComErr = 0;
    }
    else
    {
        MD_MASTER_ComErr = 255;
    }
}

//连续错误处理
void mbh_hook_timesErr(uint8_t add, uint8_t cmd)
{
	mbHost.state = MBH_STATE_IDLE;//状态切换为空闲
	MD_MASTER_ComErr = 0;
}

我再连续出错里没有做什么处理,只是将状态消除好继续下一个发送命令,这里可以加入自己想要的处理。

  1. 在mb_host.c主要修改3个函数
/**
 * 	@brief  MODBUS主机给从机发送一条命令
 * 	@param	add:从机地址
 * 	@param 	cmd:功能码
 * 	@param	start_address:数据起始地址
 *	@param	data:要发送的数据
 *	@param	len:发送的数据长度
 * 	@return	-1:发送失败	0:发送成功
 * 	@note	该函数为非阻塞式,调用后立即返回
 */
int8_t mbh_send(uint8_t add,uint8_t cmd,uint16_t start_address,uint16_t *data,uint16_t data_len);
以及
void mbh_uartRxIsr(void);
/**
 * 	@brief  modbus主机串口接收中断处理
 * 	@return	none
 * 	@note	放在mcu的tx中断中调用
 *			
 */
void mbh_uartTxIsr(void);

修改主机命令发送函数:

//发送一帧命令
int8_t mbh_send(uint8_t add, uint8_t cmd, uint16_t start_address, uint16_t *data, uint16_t data_len)
{
    uint16_t crc;
    uint16_t temp = 0;
    uint16_t i;
    int16_t Coils = data_len;
    //偏移量
    uint16_t  BitOffset = start_address;
    uint8_t *data8_P = (uint8_t *)data; //
    if (mbHost.state != MBH_STATE_IDLE)return -1; //busy state

    mbHost.txCounter = 0;
    mbHost.rxCounter = 0;
    mbHost.txBuf[0] = add;
    mbHost.txBuf[1] = cmd;
    mbHost.txBuf[2] = HI(start_address);
    mbHost.txBuf[3] = LOW(start_address);
    switch (cmd)
    {
    case READ_COIL:
    case READ_DI:
    case READ_HLD_REG:
    case READ_AI:
        mbHost.txBuf[4] = HI(data_len);
        mbHost.txBuf[5] = LOW(data_len);
        break;
    case SET_COIL:
        if (DataORLenth)temp = 0xFF00;
        else temp = 0x0000;
        mbHost.txBuf[4] = HI(temp);
        mbHost.txBuf[5] = LOW(temp);
        break;
    case SET_HLD_REG:
        mbHost.txBuf[4] = HI(data[start_address]);
        mbHost.txBuf[5] = LOW(data[start_address]);
        break;
    case WRITE_COIL:
        temp = 0;
        while (Coils > 0)
        {
            mbHost.txBuf[7 + temp] = MBGetBits(data8_P, BitOffset,
                                               (uint8_t)(Coils > 8 ? 8 : Coils));
            Coils -= 8;
            temp++;
            BitOffset += 8;
        }
        mbHost.txBuf[4] = HI(data_len);
        mbHost.txBuf[5] = LOW(data_len);
        mbHost.txBuf[6] = temp;
        break;
    case WRITE_HLD_REG:
        temp = 2 * data_len;
        for (i = 0; i < data_len; i++)
        {
            mbHost.txBuf[7 + i * 2] = data[start_address + i] >> 8; //高字节在前
            mbHost.txBuf[8 + i * 2] = data[start_address + i]; //低字节在后
        }
        mbHost.txBuf[4] = HI(data_len);
        mbHost.txBuf[5] = LOW(data_len);
        mbHost.txBuf[6] = temp;
        break;
    }
    mbHost.txLen = 6; //data_len(2)+start_address(2)+add(1)+cmd(1)
    if (cmd == WRITE_COIL || cmd == WRITE_HLD_REG)
    {
        mbHost.txLen += temp + 1; //mbHost.txLen=7
    }
    crc = mb_crc16(mbHost.txBuf, mbHost.txLen);
    mbHost.txBuf[mbHost.txLen++] = (uint8_t)(crc & 0xff);
    mbHost.txBuf[mbHost.txLen++] = (uint8_t)(crc >> 8);

    mbHost.state = MBH_STATE_TX;
    mb_port_uartEnable(1, 0); //enable tx,disable rx
    /*先发送一个byte,触发TC中断*/
    mb_port_putchar(mbHost.txBuf[mbHost.txCounter++]); //send first char,then enter tx isr
    return 0;
}

另外两个发送字节处理与接收字节处理:

void mbh_uartRxIsr()
{
    uint8_t ch;
    mb_port_getchar(&ch);
    switch (mbHost.state)
    {
    case MBH_STATE_TX_END:
        mbHost.rxCounter = 0;
        mbHost.rxBuf[mbHost.rxCounter++] = ch;
        mbHost.state = MBH_STATE_RX;
        mb_port_timerReset();//收到数据,重新从0计数
        break;
    case MBH_STATE_RX:
        if (mbHost.rxCounter < MBH_RTU_MAX_SIZE)
        {
            mbHost.rxBuf[mbHost.rxCounter++] = ch;
        }
        mb_port_timerReset();//收到数据,重新从0计数
        break;
    default:
//        mb_port_timerEnable();
        break;
    }
}
void mbh_uartTxIsr()
{
    switch (mbHost.state)
    {
    case MBH_STATE_TX:
        if (mbHost.txCounter == mbHost.txLen) //全部发送完
        {
            mbHost.state = MBH_STATE_TX_END;
            mb_port_uartEnable(0, 1); //disable tx,enable rx
            mbHost.rxTimeOut = 0;     //清除接收超时计数
            mb_port_timerEnable();    //open timer
        }
        else
        {
            mb_port_putchar(mbHost.txBuf[mbHost.txCounter++]);
        }
        break;
    case MBH_STATE_TX_END:
        mb_port_uartEnable(0, 1);     //disable tx,enable rx
//				mb_port_timerEnable();    //open timer
        break;
    }
}
  1. 在main函数中添加测试代码
int main(void)
{
    uint8_t Tx_state = 0x00;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    mbh_init(9600, 0);
    SaveStartAddr = 0;
    DataORLenth = 9;
    mbh_send(1, READ_AI, 0, usRegInputBuf, DataORLenth);
		delay_init();
		
    while (1)
    {
        mbh_poll();
            if (MD_MASTER_ComErr == 0)
            {
								MD_MASTER_ComErr=255;
								delay_ms(1000);
                switch (Tx_state)
                {
                case 0:
                    mbh_send(1, WRITE_HLD_REG, 0, usRegInputBuf, DataORLenth);
                    Tx_state++;
                    break;
                case 1:
                    mbh_send(1, READ_DI, 0, (uint16_t *)ucRegDiscreteBuf, DataORLenth);
                    Tx_state++;
                    break;
                case 2:
                    mbh_send(1, WRITE_COIL, 0, (uint16_t *)ucRegDiscreteBuf, DataORLenth);
                    Tx_state++;
                    break;
                case 3:
//                    memset(ucRegCoilsBuf, 0, 2); //将数据清0
                    mbh_send(1, READ_COIL, 0, (uint16_t *)ucRegCoilsBuf, DataORLenth);
                    Tx_state++;
                    break;
                case 4:
										ucRegCoilsBuf[0]=0xEF;
                    mbh_send(1, WRITE_COIL, 0, (uint16_t *)ucRegCoilsBuf, DataORLenth);
                    Tx_state++;
                    break;
                case 5:
                    memset(usRegHoldingBuf, 0, 20); //将数据清0
                    mbh_send(1, READ_HLD_REG, 0, usRegHoldingBuf, DataORLenth);
                    Tx_state++;
                    break;
                case 6:
										usRegHoldingBuf[0]=12345;
                    mbh_send(1, WRITE_HLD_REG, 0, usRegHoldingBuf, DataORLenth);
                    Tx_state++;
                    break;
                }
								
            }
    }
}
STM32 Modbus-RTU主机程序是一种针对STM32单片机开发的软件程序,用于实现Modbus通信协议中主站(主机)的功能。Modbus-RTU是一种常见的串行通信协议,用于在工业自动化系统中进行数据交换。 STM32 Modbus-RTU主机程序的设计目的是实现STM32单片机作为Modbus通信系统中的主站,具备与从站进行通信的能力。主机程序中通过串口与从站进行通信,接收和发送数据。主机程序需要实现Modbus协议的相关功能,包括函数码解析、地址解析、数据读写操作等。 在设计STM32 Modbus-RTU主机程序时,需要考虑以下几个关键因素。首先,需要确定从站的地址和通信参数,包括波特率、数据位数、停止位等。其次,需要实现Modbus通信协议的各种功能,如读取保持寄存器、读取输入寄存器、写入单个线圈等。此外,还要考虑主机与从站的通信方式和通信频率,以及数据的处理和解析方式。 在编写STM32 Modbus-RTU主机程序时,可以使用STM32开发环境进行开发,如Keil等。首先建立串口通信功能,然后根据主机与从站的通信协议,实现相应的Modbus函数码解析和数据读写操作。最后进行测试和调试,确保主机程序能够正确地与从站进行通信,并实现所需的数据交换功能。 总之,STM32 Modbus-RTU主机程序是一种通过STM32单片机实现Modbus通信协议的软件程序。它能够使STM32单片机具备作为主站与从站进行通信的能力,并实现相关的数据读写操作。通过该主机程序,能够在工业自动化系统中实现高效可靠的通信。
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值