不含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

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

备注

tim 由于不能在中断中启用timer, 所以不能直接用freertos 定时器去操作

serial (__HAL_UART_GET_IT_SOURCE(pMobusHuart, UART_IT_RXNE) != RESET && __HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) != RESET )

串口中断优先级别高于 定时器 不添加 __HAL_UART_GET_IT_SOURCE(pMobusHuart, UART_IT_RXNE , eSndState != STATE_TX_IDLE 具体原因不详 Checks whether the specified UART interrupt source is enabled or not. 启用中断改变的是这个值

启用modbus的不同功能码

eMBRegHoldingCB 函数对应功能码 04 eMBRegInputCB 函数对应功能码 03 06

snd 01 06 00 01 0A 9D 1F 03, 返回同样, 对应的查询值 06 将地址为01的寄存器 写为 0A 9D (2177) snd 01 10 00 00 00 02 04 87 65 43 21 3A 2C , 返回 01 10 00 00 00 02 41 C8 id func(0x10 16 ), 起始地址 00 寄存器个数2个, 返回4个字节, 0下7654321 CRC16

    if((usAddress >= REG_HOLD_START) && ((usAddress+usNRegs) <= (REG_HOLD_START + REG_HOLD_NREGS)))
    {
        iRegIndex = (int)(usAddress - usRegHoldStart);
        switch(eMode)
        {
        case MB_REG_READ://读寄存器
            while(usNRegs > 0)
            {
                *pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] >> 8);
                *pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] & 0xFF);
                iRegIndex++;
                usNRegs--;
            }
            break;
        case MB_REG_WRITE://写寄存器

            while(usNRegs > 0)
            {
                usRegHoldBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
 
        }
    }

eMBRegCoilsCB 线圈 01 snd 01 01 00 00 00 03 7C 0B 读三个线圈 rcv 01 01 01 06 D1 8A 返回值为06 (0000 0110)的后三位 ,反过来 0,1,1

eMBRegDiscreteCB 线圈 02 snd 01 02 00 00 00 03 7C 0B 读三个线圈 rcv 01 01 01 06 D1 8A 返回值为06 (0000 0110)的后三位 ,反过来 0,1,1

报告从机状态 17功能码 0x01 0x11 01 11 C0 2C

了解接收机制:xMBRTUReceiveFSM

了解发送机制: xMBRTUTransmitFSM