FreeModbus在STM32上移植

2019-07-20 01:21发布

本帖最后由 八度空间 于 2016-3-8 08:18 编辑

前段时间了解了一下Modbus协议,移植了一次,不成功http://www.openedv.com/thread-67471-1-1.html,不过找到方法之后,再次完善
在官网上面下载源码,度娘上面有些被人家修改过的,喜欢原汁原味的朋友去官网上下载,本贴附件也附上源码

开发平台:原子哥探索者开发板V2.2
编译环境:MDK5.17
STM32官方库函数:V1.5.1
FreeModbu版本:V1.5.0

一、解压源码后,看到文件目录结构
FreeModbus_1.png
文件夹demo就是官方针对不同平台移植的测试代码
文件夹doc是一些说明性文档
文件夹modbus就是功能实现的源码所在了
文件夹tools是上位机软件

二、建立工程
2.1、新建一个工程(这个就随意了),建立文件夹FreeModbus,将上面提到的modbus文件夹整个拷贝到工程的FreeModbus文件夹中
FreeModbus_2.png
2.2、进入demo文件夹,看到有各个平台的测试代码文件夹,没看到STM32的,但是看到BARE这个不带任何平台的代码文件,将里边的port文件夹拷贝到FreeModbus文件夹中
FreeModbus_3.png
2.3、打开MDK(用IAR也行,随意了),建立工程。。。(省略1万字)

三、添加代码
3.1、打开portserial.c文件,这个是移植串口的,不管是ASCII模式还是RTU模式都需要串口支持的,void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )函数,使能或失能串口的,移植代码如下
[mw_shl_code=applescript,true]void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
        
        if (xRxEnable)  //接收使能
        {
                USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);  //使能接收中断
                GPIO_ResetBits(GPIOG, GPIO_Pin_8);  //接收
        }
        else  //失能
        {
                USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);  //失能接收中断
                GPIO_SetBits(GPIOG, GPIO_Pin_8);  //恢复发送
        }
        
        if (xTxEnable)  //发送使能
        {
                USART_ITConfig(USART2, USART_IT_TC, ENABLE);  //使能
                GPIO_SetBits(GPIOG, GPIO_Pin_8);  //发送
        }
        else  //失能
        {
                USART_ITConfig(USART2, USART_IT_TC, DISABLE);  //失能
                GPIO_ResetBits(GPIOG, GPIO_Pin_8);  //设置接收
        }
}[/mw_shl_code]
3.1.1、串口初始化函数BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ),使用的是串口2进行通讯
[mw_shl_code=applescript,true]BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
//    return FALSE;
        
        GPIO_InitTypeDef  GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
        NVIC_InitTypeDef  NVIC_InitStructure;
        
        (void)ucPORT;  //不修改串口号
        (void)ucDataBits;  //不修改数据位长度
        (void)eParity;  //不修改检验格式
        
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOG, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
        
        //
        //管脚复用
        //
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
        
        //
        //发送管脚 PA.02
        //接收管脚 PA.03
        //
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2 | GPIO_Pin_3;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        
        //
        //485芯片发送接收控制管脚
        //
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
        GPIO_Init(GPIOG, &GPIO_InitStructure);
        
        //
        //配置串口参数
        //
        USART_InitStructure.USART_BaudRate            = ulBaudRate;  //只修改波特率
        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_Parity              = USART_Mode_Rx | USART_Mode_Tx;
        USART_Init(USART2, &USART_InitStructure);
        //
        //使能串口
        //
        USART_Cmd(USART2, ENABLE);
        
        //
        //配置中断优先级
        //
        NVIC_InitStructure.NVIC_IRQChannel                   = USART2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        
        return TRUE;
}[/mw_shl_code]
3.1.2、发送一个字节函数BOOL xMBPortSerialPutByte( CHAR ucByte )
[mw_shl_code=applescript,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);  //发送一个字节
        
        return TRUE;
}[/mw_shl_code]
3.1.3、接收一个字节函数BOOL xMBPortSerialGetByte( CHAR * pucByte )
[mw_shl_code=applescript,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;
}[/mw_shl_code]
3.1.4、串口中断服务函数void USART2_IRQHandler(void)
[mw_shl_code=applescript,true]/**
  *****************************************************************************
  * @Name   : 串口2中断服务函数
  *
  * @Brief  : none
  *
  * @Input  : none
  *
  * @Output : none
  *
  * @Return : none
  *****************************************************************************
**/
void USART2_IRQHandler(void)
{
        if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET)  //接收中断
        {
                prvvUARTRxISR();
                USART_ClearITPendingBit(USART2, USART_IT_RXNE);
        }
        
        if (USART_GetITStatus(USART2, USART_IT_TC) == SET)  //发送中断
        {
                prvvUARTTxReadyISR();
                USART_ClearITPendingBit(USART2, USART_IT_TC);
        }
}[/mw_shl_code]
3.2、打开porttimer.c文件,RTU模式需要定时器支持,定时器初始化函数BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
[mw_shl_code=applescript,true]BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
//    return FALSE;
        
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
        NVIC_InitTypeDef        NVIC_InitStructure;
        
        uint16_t PrescalerValue = 0;
        
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        
        //
        //HCLK为72MHz
        //时基频率72 / (1 + Prescaler) = 20KHz
        //
        PrescalerValue = (uint16_t)((SystemCoreClock / 20000) - 1);
        //
        //初始化定时器参数
        //
        TIM_TimeBaseStructure.TIM_Period = (uint16_t)usTim1Timerout50us;
        TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
        TIM_TimeBaseStructure.TIM_ClockDivision = 0;
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
        //
        //使能预装
        //
        TIM_ARRPreloadConfig(TIM2, ENABLE);
        
        //
        //初始化中断优先级
        //
        NVIC_InitStructure.NVIC_IRQChannel                   = TIM2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 2;
        NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
        TIM_Cmd(TIM2, DISABLE);
        
        return TRUE;
}[/mw_shl_code]
3.2.1、定时器使能函数void vMBPortTimersEnable(  )
[mw_shl_code=applescript,true]void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
        TIM_SetCounter(TIM2, 0x00000000);
        TIM_Cmd(TIM2, ENABLE);
}[/mw_shl_code]
3.2.2、定时器失能函数void vMBPortTimersDisable(  )
[mw_shl_code=applescript,true]void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
        TIM_SetCounter(TIM2, 0x00000000);
        TIM_Cmd(TIM2, DISABLE);
}[/mw_shl_code]
3.2.3、定时器中断服务函数void TIM2_IRQHandler(void)
[mw_shl_code=applescript,true]/**
  *****************************************************************************
  * @Name   : 定时器4中断服务函数
  *
  * @Brief  : none
  *
  * @Input  : none
  *
  * @Output : none
  *
  * @Return : none
  *****************************************************************************
**/
void TIM2_IRQHandler(void)
{
        if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
        {
                TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
                prvvTIMERExpiredISR();
        }
}[/mw_shl_code]
四、打开port.c文件,这个文件我是从别的工程拷贝过来的
4.1、补充完成以下两个函数,这两个函数跟中断有关,开或者关
[mw_shl_code=applescript,true]void
EnterCriticalSection(  )
{
        __ASM volatile("cpsid i");
}

void
ExitCriticalSection(  )
{
    __ASM volatile("cpsie i");
}[/mw_shl_code]
4.2、定义相关寄存器地址什么的,详细看工程文件
4.3、分别完善一下函数
4.3.1、操作输入寄存器eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
4.3.2、操作保持寄存器eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
4.3.3、操作线圈eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
4.3.4、操作离散寄存器eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
4.4、打开port.h文件,完善两个宏定义
4.4.1、#define ENTER_CRITICAL_SECTION( )也可以空着
4.4.2、#define EXIT_CRITICAL_SECTION( )
五、编写main函数
[mw_shl_code=applescript,true]/**
  *****************************************************************************
  * @Name   : 主函数
  *
  * @Brief  : none
  *
  * @Input  : none
  *
  * @Output : none
  *
  * @Return : none
  *****************************************************************************
**/
int main( void )
{
        u8 i = 0;
        
        //
        //初始化硬件
        //
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        delay_init();
        uart_init(38400);
        LED_Init();
        
        usmart_dev.init(72);
        
        LCDTFT_Init();

    eMBInit(MB_RTU, 0x0A, 1, 38400, MB_PAR_NONE);  //初始化FreeModbus

    eMBEnable();  //启动FreeModbus
        
        LCD_ShowString(30, 50, 480, 24, 24, "FreeModbus Test.");
        
        while (1)
    {
        (void)eMBPoll();  //查询数据帧
               
        usRegInputBuf[0]++;  //测试读取输入寄存器用
               
                if (usRegInputBuf[0] > 250)  //防止超标
                {
                        usRegInputBuf[0] = 0;
                }
               
                delay_ms(10);
                i++;
                if (i >= 50)
                {
                        i = 0;
                        LED0 = !LED0;
                }
    }
}[/mw_shl_code]
为了测试我将输入寄存器在while循环中加1
六、编译,问题来了,提示错误
FreeModbus_4.png
FreeModbus作者使用了assert这货,各种百度,说什么乱七八糟的,没解决什么问题,最后还是自己解决了http://www.openedv.com/thread-67471-1-1.html
在main函数最后增加代码
[mw_shl_code=applescript,true]#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 ", file, line) */  
   
  /* Infinite loop */  
  while (1)  
  {  
  }  
}
#else
void __aeabi_assert(const char * x1, const char * x2, int x3)
{
}
#endif[/mw_shl_code]
再次编译,发现没错误了,下载到板子上面运行,用串口助手(Modbus调试精灵也行)发送命令0A 04 00 01 00 01 61 71读取输入寄存器,发现没返回数据,各种仿真之后,发现串口中断的问题,前面初始化使用了TC,发送完成中断,打开手册看到了TXE这个,修改编译下载,再次发送命令,这回有返回了,不过数据不对,返回0
在eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )函数中将usAddress变量显示到液晶上面一看,不对,比发送的地址多了1,跳转到函数eMBFuncReadInputRegister( UCHAR * pucFrame, USHORT * usLen )一看,解释之后来了一句usRegAddress++;,赶紧注释掉,再来,这回对了
接着测试其他的,也发现了最后操作的地址比发送过去的地址多了1,又打开相关的函数文件,都是在地址解释之后有usRegAddress++;这句存在,赶紧注释掉,再来,这回正确了
七、回到刚才的串口发送中断标志TC和TXE的问题上来,对照手册一看
FreeModbus_5.png
大家对比一下说明,我就不解释了
打开mbrtu.c文件,在下面位置增加一段代码,修正TC中断标志发送的错误
FreeModbus_6.png
如果大家不想修改源码的话就用TXE这个标志,不过增加了以上代码再使用TXE标志也是没有问题的,无所谓了
好了到此为止,移植算是完成了,继续深究的朋友可以接着,否则就直接用吧


附件上传我调试用到的工具软件

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
50条回答
黑夜之狼
1楼-- · 2019-07-20 03:12
八度大神的帖子,必须来顶!
solo
2楼-- · 2019-07-20 04:43
 精彩回答 2  元偷偷看……
shiqinghai
3楼-- · 2019-07-20 07:09
 精彩回答 2  元偷偷看……
八度空间
4楼-- · 2019-07-20 11:25
solo 发表于 2016-8-1 09:32
。。。我把重定义的重写了一个文件,port理应合并在一起,但是怕移植性变差(耦合),就重命名了。这样把 ...

呵呵
yiller
5楼-- · 2019-07-20 13:37
 精彩回答 2  元偷偷看……
八度空间
6楼-- · 2019-07-20 17:44
geo 发表于 2016-3-9 13:00
谢谢分享,原来还有专门的API函数,以前都是自己写的协议

呵呵

一周热门 更多>