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,说明协议已经移植成功了。
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,说明协议已经移植成功了。
举报