STM32L476+STM32cubeMx+Freemodbus移植成功记录

modbus通信需要一个串口和定时器,在STM32L476上串口使用USART3,定时器使用TIM4,同时由于使用了485通信,需要一个GPIO引脚PB1控制485芯片的数据收发,移植Freemodbus过程记录如下:

一、CubeMX的配置

基于STM32L476RCTx的Modbus移植(新建工程,选择对应的芯片型号),使用Serial wire(ST_Link)串行调试方式,选用时钟为HSE外接石英晶振。

配置时钟树,外部晶振为8MHz,时钟频率最高只能到80MHz,这里设置为64MHz。

串口配置

串口采用异步通信的方式(Asynchronous异步通信),这里随便配置即可,因为modbus移植过程中还会对串口重新进行初始化。

STM32L476有485硬件流控制功能,查阅资料说如果使用硬件流控来控制485芯片的DE引脚,可以省去手动操作RS485收发器的使能引脚步骤,但是在实际使用中发现串口不能正常收发数据,所以还是通过IO口来控制485芯片。

GPIO设置为推挽输出模式,作为RS485的发送接收控制端,输出速度选择High。

串口3的配置如下:

STM32L476与STM32F103系列相比多了一些advanced Features,STM32串口默认是打开Overrun、DMA on Rx Error,如果使能后出现错误会关闭串口接收,调用错误回调函数,这里直接关闭使能,不影响正常通信。

定时器配置

Modbus协议的RTU模式规定报文帧的时长至少为3.5个字符的空闲间隔区分,如果在指定的时间内没有接收到新的字符数据,则认为收到了新的帧。

在freemodbus中默认定义:当波特率大于19200时,判断一帧数据超时时间固定为1750us,当波特率小于19200时,超时时间为3.5个字符时间。Freemodbus协议中使用了一个公式来实现不同波特率下超时时间的设置

usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate )

为了后续项目移植方便,需要用定时器产生50us的基准。

STM32L476中采用TIM4,TIM4挂载在APB1上,pclk1为64MHz,预分频系数3199,计数周期为49,对应时间为50us,随便配置就行,因为在modbus移植过程中还会对定时器初始化。

自动重载ENABLE,设置定时器触发方式为Update  Event

定时器的溢出时间的计算公式为:

Tout = ((arr+1)*(psc+1))/Tclk  这里arr=49,psc=3199,Tclk=64MHz,

于是Tout = (3200*(arr+1))/64us = (arr+1)*50us,后面可以通过改变arr的值改变定时时间。

NVIC

使能定时器4和串口3的中断,还需要配置中断优先级,定时器的中断优先级低于串口中断即可。(优先级数字越小,优先级越高)

取消自动生成中断服务程序,在移植过程中要自己编写串口和定时器服务程序。

Code Generator

只拷贝相关文件库到工程下 每个外设生成独立的”.c/h”文件,Project Manager 项目生成工程路径中最好不要用中文,不然STM32CubeMX会在生成项目时报错,IDE选择MDK-ARM

二、Freemodbus移植

Freemodbus 是一款微型modbus协议栈,之前对各种单片机、小型处理器支持比较好,从v1.6版本开始,也支持linux了,modbus通信的重点一是数据解析,二是串口的不定长数据接收,每接收一个字节,需要重置定时器,这样能保证在接收数据的过程中,定时器是不会溢出的,在串口中断服务程序中,将要更新状态机FSM,而eMBPoll函数则是会进行modbus协议的解析。

下载Freemodbus

https://www.embedded-experts.at/en/freemodbus-downloads/

点击freemodbus-v1.6.zip进行下载,下载后解压的文件

Tools为上位机测试modbus程序,doc是一些说明文件

STM32移植就是需要修改demo目录下的BARE中的portserial和porttimer文件(portevent不做任何修改)移植时可将BARE文件夹下的所有文件复制到STM32工程目录下

Modbus文件夹下的全部文件也复制到STM32的工程目录下

新建FreeModbus文件夹,用于存放移植需要用到的文件(这里最好将所有要用到的头文件和.c文件放在一起,便于后面在STM32中添加头文件路径和C文件)

打开keil工程添加modbus源码:

新建ModBus group     点击 添加c文件

点击 添加h文件

需要将demo.c文件下的int main()注释掉

点击编译——链接,无误后再进行下一步操作。

三、代码修改

1.portserial.c文件修改

#include "port.h"
#include "stm32l4xx_hal.h"
#include "usart.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.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.*/if(xRxEnable == TRUE){HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);//__HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE);        // 使能接收非空中断}else {HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);__HAL_UART_DISABLE_IT(&huart3, UART_IT_RXNE);        // 禁能接收非空中断   }if(xTxEnable == TRUE){HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);__HAL_UART_ENABLE_IT(&huart3, UART_IT_TXE);            // 使能发送为空中断}else{HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);__HAL_UART_DISABLE_IT(&huart3, UART_IT_TXE);        // 禁能发送为空中断}}BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{huart3.Instance = USART3;huart3.Init.BaudRate = ulBaudRate;huart3.Init.StopBits = UART_STOPBITS_1;huart3.Init.Mode = UART_MODE_TX_RX;huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart3.Init.OverSampling = UART_OVERSAMPLING_16;switch(eParity){// 奇校验case MB_PAR_ODD:huart3.Init.Parity = UART_PARITY_ODD;huart3.Init.WordLength = UART_WORDLENGTH_9B;            // 带奇偶校验数据位为9bitsbreak;// 偶校验case MB_PAR_EVEN:huart3.Init.Parity = UART_PARITY_EVEN;huart3.Init.WordLength = UART_WORDLENGTH_9B;            // 带奇偶校验数据位为9bitsbreak;// 无校验default:huart3.Init.Parity = UART_PARITY_NONE;huart3.Init.WordLength = UART_WORDLENGTH_8B;            // 无奇偶校验数据位为8bitsbreak;}return HAL_UART_Init(&huart3) == 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. */HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);//使485芯片发送状态if(HAL_UART_Transmit (&huart3 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )return FALSE ;elsereturn 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.*/HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);if(HAL_UART_Receive (&huart3 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )return FALSE ;elsereturn 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标志,便于在其他文件中调用
//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(  );
}

2.porttimer.c文件修改

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "stm32l4xx_hal.h"
#include "tim.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"/* ----------------------- static functions ---------------------------------*/
//static
//  void prvvTIMERExpiredISR( void );/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{TIM_ClockConfigTypeDef sClockSourceConfig = {0};TIM_MasterConfigTypeDef sMasterConfig = {0};htim4.Instance = TIM4;htim4.Init.Prescaler = 3199;                             // 50us记一次数htim4.Init.CounterMode = TIM_COUNTERMODE_UP;htim4.Init.Period = usTim1Timerout50us - 1;                  // usTim1Timerout50us * 50即为定时器溢出时间htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim4) != HAL_OK){return FALSE;}sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK){return FALSE;}sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK){return FALSE;}return TRUE;
}inline void
vMBPortTimersEnable(  )
{/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */__HAL_TIM_CLEAR_IT(&htim4,TIM_IT_UPDATE);//避免程序一上电就进入定时器中断__HAL_TIM_ENABLE_IT(&htim4,TIM_IT_UPDATE);__HAL_TIM_SET_COUNTER(&htim4, 0);        // 清空计数器__HAL_TIM_ENABLE(&htim4);               // 使能定时器}inline void
vMBPortTimersDisable(  )
{__HAL_TIM_DISABLE(&htim4);             // 禁能定时器__HAL_TIM_SET_COUNTER(&htim4,0);__HAL_TIM_DISABLE_IT(&htim4,TIM_IT_UPDATE);__HAL_TIM_CLEAR_IT(&htim4,TIM_IT_UPDATE);}/* 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(  );
}

3.mbrtu.c文件修改

修改eMBRTUSend函数,便于第一次发送数据

eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{eMBErrorCode    eStatus = MB_ENOERR;USHORT          usCRC16;ENTER_CRITICAL_SECTION(  );/* Check if the receiver is still in idle state. If not we where to* slow with processing the received frame and the master sent another* frame on the network. We have to abort sending the frame.*/if( eRcvState == STATE_RX_IDLE ){/* First byte before the Modbus-PDU is the slave address. */pucSndBufferCur = ( UCHAR * ) pucFrame - 1;usSndBufferCount = 1;/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;usSndBufferCount += usLength;/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );/* Activate the transmitter. */eSndState = STATE_TX_XMIT;//添加代码begin//启动第一次发送,进入发送完成中断xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );pucSndBufferCur++;  /* next byte in sendbuffer. */usSndBufferCount--;//添加代码endvMBPortSerialEnable( FALSE, TRUE );}else{eStatus = MB_EIO;}EXIT_CRITICAL_SECTION(  );return eStatus;
}

4.stm32l4xx_it.c文件修改

外部声明使用到的定时器和串口

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */

extern TIM_HandleTypeDef htim4;
extern UART_HandleTypeDef huart3;

/* USER CODE END TD */

添加定时器和串口中断服务函数

void TIM4_IRQHandler(void)
{HAL_NVIC_ClearPendingIRQ(TIM4_IRQn);HAL_TIM_IRQHandler(&htim4);}extern void prvvTIMERExpiredISR( void );
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{/* NOTE : This function Should not be modified, when the callback is needed,the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file*/if(htim->Instance == TIM4){prvvTIMERExpiredISR( );}}extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);void USART3_IRQHandler(void)
{if(__HAL_UART_GET_IT_SOURCE(&huart3, UART_IT_RXNE)!= RESET) {prvvUARTRxISR();//接收中断}if(__HAL_UART_GET_IT_SOURCE(&huart3, UART_IT_TXE)!= RESET) {prvvUARTTxReadyISR();//发送缓冲区空中断}HAL_NVIC_ClearPendingIRQ(USART3_IRQn);HAL_UART_IRQHandler(&huart3);
}

5.main.c文件的修改

添加头文件

#include "mb.h"
#include "mbport.h"

在while循环前添加

//modbus初始化
  eMBInit(MB_RTU,0x01,3,9600,MB_PAR_NONE);
  eMBEnable();

在主函数中启动modbus监听

eMBPoll();

至此modbus移植通信的基本外设已完成,接下来是功能码函数的实现

四、功能码函数实现

功能码0x04

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;//例子usRegInputBuf[0] = 0x11;usRegInputBuf[1] = 0x22;usRegInputBuf[2] = 0x33;usRegInputBuf[3] = 0x44;//if( ( usAddress >= REG_INPUT_START )&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ){iRegIndex = ( int )( usAddress - usRegInputStart );while( usNRegs > 0 ){*pucRegBuffer++ =( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );//modbus寄存器是16位*pucRegBuffer++ =( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );iRegIndex++;usNRegs--;}}else{eStatus = MB_ENOREG;}return eStatus;
}

使用modbus poll软件进行测试

连接方式设置:

串口通信(需要使用485或232转串口线)、通信参数设置与程序中一致

读写定义:

功能码0x04,寄存器地址,寄存器数量,扫描时间

然后运行程序,连接上串口,把寄存器的格式改为hex型,就能看到modbus通信成功。

功能码0x03与0x10

//保持寄存器起始地址
#define REG_HOLDING_START     0x0001
//保持寄存器数量
#define REG_HOLDING_NREGS     8
//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if((usAddress >= REG_HOLDING_START)&&\((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS))){iRegIndex = (int)(usAddress - usRegHoldingStart);//计算偏移量switch(eMode){                                       case MB_REG_READ://读 MB_REG_READ = 0while(usNRegs > 0){*pucRegBuffer++ = (uint8_t)(usRegHoldingBuf[iRegIndex] >> 8);            *pucRegBuffer++ = (uint8_t)(usRegHoldingBuf[iRegIndex] & 0xFF); iRegIndex++;usNRegs--;                   }                            break;case MB_REG_WRITE://写 MB_REG_WRITE = 0while(usNRegs > 0){         usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;iRegIndex++;usNRegs--;}               }}else//错误{eStatus = MB_ENOREG;}    return eStatus;
}

!!!注意事项

1.modbus需要485芯片或者232通信芯片,如果是开发板,不要在串口转TTL上浪费时间呀

2.移植过程中一定要注意自己使用的串口号和定时器编号,一定要把所有参数都改成自己初始化使用的串口和定时器,不然可能又是浪费时间

3.浪费时间也不要紧,调试过程可以帮你更好的理解freemobus通信过程和STM32的定时器、串口的工作过程和寄存器,也算是其他的收获吧

STM32L476+STM32cubeMx+Freemodbus移植记录相关推荐

  1. android touch screen keyboard input移植记录

    android touch screen keyboard input移植记录  仅仅是作为记录: Andorid 的 touchscreen 事件必须要有  BTN_TOUCH 才可以. 所以初始化 ...

  2. 安卓平台下的GPS架构介绍及驱动移植记录

    一.前言 我的工作是关于汽车车机BSP部分. 汽车车机,其实基本和人们日常所用的手机一样,也是安卓平台的.所谓安卓,就是一层安卓服务包裹着Linux内核所形成的操作系统. BSP组,主要工作内容就是负 ...

  3. FreeModbus 移植笔记- 1-认识FreeModbus

    FreeModbus 移植笔记 目录 1 FreeMODBUS介绍 2 FreeMODBUS官网及源码下载地址 3 移植之前的准备 3.1 FreeModbus V1.6 ​​​​​​​3.2 Mod ...

  4. STM32F407 freemodbus移植

    STM32F407 freemodbus移植 一.ModBus介绍 Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程 ...

  5. mjpg-streamer移植记录

    一.基于ubuntu18.04系统的mjpg-streamer移植记录 1.移植之前使用ubuntu的软件测试USB摄像头是否正常工作 (1).插上摄像头之后,ubuntu右下角有摄像头图标 (2). ...

  6. 基于Android8.1的博通bcm89342蓝牙驱动的驱动移植记录

    基于Android8.1的博通蓝牙BCM89342的驱动移植记录 说明 一 .软硬件平台 二.蓝牙移植流程 2.1 kernel 对蓝牙的驱动支持配置 2.2 kernel层编写蓝牙电源管理(bt r ...

  7. Freemodbus 移植过程记录

    前言 Freemodbus 是一个协议栈:纯代码,按照一定逻辑性实现: 比如串口,用它来收发二进制数据,人们就制定一种规则(数据帧)来达到高效稳定的数据串数目的.再详细的内容可以自行网上检索一下相关介 ...

  8. FreeModbus 移植于STM32 实现Modbus RTU通信

    http://ntn314.blog.163.com/blog/static/161743584201233084434579/ 毕业设计自己要做个基于STM32的PLC能直接跑语句表的,现在看来好像 ...

  9. 华大 MCU 之一 HC32F460 替换 STM32F411 移植记录

    更新 2020年 10 月 21 日,将驱动库更新到了最新版 1.1.1 2020年 10 月 20 日,MCU 由原来的 HC32F460KCTA 更换为 HC32F460KETA 简介   目前, ...

  10. real210移植记录-支持eMMC,增加菜单操作

    本次记录的移植是使该u-boot支持eMMC,开发板为real210最新版的开发板,标配eMMC 8GB flash,之前的移植都是在之前的210硬件上进行的核心板版本为v2,flash为nand 5 ...

最新文章

  1. Servlet--05--HttpServletRequest; HttpServletResponse
  2. UITextField
  3. 软件开发如同木匠做桌子
  4. 粤教版小学认识计算机教案,粤教版八年级信息技术下册教案:第一章第一节初识计算机程序oc.pdf...
  5. Careercup | Chapter 3
  6. asp.net高校宿舍后勤管理系统
  7. 网易云Android高级,网易云音乐Android新版 一键升本地音质
  8. 使用预计算实时全局光照优化照明-优化实时光照贴图
  9. 在西安参加Java培训该怎么学习?
  10. 刘国忠:顺周期股受资金青睐,但能走多远还是未知!
  11. linux系统获取root权限,linux怎么进入root权限
  12. Libre密聊——致力于私密聊天的用心APP
  13. 看到校友录一位同学的留言,想起来一首诗
  14. 发送到谷歌邮箱的邮件在哪找_如何让Google表格为您发送个性化电子邮件
  15. 线段树模板(建树+更新)
  16. 如何成为一名架构师,架构师成长之路
  17. 英语发音规则---I字母
  18. Python笔记之不可不练
  19. PS 学习笔记 18-加深工具组
  20. 图神经网络学习记录:《图神经网络综述:模型与应用》

热门文章

  1. OpenNLP进行中文命名实体识别(下:载入模型识别实体)
  2. html自定义的DIV垂直滚动条
  3. 迪文屏DMT12800K070_A2WTC踩坑实录(一)
  4. 迪文屏幕T5UID3平台学习笔记零:迪文屏幕的学习和开发
  5. rocketmq 消息删除_清空rocketmq消息方法
  6. 【物联网毕设基础】单片机:红外遥控通信原理
  7. 计算机发明于1946年用英语怎么说,电子计算机发明于哪一年,电子计算机发明与1946年...
  8. entity framework 新手入门篇(2)-entity framework基本的增删改查
  9. 计算机去掉everyone访问权限,Win7提示您需要Everyone提供的权限才能对此文件进行更改的解决方法...
  10. MATLAB仿真任意带宽的窄带信号、宽带信号以及全频带信号