不含stm32 底层的代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
MyStm32Code/DOC/15_modbus.md

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;
}