电力电子技术
直播中

杜喜喜

7年用户 1519经验值
私信 关注
[问答]

移植FreeModbus到usart3并运行示例代码分享

移植FreeModbus到usart3并运行示例代码

回帖(1)

马雁翔

2020-12-31 09:36:34
  FreeModbus是Modbus的一个被广泛移植的实现。其源码在github,最新版是1.6。
  FreeModbus支持Modbus功能码里的0x01~0x06,0x0F~0x11和0x17,对其他功能码比如异常诊断和事件计数等并没有提供支持,但并不影响Modbus的使用。
  另外,FreeModbus仅提供了服务器(从机)的实现,客户端(主机)的实现可以在github上找到一些。
  FreeModbus的文件主要在两个文件夹里,一个在/modbus/,一个在/demo/BARE/。前一个文件夹是协议的上层功能,包括协议的几个应用函数,基本不用修改。第二个文件夹有一个demo.c,是一个运行示例,也不需要修改。需要修改的是/demo/BARE/port/里的内容,这几个文件的功能完成了硬件的配置,包括串口和定时器的初始化,以及中断函数等。
  对于STM32,FreeModbus的源码并没有给出示例,不过网上的相关移植已经有很多了,很容易就能找到,所以只需要复制/demo/BARE/port里的内容,然后稍微修改一下就好了。
  一个个文件来说。
  第一个是port.h,只需要包含库文件“stm32f10x.h”,并增加临界区的进入和离开指令即可。我这里使用的是__set_PRIMASK函数,关闭除了NMI和硬fault以外的中断。
  #define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1); //disable interrupts
  #define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0); //enable interrupts
  第二个是portserial.c,需要填满几个串口的相关函数。
  使能或失能串口发送完成中断和串口接收中断:
  void
  vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  {
  /* If xRXEnable enable serial receive interrupts. If xTxENable enable
  * transmitter empty interrupts.
  */
  if (xRxEnable == TRUE)
  {
  USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
  }
  else if (xRxEnable == FALSE)
  {
  USART_ITConfig(USART3, USART_IT_RXNE, DISABLE);
  }
  if (xTxEnable == TRUE)
  {
  USART_ITConfig(USART3, USART_IT_TC, ENABLE);
  }
  else if (xTxEnable == FALSE)
  {
  USART_ITConfig(USART3, USART_IT_TC, DISABLE);
  }
  }
  初始化串口3和相应引脚,初始化NVIC。为了方便,这里就不管串口号、数据位、校验位了,只有波特率可以起作用:
  BOOL
  xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  {
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
  USART_InitTypeDef USART_InitStructure;
  USART_InitStructure.USART_BaudRate = ulBaudRate;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_Init(USART3, &USART_InitStructure);
  USART_Cmd(USART3, ENABLE);
  vMBPortSerialEnable(FALSE, FALSE);
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_Init(&NVIC_InitStructure);
  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(USART3, ucByte);
  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(USART3);
  return TRUE;
  }
  串口中断函数,只需要判断中断类型,并调用FreeModbus已经实现好的prvvUARTTxReadyISR和prvvUARTRxISR函数即可。注意到发送状态机在发送完毕后会关闭串口中断,所以不需要清零TC位;STM32F103的manual中讲到,TC不需要软清零,只需要读一下TC位,下次写TDR寄存器时就会自动清零;如果手动清零了TC位,那么下次要发送时,即使使能了TCIE,会因为TC=0无法进入中断。所以这里只读了一下TC寄存器,并不清零。另外因为STM32F103使能RXNE后,ORE也会触发中断,所以需要做相应的清除操作,否则就会卡在中断里:
  void USART3_IRQHandler( void )
  {
  if(USART_GetITStatus(USART3, USART_IT_TC) == SET)
  {
  prvvUARTTxReadyISR();
  }
  if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
  {
  prvvUARTRxISR();
  }
  else
  {
  USART_ClearITPendingBit(USART3, USART_IT_ORE);
  }
  }
  第三个是porttimer.c,也有几个函数需要填空。
  初始化定时器和NVIC。这里用的是定时器2,挂在APB1上,我的APB1总线时钟是36MHz,所以预分频器设为36MHz/20kHz=1800:
  BOOL
  xMBPortTimersInit( USHORT usTim1Timerout50us )
  {
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  TIM_TimeBaseStructure.TIM_Period = usTim1Timerout50us - 1;
  TIM_TimeBaseStructure.TIM_Prescaler = 1800 - 1;
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  vMBPortTimersDisable();
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
  NVIC_Init(&NVIC_InitStructure);
  return TRUE;
  }
  使能定时器(注意到这两个函数定义成了内嵌函数,可以让定时器启动和关闭更快):
  inline void
  vMBPortTimersEnable( )
  {
  /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
  vMBPortTimersDisable();
  TIM_Cmd(TIM2, DISABLE);
  TIM_SetCounter(TIM2, 0);
  TIM_ClearFlag(TIM2, TIM_FLAG_Update);
  TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  TIM_Cmd(TIM2, ENABLE);
  }
  失能定时器:
  inline void
  vMBPortTimersDisable( )
  {
  /* Disable any pending timers. */
  TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
  TIM_Cmd(TIM2, DISABLE);
  }
  定时器中断函数:
  void TIM2_IRQHandler(void)
  {
  if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
  {
  TIM_ClearFlag(TIM2, TIM_FLAG_Update);
  prvvTIMERExpiredISR();
  }
  }
  使用Modbus Poll来测试demo.c的运行情况,配置如下:
  
  
  结果应该能看到地址为999的寄存器数据在变化,其他两个寄存器值都是0,说明协议已经移植成功了。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分