使用芯片:stm32f103ret
开发环境:cubemx5.3 + keil5
freemodbus版本:V1.6
CUBEMX配置步骤:
- 系统及时钟配置:使用外部时钟
- TIM4作为freemodbus串口定时器:
在RTU模式下,串行链路是以byte为单位进行发送数据的。Modbus-RTU协议中有一个字符时间的概念,Modbus-RTU总线是通过时间间隔来判断一帧数据结束的。波特率小于19200时,3.5个字符时间内没有收到新的数据,则认为这一帧数据结束。将定时器设置为每50us的时长为一个基准,传入的usTim1Timerout50us
变量给自动装载即可,prvvTIMERExpiredISR
函数需要在定时器中断服务函数中调用,它的作用是用于通知modbus协议栈3.5个字符 的等待时间已经到;波特率大于19200时为固定时长1750us。
图中位置在程序中传入。
- 串口2用作modbus通信接口
- NVIC配置:取消自动生成中断函数选项,最后自己在程序中添加修改
- 串口的中断优先级高于定时器
Freemodbus移植
- 下载freemodbus-v1.6解压,
- 需要用到的文件有:modbus文件中的所有文件,port中的文件
第一次编译报错如下(需要删除掉demo.c中的main函数):
其中串口需要修改的文件有portserial.c 定时器需要修改的文件 有porttimer.c
寄存器的读写功能通过新建Modbusdemo.c中实现。
- portserial.c中的函数:保留prvvUARTTxReadyISR,prvvUARTRxISR
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if(xRxEnable)//接收使能
{
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); // 使能接收非空中断
//低电平接收
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET); //根据实际使用芯片决定是否有此步骤
}
else
{
__HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE); // 禁能接收非空中断
}
if(xTxEnable)//发送使能
{
//高电平发送
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET); //根据实际使用芯片决定是否有此步骤
__HAL_UART_ENABLE_IT(&huart2, UART_IT_TXE); // 使能发送为空中断
}
else
{
__HAL_UART_DISABLE_IT(&huart2, UART_IT_TXE); // 禁能发送为空中断
}
}
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
huart2.Instance = USART2;
huart2.Init.BaudRate = ulBaudRate;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
switch(eParity)
{
// 奇校验
case MB_PAR_ODD:
huart2.Init.Parity = UART_PARITY_ODD;
huart2.Init.WordLength = UART_WORDLENGTH_9B; // 带奇偶校验数据位为9bits
break;
// 偶校验
case MB_PAR_EVEN:
huart2.Init.Parity = UART_PARITY_EVEN;
huart2.Init.WordLength = UART_WORDLENGTH_9B; // 带奇偶校验数据位为9bits
break;
// 无校验
default:
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.WordLength = UART_WORDLENGTH_8B; // 无奇偶校验数据位为8bits
break;
}
return HAL_UART_Init(&huart2) == HAL_OK ? TRUE : 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. */
USART2->DR = ucByte;
while(__HAL_UART_GET_FLAG(&huart2, UART_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 = (USART2->DR & (uint16_t)0x00FF);
return TRUE;
}
void USART2_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE)) // 接收非空中断标记被置位
{
__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE); // 清除中断标记
prvvUARTRxISR(); // 通知modbus有数据到达
}
if((__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE) == SET)&&(__HAL_UART_GET_IT_SOURCE(&huart2, UART_FLAG_TC) == RESET)) // 发送为空中断标记被置位
{
__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TXE); // 清除中断标记
prvvUARTTxReadyISR(); // 通知modbus数据可以发松
}
}
porttimer.c中的函数如下:保留vMBPortTimersDisable,prvvTIMERExpiredISR
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM4_Init 1 */
/* USER CODE END TIM4_Init 1 */
htim4.Instance = TIM4;
htim4.Init.Prescaler = 7199;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = usTim1Timerout50us-1; //
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE); // 清除一下定时器的中断标记,防止使能中断后直接触发中断
__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE); // 使能定时器更新中断
return TRUE;
}
inline void vMBPortTimersEnable( )
{
__HAL_TIM_SET_COUNTER(&htim4, 0); // 清空计数器
__HAL_TIM_ENABLE(&htim4); // 使能定时器
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
}
inline void vMBPortTimersDisable( )
{
__HAL_TIM_DISABLE(&htim4); // 禁能定时器
/* Disable any pending timers. */
}
/// 定时器4中断服务程序
void TIM4_IRQHandler(void)
{
if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE)) // 更新中断标记被置位
{
__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE); // 清除中断标记
prvvTIMERExpiredISR(); // 通知modbus3.5个字符等待时间到
}
}
文件demo.c中主要实现0X04
以上是移植过程,只实现了04测试。为了防止运行异常,可以加入看门狗定时器