14 KiB
Modbus
https://blog.csdn.net/ASWaterbenben/article/details/105549750 https://blog.51cto.com/u_15830484/5767899 https://www.amobbs.com/thread-5491615-1-1.html https://blog.csdn.net/qq153471503/article/details/104840279 https://blog.csdn.net/childbor/article/details/123803534 野火 30001为输入寄存器,需要使用04指令访问,而40001为保持寄存器,可以使用03、06和16指令访问
在modbus中,需要判断一帧什么时候发送结束,我们采用定时器进行判断。 当定时器时间大于我们设定的时间时,发生定时器中断,告诉系统,一帧数据发送完成。
首先选择TIM7下的Parameter Settings,将PSC设置为7199,Counter Mode设置为UP,Counter Period 设置为39,Auto-reload preload设置为Enable。 拷贝mobdus文件加 拷贝demo/bare 到 port文件夹, demo.c 改modbus_port.c mbconfig.h 配置启用哪些模块 对于元文件需要做修改的,加 .port 后缀,放到code/port文件夹
移植思路
串口定时器初始化放到 Modbus里
暂无
串口定时器单独初始化
portserial.c extern UART_HandleTypeDef huart2; // 引入串口
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
依据参考开启相关中断
UART_IT_RXNE 接收非空
UART_IT_TXE 发送完成
xMBPortSerialPutByte( CHAR ucByte ) // 处理发送字节
xMBPortSerialGetByte( CHAR * pucByte ) //处理接收字节
void USART2_IRQHandler(void) prvvUARTRxISR();//接收中断 prvvUARTTxReadyISR();//发送中断
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数
porttimer.c 定时器启用,停用。 请中断等操作 定时器是中断回调 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) prvvTIMERExpiredISR( ); //定时器中断
modbus_port.c 定义Input Holding 寄存器操作
中断建立联系 -- 如何无缝,不改中断函数代码,回调函数
前端初始化 eMBInit( MB_RTU, 0x01, 2, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。 eMBEnable( );//使能modbus
while ( void )eMBPoll( );//启动modbus侦听
源码结构
定义结构体接收数据 usart.h
typedef struct
{
uint8_t *rx_buf; //接收缓冲数组
uint16_t rx_buf_cnt; //接收缓冲计数值
uint16_t rx_size; //接收数据大小
uint8_t rx_flag; //接收完成标志位
uint8_t *tx_buf; //发送缓冲数组
uint16_t tx_buf_cnt; //发送缓冲计数值
uint16_t tx_size; //实际发送数据大小
}UART_BUF; //串口结构体
extern UART_BUF uart_buf_2; //预留一个定义的buf
usart.c
/* USER CODE BEGIN 0 */
#include "string.h"
#include "tim.h"
#define UART4_RXSIZE 1024 //一帧接收数据的最大值
#define UART4_TXSIZE 1024 //一帧发送数据的最大值
uint8_t uart4_rx_buf[UART4_RXSIZE]; //发送数据缓冲数组
uint8_t uart4_tx_buf[UART4_TXSIZE]; //接收数据缓冲数据
UART_BUF uart4; //串口结构体实体
uint8_t RxBuffer; //接收数据中间变量
/* USER CODE END 0 */
重写回调
/*****************************重写回调函数,实现串口数据接收**********************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == UART4)
{
if(uart4.rx_buf_cnt >= UART4_RXSIZE-1) //接收数据量超限,错误
{
uart4.rx_buf_cnt = 0;
memset(uart4.rx_buf, 0x00, sizeof(uart4.rx_buf));
HAL_UART_Transmit(huart, (uint8_t *)"数据溢出", 10, 0xFFFF);
}
else //接收正常
{
uart4.rx_buf[uart4.rx_buf_cnt++] = RxBuffer; //接收数据存储到rx_buf
HAL_TIM_Base_Stop_IT(&htim7);
__HAL_TIM_SET_COUNTER(&htim7, 0);
HAL_TIM_Base_Start_IT(&htim7); //将定时器7的计数值清零后重新计数
}
HAL_UART_Receive_IT(huart, (uint8_t *)&RxBuffer, 1);
}
}
port.h #define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1);//关总中断 #define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0);//开总中断 如何改DMA ?
tim 初始化及回调 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
main
buf 重置 ---写函数实现供调用 HAL_UART_Receive_IT(huart, uart4.rx_buf, 1); //开启接收中断
tim清理标记 -- 写函数实现供调用 开启中断
modbus slave
将已经编写好的modbus.c和modbus.h分别复制到Src和Inc文件夹内
freemodbus
解压freemodbus文件后打开, modbus 目录组合,重新分Src Inc 我们需要demo目录下的BARE,该目录下的代码是空的,STM32移植工作基本就是修改:portserial.c、porttimer.c、port.h这三个文件。
参考: https://github.com/eziya/STM32_HAL_FREEMODBUS_RTU/tree/master/Middlewares/Third_Party/modbus bare/port 目录下的Makefile 可以直接编译 modbus 目录拷贝到 Middlewares/Third_Party/modbus/ bare/port 目录下的Makefile 可以直接编译, demo 包含main()
functions include 是基础目录 rtu 需要引用 rtu目录
porttimer.c
将定时器设置为每50us的时长记一个数,传入的usTim1Timerout50us变量给自动装载即可 prvvTIMERExpiredISR函数需要在定时器中断服务函数中调用,它的作用是用于通知modbus协议栈3.5个字符的等待时间已经到达
注意一些标志清除
定时器中断 TimX_IRQHandler 通知通知modbus3.5个字符等待时间到 prvvTIMERExpiredISR(); // 通知modbus3.5个字符等待时间到
如果使用了485芯片的话,那么同一时刻只能接收或者发送,可以将函数vMBPortSerialEnable修改成这样 拉高拉低,并增加延时。
定义port.c供前端调用
数据定义
// 十路输入寄存器
#define REG_INPUT_SIZE 10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];
// 十路保持寄存器
#define REG_HOLD_SIZE 10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];
// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};
// 十路离散量
// 十路离散量
#define REG_DISC_SIZE 10
uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};
对应功能函数
/* 功能码 4*/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
return eStatus;
}
/* 功能码 6 3 16*/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
{
return MB_ENOREG;
}
/* 功能码 1 5 15*/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
eMBRegisterMode eMode )
{
return MB_ENOREG;
}
/* 功能码 2*/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
完整port.c
#include "mb.h"
#include "mbport.h"
// 十路输入寄存器
#define REG_INPUT_SIZE 10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];
// 十路保持寄存器
#define REG_HOLD_SIZE 10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];
// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};
// 十路离散量
#define REG_DISC_SIZE 10
uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};
/// CMD4命令处理回调函数
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
USHORT usRegIndex = usAddress - 1;
// 非法检测
if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
{
return MB_ENOREG;
}
// 循环读取
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}
// 模拟输入寄存器被改变
for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++)
{
REG_INPUT_BUF[usRegIndex]++;
}
return MB_ENOERR;
}
/// CMD6、3、16命令处理回调函数
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;
// 非法检测
if((usRegIndex + usNRegs) > REG_HOLD_SIZE)
{
return MB_ENOREG;
}
// 写寄存器
if(eMode == MB_REG_WRITE)
{
while( usNRegs > 0 )
{
REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1];
pucRegBuffer += 2;
usRegIndex++;
usNRegs--;
}
}
// 读寄存器
else
{
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}
}
return MB_ENOERR;
}
/// CMD1、5、15命令处理回调函数
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;
UCHAR ucBits = 0;
UCHAR ucState = 0;
UCHAR ucLoops = 0;
// 非法检测
if((usRegIndex + usNCoils) > REG_COILS_SIZE)
{
return MB_ENOREG;
}
if(eMode == MB_REG_WRITE)
{
ucLoops = (usNCoils - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = *pucRegBuffer++;
ucBits = 0;
while(usNCoils != 0 && ucBits < 8)
{
REG_COILS_BUF[usRegIndex++] = (ucState >> ucBits) & 0X01;
usNCoils--;
ucBits++;
}
ucLoops--;
}
}
else
{
ucLoops = (usNCoils - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = 0;
ucBits = 0;
while(usNCoils != 0 && ucBits < 8)
{
if(REG_COILS_BUF[usRegIndex])
{
ucState |= (1 << ucBits);
}
usNCoils--;
usRegIndex++;
ucBits++;
}
*pucRegBuffer++ = ucState;
ucLoops--;
}
}
return MB_ENOERR;
}
/// CMD2命令处理回调函数
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
USHORT usRegIndex = usAddress - 1;
UCHAR ucBits = 0;
UCHAR ucState = 0;
UCHAR ucLoops = 0;
// 非法检测
if((usRegIndex + usNDiscrete) > REG_DISC_SIZE)
{
return MB_ENOREG;
}
ucLoops = (usNDiscrete - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = 0;
ucBits = 0;
while(usNDiscrete != 0 && ucBits < 8)
{
if(REG_DISC_BUF[usRegIndex])
{
ucState |= (1 << ucBits);
}
usNDiscrete--;
usRegIndex++;
ucBits++;
}
*pucRegBuffer++ = ucState;
ucLoops--;
}
// 模拟离散量输入被改变
for(usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++)
{
REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex];
}
return MB_ENOERR;
}
main.c
将串口定时器的初始化都整到 Modbus 部分,这里只需要调用modbus初始化就可以
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_ODD); // 初始化modbus为RTU方式,波特率9600,奇校验
eMBEnable(); // 使能modbus协议栈
for( ;; )
{
eMBPoll(); // 轮训查询
}
}
我们需要demo目录下的BARE,该目录下的代码是空的,STM32移植工作基本就是修改:portserial.c 串口的中断服务程序, prvvUARTTxReadyISR和prvvUARTRxISR函数需要填写进中断服务程序, 前者得到作用为通知modbus协议栈串口已经空闲可以发送数据了 后者的作用为通知modbus串口1有数据到达
调用
#include "stm32f4xx_hal.h"
#include "cmsis_os.h"
#include "mb.h"
#include "mbport.h"
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 8
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS];
void ModbusRTUTask(void const * argument)
{
/* ABCDEF */
usRegInputBuf[0] = 11;
usRegInputBuf[1] = 22;
usRegInputBuf[2] = 33;
usRegInputBuf[3] = 44;
usRegInputBuf[4] = 55;
usRegInputBuf[5] = 66;
usRegInputBuf[6] = 77;
usRegInputBuf[7] = 88;
eMBErrorCode eStatus = eMBInit( MB_RTU, 1, 3, 19200, MB_PAR_NONE );
eStatus = eMBEnable();
while(1) {
eMBPoll();
}
}
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
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 );
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
HAL_GPIO_TogglePin(LD4_GPIO_Port, LD4_Pin);
}
else
{
HAL_GPIO_TogglePin(LD5_GPIO_Port, LD5_Pin);
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}