first commit

This commit is contained in:
2026-01-24 20:03:14 +08:00
commit a2224908e3
318 changed files with 278538 additions and 0 deletions

357
Middlewares/Modbus/MODBUS.c Normal file
View File

@@ -0,0 +1,357 @@
/******************************************************************************
* @file MODBUS.c
* @brief Modbus RTU协议通信处理模块
* @details 本文件实现了基于Modbus RTU协议的RS485通信功能包括
* - Modbus命令帧的构建和发送
* - 接收数据的解析和处理
* - 实时数据轮询机制
* - 屏幕刷新命令处理
* - 保护定值、控制字等数据的更新
* @author 阜阳师范大学物电学院
* @version V0.01
* @date 2026-01-23
* @note 通信协议Modbus RTU
* 通信接口RS485
* 主站地址0x01
******************************************************************************/
#include "MODBUS.h"
#include <string.h>
#include "rs485.h"
#include "CRC16.h"
#include "led.h"
#include "160160D.h"
#include "filehandle.h"
/* ============================================================================
* 命令类型宏定义
* ============================================================================ */
#define RT_CMD (1) /**< 实时数据刷新命令类型 */
#define KEY_CMD (0) /**< 按键事件命令类型 */
/* ============================================================================
* 图片宏定义
* ============================================================================ */
#define BMP_ADDR (0x0803F000) /**< BMP数据在Flash中的存储地址扇区126 */
#define BMP_LEN (3208) /**< BMP数据长度单位字节 */
typedef struct
{
uint8_t bmpdata[3200];
uint32_t biWidth;
uint32_t biHeight;
} logo_type;
typedef struct
{
volatile uint8_t RT_count; /* 实时命令计数器 */
volatile uint16_t CMD_ACK; /* 普通命令接收完成标志位 */
volatile uint8_t ACK_COUNT_ABLE; //命令发出后计时使能
volatile uint8_t ACK_count; /* 应答计时计数器 */
volatile uint8_t ACK_OverTime; /* 超时标志位 */
volatile uint8_t ACK_OverTimeCnt; /* 超时次数技术器 */
} RS485_POLL_TYPE;
RS485_POLL_TYPE RS485POLL = {0};
#define RS485_RT_COUNT_MAX (55) //轮询限值 275ms
#define RS485_ACK_COUNT_MAX (40) //应答超时限值 200ms
#define RS485_ACK_OVERTIME_MAX (5) // 75ms 应答超时计数最大次数,超过该次数显示通讯异常
/* ============================================================================
* 私有变量定义
* ============================================================================ */
static logo_type logo; /**< LOGO图片数据结构体包含BMP图片数据和尺寸信息 */
static uint8_t Picture[3200];
/* ============================================================================
* MODBUS协议处理部分
* ============================================================================ */
/**
* @brief 发送屏幕刷新命令特殊格式的Modbus命令
* @param func 功能码通常为READ_RIGISTER = 0x03
* @param type 命令类型RT_CMD(1)=实时刷新KEY_CMD(0)=按键事件
* @param key 按键值当type=KEY_CMD时有效
* @note 命令帧格式6字节
* [0] = 从站地址Master_Address = 0x01
* [1] = 功能码func
* [2] = 命令类型type
* [3] = 按键值key
* [4] = CRC校验高字节
* [5] = CRC校验低字节
* CRC校验范围前4字节
* 发送后重置命令应答标志
* @retval 无
*/
void RS485_RefreshScreenCMD(uint8_t func, uint8_t type, uint8_t key)
{
uint16_t crc = 0;
uint8_t cmdbuf[6];
cmdbuf[0] = 0x01; /* 从站地址 */
cmdbuf[1] = func; /* 功能码 */
cmdbuf[2] = type; /* 命令类型 */
cmdbuf[3] = key; /* 按键值 */
crc = Calculate_CRC(&cmdbuf[0],4); /* 计算CRC校验前4字节 */
cmdbuf[4] = crc >> 8; /* CRC校验高字节 */
cmdbuf[5] = (uint8_t) crc; /* CRC校验低字节 */
RS485_SendBuff(cmdbuf, 6); /* 发送6字节命令帧 */
RS485POLL.CMD_ACK = RESET; /* 重置命令应答标志 */
}
///* ============================================================================
// * 屏幕刷新处理 远程处理图片
// * ============================================================================ */
/**
* @brief 发送屏幕刷新命令
* @param type 命令类型RT_CMD(1)=实时刷新KEY_CMD(0)=按键事件
* @param key 按键值当type=KEY_CMD时有效
* @note 发送屏幕刷新命令到138端请求更新屏幕显示
* 命令发送后:
* - 重置命令应答标志
* - 重置实时数据计数
* - 使能应答计数
* - 重置应答计数值
* @retval 无
*/
void RefreshScreen(uint8_t type, uint8_t key)
{
RS485_RefreshScreenCMD(0x03, type, key); /* 发送刷新屏幕命令 */
RS485POLL.CMD_ACK = RESET; /* 重置命令响应标志 */
RS485POLL.RT_count = 0; /* 重置实时召唤计数 */
RS485POLL.ACK_COUNT_ABLE = SET; /* 使能应答计数 */
RS485POLL.ACK_count = 0; /* 重置应答计数值 */
}
/**
* @brief 在LCD屏幕上显示LOGO图片
* @note 调用Display_BMP函数将全局变量logo中的BMP图片数据显示在LCD屏幕上
* 显示位置屏幕居中由Display_BMP函数自动计算
* 图片尺寸使用logo结构体中的biWidth和biHeight
* 图片数据使用logo结构体中的bmpdata数组
* 注意显示前需要确保logo数据已通过BMP_Get()从Flash读取或已初始化
* @retval 无
*/
void LOGO_Printf(void)
{
/* 调用Display_BMP函数显示LOGO图片 */
/* 参数:宽度、高度、图片数据指针 */
Display_BMP(logo.biWidth, logo.biHeight, (const uint8_t*)logo.bmpdata);
}
/**
* @brief 保存LOGO的BMP图片信息到Flash
* @note 将全局变量logo中的BMP图片数据保存到Flash的指定地址BMP_ADDR
* 保存的数据包括:
* - BMP图片数据bmpdata数组3200字节
* - 图片宽度biWidth
* - 图片高度biHeight
* 注意保存前Flash相关扇区会被自动擦除
* 保存地址0x0803F000扇区126
* @retval 无
*/
void BMP_SAVE2False(void)
{
/* 调用Flash写入函数将logo结构体数据写入Flash */
/* sizeof(logo)/2 将字节数转换为半字16位个数 */
FLASH_WriteMoreData(BMP_ADDR, (uint16_t*)(&logo), (sizeof(logo)/2));
}
/**
* @brief 从Flash中读取LOGO的BMP图片数据
* @note 从Flash的指定地址BMP_ADDR读取BMP图片数据到全局变量logo中
* 读取的数据包括:
* - BMP图片数据bmpdata数组3200字节
* - 图片宽度biWidth
* - 图片高度biHeight
* 读取地址0x0803F000扇区126
* 注意使用volatile指针确保从Flash直接读取避免缓存问题
* @retval 无
*/
void BMP_Get(void)
{
uint32_t i = 0;
/* 将Flash地址转换为 volatile 指针确保直接从Flash读取数据 */
volatile logo_type* ptr = (volatile logo_type*)BMP_ADDR;
/* 循环读取BMP图片数据3200字节 */
for(i = 0; i < 3200; i++)
{
logo.bmpdata[i] = ptr->bmpdata[i];
}
/* 读取图片宽度 */
logo.biWidth = ptr->biWidth;
/* 读取图片高度 */
logo.biHeight = ptr->biHeight;
}
void NL_LOGO_Printf(void)
{
BMP_Get();
LOGO_Printf();
delay_ms(500);
delay_ms(500);
delay_ms(500);
delay_ms(500);
}
/**
* @brief Modbus通信主处理函数-远程处理图片
* @param key 按键值KEY_TYPE类型
* @note 采用138端处理图片整体传上来显示的方式刷图
* 处理流程:
* 1. 按键处理:
* - 保存按键值到静态变量key_bk
* - 等待可发送状态时发送按键命令
* 2. 接收新消息处理:
* - 设置连接标志
* - CRC校验通过
* * 提取图片数据3200字节和命令信息8字节
* * 判断命令类型:
* - 0x89 0x45LOGO图片保存并显示
* - 其他普通图片显示图片和LED状态
* - CRC校验失败
* * 累计错误计数
* * 连续3次错误后重启DMA接收
* - 清除控制字和应答相关标志
* 3. 定时刷新处理:
* - 实时数据计数超限时发送实时刷新命令
* 4. 应答超时处理:
* - 应答计数超限时设置超时标志
* - 连续超时次数达到上限时重启DMA接收
* 数据帧长度:
* 3000 8
* └──────┬──────┘ └─┬─┘
* Picture info
* info 组成:
* info[0]info[1] info[2]info[3] info[4]info[5]
* └──────┬─────┘ └───────┬────┘ └───────┬────┘
* 图片宽度 图片高度 图片类型标识
* info[0]info[1]info[2]info[3]
* └────────────┬─────────────┘
* LED 灯状态信息
* 图片类型:
* 1. info[4] = 0x89 info[5] = 0x45 logo 图片
* 2. 其他图片
* @retval 无
*/
uint8_t RS485_Process(KEY_TYPE key, uint8_t flag)
{
static KEY_TYPE key_bk = KEY_NONE; /* 按键备份(静态变量,保持状态) */
static uint8_t LED_BUFF[4]; /* LED状态缓冲区静态变量 */
static uint8_t info[8]; /* 命令信息缓冲区(静态变量) */
static uint32_t CRC_ERR_COUNT = 0; /* CRC错误计数静态变量 */
uint8_t ConnectFlg = flag;
/* 按键处理:保存按键值 */
if(key != KEY_NONE)
{
if(key_bk != key) /* 按键值变化时更新备份 */
{
key_bk = key;
}
}
if(RS485REG.NewMessageFlag) /* 接收到新消息 */
{
RS485REG.NewMessageFlag = RESET; /* 清除控制字 */
ConnectFlg = 1; /* 设置连接标志 */
if(Check_CRC((uint8_t*)&RS485REG.DR[0], UART_RX_LEN) == TRUE) /* CRC校验通过 */
{
CRC_ERR_COUNT = 0; /* 重置CRC错误计数 */
memcpy(Picture, (uint8_t*)RS485REG.DR, 3200); /* 提取图片数据前3200字节 */
memcpy(info, (uint8_t*)RS485REG.DR + 3200, 8); /* 提取命令信息后8字节 */
if(info[4] == 0x89 && info[5] == 0x45) /* LOGO图片命令特殊标识 */
{
memcpy(logo.bmpdata, Picture, 3200); /* 复制图片到LOGO结构体 */
logo.biWidth = info[0] << 8 | info[1]; /* 解析图片宽度高字节左移8位 + 低字节) */
logo.biHeight = info[2] << 8 | info[3]; /* 解析图片高度高字节左移8位 + 低字节) */
BMP_SAVE2False(); /* 保存 BMP 图片到 Flash */
/* 显示LOGO */
BackLight_ON(); /* 开启背光 */
ClearScreen(); /* 清屏 */
LOGO_Printf(); /* 显示LOGO */
delay_ms(500); /* 延时500ms */
delay_ms(500); /* 延时500ms */
delay_ms(500); /* 延时500ms */
delay_ms(500); /* 延时500ms总共2秒 */
}
else /* 普通图片命令 */
{
memcpy(LED_BUFF, (uint8_t*)RS485REG.DR + 3200, 4); /* 提取LED状态后4字节 */
LED_Printf(LED_BUFF); /* 显示LED状态 */
ScreenPrintf(Picture); /* 显示图片 */
}
}
else /* CRC校验失败 */
{
CRC_ERR_COUNT++; /* CRC错误计数递增 */
if(CRC_ERR_COUNT >= 3) /* 连续3次CRC错误 */
{
/* 接收错位,重新初始化接收 */
RS485_DMA_init(); /* 重新初始化*/
CRC_ERR_COUNT = 0; /* 重置CRC错误计数 */
}
}
/* 清除控制字和应答相关标志 */
RS485POLL.ACK_OverTime = RESET; /* 重置应答超时标志 */
RS485POLL.ACK_COUNT_ABLE = RESET; /* 禁用应答计数 */
RS485POLL.ACK_count = 0; /* 重置应答计数 */
RS485POLL.ACK_OverTimeCnt = 0; /* 重置应答超时计数 */
RS485POLL.CMD_ACK = SET; /* 设置接收完成标志 */
}
if((RS485POLL.CMD_ACK)||(RS485POLL.ACK_OverTime)) /* 可以应答或者超时*/
{
/* 按键命令发送:有按键且处于可发送状态 */
if(key_bk != KEY_NONE) /* 有按键 */
{
RefreshScreen(KEY_CMD, key_bk); /* 发送按键刷新命令 */
key_bk = KEY_NONE; /* 清除按键备份 */
}
/* 定时刷新:实时数据计数超限且处于可发送状态 */
if(RS485POLL.RT_count > RS485_RT_COUNT_MAX) /* 定时到*/
{
RefreshScreen(RT_CMD, key_bk); /* 发送实时刷新命令 */
key_bk = KEY_NONE; /* 清除按键备份 */
}
}
/* 应答超时处理:应答计数超限 */
if(RS485POLL.ACK_count > RS485_ACK_COUNT_MAX) /* 应答超时 */
{
RS485POLL.ACK_OverTime = SET; /* 设置应答超时标志 */
RS485POLL.ACK_count = 0; /* 重置应答计数 */
RS485POLL.ACK_OverTimeCnt++; /* 应答超时计数递增 */
}
/* 连续超时处理:应答超时次数达到上限 */
if((RS485POLL.ACK_OverTimeCnt >= RS485_ACK_OVERTIME_MAX)) /* 连续超时次数达到上限 */
{
ConnectFlg = 0;
RS485POLL.ACK_OverTimeCnt = RS485_ACK_OVERTIME_MAX; /* 限制最大计数(防止溢出) */
RS485_DMA_init(); /* 重新初始化RS485 */
RS485POLL.CMD_ACK = SET; /* 设置命令应答标志(允许重新发送) */
}
return ConnectFlg;
}
void Process_Count(void)
{
RS485POLL.RT_count++;
if(RS485POLL.ACK_COUNT_ABLE)
{
RS485POLL.ACK_count++;
}
}
void Process_Init(void)
{
/* 清除控制字和应答相关标志 */
RS485POLL.ACK_OverTime = RESET; /* 重置应答超时标志 */
RS485POLL.ACK_COUNT_ABLE = RESET; /* 禁用应答计数 */
RS485POLL.ACK_count = 0; /* 重置应答计数 */
RS485POLL.ACK_OverTimeCnt = 0; /* 重置应答超时计数 */
RS485POLL.CMD_ACK = SET; /* 设置接收完成标志 */
}