Parcourir la source

新增掉电保持功能

deepseek
ywh il y a 1 mois
Parent
révision
d6825564ee
4 fichiers modifiés avec 152 ajouts et 91 suppressions
  1. +19
    -14
      Core/Src/main.c
  2. +10
    -9
      EWARM/Modbus.ewp
  3. +25
    -8
      Modbus/Inc/modbus_rtu_slave.h
  4. +98
    -60
      Modbus/Src/modbus_rtu_slave.c

+ 19
- 14
Core/Src/main.c Voir le fichier

@@ -22,7 +22,6 @@

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* uC/OS-II的公开接口、任务类型和配置宏都由此头文件提供。 */
#include "ucos_ii.h"
#include "modbus_rtu_slave.h"
/* USER CODE END Includes */
@@ -48,16 +47,13 @@ DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

/* USER CODE BEGIN PV */
/*
* 每个任务必须拥有独立栈空间。
* OS_STK在本工程中为32位,因此256个OS_STK占用1024字节RAM。
* Cortex-M栈从高地址向低地址增长,创建任务时要把数组末元素作为栈顶传入。
*/

static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static HAL_StatusTypeDef BackupSramInit(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
@@ -71,14 +67,10 @@ static void AppTaskStart(void *p_arg);
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/*
* uC/OS-II任务函数的固定形式是 void Task(void *p_arg)。
* 任务通常包含一个永久循环;需要等待时应调用OS延时或等待内核对象,
* 让出CPU给其他就绪任务,而不是在任务里忙等待。
*/

static void AppTaskStart(void *p_arg)
{
/* 当前没有给任务传递参数,显式转换可避免编译器产生未使用警告。 */

(void)p_arg;

/*
@@ -86,6 +78,7 @@ static void AppTaskStart(void *p_arg)
* 初始化函数会立即启动USART1的DMA空闲接收。
*/
ModbusSlaveInit(&huart1, MODBUS_SLAVE_DEFAULT_ADDRESS);
ModbusRetainedRegistersLoad();
while (1)
{
/*
@@ -107,6 +100,7 @@ static void AppTaskStart(void *p_arg)
* 每1ms轮询一次,不在串口中断中执行耗时的CRC和寄存器操作。
*/
ModbusSlavePoll();
ModbusRetainedRegistersPoll();
OSTimeDly(1U);
}
}
@@ -146,6 +140,9 @@ int main(void)
MX_DMA_Init();
MX_USB_DEVICE_Init();
MX_USART1_UART_Init();
BackupSramInit();


/* USER CODE BEGIN 2 */
/* uC/OS-II接口通常用返回错误码的方式报告执行结果。 */
INT8U os_err;
@@ -203,6 +200,14 @@ int main(void)
/* USER CODE END 3 */
}

static HAL_StatusTypeDef BackupSramInit(void)
{
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_BKPSRAM_CLK_ENABLE();

return HAL_PWREx_EnableBkUpReg();
}

/**
* @brief System Clock Configuration
* @retval None
@@ -268,7 +273,7 @@ static void MX_USART1_UART_Init(void)

/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.BaudRate = 19200;
huart1.Init.WordLength = UART_WORDLENGTH_9B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_EVEN;
@@ -341,7 +346,7 @@ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
/*
* DMA遇到串口空闲事件后进入这里。
* 仅复制帧并重启DMA,协议解析由AppTaskStart任务完成。
* 仅复制帧并重启DMA
*/
ModbusSlaveOnRxEvent(huart, Size);
}


+ 10
- 9
EWARM/Modbus.ewp Voir le fichier

@@ -366,6 +366,7 @@
<state>$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-CPU</state>
<state>$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-CPU\ARM-Cortex-M4\IAR</state>
<state>$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-LIB</state>
<state>$PROJ_DIR$\..\Modbus\Inc</state>
</option>
<option>
<name>CCStdIncCheck</name>
@@ -1181,15 +1182,6 @@
</file>
</group>
</group>
<group>
<name>Modbus</name>
<file>
<name>$PROJ_DIR$\..\Modbus\Inc\modbus_rtu_slave.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\Modbus\Src\modbus_rtu_slave.c</name>
</file>
</group>
<group>
<name>Micrium</name>
<group>
@@ -1268,4 +1260,13 @@
</file>
</group>
</group>
<group>
<name>Modbus</name>
<file>
<name>$PROJ_DIR$\..\Modbus\Src\modbus_rtu_slave.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\Modbus\Inc\modbus_rtu_slave.h</name>
</file>
</group>
</project>

+ 25
- 8
Modbus/Inc/modbus_rtu_slave.h Voir le fichier

@@ -1,8 +1,3 @@
/**
* @file modbus_rtu_slave.h
* @brief Modbus RTU 从站模块公开接口。
*/

#ifndef MODBUS_RTU_SLAVE_H
#define MODBUS_RTU_SLAVE_H

@@ -19,8 +14,11 @@ extern "C"
* 任务要求规定线圈和保持寄存器地址范围均为 0~0x270F,
* 因此每种数据共有 10000 项。
*/
#define MODBUS_MAP_LAST_ADDRESS (0x270FU)
#define MODBUS_MAP_ITEM_COUNT (0x2710U)
#define MODBUS_MAP_LAST_ADDRESS (0x270FU)//保持寄存器或线圈地址
#define MODBUS_MAP_ITEM_COUNT (0x2710U)//保持寄存器或线圈数量




#define MODBUS_SLAVE_DEFAULT_ADDRESS (1U)
#define MODBUS_CONNECTION_TIMEOUT_MS (1000U)
@@ -36,6 +34,24 @@ extern "C"
#define HMI_REG_WRITE_TEST (10U)
#define HMI_COIL_WRITE_TEST (0U)


#define MODBUS_RETAINED_D_START (100U)
#define MODBUS_RETAINED_D_END (120U)
#define MODBUS_RETAINED_D_COUNT (21U)

#define MODBUS_BACKUP_MAGIC (0x44313030UL)

typedef struct
{
uint32_t magic;
uint16_t retainedD[MODBUS_RETAINED_D_COUNT];
} MODBUS_BACKUP_DATA;

static volatile MODBUS_BACKUP_DATA *backupData =(volatile MODBUS_BACKUP_DATA *)BKPSRAM_BASE;


/**
* @brief Modbus 从站通信统计信息。
*/
@@ -129,7 +145,8 @@ uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state);
* @retval 0 尚未收到有效请求或连接已经超时。
*/
uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs);

void ModbusRetainedRegistersLoad(void);
void ModbusRetainedRegistersPoll(void);
#ifdef __cplusplus
}
#endif


+ 98
- 60
Modbus/Src/modbus_rtu_slave.c Voir le fichier

@@ -1,33 +1,22 @@
/**
* @file modbus_rtu_slave.c
* @brief Modbus RTU 从站协议处理模块。
*/

#include "modbus_rtu_slave.h"

#include <string.h>

/** @brief Modbus RTU 最大 ADU 长度,单位为字节。 */
#define MODBUS_RTU_ADU_SIZE_MAX (256U)
#define MODBUS_RTU_ADU_SIZE_MAX (256U)//Modbus RTU 最大 ADU 长度,单位为字节

/**
* @brief 当前通信参数下使用的 RTU 帧间隔。
*
* 当前波特率为 115200 bit/s。Modbus 规范建议波特率高于
* 19200 bit/s 时采用固定 1.75 ms 帧间隔,此处向上取整为 2 ms。
*/
#define MODBUS_RTU_FRAME_GAP_MS (2U)

#define MODBUS_BROADCAST_ADDRESS (0U)
#define MODBUS_BROADCAST_ADDRESS (0U)//广播地址

#define MODBUS_EX_ILLEGAL_FUNCTION (0x01U)
#define MODBUS_EX_ILLEGAL_ADDRESS (0x02U)
#define MODBUS_EX_ILLEGAL_VALUE (0x03U)
#define MODBUS_EX_ILLEGAL_FUNCTION (0x01U)//非法功能码的异常码
#define MODBUS_EX_ILLEGAL_ADDRESS (0x02U)//非法地址的异常码
#define MODBUS_EX_ILLEGAL_VALUE (0x03U)//非法数据的异常码

#define MODBUS_READ_COILS_MAX (2000U)
#define MODBUS_READ_REGS_MAX (125U)
#define MODBUS_WRITE_COILS_MAX (1968U)
#define MODBUS_WRITE_REGS_MAX (123U)
#define MODBUS_READ_COILS_MAX (2000U)//一次ADU最大线圈读取数量
#define MODBUS_READ_REGS_MAX (125U) //一次ADU最大寄存器读取数量
#define MODBUS_WRITE_COILS_MAX (1968U)//一次ADU最大线圈写入数量
#define MODBUS_WRITE_REGS_MAX (123U) //一次ADU最大寄存器写入数量

#define MODBUS_COIL_VALUE_ON (0xFF00U)
#define MODBUS_COIL_VALUE_OFF (0x0000U)
@@ -37,12 +26,13 @@ static uint8_t ModbusSlaveAddress;

/**
* DMA 缓冲区只由 DMA 写入;帧缓冲区在接收回调中完成一次快照,
* 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据
* 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据
*/
static uint8_t ModbusRxDmaBuffer[MODBUS_RTU_ADU_SIZE_MAX];
static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX];
static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX];

/* D100~D120 上一次已保存的值,用于检测数据是否变化 */
static uint16_t ModbusRetainedSnapshot[MODBUS_RETAINED_D_COUNT];
static volatile uint16_t ModbusRxFrameLength;
static volatile uint8_t ModbusRxFrameReady;
static volatile uint8_t ModbusTxBusy;
@@ -52,18 +42,19 @@ static volatile uint8_t ModbusHasReceivedValidFrame;

/**
* 10000 个保持寄存器占用 20000 字节;10000 个线圈按位存储,
* 占用 1250 字节
* 占用 1250 字节
*/
static uint16_t ModbusHoldingRegisters[MODBUS_MAP_ITEM_COUNT];

static uint8_t ModbusCoils[(MODBUS_MAP_ITEM_COUNT + 7U) / 8U];

volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics;

/**
* @brief 计算 Modbus RTU CRC16 校验值
* @param[in] data 待校验数据
* @param[in] length 待校验数据长度
* @return CRC16 校验值
* @brief 计算 Modbus RTU CRC16 校验值
* @param[in] data 待校验数据
* @param[in] length 待校验数据长度
* @return CRC16 校验值
*/
static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length)
{
@@ -92,9 +83,9 @@ static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length)
}

/**
* @brief 从网络字节序数据中读取一个 16 位无符号整数。
* @param[in] data 至少包含两个字节的数据地址
* @return 转换后的 16 位无符号整数
* @brief 提取一个 16 位无符号整数
* @param[in] data 两个字节的数据地址
* @return 转换后的 16 位无符号整数
*/
static uint16_t ModbusGetU16Be(const uint8_t *data)
{
@@ -105,17 +96,15 @@ static uint16_t ModbusGetU16Be(const uint8_t *data)
* @brief 检查连续数据地址范围是否合法。
* @param[in] start 起始地址。
* @param[in] quantity 数据项数量。
* @retval 1 地址范围合法。
* @retval 0 地址范围非法。
*/
static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity)
{
if ((quantity == 0U) || (start >= MODBUS_MAP_ITEM_COUNT))
if (start >= MODBUS_MAP_ITEM_COUNT)
{
return 0U;
}

/* 先做减法再比较,避免 start + quantity 发生 16 位溢出。 */
// 先做减法再比较,避免溢出
return (quantity <= (MODBUS_MAP_ITEM_COUNT - start)) ? 1U : 0U;
}

@@ -214,14 +203,14 @@ static uint16_t ModbusBuildException(uint8_t function, uint8_t exception)
}

/**
* @brief 处理读线圈功能码 0x01
* @param[in] request RTU 请求帧
* @param[in] requestLength 请求帧长度
* @return 待发送响应长度,异常请求返回异常响应长度
* @brief 处理读线圈功能码 0x01
* @param[in] request RTU 请求帧
* @param[in] requestLength 请求帧长度
* @return 待发送响应长度,异常请求返回异常响应长度
*/
static uint16_t ModbusProcessReadCoils(const uint8_t *request,
uint16_t requestLength)
static uint16_t ModbusProcessReadCoils(const uint8_t *request, uint16_t requestLength)
{
uint16_t start;
uint16_t quantity;
uint16_t index;
@@ -236,13 +225,13 @@ static uint16_t ModbusProcessReadCoils(const uint8_t *request,
start = ModbusGetU16Be(&request[2]);
quantity = ModbusGetU16Be(&request[4]);

if ((quantity == 0U) || (quantity > MODBUS_READ_COILS_MAX))
if ((quantity == 0U) || (quantity > MODBUS_READ_COILS_MAX))//数量0或者数量大于最大值
{
ModbusSlaveStatistics.illegalValueCount++;
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
}

if (ModbusAddressRangeIsValid(start, quantity) == 0U)
if (ModbusAddressRangeIsValid(start, quantity) == 0U)//地址检查
{
ModbusSlaveStatistics.illegalAddressCount++;
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
@@ -254,13 +243,12 @@ static uint16_t ModbusProcessReadCoils(const uint8_t *request,
ModbusTxFrame[2] = byteCount;
(void)memset(&ModbusTxFrame[3], 0, byteCount);

/* Modbus 规定第一个线圈放在响应数据首字节的最低位。 */
for (index = 0U; index < quantity; index++)
{
if (ModbusCoilGetUnchecked((uint16_t)(start + index)) != 0U)
{
ModbusTxFrame[3U + (index >> 3U)] |=
(uint8_t)(1U << (index & 0x0007U));
ModbusTxFrame[3U + (index >> 3U)] |= (uint8_t)(1U << (index & 0x0007U));
}
}

@@ -269,13 +257,13 @@ static uint16_t ModbusProcessReadCoils(const uint8_t *request,
}

/**
* @brief 处理读保持寄存器功能码 0x03
* @param[in] request RTU 请求帧
* @param[in] requestLength 请求帧长度
* @return 待发送响应长度,异常请求返回异常响应长度
* @brief 处理读保持寄存器功能码 0x03
* @param[in] request RTU 请求帧
* @param[in] requestLength 请求帧长度
* @return 待发送响应长度,异常请求返回异常响应长度
*/
static uint16_t ModbusProcessReadHolding(const uint8_t *request,
uint16_t requestLength)
static uint16_t ModbusProcessReadHolding(const uint8_t *request,uint16_t requestLength)
{
uint16_t start;
uint16_t quantity;
@@ -291,13 +279,13 @@ static uint16_t ModbusProcessReadHolding(const uint8_t *request,
start = ModbusGetU16Be(&request[2]);
quantity = ModbusGetU16Be(&request[4]);

if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX))
if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX))//数量0或者数量大于最大值
{
ModbusSlaveStatistics.illegalValueCount++;
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
}

if (ModbusAddressRangeIsValid(start, quantity) == 0U)
if (ModbusAddressRangeIsValid(start, quantity) == 0U)//地址检查
{
ModbusSlaveStatistics.illegalAddressCount++;
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
@@ -560,8 +548,7 @@ static uint16_t ModbusProcessWriteMultipleRegisters(
* @param[in] requestLength 请求帧长度。
* @return 待发送响应长度;无需响应时返回 0。
*/
static uint16_t ModbusProcessRequest(const uint8_t *request,
uint16_t requestLength)
static uint16_t ModbusProcessRequest(const uint8_t *request,uint16_t requestLength)
{
uint16_t calculatedCrc;
uint16_t receivedCrc;
@@ -577,20 +564,20 @@ static uint16_t ModbusProcessRequest(const uint8_t *request,
(uint16_t)(request[requestLength - 2U]
| ((uint16_t)request[requestLength - 1U] << 8U));

if (calculatedCrc != receivedCrc)
if (calculatedCrc != receivedCrc)//CRC校验
{
ModbusSlaveStatistics.crcErrorCount++;
return 0U;
}

if ((request[0] != ModbusSlaveAddress)
&& (request[0] != MODBUS_BROADCAST_ADDRESS))
&& (request[0] != MODBUS_BROADCAST_ADDRESS))//非法从站地址
{
ModbusSlaveStatistics.ignoredAddressCount++;
return 0U;
}

isBroadcast = (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U;
isBroadcast = (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U;//是否广播请求
ModbusSlaveStatistics.validFrameCount++;
ModbusLastValidFrameTick = HAL_GetTick();
ModbusHasReceivedValidFrame = 1U;
@@ -625,7 +612,7 @@ static uint16_t ModbusProcessRequest(const uint8_t *request,
return ModbusProcessWriteMultipleRegisters(request,
requestLength,
isBroadcast);
default://未知功能码
ModbusSlaveStatistics.illegalFunctionCount++;
return (isBroadcast != 0U) ? 0U :
@@ -794,3 +781,54 @@ uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs)
? 1U
: 0U;
}

//上电恢复函数
void ModbusRetainedRegistersLoad(void)
{
uint16_t index;

if (backupData->magic == MODBUS_BACKUP_MAGIC)
{
for (index = 0U; index < MODBUS_RETAINED_D_COUNT;index++)
{
ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] =
backupData->retainedD[index];
}
}
else
{
for (index = 0U;index < MODBUS_RETAINED_D_COUNT;index++)
{
ModbusHoldingRegisters[
MODBUS_RETAINED_D_START + index] = 0U;

backupData->retainedD[index] = 0U;
}

/*
* 数据初始化完成后最后写magic,避免初始化中途断电
* 却把不完整数据标记成有效。
*/
backupData->magic = MODBUS_BACKUP_MAGIC;
}
}
void ModbusRetainedRegistersPoll()
{
uint16_t index;
uint16_t value;

for(index=0;index<MODBUS_RETAINED_D_COUNT;index++)
{
value=ModbusHoldingRegisters[MODBUS_RETAINED_D_START +index];
if(value != ModbusRetainedSnapshot[index])
{
backupData->retainedD[index]=value;
}

ModbusRetainedSnapshot[index] = value;
}




}

Chargement…
Annuler
Enregistrer