STM32F407 freemodbus移植

12 篇文章 12 订阅
7 篇文章 9 订阅

STM32F407 freemodbus移植

一、ModBus介绍

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。 [1] Modbus比其他通信协议使用的更广泛的主要原因有:
(1) 公开发表并且无版权要求
(2) 易于部署和维护
(3) 对供应商来说,修改移动本地的比特或字节没有很多限制
Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信,举个例子,一个由测量温度和湿度的装置,并且将结果发送给计算机。在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。

支持的功能码
目前版本的FreeModbus支持如下的功能码:

	读输入寄存器 (0x04)
	读保持寄存器 (0x03)
	写单个寄存器 (0x06)
	写多个寄存器 (0x10)
	读/写多个寄存器 (0x17)
	读取线圈状态 (0x01)
	写单个线圈 (0x05)
	写多个线圈 (0x0F)
	读输入状态 (0x02)
	报告从机标识 (0x11)

二、freemodbus准备

1、freemodbus可以从官网进行下载,从官网下载可以获取到最新的freemodbus源码,freemodbus官网下载地址:
官网:
https://www.embedded-solutions.at/en/freemodbus/

下载地址:
https://github.com/cwalter-at/freemodbus

2、从我个人百度网盘直接下载,现在我网盘是v1.5版本,网盘链接地址:
链接:https://pan.baidu.com/s/1Jc1YhdinmmKvNt107wsMeA
提取码:88mc

三、freemodbus移植到STM32F407

1、准备STM32工程与freemodbus源码

准备STM32F407工程,工程时钟必须配置正确,如果开发板串口能正确收数据,表示工程是正确的,同时要准备好freemodbus源码。

2、freemodbus源码添加到STM32工程

(1)解压freemodbus源码,解压得到如理内容。
在这里插入图片描述
我们需要的是modbus这个文件夹,和demo->BARE下的port文件夹。

核心文件结构
FreeModbus\modbus\mb.c
给应用层提供Modbus从机设置及轮询相关接口

FreeModbus\modbus\mb_m.c
给应用层提供Modbus主机设置及轮询相关接口

FreeModbus\modbus\ascii\mbascii.c
ASCII模式设置及其状态机

FreeModbus\modbus\functions\mbfunccoils.c
从机线圈相关功能

FreeModbus\modbus\functions\mbfunccoils_m.c
主机线圈相关功能

FreeModbus\modbus\functions\mbfuncdisc.c
从机离散输入相关功能

FreeModbus\modbus\functions\mbfuncdisc_m.c
主机离散输入相关功能

FreeModbus\modbus\functions\mbfuncholding.c
从机保持寄存器相关功能

FreeModbus\modbus\functions\mbfuncholding_m.c
主机保持寄存器相关功能

FreeModbus\modbus\functions\mbfuncinput.c
从机输入寄存器相关功能

FreeModbus\modbus\functions\mbfuncinput_m.c
主机输入寄存器相关功能

FreeModbus\modbus\functions\mbfuncother.c
其余Modbus功能

FreeModbus\modbus\functions\mbutils.c
一些协议栈中需要用到的小工具

FreeModbus\modbus\rtu\mbcrc.c
CRC校验功能

FreeModbus\modbus\rtu\mbrtu.c
从机RTU模式设置及其状态机

FreeModbus\modbus\rtu\mbrtu_m.c
主机RTU模式设置及其状态机

FreeModbus\modbus\tcp\mbtcp.c
TCP模式设置及其状态机

FreeModbus\port\port.c
实现硬件移植部分接口

FreeModbus\port\portevent.c
实现从机事件移植接口

FreeModbus\port\portevent_m.c
实现主机事件及错误处理移植接口

FreeModbus\port\portserial.c
从机串口移植

FreeModbus\port\portserial_m.c
主机串口移植

FreeModbus\port\porttimer.c
从机定时器移植

FreeModbus\port\porttimer_m.c
主机定时器移植

FreeModbus\port\user_mb_app.c
定义从机数据缓冲区,实现从机Modbus功能的回调接口

FreeModbus\port\user_mb_app_m.c
定义主机数据缓冲区,实现主机Modbus功能的回调接口

(2)将上面两个文件夹内容拷贝到STM32工程文件夹中,并在STM32工程中添加管理及下图中的文件
在这里插入图片描述
在这里插入图片描述

工程中包含modbus头文件
在这里插入图片描述

在主函数中最下面添加代码,这部分非常重要

#ifdef  USE_FULL_ASSERT  
/**  
  * @brief  Reports the name of the source file and the source line number  
  *         where the assert_param error has occurred.  
  * @param  file: pointer to the source file name  
  * @param  line: assert_param error line source number  
  * @retval None  
  */    
void assert_failed(uint8_t* file, uint32_t line)    
{    
  /* User can add his own implementation to report the file name and line number,  
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */    
     
  /* Infinite loop */    
  while (1)    
  {    
  }    
}  
#else  
void __aeabi_assert(const char * x1, const char * x2, int x3)  
{  
}  
#endif  

3、修改工程代码,完成Modbus移植

(1)完善portserial.c文件。该文件就是modbus通信中用到的串口的初始化配置文件。我这里选择USART2,波特率9600,由于我使用的开发板RS485连接在USART2.

USART2初始化代码如下:

/*********************************
引脚说明:

PA2  ---- USART2_TX(发送端)
PA3  ---- USART2_RX(接收端)

**********************************/


void Usart2_Init(int MyBaudRate)
{
	GPIO_InitTypeDef  	GPIO_InitStruct;
	USART_InitTypeDef	USART_InitStruct;
	NVIC_InitTypeDef  	NVIC_InitStruct;
	
	//GPIO 时钟使能。
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);	
	//串口时钟使能
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

	
	//设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); 	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); 
	
	//初始化设置:要设置模式为复用功能。
	GPIO_InitStruct.GPIO_Pin	= GPIO_Pin_2|GPIO_Pin_3;		//引脚2 3
	GPIO_InitStruct.GPIO_Mode	= GPIO_Mode_AF;					//复用功能模式
	GPIO_InitStruct.GPIO_OType	= GPIO_OType_PP;				//推挽输出
	GPIO_InitStruct.GPIO_PuPd	= GPIO_PuPd_UP;					//上拉
	GPIO_InitStruct.GPIO_Speed	= GPIO_Speed_50MHz; 			//速度 
	//初始化IO口为复用功能输出
	GPIO_Init(GPIOA, &GPIO_InitStruct);	

	
	USART_InitStruct.USART_BaudRate				= MyBaudRate;					//波特率
	USART_InitStruct.USART_Mode					= USART_Mode_Tx|USART_Mode_Rx;	//全双工模式
	USART_InitStruct.USART_StopBits				= USART_StopBits_1;				//停止位为1
	USART_InitStruct.USART_WordLength			= USART_WordLength_8b;			//8位
	USART_InitStruct.USART_Parity				= USART_Parity_No;				//无奇偶校验位
	USART_InitStruct.USART_HardwareFlowControl	= USART_HardwareFlowControl_None;//无硬件控制流
	//串口参数初始化:设置波特率,字长,奇偶校验等参数。
	USART_Init(USART2, &USART_InitStruct);
	
	
	NVIC_InitStruct.NVIC_IRQChannel						= USART2_IRQn; 		//中断通道,可在stm32f4xx.h文件当中查找
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority	= 1;				//抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority			= 1;				//响应优先级
	NVIC_InitStruct.NVIC_IRQChannelCmd					= ENABLE;			//通道使能
	//开启中断并且初始化 NVIC,使能中断
	NVIC_Init(&NVIC_InitStruct);


	//配置为接收中断(表示有数据过来,CPU要中断进行接收)
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

	//使能串口。
	USART_Cmd(USART2, ENABLE);

}



打开portserial.c,内容如下

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return FALSE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    /* Put a byte in the UARTs transmit buffer. This function is called
     * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
     * called. */
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    /* Return the byte in the UARTs receive buffer. This function is called
     * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
     */
    return TRUE;
}

认真看一下函数名字,你会发现这些函数分别是:串口使能、串口初始化、发送一个字节、接收一个字节。

将上面代码修改如下

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "stm32f4xx.h"	//添加STM32头文件
#include "usart.h"		//添加串口头文件

/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
	//STM32串口 接收中断使能  
	if(xRxEnable==TRUE)   
	{   
		//使能接收和接收中断  
		 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);  
	}   
	else if(xRxEnable == FALSE)  
	{   
		 //禁止接收和接收中断    
		 USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);  
	}  
	
	//STM32串口 发送中断使能  
	if(xTxEnable==TRUE)   
	{  
		//使能发送完成中断  
		USART_ITConfig(USART2, USART_IT_TXE, ENABLE);  
	}   
	else if(xTxEnable == FALSE)   
	{  
		//禁止发送完成中断  
		USART_ITConfig(USART2, USART_IT_TXE, DISABLE);  
	}  
	else if(xTxEnable == FALSE)   
	{  
		 //MODBUS_RECIEVE();  
		 USART_ITConfig(USART2, USART_IT_TC, DISABLE);  
	}	
	
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	
	//串口初始化,只需要传输串口波特率
	Usart2_Init((uint16_t)ulBaudRate);	
	
    return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    /* Put a byte in the UARTs transmit buffer. This function is called
     * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
     * called. */
	//发送数据
	USART_SendData(USART2, ucByte);  
	while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET){};	
	
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    /* Return the byte in the UARTs receive buffer. This function is called
     * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
     */
	//接收数据
	*pucByte = USART_ReceiveData(USART2);    
    return TRUE;  
}

/* Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call 
 * xMBPortSerialPutByte( ) to send the character.
 */
static void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

/* Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}


//串口中断处理函数  
void USART2_IRQHandler(void)  
{  
	if(USART_GetITStatus(USART2, USART_IT_TXE) == SET)   
	{  
		prvvUARTTxReadyISR();  
		USART_ClearITPendingBit(USART2, USART_IT_TXE);  
	}  
   
	if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)   
	{  
		prvvUARTRxISR();  
		USART_ClearITPendingBit(USART2, USART_IT_RXNE);  
	}  
}  

(2)完善porttimer.c文件。并配置一个大于3.5字节的定时器中断

/*********************************
定时器说明
TIM3 -- APB1(定时器频率:84MHZ)

TIM3是16位定时器
**********************************/

void Tim3_Init(uint16_t tim)
{

	//4、设置 TIM3_DIER  允许更新中断
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	
	//5、使能定时器。
	TIM_Cmd(TIM3, ENABLE);


	TIM_TimeBaseInitTypeDef  	TIM_TimeBaseInitStruct;
	NVIC_InitTypeDef  			NVIC_InitStruct;
	//1、能定时器时钟。
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	
	TIM_TimeBaseInitStruct.TIM_Prescaler	= (4200-1);			//4200分频,定时器频率84MHZ/4200 = 20000HZ
	TIM_TimeBaseInitStruct.TIM_Period		= tim;				//定时器周期 20000
	TIM_TimeBaseInitStruct.TIM_CounterMode	= TIM_CounterMode_Up;	//向上计数
	TIM_TimeBaseInitStruct.TIM_ClockDivision= TIM_CKD_DIV1;			//分频因子	1脉冲计一个数
	//2、初始化定时器,配置ARR,PSC。
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);


	
	NVIC_InitStruct.NVIC_IRQChannel						= TIM3_IRQn; 		//中断通道,可在stm32f4xx.h文件当中查找
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority	= 1;				//抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority			= 1;				//响应优先级
	NVIC_InitStruct.NVIC_IRQChannelCmd					= ENABLE;			//通道使能
	//3、启定时器中断,配置NVIC。
	NVIC_Init(&NVIC_InitStruct);	

	//清除溢出中断标志位  
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  
	//定时器溢出中断关闭  
	  TIM_ITConfig(TIM3,TIM_IT_Update,DISABLE);  
	//失能定时器  
	TIM_Cmd(TIM3, DISABLE);   
 
}

porttimer.c修改如下

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "tim.h"

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	
	/********************************************
	usTim1Timerout50us实际值	
    usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
    ulBaudRate为9600,计算得出usTimerT35_50us为80

   ********************************************/
	Tim3_Init(usTim1Timerout50us);
	
	
    return TRUE; 
}


inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);  
	//设置定时器的初值  
	TIM_SetCounter(TIM3,0x0000);  
	TIM_Cmd(TIM3, ENABLE); 

}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  
	TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE);  
	TIM_SetCounter(TIM3,0x0000);   
	//关闭定时器  
	TIM_Cmd(TIM3, DISABLE);  

}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}


//Modbus 定时器中断函数  
void TIM3_IRQHandler( void )  
{  
  if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)   
{  
     TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  
     prvvTIMERExpiredISR();  
   }  
}  

(3)在main.c文件中,定义各个模拟寄存器的地址和大小。

#include "stm32f4xx.h"
#include "led.h"
#include "delay.h" 
#include "tim.h"
#include "usart.h"
#include "string.h"
#include "mb.h"
#include "mbport.h"

// 十路输入寄存器
#define REG_INPUT_SIZE  10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];


// 十路保持寄存器
#define REG_HOLD_SIZE   10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];


// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE];


// 十路离散量
#define REG_DISC_SIZE  10
uint8_t REG_DISC_BUF[10];







int main(void)
{
	//NVIC分组一个工程只能设置一次
	//设置NVIC分组为第二分组; 抢占优先级取值范围:0~3, 响应优先级取值范围:0~3
	int i = 0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	Delay_Init();
	Led_Init();

	Usart1_Init(115200);

	
	//printf("helloworld\r\n");
	
	
	eMBInit(MB_RTU, 0x01, 3, 9600, MB_PAR_NONE);
	
	/* Enable the Modbus Protocol Stack. */
	eMBEnable();	
	
	while(1)
	{
		(void)eMBPoll();
	}

	return 0;
}


// CMD4
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    USHORT usRegIndex = usAddress - 1; 

    // 非法检测
    if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
    {
        return MB_ENOREG;
    }

    // 循环读取
    while( usNRegs > 0 )
    {
        *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
        *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
        usRegIndex++;
        usNRegs--;
    }

    // 模拟输入寄存器被改变
    for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++)
    {
        REG_INPUT_BUF[usRegIndex]++;
    }

    return MB_ENOERR;
}

// CMD6、3、16
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    USHORT usRegIndex = usAddress - 1;  
	
    // 非法检测
    if((usRegIndex + usNRegs) > REG_HOLD_SIZE)
    {
        return MB_ENOREG;
    }
	
	// 写寄存器
    if(eMode == MB_REG_WRITE)
    {
		//判断寄存器,用于控制灯
		if(pucRegBuffer[1] ==0)
		{
			GPIO_SetBits(GPIOF, GPIO_Pin_9);
			GPIO_SetBits(GPIOF, GPIO_Pin_10);
			GPIO_SetBits(GPIOE, GPIO_Pin_13);			
			
		}	
		if(pucRegBuffer[1] ==1)
		{
			GPIO_ResetBits(GPIOF, GPIO_Pin_9);
		}
		if(pucRegBuffer[1] ==2)
		{
			GPIO_ResetBits(GPIOF, GPIO_Pin_10);
		}		
		if(pucRegBuffer[1] ==3)
		{
			GPIO_ResetBits(GPIOE, GPIO_Pin_13);
		}		
				
		
        while( usNRegs > 0 )
        {
            REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1];
            pucRegBuffer += 2;
            usRegIndex++;
            usNRegs--;
        }
    }
	
	// 读寄存器
    else
    {
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );
            *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );
            usRegIndex++;
            usNRegs--;
        }
    }

    return MB_ENOERR;
}

// CMD1、5、15
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    USHORT usRegIndex   = usAddress - 1;
    USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1);
    UCHAR  ucStatus     = 0;
    UCHAR  ucBits       = 0;
    UCHAR  ucDisp       = 0;

    // 非法检测
    if((usRegIndex + usNCoils) > REG_COILS_SIZE)
    {
        return MB_ENOREG;
    }

    // 写线圈
    if(eMode == MB_REG_WRITE)
    {
        while(usCoilGroups--)
        {
            ucStatus = *pucRegBuffer++;
            ucBits   = 8;
            while((usNCoils--) != 0 && (ucBits--) != 0)
            {
                REG_COILS_BUF[usRegIndex++] = ucStatus & 0X01;
                ucStatus >>= 1;
            }
        }
    }

    // 读线圈
    else
    {
        while(usCoilGroups--)
        {
            ucDisp = 0;
            ucBits = 8;
            while((usNCoils--) != 0 && (ucBits--) != 0)
            {
                ucStatus |= (REG_COILS_BUF[usRegIndex++] << (ucDisp++));
            }
            *pucRegBuffer++ = ucStatus;
        }
    }
    return MB_ENOERR;
}


// CMD4
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    USHORT usRegIndex   = usAddress - 1;
    USHORT usCoilGroups = ((usNDiscrete - 1) / 8 + 1);
    UCHAR  ucStatus     = 0;
    UCHAR  ucBits       = 0;
    UCHAR  ucDisp       = 0;

    // 非法检测
    if((usRegIndex + usNDiscrete) > REG_DISC_SIZE)
    {
        return MB_ENOREG;
    }

	// 读离散输入
	while(usCoilGroups--)
	{
		ucDisp = 0;
		ucBits = 8;
		while((usNDiscrete--) != 0 && (ucBits--) != 0)
		{
			if(REG_DISC_BUF[usRegIndex])
			{
				ucStatus |= (1 << ucDisp);
			}
			ucDisp++;
		}
		*pucRegBuffer++ = ucStatus;
	}

    // 模拟改变
    for(usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++)
    {
        REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex];
    }

    return MB_ENOERR;
}


//这里的代码就是上面所说的主函数最下面的代码,如果上面已经添加,这里可以忽略

#ifdef  USE_FULL_ASSERT  
/**  
  * @brief  Reports the name of the source file and the source line number  
  *         where the assert_param error has occurred.  
  * @param  file: pointer to the source file name  
  * @param  line: assert_param error line source number  
  * @retval None  
  */    
void assert_failed(uint8_t* file, uint32_t line)    
{    
  /* User can add his own implementation to report the file name and line number,  
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */    
     
  /* Infinite loop */    
  while (1)    
  {    
  }    
}  
#else  
void __aeabi_assert(const char * x1, const char * x2, int x3)  
{  
}  
#endif  

编译通过,nice
在这里插入图片描述

三、下载及验证

1、功能码,04,读线圈

在这里插入图片描述

2、功能码,06,写保持寄存器

“01 06 00 01 00 01 19 CA”,//点亮一盏LED
“01 06 00 01 00 02 59 CB”,//点亮二盏LED
“01 06 00 01 00 03 98 0B”,//点亮三盏LED
“01 06 00 01 00 00 D8 0A”,//熄灭所有LED

“01 06 00 01 00 01 19 CA”,//点亮一盏LED
在这里插入图片描述

在这里插入图片描述

“01 06 00 01 00 02 59 CB”,//点亮二盏LED
在这里插入图片描述
在这里插入图片描述
这里是看着亮两个灯,原因上面演示里灯1没有关闭,须知

“01 06 00 01 00 03 98 0B”,//点亮三盏LED
在这里插入图片描述
在这里插入图片描述
这里是看着亮两个灯,原因上面演示里灯1, 灯2没有关闭,须知

“01 06 00 01 00 00 D8 0A”,//熄灭所有LED
在这里插入图片描述
在这里插入图片描述
移植好的代码下载地址:https://download.csdn.net/download/wwwqqq2014/75435778

  • 9
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C是最好的编程语言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值