硬汉嵌入式论坛

 找回密码
 立即注册
查看: 430|回复: 6
收起左侧

[STM32H7] MODBUS SLAVE

[复制链接]

6

主题

28

回帖

46

积分

新手上路

leduo

积分
46
发表于 2024-3-26 19:18:52 | 显示全部楼层 |阅读模式
前几天准备了一个带有串口,LED和按键的基础工程,今天把modbus专题中的从机代码移植一下。
目标是实现4路LED控制,按键作为DI检测

那么首先,把按键驱动里边的按键检测开放出来
[C] 纯文本查看 复制代码
uint8_t is_key_down(uint8_t _id);



一,基础工程的介绍

1,LED部分

[C] 纯文本查看 复制代码
/* ¹©Íⲿµ÷Óõĺ¯ÊýÉùÃ÷ */
void                 bsp_led_init(void);
void                 bsp_led_on(uint8_t _no);
void                 bsp_led_off(uint8_t _no);
void                 bsp_led_toggle(uint8_t _no);
uint8_t bsp_led_is_on(uint8_t _no);

void bsp_led_set_state(uint8_t num,uint8_t state);

void bsp_led_tick(void);
void bsp_led_task(void);


2,KEY部分

[C] 纯文本查看 复制代码
uint8_t is_key_down(uint8_t _id);



3,串口部分

[C] 纯文本查看 复制代码
void rs485_uart_queue_init(void);
void rs485_uart_queue_tick(void);
void rs485_uart_queue_task(void);
int  rs485_uart_queue_blocking_read(uint8_t * buff,int buffsize,int timeout);
void rs485_uart_queue_message_in(unsigned char *buf);
void rs485_uart_queue_message_in_ex(unsigned char *buf,unsigned long len);
void rs485_uart_queue_handler(unsigned char *message, int messagelen);
void rs485_queue_settle(void);


其中串口部分用到了自己的队列,主要的功能有,根据时间的设定自己实现分包,rs485_uart_queue_tick();这行代码是在systycik中运行的,rs485_uart_queue_task();这个代码是在while(1)中不断循环调用的,当有数据入队以后,会触发生成数据包的回调

void rs485_uart_queue_handler(unsigned char *message, int messagelen)
{
        log_hex_msg("rs485 rev:",message,messagelen);
        modbus_poll(message,messagelen);
}


之前做过modbus一个小工程,但是没有严格按照3.5字符做超时判断,今天重新实现一下,所以用到了硬件定时器,所以同时移植了H7裸机工程的定时器部分。


二,移植

移植的过程,参考了MODBUS从机例程和H7 TOOL开源的工程代码,包含一下几个文件
1.png


串口的参数配置如下:

2.1  fifo结构体配置

2.1.png

2.2 队列入口

在中断中,只是把数据取走,触发recivenew的回调
2.2.1.png


为了方便测试,定时器的超时时间,直接设置成了16000us
2.2.2.png

这里做一个说明:
1,在调试阶段,没有直接通过波特率计算出超时时间而是直接用了固定的参数
2,在定时器超时回调函数里,没有用到g_mods_timeout的变量,当超时触发以后,队列里边提供了一个函数,来手动settle结算队列,并触发队列的回调
3,回调中做modbus的解析
2.2.3.png


三,调试

在调试的时候发现h7 tool接两个串口只支持一种数据格式,导致modbus的数据回来是乱码的,所以在调试串口接的TOOL,modbus的部分通过一个485转USB接到了电脑上

3.1
  
3.1.1.png

在首次调试的时候发现,发送完modbus以后,板子直接就死了,因为我不太熟悉板子的调试手段(寄存器,error handler那些后边还是得熟悉一下,如果大家有好的文档,可以留言给我,感谢),只能把从基础工程添加的几个函数逐一排查一下

最终在我把定时器启动函数放到开头的时候,发现直接死机了,
3.1.2.png

最后定位到的问题是这里
3.1.3.png

当时不太习惯大小写的方式,在改动定时器中断入口的时候,改成了这个。最终导致定时器产生中断以后找不到函数入口。


3.2

3.2.1.png

在经过一番波折,终于实现了,485接收数据并打印接收到的数据,但是这个数据包究竟是不是定时器超时以后自己结算的包,还不确定,所以就有了如下的代码


3.2.2.png
3.2.3.png


在队列中,设置了分包的时间为3000ms,也就是3s中,同时开了一个16000us的超时定时器。如果是定时器分包,那么必然是很快的,测试如下:

3.2.4.png

为了进一步验证,我把定时器关掉:

3.2.6.png

按键按下的报文:

3.2.7.png

移植的部分,就算完成了。之前看过h7 tool的代码,下一步要把一些参数的读取和设置也一并加进来,成功以后就可以在rtos上跑跑试试了。


评分

参与人数 1金币 +100 收起 理由
eric2013 + 100

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2024-3-27 09:26:57 | 显示全部楼层
谢谢楼主分享,整理的很好。
回复

使用道具 举报

6

主题

28

回帖

46

积分

新手上路

leduo

积分
46
 楼主| 发表于 2024-3-28 10:34:49 | 显示全部楼层
前几天移植好了modbus程序,今天再来增加一些功能:


1,参数存储
2,参数设置
3,测试


第一步,移植STMH7 裸机工程的w25q256驱动程序,不过这里有个需要注意的点就是,他每次写之前要擦除4K的扇区,后边再AT指令里边会用到





第二步,移植H7  TOOL的param文件,修改modify_param文件,这里我只保留了网络和modbus,输入和输出的参数部分,代码如下:


[C] 纯文本查看 复制代码
#define PARAM_ADDR          0           /* 基本参数区地址 */
#define PARAM_SIZE          256         /* 最大空间,用于编译查错 */


#define PARAM_VER           0x00000101  /* 基本参数版本 100 */

/* 暂未启用 程序缓存 */
#define APP_BUF_ADDR        0x08000000 + 1 * 1024 * 1024

enum
{
    LINK_NONE = 0,      /* 无连接 */
    LINK_USB_OK = 1,    /* USB连接中 */
    LINK_RJ45_OK = 2,   /* RJ45连接中 */
    LINK_WIFI_OK = 3,   /* WiFi连接中 */
};



/* 全局参数 */
typedef struct
{
    uint32_t UpgradeFlag;       /*升级标记,0x55AAA55A表示需要更新APP,0xFFFF表示更新完毕*/
    uint32_t ParamVer;          /* 参数区版本控制(可用于程序升级时,决定是否对参数区进行升级) */
		uint8_t flag;
    uint8_t Addr485;

    uint8_t LocalIPAddr[4];     /* 本机IP地址 */
    uint8_t NetMask[4];         /* 子网掩码 */
    uint8_t Gateway[4];         /* 网关 */
    uint16_t LocalTCPPort;      /* 本机TCP端口 */
    uint16_t LocalUDPPort;      /* 本机UDP端口 */

    uint8_t RemoteIPAddr[4];    /* 远端(前置)IP地址 */
    uint16_t RemoteTcpPort;     /* 远端(前置)TCP端口 */

    uint8_t APSelfEn;           /* 本机扮演AP */
    uint8_t AP_SSID[32 + 1];    /* AP名字 */
    uint8_t AP_PASS[16 + 1];    /* AP密码 */
    uint8_t WiFiIPAddr[4];      /* IP地址  192.168.1.50 */
    uint8_t WiFiNetMask[4];     /* 子网掩码 255.255.255.0 */
    uint8_t WiFiGateway[4];     /* 网关 192.168.1.1 */
    uint8_t DHCPEn;             /* DHCP使能  */
    uint8_t WiFiMac[6];


} PARAM_T;

/* 全局变量 */
typedef struct
{
    /* MCU ID */
    uint32_t CPU_Sn[3];

    uint8_t WiFiDebugEn;
    uint8_t RemoteTCPServerOk;
    uint8_t HomeWiFiLinkOk;

    uint8_t WiFiRecivedIPD;

    uint8_t MACaddr[6];         /* 以太网MAC地址 */

    uint8_t InputState[32];
    uint8_t RelayState[32];

    float CH1Volt;
    float CH2Volt;

    uint8_t GpioMode[16];       /* 保存GPIO模式参数、输入、输出或特殊功能 */


} VAR_T;



[C] 纯文本查看 复制代码
/*
*********************************************************************************************************
*
*    模块名称 : 应用程序参数模块
*    文件名称 : param.c
*    版    本 : V1.0
*    说    明 : 读取和保存应用程序的参数
*    修改记录 :
*        版本号  日期        作者     说明
*        V1.0    2013-01-01 armfly  正式发布
*
*    Copyright (C), 2012-2013, 安富莱电子 [url]www.armfly.com[/url]
*
*********************************************************************************************************
*/

#include "main.h"

PARAM_T g_tParam;            /* 基本参数 */
VAR_T g_tVar;                /* 全局变量 */


/*
*********************************************************************************************************
*    函 数 名: LoadParam
*    功能说明: 从eeprom读参数到g_tParam
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void load_param(void)
{
    /* 读取EEPROM中的参数 */
		qspi_read_buffer((uint8_t *)&g_tParam, PARAM_ADDR, sizeof(PARAM_T));
	
    if (sizeof(PARAM_T) > PARAM_SIZE)
    {
        /* 基本参数分配空间不足 */
        while(1);
    }

#if 1   
    if (g_tParam.ParamVer != PARAM_VER)
    {
        load_base_param();    
    }
#else		

		 if(g_tParam.flag==0)
		 {
				load_base_param();
		 }

   
#endif		
    bsp_get_cpu_id(g_tVar.CPU_Sn);    /* 读取CPU ID */
    
    /* 自动生成以太网MAC */
    g_tVar.MACaddr[0] = 0xC8;
    g_tVar.MACaddr[1] = 0xF4;
    g_tVar.MACaddr[2] = 0x8D;
    g_tVar.MACaddr[3] = g_tVar.CPU_Sn[0] >> 16;
    g_tVar.MACaddr[4] = g_tVar.CPU_Sn[0] >> 8;
    g_tVar.MACaddr[5] = g_tVar.CPU_Sn[0] >> 0;
      
		g_tVar.RelayState[0]=1;
		g_tVar.RelayState[1]=1;
		g_tVar.RelayState[2]=1;
		g_tVar.RelayState[3]=1;

//		SaveParam();
}


uint8_t load_realy_state(uint8_t num)
{
	return g_tVar.RelayState[num];
}


/*
*********************************************************************************************************
*    函 数 名: SaveParam
*    功能说明: 将全局变量g_tParam 写入到eeprom
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void SaveParam(void)
{
  /* 将全局的参数变量保存到EEPROM */
	qspi_erase_sector(0);
	qspi_write_buffer((uint8_t *)&g_tParam,PARAM_ADDR,sizeof(PARAM_T));
}

/*
*********************************************************************************************************
*    函 数 名: InitBaseParam
*    功能说明: 初始化基本参数为缺省值
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void load_base_param(void)
{
    g_tParam.UpgradeFlag = 0;           /*升级标记,0x55AAA55A表示需要更新APP,0xFFFF表示更新完毕*/
    g_tParam.ParamVer = PARAM_VER;      /* 参数区版本控制(可用于程序升级时,决定是否对参数区进行升级) */
    
		g_tParam.flag=1;	
	
    g_tParam.Addr485 = 1;
    
    g_tParam.LocalIPAddr[0] = 192;      /* 本机IP地址 */
    g_tParam.LocalIPAddr[1] = 168;
    g_tParam.LocalIPAddr[2] = 1;
    g_tParam.LocalIPAddr[3] = 111;
    
    g_tParam.NetMask[0] = 255;          /* 子网掩码 */
    g_tParam.NetMask[1] = 255;
    g_tParam.NetMask[2] = 255;
    g_tParam.NetMask[3] = 0;
    
    g_tParam.Gateway[0] = 192;          /* 网关 */
    g_tParam.Gateway[1] = 168;
    g_tParam.Gateway[2] = 1;
    g_tParam.Gateway[3] = 1;
    
    g_tParam.LocalTCPPort = 30010;      /* 本机TCP端口和UDP端口号,相同 */    

    g_tParam.RemoteIPAddr[0] = 192;     /* 远端(前置)IP地址 */
    g_tParam.RemoteIPAddr[1] = 168;
    g_tParam.RemoteIPAddr[2] = 1;
    g_tParam.RemoteIPAddr[3] = 8;
    
    g_tParam.RemoteTcpPort = 30000;     /* 远端(前置)TCP端口 */

    g_tParam.APSelfEn = 0;              /* 0作为客户端,1作为AP */
    memset(g_tParam.AP_SSID, 0, 32 + 1);    /* AP名字 */
    memset(g_tParam.AP_PASS, 0, 16 + 1);    /* AP密码 */
    g_tParam.WiFiIPAddr[0] = 192;       /* 静态IP地址  */
    g_tParam.WiFiIPAddr[1] = 168;
    g_tParam.WiFiIPAddr[2] = 1;    
    g_tParam.WiFiIPAddr[3] = 105;
    
    g_tParam.WiFiNetMask[0] = 255;      /* 子网掩码 255.255.255.0 */    
    g_tParam.WiFiNetMask[1] = 255;
    g_tParam.WiFiNetMask[2] = 255;
    g_tParam.WiFiNetMask[3] = 0;    
    
    g_tParam.WiFiGateway[0] = 192;      /* 网关 192.168.1.1 */    
    g_tParam.WiFiGateway[1] = 168;
    g_tParam.WiFiGateway[2] = 1;
    g_tParam.WiFiGateway[3] = 1;
    
    g_tParam.DHCPEn = 0;                /* DHCP使能  */

    SaveParam();
		
}


void restroe_factory_param_setting(void)
{
	load_base_param();
}


void param_rs485_addr_set(uint8_t addr)
{
	g_tParam.Addr485=addr;
	SaveParam();
}

uint8_t param_rs485_addr_get(void)
{
	return g_tParam.Addr485;
	
}

void printf_param_info(void)
{
	uint32_t var;
	var=*(unsigned int*)(0x08000000+28);
	printf("设备固件版本:%X.%02X\r\n",var>>8,var&0xFF);
	printf("设备参数版本:%X.%02X\r\n",g_tParam.ParamVer>>8,g_tParam.ParamVer&0xFF);   
	printf("MODBUS地址  :%d\r\n",g_tParam.Addr485);
	printf("本地IP地址  :%d.%d.%d.%d\r\n",g_tParam.LocalIPAddr[0],g_tParam.LocalIPAddr[1],g_tParam.LocalIPAddr[2],g_tParam.LocalIPAddr[3]);
	printf("子网掩码    :%d.%d.%d.%d\r\n",g_tParam.NetMask[0],g_tParam.NetMask[1],g_tParam.NetMask[2],g_tParam.NetMask[3]);
	printf("本地网关    :%d.%d.%d.%d\r\n",g_tParam.Gateway[0],g_tParam.Gateway[1],g_tParam.Gateway[2],g_tParam.Gateway[3]);
	printf("本地端口    :%d\r\n",g_tParam.LocalTCPPort);
	printf("服务器IP地址:%d.%d.%d.%d\r\n",g_tParam.RemoteIPAddr[0],g_tParam.RemoteIPAddr[1],g_tParam.RemoteIPAddr[2],g_tParam.RemoteIPAddr[3]);
	printf("服务器端口  :%d\r\n",g_tParam.RemoteTcpPort);
	
	printf("开关状态    :%d %d %d %d\r\n",g_tVar.RelayState[0],g_tVar.RelayState[1],g_tVar.RelayState[2],g_tVar.RelayState[3]);
	
	printf("*************************************************************\n\r");
}


/***************************** 安富莱电子 [url]www.armfly.com[/url] (END OF FILE) *********************************/



其中,void restroe_factory_param_setting(void);  void param_rs485_addr_set(uint8_t addr);uint8_t param_rs485_addr_get(void); void printf_param_info(void);这几个函数是新增的,作用是为了后边通过debug 串口来设置参数。

开机打印:





第三步,增加串口设置功能












485的部分,只设置了设备地址,后期可以增加一个波特率的设置



第四步,修改之前遗留的问题

1,modbus_rev_new




2,modbus地址判断的部分



第四步,测试

1,设备固件版本和参数版本查询


2,通过串口AT指令更改设备ID



查询下是不是更改成功


w25q256重启是不丢失的,重启再验证一下




3,测试更改以后主机给从机发送命令

以modbus从机地址4测试情况


log


从机返回


回复

使用道具 举报

6

主题

28

回帖

46

积分

新手上路

leduo

积分
46
 楼主| 发表于 2024-3-28 10:36:10 | 显示全部楼层
更改modbus从机地址再测试:



回复

使用道具 举报

6

主题

28

回帖

46

积分

新手上路

leduo

积分
46
 楼主| 发表于 2024-3-28 10:36:54 | 显示全部楼层
eric2013 发表于 2024-3-27 09:26
谢谢楼主分享,整理的很好。

感谢硬汉老师提供的平台。
回复

使用道具 举报

6

主题

28

回帖

46

积分

新手上路

leduo

积分
46
 楼主| 发表于 2024-4-9 09:55:37 | 显示全部楼层
文件太大了上传不了,分享个百度云盘,希望能帮助到新学的朋友
链接:https://pan.baidu.com/s/1drQc-vS9P5skyXZNlvmOWA?pwd=0bfr
提取码:0bfr
--来自百度网盘超级会员V7的分享
回复

使用道具 举报

0

主题

7

回帖

7

积分

新手上路

积分
7
发表于 2024-4-9 18:29:44 | 显示全部楼层
感谢楼主分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-29 02:35 , Processed in 0.427572 second(s), 30 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表