IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32CubeMX | Modbus RTU 主机协议栈实现(国产单片机、FreeModbus无缝使用) -> 正文阅读

[嵌入式]STM32CubeMX | Modbus RTU 主机协议栈实现(国产单片机、FreeModbus无缝使用)

STM32CubeMX | Modbus RTU 主机协议栈实现



本章博客涉及代码,关注以下公众号,回复关键字mbrtu_master获取下载链接!


1、前言

???????? ~~~~~~~~ ????????modbus rtu在嵌入式方面非常的常见和使用,嵌入式linux中可以使用libmodbus这个库,但是对于嵌入式单片机,开源的有FreeModbus这个库,但是只是从机,对于modbus rtu主机的实现,网上却找不到开源的库,或者找到了但是不方便移植,使用者想要去使用还要去搞明白是怎么实现的,本博客基于以上原因,实现了一套modbus rtu主机协议栈。

本主机协议栈优点如下:

  • 接口明确清晰,使用者无需关心协议栈内部实现
  • 面向对象编程思想,使用C语言的struct作为一个modbus rtu主机的控制接口,此方法的好处是可以灵活的实现多个主机,例如:实现一个多主机的modbus pdu。
  • 支持RTOS
  • 可搭配FreeModbus协议栈无缝使用
  • 移植简单、可很方便的移植到其他单片机如GD32、MM32等
  • 源码简单、只有一个头文件、一个源文件、一个移植接口示例文件

2、协议栈API介绍

2.1 控制结构

typedef struct
{
	//
	// 收发数据缓存
	//
	uint8_t ucBuf[128];
	
	//
	// 收发数据状态
	//
	uint16_t usStatus;
	
	//
	// 如果使用了RTOS需要进行互斥,那么需要实现以下两个函数的绑定
	//
	void (*lock)(void);
	void (*unlock)(void);
	
	//
	// 微秒延时函数,用于等待超时
	//
	void (*delayms)(uint32_t nms);
	
	//
	// 定时器启动和停止函数
	//
	void (*timerStop)(void);
	void (*timerStart)(void);
	
	//
	// 发送数据函数,可以是串口、TCP等
	//
	uint32_t (*sendData)(const void* buf, uint32_t len);

	//
	// 以下四个回调函数分别是:读线圈、读离散量输入、读保持寄存器、读输入寄存器
	//
	void (*readCoilsCallback)(uint16_t usStartAddr, uint16_t usNum, const uint8_t* pucBitsOfCoilsState, uint16_t usLen);
	void (*readDiscreteInputsCallback)(uint16_t usStartAddr, uint16_t usNum, const uint8_t* pucBitsOfDiscreteInputsState, uint16_t usLen);
	void (*readHoldingRegistersCallback)(uint16_t usStartAddr, uint16_t usNum, const uint16_t* pusHoldingRegistersVal, uint16_t usLen);
	void (*readInputRegistersCallback)(uint16_t usStartAddr, uint16_t usNum, const uint16_t* pusInputRegistersVal, uint16_t usLen);

}MBRTUMaterTypeDef;


2.2 主机读线圈状态(CMD1)

2.2 主机读离散量输入(CMD2)

2.2 主机读保持寄存器(CMD3)

2.2 主机读输入寄存器(CMD4)

2.2 主机写单个线圈(CMD5)

2.2 主机写单个寄存器(CMD6)

2.2 主机写多个线圈(CMD15)

2.2 主机写多个寄存器(CMD16)


3、移植前的基础工程生成

基础工程这里我使用STM32CubeMX生成,使用的是STM32F103C8单片机,配置步骤如下,首先将时钟配置到72M:

配置串口1用于调试打印,配置串口3用于modbus主机通信:

配置用于检测3.5个字符超时时间的定时器,我配置成了5ms超时。

这里需要跟你实际使用的波特率进行超时时间的计算,以:波特率9600、8bit数据位、1bit停止位,奇校验、无流控为例,那么1s内就可以传输9600bits÷(8+1+1)=960bytes,那么3.5个字节的时间就是1000ms÷960×3.5≈3.65ms,所以,我设置5ms的超时时间是没有问题的。


开启定时器和串口中断,注意:串口的中断要比定时器中断等级高:

最后输出工程就可以了:


4、移植主机协议栈

主机协议栈源码就只有三个文件:

其中,mbrtu_master.hmbrtu_master.c是协议栈实现,无需动,mbrtu_master_example.c是移植参考示例。

下面讲解一下移植过程。

首先定义一个modbus主机的全局控制结构并初始化:

MBRTUMaterTypeDef MBRTUHandle =
{
    .delayms                      = delayms,
    .timerStart                   = timerStart,
    .timerStop                    = timerStop,
    .sendData                     = sendData,
    .readCoilsCallback            = readCoilsCallback,
    .readDiscreteInputsCallback   = readDiscreteInputsCallback,
    .readHoldingRegistersCallback = readHoldingRegistersCallback,
    .readInputRegistersCallback   = readInputRegistersCallback,

#ifdef USE_RTOS  // 使用了RTOS那么需要实现互斥
    .lock                         = mutex_lock,
    .unlock                       = mutex_unlock,
#endif
};

注意:如果使用了实时系统,需要实现lock和unlock函数。

结构体中的函数实现如下:

#ifdef USE_RTOS

static void mutex_lock(void)
{

}

static void mutex_unlock(void)
{

}

#endif

static void timerStop(void)
{
    HAL_TIM_Base_Stop_IT(&htim3);
}

static void timerStart(void)
{
    __HAL_TIM_SET_COUNTER(&htim3, 0);
    HAL_TIM_Base_Start_IT(&htim3);
}

static void delayms(uint32_t nms)
{
#ifdef USE_RTOS
	osDelay(nms);
#else
    HAL_Delay(nms);
#endif
}

static uint32_t sendData(const void* buf, uint32_t len)
{
    if(HAL_UART_Transmit(&huart3, (uint8_t *)buf, len, 100) != HAL_OK)
    {
        len = 0;
    }
    return len;
}

static void readCoilsCallback(uint16_t usStartAddr, uint16_t usNum, const uint8_t* pucBitsOfCoilsState, uint16_t usLen)
{
    uint8_t ucLoops = (usNum - 1) / 8 + 1;
    uint8_t ucState, ucBits;

	printf(" Read %d coils starting at start address %d: ", usNum, usStartAddr);
	
    while(ucLoops != 0)
    {
        ucState = *pucBitsOfCoilsState++;
        ucBits  = 0;
        while(usNum != 0 && ucBits < 8)
        {
            printf("%d ", ucState & 0X01 ? 1 : 0);
            ucState >>= 1;
            usNum--;
            ucBits++;
        }
        ucLoops--;
    }

    printf("\r\n");
}

static void readDiscreteInputsCallback(uint16_t usStartAddr, uint16_t usNum, const uint8_t* pucBitsOfDiscreteInputsState, uint16_t usLen)
{
    uint8_t ucLoops = (usNum - 1) / 8 + 1;
    uint8_t ucState, ucBits;

    printf(" Read %d discrete inputs starting at start address %d: ", usNum, usStartAddr);
	
    while(ucLoops != 0)
    {
        ucState = *pucBitsOfDiscreteInputsState++;
        ucBits  = 0;
        while(usNum != 0 && ucBits < 8)
        {
            printf("%d ", ucState & 0X01 ? 1 : 0);
            ucState >>= 1;
            usNum--;
            ucBits++;
        }
        ucLoops--;
    }

    printf("\r\n");
}

static void readHoldingRegistersCallback(uint16_t usStartAddr, uint16_t usNum, const uint16_t* pusHoldingRegistersVal, uint16_t usLen)
{
    uint16_t val;
    printf(" Read %d hold registers starting at start address %d: ", usNum, usStartAddr);
    while(usLen--)
    {
        val = *pusHoldingRegistersVal++;
        val = ((val & 0X00FF) << 8) | ((val & 0XFF00) >> 8);  // 转换大小端
        printf("%04X ", val);
    }
    printf("\r\n");
}

static void readInputRegistersCallback(uint16_t usStartAddr, uint16_t usNum, const uint16_t* pusInputRegistersVal, uint16_t usLen)
{
    uint16_t val;
    printf(" Read %d input registers starting at start address %d: ", usNum, usStartAddr);
    while(usLen--)
    {
        val = *pusInputRegistersVal++;
        val = ((val & 0X00FF) << 8) | ((val & 0XFF00) >> 8);  // 转换大小端
        printf("%04X ", val);
    }
    printf("\r\n");
}

MBRTUMasterTimerISRCallback函数放置于定时器中断函数中,对于HAL库那就是这样的:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == htim3.Instance)
    {
        MBRTUMasterTimerISRCallback(&MBRTUHandle);
    }
}

MBRTUMasterRecvByteISRCallback函数放置于串口中断函数中,对于HAL库那就是这样的:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == huart3.Instance)
    {
        MBRTUMasterRecvByteISRCallback(&MBRTUHandle, g_Uart3RxByte);
        HAL_UART_Receive_IT(&huart3, &g_Uart3RxByte, 1);  // 注册接收
    }
}

重定向printf到串口1:

int fputc(int ch, FILE* fp)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
    return ch;
}

至此,就移植完毕了,main函数如下:

5、移植测试验证

移植完毕了现在需要测试,测试我选用FreeModbus作为从机,关于FreeModbus从机移植使用可以参考我的另一篇博客:STM32CubeMX | STM32 HAL库移植FreeModbus详细步骤

首先启动从机,然后启动主机,调试打印如下:

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:35:52  更:2022-05-05 11:38:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 1:17:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码