Initial commit: CCU621_M firmware project with BLE debug link support.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:28:36 +08:00
commit 9ceb218f80
1597 changed files with 724159 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
/*!
\file gpio.h
\brief the header file of systick
\version 2026-01-01, for JC
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32h7xx.h"
#include "gd32h7xx_gpio.h"
#include "gpio.h"
/*!
\brief Configure GPIO OUT pins
\param[in] none
\param[out] none
\retval none
*/
void v_outpin_config(void)
{
/* 开启GPIO - outpin 外设时钟 经测试一句使能所有时钟不起作用 */
//rcu_periph_clock_enable(RCU_GPIOA | RCU_GPIOB | RCU_GPIOC | RCU_GPIOD | RCU_GPIOE | RCU_GPIOF | RCU_GPIOG | RCU_GPIOJ | RCU_GPIOK);
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_GPIOF);
rcu_periph_clock_enable(RCU_GPIOG);
rcu_periph_clock_enable(RCU_GPIOJ);
rcu_periph_clock_enable(RCU_GPIOK);
/* 单阶段初始化:先预置 OCTL 为高电平(断开态),再切换到推挽输出。 */
gpio_bit_set(GPIOG,
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 |
GPIO_PIN_11);
gpio_bit_set(GPIOC, GPIO_PIN_8 | GPIO_PIN_9);
//gpio_bit_set(GPIOK, GPIO_PIN_1 | GPIO_PIN_2);
/* 主控板开关量输出控制(DO)
PG4 114 DO OUT1|板载继电器K1控制
PG5 115 DO OUT2|板载继电器K2控制
PG6 116 DO OUT3|板载继电器K3控制
PG7 117 DO OUT4|板载继电器K4控制
PG8 118 DO OUT5|板载继电器K5控制
PC8 124 DO OUT6|板载继电器K6控制
PC9 125 DO OUT7|板载继电器K7控制
PG9 153 DO OUT8|板载继电器K8控制
PG10 154 DO OUT9|板载继电器K9控制
PG11 155 DO OUT10|板载继电器K10控制
PK1 108 D0 A_gun_elelock_ ctr|A枪电子锁控制
PK2 109 DO B_gun_elelock_ ctr|B枪电子锁控制
*/
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOG, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_4 | GPIO_PIN_5 |GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_4 | GPIO_PIN_5 |GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11);
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_8 | GPIO_PIN_9);
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_8 | GPIO_PIN_9);
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOK, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_1 | GPIO_PIN_2 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOK, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_1 | GPIO_PIN_2 );
/* DO/电子锁电平已在切换为输出模式前预置为高电平(断开态),避免继电器抖动。 */
/* 主控板内部开关量输出控制(DO)
PE4 3 DO INSA_CTL|A枪绝缘监测接地开关
PE3 2 DO UNBALA_CTL|A枪绝缘监测正极回路开关
PA5 50 DO INSB_CTL|B枪绝缘监测接地开关
PE5 4 DO UNBALB_CTL|B枪绝缘监测正极回路开关
*/
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5);
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5);
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_5 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_5 );
/* 板上LED指示灯
PG15 DO H7_RUN
*/
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOG, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,GPIO_PIN_15);
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,GPIO_PIN_15);
/* 主控板内部连接确认模式选择控制
PF8 28 DO CHK_CSA_CTL
PF2 20 DO CHK_CSB_CTL
*/
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,GPIO_PIN_2 | GPIO_PIN_8);
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,GPIO_PIN_2 | GPIO_PIN_8);
/* 主控板内部控制输出
PE2 1 DO WDO
PB7 166 DO ESP8685_EN|ESP8685蓝牙WIFI模块使能控制
PE6 5 DO FC41D_RST|FC41D蓝牙WIFI模块复位(高有效,常态低)
PF3 21 DO 4G_RST|WH606 4G模块复位控制
PC13 9 DO 4G_POWER|WH606 4G模块电源控制
PA10 129 DO CIU_RST|CIU98_A加密芯片复位控制
*/
gpio_bit_reset(FC41D_RST_PORT, FC41D_RST_PIN); /* FC41D 常态低(高电平复位) */
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,GPIO_PIN_2 | GPIO_PIN_6);
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,GPIO_PIN_2 | GPIO_PIN_6);
gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_7 );
gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_3 );
gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_13 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_13 );
gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_10 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_10 );
/* 温度采集通道选择控制
PD10 数字地址“A”输出控制
PD13 数字地址“B”输出控制
PJ8 数字地址“C”输出控制
*/
/* Configure GPIO pins as output mode (no pull-up/pull-down) */
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,GPIO_PIN_10 | GPIO_PIN_13);
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,GPIO_PIN_10 | GPIO_PIN_13);
gpio_mode_set(GPIOJ, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_8 );
/* Set output type to push-pull and speed to 60MHz */
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_8);
}
void v_inpin_config(void)
{
/* 开启GPIO - inpin 外设时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_GPIOF);
rcu_periph_clock_enable(RCU_GPIOG);
/* 开关量输入检测
PC0 DI YX1|系统开关量输入检测
PB0 55 DI YX2|系统开关量输入检测
PF14 61 DI YX3|系统开关量输入检测
PB1 56 DI YX4|系统开关量输入检测
PF15 62 DI YX5|系统开关量输入检测
PB2 57 DI YX6|系统开关量输入检测
PG0 63 DI YX7|系统开关量输入检测
PF11 58 DI YX8|系统开关量输入检测
PE11 73 DI YX9|系统开关量输入检测
PF12 59 DI YX10|系统开关量输入检测
PE15 77 DI YX11|系统开关量输入检测
PF13 60 DI YX12|系统开关量输入检测
PG1 66 DI Aux_Padet|A枪辅助电源检测
PB10 78 DI Aux_Pbdet|B枪辅助电源检测
*/
/* configure pin as input */
gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_mode_set(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_mode_set(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_mode_set(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_mode_set(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_15);
gpio_mode_set(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_2);
gpio_mode_set(GPIOG, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_mode_set(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_mode_set(GPIOE, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_mode_set(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_12);
gpio_mode_set(GPIOE, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_15);
gpio_mode_set(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_mode_set(GPIOG, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_mode_set(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_10);
/* 连接确认开关量输入检测
PF10 30 DI CHECKOUTA1|A枪链接确认开关量输入检测
PF9 29 DI CHECKOUTA2|A枪链接确认开关量输入检测
PD3 146 DI CHECKOUTB1|B枪链接确认开关量输入检测
PA15 139 DI CHECKOUTB2|B枪链接确认开关量输入检测
*/
/* configure pin as input */
gpio_mode_set(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_9 | GPIO_PIN_10);
gpio_mode_set(GPIOD, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_15);
}
//温度采集通道选择控制
void v_temp_config(void)
{
//开启GPIO时钟
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOJ);
//数字地址“A”配置
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_10);
//数字地址“B”配置
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_13);
//数字地址“C”配置
gpio_mode_set(GPIOJ, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_8);
}
void v_4g_config()
{
//开启GPIO时钟
rcu_periph_clock_enable(RCU_GPIOF);
rcu_periph_clock_enable(RCU_GPIOC);
//4G模块RTS配置
gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
//4G模块电源控制
gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_13);
}
+148
View File
@@ -0,0 +1,148 @@
#ifndef GPIO_H
#define GPIO_H
#include <stdint.h>
#ifndef __PIN_DEF_H
#define __PIN_DEF_H
#include "gd32h7xx.h" // 根据实际芯片替换
/* ==================== 板载继电器控制 (K1K10) ==================== */
#define RELAY_K1_PORT GPIOG
#define RELAY_K1_PIN GPIO_PIN_4
#define RELAY_K2_PORT GPIOG
#define RELAY_K2_PIN GPIO_PIN_5
#define RELAY_K3_PORT GPIOG
#define RELAY_K3_PIN GPIO_PIN_6
#define RELAY_K4_PORT GPIOG
#define RELAY_K4_PIN GPIO_PIN_7
#define RELAY_K5_PORT GPIOG
#define RELAY_K5_PIN GPIO_PIN_8
#define RELAY_K6_PORT GPIOC
#define RELAY_K6_PIN GPIO_PIN_8
#define RELAY_K7_PORT GPIOC
#define RELAY_K7_PIN GPIO_PIN_9
#define RELAY_K8_PORT GPIOG
#define RELAY_K8_PIN GPIO_PIN_9
#define RELAY_K9_PORT GPIOG
#define RELAY_K9_PIN GPIO_PIN_10
#define RELAY_K10_PORT GPIOG
#define RELAY_K10_PIN GPIO_PIN_11
/* 快速操作宏(可选) */
#define RELAY_ON(port, pin) gpio_bit_set(port, pin)
#define RELAY_OFF(port, pin) gpio_bit_reset(port, pin)
/* ==================== 电子锁控制 ==================== */
#define ELOCK_A_PORT GPIOK
#define ELOCK_A_PIN GPIO_PIN_1
#define ELOCK_B_PORT GPIOK
#define ELOCK_B_PIN GPIO_PIN_2
/* ==================== 绝缘监测开关控制 ==================== */
#define INSA_GND_PORT GPIOE
#define INSA_GND_PIN GPIO_PIN_4 // INSA_CTL
#define INSA_POS_PORT GPIOE
#define INSA_POS_PIN GPIO_PIN_3 // UNBALA_CTL
#define INSB_GND_PORT GPIOA
#define INSB_GND_PIN GPIO_PIN_5 // INSB_CTL
#define INSB_POS_PORT GPIOE
#define INSB_POS_PIN GPIO_PIN_5 // UNBALB_CTL
/* ==================== LED 指示灯 ==================== */
#define LED_RUN_PORT GPIOG
#define LED_RUN_PIN GPIO_PIN_15
/* ==================== 连接确认模式选择 ==================== */
#define CHK_CSA_PORT GPIOF
#define CHK_CSA_PIN GPIO_PIN_8
#define CHK_CSB_PORT GPIOF
#define CHK_CSB_PIN GPIO_PIN_2
/* ==================== 内部控制输出 ==================== */
#define WDO_PORT GPIOE
#define WDO_PIN GPIO_PIN_2
#define ESP8685_EN_PORT GPIOB
#define ESP8685_EN_PIN GPIO_PIN_7
/* FC41D 蓝牙/WiFi 模组复位(PE6,高有效,常态低) */
#define FC41D_RST_PORT GPIOE
#define FC41D_RST_PIN GPIO_PIN_6
#define BLUE_EN_PORT FC41D_RST_PORT /* 兼容旧名 */
#define BLUE_EN_PIN FC41D_RST_PIN
#define CIU_RST_PORT GPIOA
#define CIU_RST_PIN GPIO_PIN_10
/* ==================== 温度通道地址选择 ==================== */
#define TEMP_ADDR_A_PORT GPIOD
#define TEMP_ADDR_A_PIN GPIO_PIN_10
#define TEMP_ADDR_B_PORT GPIOD
#define TEMP_ADDR_B_PIN GPIO_PIN_13
#define TEMP_ADDR_C_PORT GPIOJ
#define TEMP_ADDR_C_PIN GPIO_PIN_8
/* ==================== 开关量输入 (DI) ==================== */
// YX 系列
#define YX1_PORT GPIOC
#define YX1_PIN GPIO_PIN_0
#define YX2_PORT GPIOB
#define YX2_PIN GPIO_PIN_0
#define YX3_PORT GPIOF
#define YX3_PIN GPIO_PIN_14
#define YX4_PORT GPIOB
#define YX4_PIN GPIO_PIN_1
#define YX5_PORT GPIOF
#define YX5_PIN GPIO_PIN_15
#define YX6_PORT GPIOB
#define YX6_PIN GPIO_PIN_2
#define YX7_PORT GPIOG
#define YX7_PIN GPIO_PIN_0
#define YX8_PORT GPIOF
#define YX8_PIN GPIO_PIN_11
#define YX9_PORT GPIOE
#define YX9_PIN GPIO_PIN_11
#define YX10_PORT GPIOF
#define YX10_PIN GPIO_PIN_12
#define YX11_PORT GPIOE
#define YX11_PIN GPIO_PIN_15
#define YX12_PORT GPIOF
#define YX12_PIN GPIO_PIN_13
// 辅助电源检测
#define AUX_PADET_PORT GPIOG
#define AUX_PADET_PIN GPIO_PIN_1 // A枪
#define AUX_PBDT_PORT GPIOB
#define AUX_PBDT_PIN GPIO_PIN_10 // B枪
// 连接确认输入
#define CHECKOUTA1_PORT GPIOF
#define CHECKOUTA1_PIN GPIO_PIN_10
#define CHECKOUTA2_PORT GPIOF
#define CHECKOUTA2_PIN GPIO_PIN_9
#define CHECKOUTB1_PORT GPIOD
#define CHECKOUTB1_PIN GPIO_PIN_3
#define CHECKOUTB2_PORT GPIOA
#define CHECKOUTB2_PIN GPIO_PIN_15
//4G模块
#define PWR_4G_PORT GPIOF
#define PWR_4G_PIN GPIO_PIN_3
#define RST_4G_PORT GPIOC
#define RST_4G_PIN GPIO_PIN_13
/* ==================== 输入读取宏 ==================== */
#define DI_READ(port, pin) (gpio_input_bit_get(port, pin))
#endif /* __PIN_DEF_H */
void v_outpin_config(void);
void v_inpin_config(void);
void v_4g_config(void);
void v_temp_config(void);
#endif
+484
View File
@@ -0,0 +1,484 @@
/*!
\file spi3_charger.c
\brief SPI3 for A&B gun charger voltage acquisition and insulation detection
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "adc_spi3_insuVolt.h"
#include <stdio.h>
#include "gd32h7xx.h"
#include "FreeRTOS.h"
#include "task.h"
#include "spi_if.h"
#include "sgm51652hx.h"
#include <string.h>
#include "sys_drv_init.h"
// ======== 配置 =========
#define NUM_CHS 4 //通道数:芯片1CH0、CH1+芯片2CH0、CH1
#define NUM_COLL 5 //每个通道采集次数
#define V_REF 4.096f //芯片基准电压
int16_t adc3_final[NUM_CHS][NUM_COLL];
//uint8_t dma_buf[ADC_CHANNELS][BYTES_PER_CH]; //临时接收缓存
/* 私有变量 */
static uint8_t spi3_initialized = 0;
static spi_dev_t spi3_dev1; // SPI3设备(A枪)
static spi_dev_t spi3_dev2; // SPI3设备(B枪)
static sgm51652hx_dev_t sgm51652hx_chip1; // 芯片1A枪)
static sgm51652hx_dev_t sgm51652hx_chip2; // 芯片2B枪)
/* 私有函数声明 */
static void spi3_gpio_config(void);
static void spi3_config(void);
/*!
\brief configure SPI3 GPIO peripheral for charger
\param[in] none
\param[out] none
\retval none
\note SPI3引脚配置 for SGM51652H4XTS38G/T:
- SPI3_CSA -> PA4 (A枪片选)
- SPI3_CSB -> PA3 (B枪片选)
- SPI3_SCK -> PE12 (时钟)
- SPI3_MISO -> PE13 (数据输入)
- SPI3_MOSI -> PE14 (数据输出)
*/
void spi3_gpio_config(void)
{
/* 使能GPIO时钟 */
rcu_periph_clock_enable(SPI3_RCU_CS);
rcu_periph_clock_enable(SPI3_RCU_SCK_MSIO);
/* 使能SPI3时钟 */
rcu_periph_clock_enable(RCU_SPI3);
/* 使能DMA时钟 */
// rcu_periph_clock_enable(RCU_DMA0);
// rcu_periph_clock_enable(RCU_DMAMUX);
/* 配置SPI3时钟源 */
rcu_spi_clock_config(IDX_SPI3, RCU_SPISRC_APB2); //RCU_SPISRC_PLL0Q
/* 配置A枪片选引脚 (PA4) - 软件控制 */
gpio_mode_set(SPI3_CS1_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SPI3_CS1_PIN);
gpio_output_options_set(SPI3_CS1_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, SPI3_CS1_PIN);
/* 默认拉高,不选中 */
SPI3_CS1_HIGH(); //gpio_bit_set(GPIOA, GPIO_PIN_4);
/* 配置B枪片选引脚 (PA3) - 软件控制 */
gpio_mode_set(SPI3_CS2_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SPI3_CS2_PIN);
gpio_output_options_set(SPI3_CS2_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, SPI3_CS2_PIN);
/* 默认拉高,不选中B枪 */
SPI3_CS2_HIGH(); //gpio_bit_set(GPIOA, GPIO_PIN_3);
/* 配置SPI3引脚复用功能 */
//spi_related_ios_af_en(SPI3); //SPI标准库函数,一键配置SPIx所有引脚复用(SCK/MOSI/MISO/NSSlyn260401
gpio_af_set(GPIOE, GPIO_AF_5, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14);
/* 配置SCK引脚 (PE12) - 复用功能输出 */
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_12);
/* 配置MISO引脚 (PE13) - 复用功能输入 */
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_13);
/* 配置MOSI引脚 (PE14) - 复用功能输出 */
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_14);
}
/*!
\brief configure SPI3 peripheral for charger
\param[in] none
\param[out] none
\retval none
\note SPI3基本配置,针对SGM51652H4XTS38G/T优化
*/
void spi3_config(void)
{
spi_parameter_struct spi_init_struct;
/* 去初始化SPI3 */
spi_i2s_deinit(SPI3);
/* 初始化SPI参数结构体 */
spi_struct_para_init(&spi_init_struct);
/* SPI3参数配置 for SGM51652H4XTS38G/T */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全双工模式
spi_init_struct.device_mode = SPI_MASTER; // 主模式
spi_init_struct.data_size = SPI_DATASIZE_8BIT; // 8位数据帧
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_1EDGE; // 模式3sgm51652只能工作在Model
spi_init_struct.nss = SPI_NSS_SOFT; // 软件NSS控制:soft-自己用GPIO控制CS1/CS2
spi_init_struct.prescale = SPI_PSC_32; // 分频系数,sgm51652最高时钟10Mhz~10Mhz,SPI3在APB2300Mhz
spi_init_struct.endian = SPI_ENDIAN_MSB; // MSB先行 sgm51652规定必须高位先传
/* 初始化SPI3 */
spi_init(SPI3, &spi_init_struct);
/* 使能字节访问 */
spi_byte_access_enable(SPI3);
/* 使能NSS输出 (兼容性) */
spi_nss_output_enable(SPI3);
}
#if 0 //DMA相关配置,未调通 lyn20260410
/*!
\brief configure the DMA peripheral
\param[in] none
\param[out] none
\retval none
*/
void adc_read_single(uint8_t chip, uint8_t cmd, uint8_t index)
{
dma_single_data_parameter_struct dma_init_struct;
uint8_t buf[2];
//选中芯片
if(chip == CHIP_1)
{
SPI3_CS1_LOW();
SPI3_CS2_HIGH();
}
else
{
SPI3_CS2_LOW();
SPI3_CS1_HIGH();
}
//DMA配置
/* deinitialize DMA registers of a channel */
dma_deinit(DMA0, DMA_CH0);
dma_single_data_para_struct_init(&dma_init_struct);
/* SPI0 receive DMA config: DMA_CH0 */
dma_init_struct.request = DMA_REQUEST_SPI3_RX; //选哪个外设:SPI3-RX
dma_init_struct.periph_addr = (uint32_t)&SPI_RDATA(SPI3); //外设地址:SPI3
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE; //禁止外设自增
dma_init_struct.memory0_addr = (uint32_t)buf; //内存地址:定义的数组
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //内存地址自增
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; //数据位宽:8/16/32位
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE; //是否循环模式:是
dma_init_struct.direction = DMA_PERIPH_TO_MEMORY; //方向:外设->内存
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; //优先级
dma_init_struct.number = 2; //SPI一次DMA收多少字节,与数据位宽对应 1路ADC=2字节
dma_single_data_mode_init(DMA0, DMA_CH0, &dma_init_struct); //初始化
/* enable DMA channel */
dma_channel_enable(DMA0, DMA_CH0);
//开启SPI3 DMA接收
spi_dma_enable(SPI3, SPI_DMA_RECEIVE);
#if 0
//发通道命令 = 软件启动转换
if(spi3_charger_read_data(CHARGER_GUN_A, cmd, data, 2) != 0) {
return -1.0f;
}
//等待DMA完成
// 等待 DMA 完成
while(!dma_flag_get(DMA0, DMA_CH0, DMA_INTF_FTFIF));
dma_flag_clear(DMA0, DMA_CH0, DMA_INTF_FTFIF);
// 关闭
spi_dma_disable(SPI3, SPI_DMA_RECEIVE);
dma_channel_disable(DMA0, DMA_CH0);
SPI3_CS1_HIGH();
SPI3_CS2_HIGH();
// 合并 16 位 ADC 数据
adc_result[index] = (int16_t)(buf[0] << 8 | buf[1]);
#endif
}
#endif
/*!
\brief SPI3完整初始化 for charger
\param[in] none
\param[out] none
\retval 0: 成功, -1: 失败
\note 专门为A&B枪充电电压采集和绝缘检测优化
*/
int adc_spi3_insuVolt_init(void)
{
if(spi3_initialized) {
return 0; // 已初始化
}
/* 配置GPIO */
spi3_gpio_config();
/* 配置SPI外设 */
spi3_config();
/* 使能SPI3 */
spi_enable(SPI3);
/* 初始化SPI设备。注意:芯片1/2使用不同的片选引脚,需要单独处理*/
/* 初始化芯片1A枪) */
spi_init_dev(&spi3_dev1, SPI3, GPIOA, GPIO_PIN_4, 0, 0);
sgm51652hx_init(&sgm51652hx_chip1, &spi3_dev1);
/* 初始化芯片2B枪) */
spi_init_dev(&spi3_dev2, SPI3, GPIOA, GPIO_PIN_3, 0, 0);
sgm51652hx_init(&sgm51652hx_chip2, &spi3_dev2);
/* 设置通道量程 */
for (uint8_t i = 0; i < 2; i++) {
sgm51652hx_set_range(&sgm51652hx_chip1, i, RGVL_BP_256); // ±2.56V
sgm51652hx_set_range(&sgm51652hx_chip2, i, RGVL_BP_256); // ±2.56V
}
spi3_initialized = 1;
return 0;
}
/*!
\brief SPI3去初始化
\param[in] none
\param[out] none
\retval none
*/
void adc_spi3_batVoltCurr_deinit(void)
{
/* 去初始化芯片 */
sgm51652hx_deinit(&sgm51652hx_chip1);
sgm51652hx_deinit(&sgm51652hx_chip2);
/* 去初始化SPI设备 */
spi_deinit_dev(&spi3_dev1);
spi_deinit_dev(&spi3_dev2);
/* 禁用SPI3 */
spi_disable(SPI3);
/* 去初始化SPI3 */
spi_i2s_deinit(SPI3);
/* 所有片选拉高 */
gpio_bit_set(GPIOA, GPIO_PIN_3);
gpio_bit_set(GPIOA, GPIO_PIN_4);
spi3_initialized = 0;
}
/*
函数功能:将MCU通过SPI接收到的数据转换为控制器端口采到的电压
输入: uint16_t read_data MCU通过SPI接收到的数据
uint8_t adc_zf 正负极区分,0正极,1负级
uint8_t flag 绝缘检测光电继电器闭合标志位,0:未闭合,1:已经闭合
float data 转换后的电压
备注:V_REF:芯片基准电压
芯片采集到的电压计算方式:
每个LSB代表的电压值 = 芯片输入范围(±0.625,即0.625 * 2 * 基准电压(V_REF / 2^16;
ADC读到的电压值 = 每个LSB代表的电压值 * read_data
0V对应点ADC读到电压值为0.625 * V_REFADC读到的电压值减去 0.625 * V_REF即为当前芯片采集电压值。
芯片采集到电压到控制器端口采集电压转换关系如下:
绝缘检测光电继电器闭合前:控制器端口电压 = 芯片采集电压值 * 1376.89/(0.625 *V_REF)
绝缘检测光电继电器闭合后:控制器端口电压:
正极电压 = 芯片采集电压值 * 689 /(0.625 *V_REF)
负极电压 = 芯片采集电压值 * 1380 /(0.625 *V_REF)
*/
float f_read_to_Vol(float read_data, uint8_t adc_zf, uint8_t flag)
{
float data = 0.0f;
//data = chip_volt; //等同于gm51652hx_calc_voltage(raw_value, RGVL_BP_256)
data = (0.625f * 2.0f * V_REF * read_data / 65535.0f) - (0.625f * V_REF);
if(flag == 0)
{
data = data * 1376.89f / (0.625f * V_REF);
}
else
{
if(adc_zf == 0) //正负极计算方式不同,分开计算
{
data = data * 689.0f / (0.625f * V_REF);
}
else
{
data = data * 1380.0f / (0.625f * V_REF);
}
}
return data;
}
//==================== 单通道读取 (核心函数) ====================
uint16_t sgm51652hx_spiReadBytes(uint8_t chip, uint8_t channel) //读取芯片chip通道channel的返回原始ADC值
{
uint16_t raw_value = 0;
sgm51652hx_dev_t *dev = NULL;
if(chip == CHIP_1)
dev = &sgm51652hx_chip1; //芯片1-A枪绝缘电压检测
else
dev = &sgm51652hx_chip2; //芯片2-B枪绝缘电压检测
/* 设置通道量程(TBD:上电初始化设置一次就行,还是需要每次发送都要设置?) */
//sgm51652hx_set_range(dev, channel, RGVL_BP_256);
/* 读取原始值 */
if(sgm51652hx_read_channel(dev, channel, &raw_value) != ERR_OK_SGM)
return 0;
//printf("spi3 chip=%d,channel=%d: %d\n", chip, channel, raw_value);
return raw_value;
}
/*
函数功能:获取指定控制器端口采集电压
输入:uint8_t chip 芯片号,等同于枪号,1 对应A枪,2 对应B枪
uint8_t channel 通道号,等同于正负级,channel_0对应正极,channel_1对应负级
uint8_t relayOnOff 绝缘检测光电继电器闭合标志位,0:未闭合,1:已经闭合
*/
float spi3_read_analog_voltage(uint8_t chip, uint8_t channel)
{
uint16_t raw_value[NUM_COLL] = {0};
uint16_t i = 0, j = 0;
uint32_t raw_sum_avg = 0;
uint8_t relayOnOff;
float real_voltage = 0;
if(chip == 1)
relayOnOff = GUN1_INS_KM_READ;
else
relayOnOff = GUN2_INS_KM_READ;
// printf("chip = %d,relayOnOff = %d\n",chip,relayOnOff);
//读取指定端口与通道数据NUM_COLL次
for(i=0; i<NUM_COLL; i++)
raw_value[i] = sgm51652hx_spiReadBytes(chip, channel);
for(j=0; j<NUM_COLL; j++)
raw_sum_avg += raw_value[j];
raw_sum_avg /= NUM_COLL;
/* 转换为芯片引脚电压值 --> 融合进f_read_to_vol() */
//chip_voltage = sgm51652hx_calc_voltage(raw_sum_avg, RGVL_BP_256);
#if 1
/* 根据绝缘回路,将芯片引脚电压转换为控制器端口电压数据 */
real_voltage = f_read_to_Vol(raw_sum_avg, channel, relayOnOff); //TODO: 参考DCU606
if(channel == 1) //通道1-负级
real_voltage = -real_voltage;
if(relayOnOff == 0) //光电继电器闭合标志,闭合前后,数据分开校准
{
if(channel == 0) //校准系数
{
real_voltage = real_voltage / 1000 * 993; //997(100欧/V);300V-990 ,400V-997 ,500V-997 ,600V-
//997(500欧/V);400V-997
}
else if(channel == 1)
{
real_voltage = real_voltage / 1000 * 997; //1004(100欧/V);300V-1007 ,500V-
//1004(500欧/V);400V-1007 ,
}
}
else
{
if(channel == 0) //校准系数
{
real_voltage = real_voltage / 1000 * 998; //997(100欧/V);300V-990 ,400V-997 ,500V-997 ,600V
//997(500欧/V);400V-997
}
else if(channel == 1)
{
real_voltage = real_voltage / 1000 * 995; //1004(100欧/V);300V-1007 ,500V-
//1004(500欧/V);400V-1007 ,
}
}
#endif
// printf("SPI3 chip(%d)-channel(%d): raw=%d, realU=%.2f \n", chip, channel, raw_sum_avg, real_voltage);
return real_voltage;
}
//==================== 读取全部4通道 - TODO:改为对应用层接口函数 ====================
void sgm51652_read_all_channels(void)
{
float adc_result[4] = {0};
// 芯片1 CH0
adc_result[0] = spi3_read_analog_voltage(CHIP_1, CHNL_0);
// 芯片1 CH1
adc_result[1] = spi3_read_analog_voltage(CHIP_1, CHNL_1);
// 芯片2 CH0
adc_result[2] = spi3_read_analog_voltage(CHIP_2, CHNL_0);
// 芯片2 CH1
adc_result[3] = spi3_read_analog_voltage(CHIP_2, CHNL_1);
(void)adc_result;
}
/*!
\brief 充电器自检
\param[in] none
\param[out] none
\retval 0: 正常, -1: 故障
*/
void spi3_charger_self_test(void)
{
printf("spi3 adc-insuvolt test started...\n");
/* 检查SPI3初始化状态 */
if(!spi3_initialized) {
printf("SPI3 not initialized\n");
return;
}
// spi3_charger_write_command(1, 0, 0, 0);
//
// vTaskDelay(50);
// spi3_charger_read_command(1, 0, 0, 0);
while(1)
{
//sgm51652hx_spiReadBytes(1, 0);
//spi3_read_analog_voltage(1, 0, 5);
// spi3_charger_read_data_A_0(); //
sgm51652_read_all_channels();
vTaskDelay(250);
}
}
+83
View File
@@ -0,0 +1,83 @@
/*!
\file spi3_charger.h
\brief header file of SPI3 for A&B gun charger voltage acquisition and insulation detection
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef SPI3_CHARGER_H
#define SPI3_CHARGER_H
#include <stdint.h>
/* SPI3引脚定义 for SGM51652H4XTS38G/T */
#define SPI3_SCK_PORT GPIOE // PE12 - 时钟
#define SPI3_SCK_PIN GPIO_PIN_12
#define SPI3_MISO_PORT GPIOE // PE13 - 数据输入
#define SPI3_MISO_PIN GPIO_PIN_13
#define SPI3_MOSI_PORT GPIOE // PE14 - 数据输出
#define SPI3_MOSI_PIN GPIO_PIN_14
/* SPI3复用功能编号 */
#define SPI3_AF GPIO_AF_5
/* 双芯片片选 */
#define SPI3_CS1_PORT GPIOA // PA4 - 芯片1片选
#define SPI3_CS1_PIN GPIO_PIN_4
#define SPI3_CS2_PORT GPIOA // PA3 - 芯片2片选
#define SPI3_CS2_PIN GPIO_PIN_3
/* SPIC3 时钟 */
#define SPI3_RCU_CS RCU_GPIOA
#define SPI3_RCU_SCK_MSIO RCU_GPIOE
/* 芯片/通道选择 */
#define CHIP_1 1 //芯片1-CSA
#define CHIP_2 2 //芯片2-CSB
#define CHNL_0 0 //通道0
#define CHNL_1 1 //通道1
/* 引脚宏操作 */
#define SPI3_CS1_LOW() gpio_bit_reset(SPI3_CS1_PORT, SPI3_CS1_PIN)
#define SPI3_CS1_HIGH() gpio_bit_set(SPI3_CS1_PORT, SPI3_CS1_PIN)
#define SPI3_CS2_LOW() gpio_bit_reset(SPI3_CS2_PORT, SPI3_CS2_PIN)
#define SPI3_CS2_HIGH() gpio_bit_set(SPI3_CS2_PORT, SPI3_CS2_PIN)
/* 完整初始化和去初始化 */
int adc_spi3_insuVolt_init(void);
void adc_spi3_insuVolt_deinit(void);
void spi3_charger_self_test(void);
float spi3_read_analog_voltage(uint8_t chip, uint8_t channel);
#endif /* SPI3_CHARGER_H */
+474
View File
@@ -0,0 +1,474 @@
/*!
\file spi5_analog.c
\brief SPI5 for analog data acquisition
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "adc_spi5_batVoltCurr.h"
#include <stdio.h>
#include "gd32h7xx.h"
#include "sgm51652hx.h"
#include "spi_if.h"
//#include "task.h"
#define V_REF 4.096 //芯片基准电压
#define N 50 //ADC采样次数
float adc_ref = 0.0; //基准电压值
/* 私有变量 */
static uint8_t spi5_initialized = 0;
static spi_dev_t spi5_dev; // SPI5设备
static sgm51652hx_dev_t sgm51652hx_chip; // SGM51652HX芯片
/* 私有函数声明 */
static void spi5_gpio_config(void);
static void spi5_config(void);
//static float spi5_convert_adc_value(uint16_t raw_value, uint8_t channel);
static uint8_t spi5_validate_channel(uint8_t channel);
/*!
\brief configure SPI5 GPIO peripheral for analog acquisition
\param[in] none
\param[out] none
\retval none
\note SPI5引脚配置 for analog acquisition:
- SPI5_CS -> PG12 (片选)
- SPI5_SCK -> PG13 (时钟)
- SPI5_MISO -> PG14 (数据输入)
- SPI5_MOSI -> PA6 (数据输出)
*/
void spi5_gpio_config(void)
{
/* 使能GPIO时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOG);
/* 使能SPI5时钟 */
rcu_periph_clock_enable(RCU_SPI5);
/* 配置SPI5时钟源 */
rcu_spi_clock_config(IDX_SPI5, RCU_SPISRC_APB2);
/* 配置CS引脚 (PG12) - 软件控制 */
gpio_mode_set(GPIOG, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_12);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_12);
/* 默认拉高,不选中模拟芯片 */
gpio_bit_set(GPIOG, GPIO_PIN_12);
/* 配置SPI5引脚复用功能 */
gpio_af_set(GPIOG, GPIO_AF_5, GPIO_PIN_13 | GPIO_PIN_14); // SCK, MOSI
gpio_af_set(GPIOA, GPIO_AF_8, GPIO_PIN_6); // MISO,复用AF8
/* 配置SCK引脚 (PG13) - 复用功能输出 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_13);
/* 配置MOSI引脚 (PG14) - 复用功能输出 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_14);
/* 配置MISO引脚 (PA6) - 复用功能输入 */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_6);
}
/*!
\brief configure SPI5 peripheral for analog acquisition
\param[in] none
\param[out] none
\retval none
\note SPI5基本配置,针对模拟量采集优化
*/
void spi5_config(void)
{
spi_parameter_struct spi_init_struct;
/* 去初始化SPI5 */
spi_i2s_deinit(SPI5);
/* 初始化SPI参数结构体 */
spi_struct_para_init(&spi_init_struct);
/* SPI5参数配置 for analog acquisition */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全双工模式
spi_init_struct.device_mode = SPI_MASTER; // 主模式
spi_init_struct.data_size = SPI_DATASIZE_8BIT; // 8位数据帧
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_1EDGE; // 时钟极性低,第一边沿采样 (Mode 0)
spi_init_struct.nss = SPI_NSS_SOFT; // 软件NSS控制
spi_init_struct.prescale = SPI_PSC_32; // 分频系数
spi_init_struct.endian = SPI_ENDIAN_MSB; // MSB先行
/* 初始化SPI5 */
spi_init(SPI5, &spi_init_struct);
/* 使能字节访问 */
spi_byte_access_enable(SPI5);
/* 使能NSS输出 (兼容性) */
spi_nss_output_enable(SPI5);
}
#if 0
static uint8_t spi5_transmit_receive(uint8_t data)
{
/* 等待发送缓冲区为空 */
while(RESET == spi_i2s_flag_get(SPI5, SPI_FLAG_TP));
/* 发送数据 */
spi_i2s_data_transmit(SPI5, data);
/* 等待接收缓冲区非空 */
while(RESET == spi_i2s_flag_get(SPI5, SPI_FLAG_RP));
/* 接收数据 */
return (uint8_t)spi_i2s_data_receive(SPI5);
}
static void spi5_send_data(uint8_t data)
{
/* 等待发送缓冲区为空 */
while(RESET == spi_i2s_flag_get(SPI5, SPI_FLAG_TP));
/* 发送数据 */
spi_i2s_data_transmit(SPI5, data);
}
int spi5_write_range(sgm51652hxReg_e reg, sgm51652hxRngVlu_e rangVlu) //设置指定通道量程
{
//uint8_t txbuf[3] = {0x01, };
uint8_t cmd = 0;
uint8_t rxbuf[4] = {0};
if(!spi5_initialized) {
return -1;
}
spi5_cs_control(1); //选中
cmd = (((uint8_t)reg)<<1) | 1; //寄存器地址+写指令1
//rxbuf[0] = spi5_transmit_receive(0x01); //写命令
//rxbuf[1] = spi5_transmit_receive((uint8_t)reg); //关键程序寄存器地址
rxbuf[0] = spi5_transmit_receive(cmd);
rxbuf[1] = spi5_transmit_receive((uint8_t)rangVlu); //写量程,返回的值应该是写入的值
rxbuf[2] = spi5_transmit_receive(0x00);
rxbuf[3] = spi5_transmit_receive(0x00);
spi5_cs_control(0); //取消选中
printf("spi5 regAddr[%d]: set value = %x -- %x %x %x %x \n", reg, rangVlu, rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3]);
return 0;
}
int spi5_read_range(sgm51652hxReg_e reg) //设置指定通道量程
{
//uint8_t txbuf[3] = {0x01, };
uint8_t cmd = 0;
uint8_t rxbuf[3] = {0};
if(!spi5_initialized) {
return -1;
}
spi5_cs_control(1); //选中
cmd = (((uint8_t)reg)<<1); //寄存器地址+读指令0
rxbuf[0] = spi5_transmit_receive(cmd); //
rxbuf[1] = spi5_transmit_receive(0x00); //返回值
rxbuf[2] = spi5_transmit_receive(0x00);
spi5_cs_control(0); //取消选中
printf("spi5 regAddr[%d]: read value -- %x %x %x\n", reg, rxbuf[0], rxbuf[1], rxbuf[2]);
return 0;
}
#endif
#if 0 //调试
int sgm51652hx_write_reg_test1(sgm51652hx_dev_t *dev, uint8_t reg, uint8_t value)
{
uint8_t rxbuf[4] = {0};
if (!dev || !dev->initialized) {
return ERR_INVALIDPARAMETER;
}
/* 选中芯片 */
spi_cs_control(&dev->spi_dev, 1);
//spi5_cs_control(1); //选中
/* 开启发送 */
spi_master_transfer_start(dev->spi_dev.spi, SPI_TRANS_START);
/* 发送写命令:寄存器地址 << 1 | 1 */
uint8_t cmd = (reg << 1) | 1;
#if 0
spi5_send_data(cmd);
/* 发送数据 */
spi5_send_data(value);
/* 发送两个空字节 */
spi5_send_data(0x00);
spi5_send_data(0x00);
#endif
#if 0
spi_transmit_byte(dev->spi_dev.spi, cmd);
/* 发送数据 */
spi_transmit_byte(dev->spi_dev.spi, value);
/* 发送两个空字节 */
spi_transmit_byte(dev->spi_dev.spi, 0x00);
spi_transmit_byte(dev->spi_dev.spi, 0x00);
#endif
#if 1
rxbuf[0] = spi_transmit_receive_byte(dev->spi_dev.spi, cmd);/* 发送数据 */
rxbuf[1] = spi_transmit_receive_byte(dev->spi_dev.spi, value);/* 发送两个空字节 */
rxbuf[2] = spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
rxbuf[3] = spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
#endif
#if 0
rxbuf[0] = spi5_transmit_receive(cmd);/* 发送数据 */
rxbuf[1] = spi5_transmit_receive(value);/* 发送两个空字节 */
rxbuf[2] = spi5_transmit_receive(0x00);
rxbuf[3] = spi5_transmit_receive(0x00);
#endif
/* 取消片选 */
spi_cs_control(&dev->spi_dev, 0);
//spi5_cs_control(0); //选中
printf("spi5 regAddr[%d]: set value = %x %x %x %x \n", reg, rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3]);
return ERR_OK_SGM;
}
int sgm51652hx_set_range_test1(sgm51652hx_dev_t *dev, uint8_t channel, sgm51652hxRngVlu_e range)
{
if (!dev || !dev->initialized || channel > 7) {
return ERR_INVALIDPARAMETER;
}
/* 计算量程寄存器地址 */
uint8_t reg = 0x05 + channel; /* RANGE_CH0 = 0x05 */
/* 写入量程配置 */
return sgm51652hx_write_reg_test1(dev, reg, (uint8_t)range);
}
#endif
/*!
\brief SPI5完整初始化 for analog acquisition
\param[in] none
\param[out] none
\retval 0: 成功, -1: 失败
\note 专门为模拟量采集优化
*/
int adc_spi5_batVoltCurr_init(void)
{
int i = 0;
if(spi5_initialized) {
return 0; // 已初始化
}
/* 配置GPIO */
spi5_gpio_config();
/* 配置SPI外设 */
spi5_config();
/* 使能SPI5 */
spi_enable(SPI5);
/* 初始化SPI设备 */
spi_init_dev(&spi5_dev, SPI5, GPIOG, GPIO_PIN_12, 0, 0);
/* 初始化SGM51652HX芯片 */
sgm51652hx_init(&sgm51652hx_chip, &spi5_dev);
spi5_initialized = 1;
// for(i=0; i<ANALOG_MAX_CHANNELS; i++)
// spi5_write_range(RANGE_CH0+i, RGVL_UP_512); //8个通道都设置为单极性,0~5.26V量程
/* 设置通道量程 */
for(i=0; i<ANALOG_MAX_CHANNELS; i++)
sgm51652hx_set_range(&sgm51652hx_chip, i, RGVL_UP_512); //所有通道都设置为单极性,0~5.26V量程
// printf("SPI5 analog acquisition initialized successfully, supporting %d channels with %d-bit resolution\n",
// ANALOG_MAX_CHANNELS, analog_state.resolution); //SPI5模拟采集初始化成功,支持%d通道,分辨率%d位
return 0;
}
/*!
\brief SPI5去初始化
\param[in] none
\param[out] none
\retval none
*/
void spi5_analog_deinit(void)
{
/* 去初始化芯片 */
sgm51652hx_deinit(&sgm51652hx_chip);
/* 去初始化SPI设备 */
spi_deinit_dev(&spi5_dev);
/* 禁用SPI5 */
spi_disable(SPI5);
/* 去初始化SPI5 */
spi_i2s_deinit(SPI5);
/* CS引脚拉高 */
gpio_bit_set(GPIOG, GPIO_PIN_12);
spi5_initialized = 0;
}
static uint8_t spi5_validate_channel(uint8_t channel)
{
return (channel < ANALOG_MAX_CHANNELS) ? 1 : 0;
}
/*!
\brief 读取模拟量通道原始值
\param[in] channel: 通道号 (0-7)
\param[out] none
\retval 16位原始ADC值
*/
uint16_t spi5_read_analog_raw(uint8_t channel)
{
uint16_t raw_value = 0;
if(!spi5_initialized || !spi5_validate_channel(channel)) {
return 0;
}
/* 检查通道是否启用 */
if(0 == spi5_validate_channel(channel)) {
return 0;
}
/* 读取通道数据 */
if(sgm51652hx_read_channel(&sgm51652hx_chip, channel, &raw_value) != ERR_OK_SGM) {
return 0;
}
return raw_value;
}
float f_read_spi5_to_Vol(float read_data)
{
float data = 0.0f;
data = (1.25 * V_REF * read_data / 65535) ;
return data;
}
/**************************************************************
* 函数名称: 计算采集电流
* 参 数: float vol 芯片采集电压值
* 计算公式:U=1000*UADC/16; U为分流器两端电压,单位mV;
* I=1000*UADC/16R I为分流器两端电流,R为分流器两端电阻;
* 返 回 值: 计算出的采集电流
* 描 述:
***************************************************************/
float f_cal_curr(float vol)
{
float ret = 0.0,data = 0.0;
data = f_read_spi5_to_Vol(vol);
ret = (1000 * data)/16; //分流器两端电压,单位mV
ret = ret * 300 /75; //计算电流,分流器规格,300A 75mV
return ret;
}
//获取实际电压值(将这个值取平均值 50 次,调整系数1013)
float f_actual_voltage_data(float read_data )
{
float data = 0.0,actVol = 0.0;
data = f_read_spi5_to_Vol(read_data);
actVol = (data - 0.686f) / 8.2f / 270.0f * 2000270.0f;
//1013为系数,可自行调整以趋近实际值
return actVol * 1013 /1000;
}
//读取基准电压
float f_read_reference_voltage(float read_data)
{
float data = 0.0;
data = f_read_spi5_to_Vol(read_data);
return data ;
}
/*!
\brief 读取模拟量通道电压值
\param[in] channel: 通道号 (0-7)
channel 0 A枪CC1
channel 1 B枪CC1
channel 2 B枪外侧电压
channel 3 B枪外侧基准电压
channel 4 A枪外侧电压
channel 5 A枪外侧基准电压
channel 6 B枪电流
channel 7 A枪电流
\param[out] none
\retval 电压值 (V)
*/
float spi5_read_analog_voltage(uint8_t channel)
{
float f_actua_data; //实际数据值
if(!spi5_initialized || !spi5_validate_channel(channel))
{
return 0.0f;
}
/* 读取原始值 */
uint16_t raw_value = spi5_read_analog_raw(channel);
if(channel == 0) /* A枪CC1电压 */
f_actua_data = f_read_spi5_to_Vol(raw_value)*4;
else if(channel == 1) /* B枪CC1电压 */
f_actua_data = f_read_spi5_to_Vol(raw_value)*4;
else if(channel == 2) /* B枪外侧电压值*/
f_actua_data = f_actual_voltage_data(raw_value);
else if(channel == 3) /* B枪基准电压 */
{
f_actua_data = f_read_reference_voltage(raw_value);
}
else if(channel == 4) /* A枪外侧电压值 */
{
f_actua_data = f_actual_voltage_data(raw_value);
}
else if(channel == 5) /* A枪基准电压值 */
{
f_actua_data = f_read_reference_voltage(raw_value);
}
else if(channel == 6) /*B枪电流值*/
f_actua_data = f_cal_curr(raw_value);
else if(channel == 7) /*A枪电流值*/
f_actua_data = f_cal_curr(raw_value);
//printf("SPI5 channel(%d): raw=%d, realU=%.2f \n", channel, raw_value, f_actua_data);
return f_actua_data;
}
+76
View File
@@ -0,0 +1,76 @@
/*!
\file spi5_analog.h
\brief header file of SPI5 for analog data acquisition
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef SPI5_ANALOG_H
#define SPI5_ANALOG_H
#include <stdint.h>
/* SPI5引脚定义 for analog acquisition */
#define SPI5_CS_PIN GPIO_PIN_12 // PG12 - 片选
#define SPI5_SCK_PIN GPIO_PIN_13 // PG13 - 时钟
#define SPI5_MISO_PIN GPIO_PIN_14 // PG14 - 数据输入
#define SPI5_MOSI_PIN GPIO_PIN_6 // PA6 - 数据输出
#define SPI5_CS_PORT GPIOG
#define SPI5_SCK_PORT GPIOG
#define SPI5_MISO_PORT GPIOG
#define SPI5_MOSI_PORT GPIOA
/* SPI5复用功能编号 */
#define SPI5_AF GPIO_AF_5
/* 本项目启用通道数 */
#define ANALOG_MAX_CHANNELS 8
/* 函数声明 */
/* GPIO配置 */
int adc_spi5_batVoltCurr_init(void);
/* 完整初始化和去初始化 */
int spi5_analog_init(void);
void spi5_analog_deinit(void);
/* 状态检查 */
uint8_t spi5_analog_is_initialized(void);
/* 基本读取操作 */
uint16_t spi5_read_analog_raw(uint8_t channel);
float spi5_read_analog_voltage(uint8_t channel);
uint8_t spi5_read_all_channels(float *voltages, uint8_t max_channels);
void spi5_analog_self_test(void);
#endif /* SPI5_ANALOG_H */
+303
View File
@@ -0,0 +1,303 @@
#include "gd32h7xx.h"
#include "adc_temp.h"
#include <string.h>
#include <math.h>
#include "usart.h"
#include "sys_drv_init.h"
#include "gd32h7xx_dma.h"
/* 忙等待延时函数 */
static void delay_ms(uint32_t ms)
{
/* 使用固定的时钟频率 600MHz 来计算延时,避免 SystemCoreClock 未初始化的问题 */
for(volatile uint32_t i = 0; i < (600000000 / 4000) * ms; i++)
{
__NOP();
}
}
/* ADC2 DMA连续采样缓冲:DMA中断只负责“完成标记”,数据处理放在 get_adc_data()。 */
#define ADC_TEMP_DMA_PERIPH DMA1
#define ADC_TEMP_DMA_CH DMA_CH0
#define ADC_TEMP_DMA_IRQN DMA1_Channel0_IRQn
#define ADC_TEMP_DMA_SAMPLE_CNT (16U)
static __IO uint32_t g_adc_dma_buf[ADC_TEMP_DMA_SAMPLE_CNT];
static volatile uint32_t g_adc_dma_frame_cnt;
__IO uint32_t adc_value[4];
void init_adc(void)
{
dma_single_data_parameter_struct dma_init_struct;
/* GPIO时钟使能 */
rcu_periph_clock_enable(RCU_GPIOC);
/* ADC时钟使能 */
rcu_periph_clock_enable(RCU_ADC2);
/* DMA时钟使能 */
rcu_periph_clock_enable(RCU_DMA1);
/* GPIO参数配置*/
gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_2);
/* ADC初始化 -- 根据数据手册,ADC2_IN0--PC2端子仅能使用ADC2第0个通道 */
adc_deinit(ADC2);
/* ADC时钟配置 */
adc_clock_config(ADC2, ADC_CLK_SYNC_HCLK_DIV6);
/* 单通道连续转换 + DMA */
adc_special_function_config(ADC2, ADC_SCAN_MODE, DISABLE);
adc_special_function_config(ADC2, ADC_CONTINUOUS_MODE, ENABLE);
/* 转换结果(12/14位)存放在 32 位寄存器的低 16 位中 */
adc_data_alignment_config(ADC2, ADC_DATAALIGN_RIGHT);
/* ADC 通道配置(规则通道组,单通道) */
adc_channel_length_config(ADC2, ADC_REGULAR_CHANNEL, 1);
/* 配置规则通道序列,决定 ADC 在转换时,对 PC2 引脚电压进行采样的时间长度*/
adc_regular_channel_config(ADC2, 0, ADC_CHANNEL_0, 240);
/* 禁用外部触发*/
adc_external_trigger_config(ADC2, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
/* DMA配置:ADC2 regular data -> g_adc_dma_buf[] (循环) */
dma_deinit(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH);
dma_single_data_para_struct_init(&dma_init_struct);
dma_init_struct.request = DMA_REQUEST_ADC2;
dma_init_struct.periph_addr = (uint32_t)&ADC_RDATA(ADC2);
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE;
dma_init_struct.memory0_addr = (uint32_t)g_adc_dma_buf;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_32BIT;
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE;
dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
dma_init_struct.priority = DMA_PRIORITY_HIGH;
dma_init_struct.number = ADC_TEMP_DMA_SAMPLE_CNT;
dma_single_data_mode_init(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH, &dma_init_struct);
dma_interrupt_enable(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH, DMA_INT_HTF);
dma_interrupt_enable(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH, DMA_INT_FTF);
nvic_irq_enable(ADC_TEMP_DMA_IRQN, 6U, 0U);
/* 使能 ADC2 模块 */
adc_enable(ADC2);
/* wait for ADC stability */
delay_ms(1);
/* 配置校准模式,偏移量校准,用于纠正 ADC 自身的零点误差 */
adc_calibration_mode_config(ADC2, ADC_CALIBRATION_OFFSET);
/* 配置校准次数 */
adc_calibration_number(ADC2, ADC_CALIBRATION_NUM1);
/*执行校准 */
adc_calibration_enable(ADC2);
/* 启动DMA与ADC连续转换 */
memset((void *)g_adc_dma_buf, 0, sizeof(g_adc_dma_buf));
g_adc_dma_frame_cnt = 0U;
dma_channel_enable(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH);
adc_dma_mode_enable(ADC2);
adc_dma_request_after_last_enable(ADC2);
adc_software_trigger_enable(ADC2, ADC_REGULAR_CHANNEL);
}
uint32_t adc_channel_sample(uint8_t channel)
{
uint32_t timeout = 1000000U;
/* 重新配置规则通道 */
adc_regular_channel_config(ADC2, 0U, channel, 240);
/* 软件触发转换 */
adc_software_trigger_enable(ADC2, ADC_REGULAR_CHANNEL);
/* 等待转换结束 */
while(!adc_flag_get(ADC2, ADC_FLAG_EOC)){
if (timeout-- == 0U) {
return 0U;
}
}
/* 清除标志 */
adc_flag_clear(ADC2, ADC_FLAG_EOC);
/* 读取结果并返回 */
return (adc_regular_data_read(ADC2));
}
void DMA1_Channel0_IRQHandler(void)
{
if (SET == dma_interrupt_flag_get(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH, DMA_INT_FLAG_HTF)) {
dma_interrupt_flag_clear(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH, DMA_INT_FLAG_HTF);
g_adc_dma_frame_cnt++;
}
if (SET == dma_interrupt_flag_get(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(ADC_TEMP_DMA_PERIPH, ADC_TEMP_DMA_CH, DMA_INT_FLAG_FTF);
g_adc_dma_frame_cnt++;
}
}
void select_temp_channel(uint8_t flag_set)
{
//温度采集选通,
/*
OUT8、OUT7设置为高电平,A枪正温度
OUT8设置为高电平,OUT7设置为低电平,A枪负温度
OUT8设置为低电平,OUT7设置为高电平,B枪正温度
OUT8、OUT7设置为低电平,B枪负温度
*/
if(flag_set == 0)
{
TEMP_MULT_CTRA(1);
TEMP_MULT_CTRB(1);
TEMP_MULT_CTRC(1);
}
else if(flag_set == 1)
{
TEMP_MULT_CTRA(0);
TEMP_MULT_CTRB(1);
TEMP_MULT_CTRC(1);
}
else if(flag_set == 2)
{
TEMP_MULT_CTRA(1);
TEMP_MULT_CTRB(0);
TEMP_MULT_CTRC(1);
}
else if(flag_set == 3)
{
TEMP_MULT_CTRA(0);
TEMP_MULT_CTRB(0);
TEMP_MULT_CTRC(1);
}
else if(flag_set == 4)
{
TEMP_MULT_CTRA(1);
TEMP_MULT_CTRB(1);
TEMP_MULT_CTRC(0);
}
else if(flag_set == 5)
{
TEMP_MULT_CTRA(0);
TEMP_MULT_CTRB(1);
TEMP_MULT_CTRC(0);
}
else if(flag_set == 6)
{
TEMP_MULT_CTRA(1);
TEMP_MULT_CTRB(0);
TEMP_MULT_CTRC(0);
}
else if(flag_set == 7)
{
TEMP_MULT_CTRA(0);
TEMP_MULT_CTRB(0);
TEMP_MULT_CTRC(0);
}
}
double double_Vol_to_resistance(double vol)
{
double ret = 0;
//BL1117-50CX输出+5V_Tem,可通过测量VOL_Vin2校准+5V_Tem,Rt=3kΩ*Vo/+5V_Tem-Vo
ret = (3000 * vol) / (5.04 - vol);
return ret / 1000.0;
}
double PT1000_Vol_To_Temperature(double vol)
{
// 定义PT1000常数
double R;
const double R0 = 1000.0; // 0°C时的电阻值
const double A = 3.9083e-3; // IEC751系数
const double B = -5.775e-7;
const double C = -4.183e-12; // 负温度区系数
// 计算电阻值(kΩ单位)
R = double_Vol_to_resistance(vol);
/* 校准系数(保持kΩ单位) */
if (R > 1.219)
{
R = R * 1.01778; // 高温区补偿
}
else if (R > 1)
{
R = R * 1.00395; // 常温区补偿
}
else
{
R = R * 0.98884; // 低温区补偿
}
// 转换为Ω单位
double R_ohm = R * 1000.0;
// 计算温度
if (R_ohm >= R0)
{
// 正温度区使用二次方程
double discriminant = A * A - 4 * B * (1 - R_ohm / R0);
return (-A + sqrt(discriminant)) / (2 * B);
}
else
{
// 负温度区使用牛顿迭代法
const int max_iter = 100; // 最大迭代次数
const double tol = 1e-6; // 收敛误差
double T = -50.0; // 初始温度估计值
for (int i = 0; i < max_iter; i++)
{
double T2 = T * T;
double T3 = T2 * T;
// Callendar-Van Dusen方程
double f = R0 * (1 + A * T + B * T2 + C * (T - 100) * T3) - R_ohm;
// 检查是否收敛
if (fabs(f) < tol)
{
return T;
}
// 导数计算
double df = R0 * (A + 2 * B * T + C * (4 * T3 - 300 * T2));
// 避免除零错误
if (fabs(df) < 1e-10)
{
break;
}
// 牛顿迭代法更新
T -= f / df;
}
// 返回收敛值
return T;
}
}
uint32_t get_adc_data(void)
{
uint64_t sum = 0U;
uint32_t i;
/* 仅处理DMA中断已写入的采样数据,不触发硬件采样。 */
for (i = 0U; i < ADC_TEMP_DMA_SAMPLE_CNT; i++)
{
sum += g_adc_dma_buf[i];
// printf("data = %d\n", g_adc_dma_buf[i]);
}
return (uint32_t)(sum / ADC_TEMP_DMA_SAMPLE_CNT);
}
uint32_t adc_readval(void)
{
float ret, temp_data = 0.0f;
ret = (get_adc_data() * 3.0f) / 4095.0f;
temp_data = PT1000_Vol_To_Temperature(ret);
return temp_data;
}
void adc_test(uint8_t i)
{
float val_value;
(void)i;
delay_ms(1000);
adc_value[0] = adc_channel_sample(ADC_CHANNEL_0);
val_value = (adc_value[0] * 3.0f) / 4095.0f;
(void)val_value;
}
+25
View File
@@ -0,0 +1,25 @@
/*!
\file adc_temp.h
\brief ADC温度采集头文件
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
#ifndef __ADC_TEMP_H
#define __ADC_TEMP_H
#include <stdint.h>
//#include <math.h>
#ifdef __cplusplus
extern "C" {
#endif
void select_temp_channel(uint8_t flag_set);
void init_adc(void);
uint32_t get_adc_data(void);
uint32_t adc_readval(void);
#endif /* __ADC_TEMP_H */
+230
View File
@@ -0,0 +1,230 @@
#include "sgm51652hx.h"
#include "gd32h7xx_gpio.h"
#include "FreeRTOS.h"
#include "task.h"
/*!
\brief 初始化SGM51652HX芯片
\param[in] dev: 芯片设备结构体指针
\param[in] spi_dev: SPI设备结构体指针
\param[out] none
\retval 0: 成功, -1: 失败
*/
int sgm51652hx_init(sgm51652hx_dev_t *dev, spi_dev_t *spi_dev)
{
if (!dev || !spi_dev || !spi_dev->initialized) {
return ERR_INVALIDPARAMETER;
}
/* 初始化设备结构体 */
dev->spi_dev = *spi_dev;
dev->initialized = 1;
return ERR_OK_SGM;
}
/*!
\brief 去初始化SGM51652HX芯片
\param[in] dev: 芯片设备结构体指针
\param[out] none
\retval 0: 成功, -1: 失败
*/
int sgm51652hx_deinit(sgm51652hx_dev_t *dev)
{
if (!dev || !dev->initialized) {
return ERR_INVALIDPARAMETER;
}
/* 复位芯片 */
spi_rst_control(&dev->spi_dev, 1);
dev->initialized = 0;
return ERR_OK_SGM;
}
/*!
\brief 向SGM51652HX芯片写入寄存器
\param[in] dev: 芯片设备结构体指针
\param[in] reg: 寄存器地址
\param[in] value: 要写入的值
\param[out] none
\retval 0: 成功, -1: 失败
*/
int sgm51652hx_write_reg(sgm51652hx_dev_t *dev, uint8_t reg, uint8_t value)
{
if (!dev || !dev->initialized) {
return ERR_INVALIDPARAMETER;
}
/* 选中芯片 */
spi_cs_control(&dev->spi_dev, 1);
/* 开启发送 */
spi_master_transfer_start(dev->spi_dev.spi, SPI_TRANS_START);
/* 发送写命令:寄存器地址 << 1 | 1 */
uint8_t cmd = (reg << 1) | 1;
spi_transmit_receive_byte(dev->spi_dev.spi, cmd); //TBD:经测试,调用发送接收函数,后续采样正常,如果只调用发送函数,后续采样不正常;但另一版程序没有此问题,原因待定 lyn260411
/* 发送数据 */
spi_transmit_receive_byte(dev->spi_dev.spi, value);
/* 发送两个空字节 */
spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
/* 取消片选 */
spi_cs_control(&dev->spi_dev, 0);
return ERR_OK_SGM;
}
/*!
\brief 从SGM51652HX芯片读取寄存器
\param[in] dev: 芯片设备结构体指针
\param[in] reg: 寄存器地址
\param[out] value: 读取的值
\retval 0: 成功, -1: 失败
*/
int sgm51652hx_read_reg(sgm51652hx_dev_t *dev, uint8_t reg, uint8_t *value)
{
if (!dev || !dev->initialized || !value) {
return ERR_INVALIDPARAMETER;
}
/* 选中芯片 */
spi_cs_control(&dev->spi_dev, 1);
/* 开启发送 */
spi_master_transfer_start(dev->spi_dev.spi, SPI_TRANS_START);
/* 发送读命令:寄存器地址 << 1 | 0 */
uint8_t cmd = (reg << 1) | 0;
spi_transmit_byte(dev->spi_dev.spi, cmd);
/* 发送空字节 */
spi_transmit_byte(dev->spi_dev.spi, 0x00);
/* 读取数据 */
*value = spi_receive_byte(dev->spi_dev.spi);
/* 读取最后一个空字节 */
spi_receive_byte(dev->spi_dev.spi);
/* 取消片选 */
spi_cs_control(&dev->spi_dev, 0);
return ERR_OK_SGM;
}
/*!
\brief 设置SGM51652HX芯片通道量程
\param[in] dev: 芯片设备结构体指针
\param[in] channel: 通道号 (0-7)
\param[in] range: 量程配置值
\param[out] none
\retval 0: 成功, -1: 失败
*/
int sgm51652hx_set_range(sgm51652hx_dev_t *dev, uint8_t channel, sgm51652hxRngVlu_e range)
{
if (!dev || !dev->initialized || channel > 7) {
return ERR_INVALIDPARAMETER;
}
/* 计算量程寄存器地址 */
uint8_t reg = 0x05 + channel; /* RANGE_CH0 = 0x05 */
/* 写入量程配置 */
return sgm51652hx_write_reg(dev, reg, (uint8_t)range);
}
/*!
\brief 从SGM51652HX芯片读取通道数据
\param[in] dev: 芯片设备结构体指针
\param[in] channel: 通道号 (0-7)
\param[out] value: 读取的16位ADC值
\retval 0: 成功, -1: 失败
*/
int sgm51652hx_read_channel(sgm51652hx_dev_t *dev, uint8_t channel, uint16_t *value)
{
if (!dev || !dev->initialized || !value || channel > 7) {
return ERR_INVALIDPARAMETER;
}
/* 计算通道命令 */
uint16_t cmd = 0xC000 + (channel * 0x400); /* MAN_CH_0 = 0xC000 */
/* 选中芯片 */
spi_cs_control(&dev->spi_dev, 1);
/* 开启发送 */
spi_master_transfer_start(dev->spi_dev.spi, SPI_TRANS_START);
/* 发送命令 */
spi_transmit_receive_byte(dev->spi_dev.spi, (cmd >> 8) & 0xFF); //cmd高
spi_transmit_receive_byte(dev->spi_dev.spi, cmd & 0xFF); //
spi_transmit_receive_byte(dev->spi_dev.spi, 0x00); //MSB格式,接收高8位
spi_transmit_receive_byte(dev->spi_dev.spi, 0x00); //接收低8位
/* 取消片选 */
spi_cs_control(&dev->spi_dev, 0); //
/* ADC芯片需要转换时间,添加延时确保数据就绪 */
vTaskDelay(pdMS_TO_TICKS(1)); // 延时1毫秒
/* 再次选中芯片,读取数据 */
spi_cs_control(&dev->spi_dev, 1);
/* 发送四个空字节,同时读取数据 */
uint8_t rxbuf[4] = {0};
rxbuf[0] = spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
rxbuf[1] = spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
rxbuf[2] = spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
rxbuf[3] = spi_transmit_receive_byte(dev->spi_dev.spi, 0x00);
/* 取消片选 */
spi_cs_control(&dev->spi_dev, 0);
/* 合并16位数据 */
*value = (rxbuf[2] << 8) | rxbuf[3];
//printf("spix recv:%d %d %d %d\n", rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3]);
return ERR_OK_SGM;
}
/*!
\brief 根据原始ADC值和量程配置计算电压 芯片引脚真实电压
\param[in] adc_val: 原始ADC值
\param[in] range: 量程配置值
\param[out] none
\retval 计算后的电压值 (V)
*/
float sgm51652hx_calc_voltage(uint16_t adc_val, sgm51652hxRngVlu_e range)
{
float voltage = 0.0f;
switch(range)
{
// ==================== 双极性 Bipolar ====================
case RGVL_BP_1024: // ±10.24V
voltage = (int16_t)adc_val * 10.24f / 32768.0f;
break;
case RGVL_BP_512: // ±5.12V
voltage = (int16_t)adc_val * 5.12f / 32768.0f;
break;
case RGVL_BP_256: // ±2.56V
voltage = (int16_t)adc_val * 2.56f / 32768.0f;
break;
// ==================== 单极性 Unipolar ====================
case RGVL_UP_1024: // 0~10.24V
voltage = (float)adc_val * 10.24f / 65535.0f;
break;
case RGVL_UP_512: // 0~5.12V
voltage = (float)adc_val * 5.12f / 65535.0f;
break;
default: // 错误配置
voltage = 0.0f;
break;
}
return voltage;
}
+129
View File
@@ -0,0 +1,129 @@
/*!
\file sgm51652hx
\brief
1. 芯片基本信息:
1)型号:sgm51652H4(4通道)、sgm51652H8(8通道)
2)类型:16位500kSPS单电源 SAR ADC
3)基准:片内4.096V高精度基准(温漂9ppm/℃)
4)供电:AVDD:4.75~5.25(典型5V),DVDD1.65~AVDD(典型3.3V)
2. 模拟输入特性
1)支持输入类型:双极性单端、单极性单端、双极性差分
2)量程配置(软件独立设置)
量程配置值 -- 输入范围 -- 对应VREF倍数
0000 ±10.24V ±2.5 * VREF
0001 ±5.12V ±1.25 * VREF
0010 ±2.56V ±0.625 * VREF
0101 0~10.24V 0~2.5 * VREF
0110 0~5.12V 0~1.25 * VREF
3. 数字接口与时序
1)通信接口:SPI,模式Mode3(CPOL=1 时钟空闲时为高电平、CPHA=1 数据在时钟下降沿采样)
2SCLK最高频率:17Mhz。(GD32H759芯片SPI3/SPI5挂在APB2总线,当前APB2时钟300Mhz,按32预分频设置)
3)转换时间1μs,采集时间1μs
4)帧结构:寄存器读写24个SCLK,数据读取32个SCLK
***每个通道读数,是CS拉低后【此句很重要,查看芯片数据手册波形图】,后面16个时钟读出的数据才是。虽然这个图是auto的,但跟手动是一样的。***
5)支持拓扑:菊花链,最多挂4片
4. 寄存器与命令系统
1)命令寄存器(16位,只写),详见 sgm51652hxCmd_e
2)关键程序寄存器,详见 sgm51652hxReg_e
5. 工作模式
1)自动扫描模式(AUTO_SCAN):按使能通道升序循环采集
2)手动通道模式(MAN_CH_n):固定采集指定单通道
3)待机模式(STDBY):低功耗,唤醒约20μs,保留配置
4)掉电模式(PWR_DOWN):极低功耗,唤醒需15ms
5)复位模式(RST):寄存器恢复默认值
*/
#ifndef _SGM51652HX_H_
#define _SGM51652HX_H_
#include <stdint.h>
#include "spi_if.h"
/******* Macro Definitions *******************/
#define __SGM51652H8__ 1 //SPI3-H4、SPI5-H8
#define ERR_OK_SGM 0
#define ERR_INVALIDPARAMETER -1
#define ERR_TIMEOUT_SGM -2
typedef struct {
spi_dev_t spi_dev; // SPI设备
uint8_t initialized; // 初始化状态
} sgm51652hx_dev_t;
// 兼容旧结构体名称
typedef sgm51652hx_dev_t sgm51652hxDevice_t;
//命令寄存器映射(命令寄存器是1个 16 位的只写寄存器,用于设置SGM51652H4/SGM51652H8 的工作模式。)
typedef enum{
NO_OP = 0x0000, //保持当前模式
STDBY = 0x8200, //进入待机模式
PWR_DN = 0x8300, //进入断电模式
RST = 0x8500, //重置程序寄存器
AUTO_RST = 0xA000, //启用通道自动扫描模式
MAN_CH_0 = 0xC000, //选择通道0手动读取模式
MAN_CH_1 = 0xC400, //选择通道1手动读取模式
MAN_CH_2 = 0xC800, //选择通道2手动读取模式
MAN_CH_3 = 0xCC00, //选择通道3手动读取模式
#if __SGM51652H8__ == 1
MAN_CH_4 = 0xD000,
MAN_CH_5 = 0xD400,
MAN_CH_6 = 0xD800,
MAN_CH_7 = 0xDC00,
#endif
MAN_AUX = 0xE000, //特殊功能,手动采辅助通道Aux,纯差分输入,无PGA/滤波,范围-VREF~+VREF
MAX_CMD_NUM
}sgm51652hxCmd_e;
//程序寄存器映射(程序寄存器是 16 位的可读写寄存器,用于设置SGM51652H4/SGM51652H8 的工作状态。)
/*
数据位 DB[15:9]是寄存器地址。数据位 DB[8]是写或读指令位。
在写操作周期中,SDI 引脚上的 DIN[7:0]是写入目标寄存器的数据,SDO 引脚上的 DIN[7:0]是从目标寄存器读回的数据。读回数据可用于验证写入是否成功。
在1个读周期中,SDI 数据位 DB[8]是读指令位。SDO DOUT[7:0]上的数据是来自目标地址程序寄存器的读回数据。
读回数据是MSB(高位优先)模式
*/
typedef enum{
AUTO_SEQ_EN = 0x01, // 自动扫描通道使能。通道自扫描模式下,扫描通道的选定,数据bit7-bit0代表通道7-通道9,0:不选定该通道,1:选定该通道
CHANNEL_PD = 0x02, // 通道独立断电。通道自扫描模式下,通道电源控制,数据bit7-bit0代表通道7-通道9,0:通道开启电源,1:通道电源关闭
FEATURE_SEL, // 菊花链/输出格式
RANGE_CH0 = 0x05, // 0x05~0x0C:CH0-CH7量程配置。 RANGE_CHn 通道 n 选择输入范围位,数据bit7-bit4补0,数据bit3-bit0定义如下
RANGE_CH1, // 0000 = 输入范围设置为±2.5 × V(默认)
RANGE_CH2, // 0001 = 输入范围设置为±1.25 × V
RANGE_CH3, // 0010 = 输入范围设置为±0.625 × V
#if __SGM51652H4__ == 0 // 0101 = 输入范围设置为 0 到 2.5 × V
RANGE_CH4, // 0110 = 输入范围设置为 0 到 1.25 × V
RANGE_CH5,
RANGE_CH6,
RANGE_CH7,
#endif
FLOATING_DETECTION_EN = 0x0D, //悬空检测使能。输入浮动检测启用控制,数据bit7:0 = 禁用,1 = 启用,bit6-bit0 保留,补0
FLOATING_DETECTION_STATUS, //悬空检测状态。查询输入浮动状态,数据bit7-bit0代表通道7-1,0=通道未处于浮动状态(默认)1=通道处于浮动状态
CMD_READ_BK = 0x3F, //命令回读(只读),查询前1个数据帧中执行的命令
}sgm51652hxReg_e;
/* 量程配置值(写进CHx量程寄存器的配置值)
* 详见本文件顶部2.2。 双极性bipolar,单极性unipolar
*/
typedef enum{
RGVL_BP_1024 = 0x00, //类型:双极性,输入范围:±10.24V
RGVL_BP_512,
RGVL_BP_256,
RGVL_UP_1024 = 0x05, //类型:单极性,输入范围:0~10.24V
RGVL_UP_512,
}sgm51652hxRngVlu_e;
// 芯片操作函数
int sgm51652hx_init(sgm51652hx_dev_t *dev, spi_dev_t *spi_dev);
int sgm51652hx_deinit(sgm51652hx_dev_t *dev);
int sgm51652hx_write_reg(sgm51652hx_dev_t *dev, uint8_t reg, uint8_t value);
int sgm51652hx_read_reg(sgm51652hx_dev_t *dev, uint8_t reg, uint8_t *value);
int sgm51652hx_set_range(sgm51652hx_dev_t *dev, uint8_t channel, sgm51652hxRngVlu_e range);
int sgm51652hx_read_channel(sgm51652hx_dev_t *dev, uint8_t channel, uint16_t *value);
float sgm51652hx_calc_voltage(uint16_t adc_val, sgm51652hxRngVlu_e range);
#endif
+145
View File
@@ -0,0 +1,145 @@
#include "spi_if.h"
#include <gd32h7xx_spi.h>
#include <gd32h7xx_gpio.h>
/*!
\brief SPIx发送数据 (私有函数)
\param[in] spi_periph: SPIx(x=0,1,2,3,4,5)
\param[in] data: 要发送的数据(uint8_t,一次发送1个字节!!)
\param[out] none
\retval none
*/
void spi_transmit_byte(uint32_t spi_periph, uint8_t data)
{
/* 等待发送缓冲区有空间 */
while(RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_TP));
/* 发送数据 */
spi_i2s_data_transmit(spi_periph, data);
}
/*!
\brief SPIx接收数据 (私有函数)
\param[in] spi_periph: SPIx(x=0,1,2,3,4,5)
\param[in] none
\param[out] none
\retval 接收到的数据(uint8_t 一次接收1个字节)
*/
uint8_t spi_receive_byte(uint32_t spi_periph)
{
/* 等待接收缓冲区非空 */
while(RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_RP));
/* 接收数据 */
return (uint8_t)spi_i2s_data_receive(spi_periph);
}
/*!
\brief SPIx发送接收数据 (私有函数)
\param[in] spi_periph: SPIx(x=0,1,2,3,4,5)
\param[in] tx_data: 要发送的数据
\param[out] none
\retval 接收到的数据
*/
uint8_t spi_transmit_receive_byte(uint32_t spi_periph, uint8_t tx_data)
{
/* 等待发送缓冲区有空间 */
while(RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_TP));
/* 发送数据 */
spi_i2s_data_transmit(spi_periph, tx_data);
/* 等待接收缓冲区非空 */
while(RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_RP));
/* 接收数据 */
return (uint8_t)spi_i2s_data_receive(spi_periph);
}
/*!
\brief 控制SPI设备的片选引脚
\param[in] dev: SPI设备结构体指针
\param[in] enable: 1-选中设备, 0-取消选中
\param[out] none
\retval none
*/
void spi_cs_control(spi_dev_t *dev, uint8_t enable)
{
if (dev && dev->csPort) {
if (enable) {
/* 拉低片选引脚,选中设备 */
gpio_bit_reset((uint32_t)dev->csPort, dev->csPin);
} else {
/* 拉高片选引脚,取消选中设备 */
gpio_bit_set((uint32_t)dev->csPort, dev->csPin);
}
}
}
/*!
\brief 控制SPI设备的复位引脚
\param[in] dev: SPI设备结构体指针
\param[in] enable: 1-复位, 0-正常
\param[out] none
\retval none
*/
void spi_rst_control(spi_dev_t *dev, uint8_t enable)
{
if (dev && dev->rstPort) {
if (enable) {
/* 拉低复位引脚,复位设备 */
gpio_bit_reset((uint32_t)dev->rstPort, dev->rstPin);
} else {
/* 拉高复位引脚,设备正常工作 */
gpio_bit_set((uint32_t)dev->rstPort, dev->rstPin);
}
}
}
/*!
\brief 初始化SPI设备
\param[in] dev: SPI设备结构体指针
\param[in] spi_periph: SPI外设
\param[in] csPort: 片选GPIO端口
\param[in] csPin: 片选GPIO引脚
\param[in] rstPort: 复位GPIO端口
\param[in] rstPin: 复位GPIO引脚
\param[out] none
\retval 0: 成功, -1: 失败
*/
int spi_init_dev(spi_dev_t *dev, uint32_t spi_periph, uint32_t csPort, uint16_t csPin, uint32_t rstPort, uint16_t rstPin)
{
if (!dev) {
return -1;
}
/* 初始化设备结构体 */
dev->spi = spi_periph;
dev->csPort = csPort;
dev->csPin = csPin;
dev->rstPort = rstPort;
dev->rstPin = rstPin;
dev->initialized = 1;
return 0;
}
/*!
\brief 去初始化SPI设备
\param[in] dev: SPI设备结构体指针
\param[out] none
\retval 0: 成功, -1: 失败
*/
int spi_deinit_dev(spi_dev_t *dev)
{
if (!dev || !dev->initialized) {
return -1;
}
dev->initialized = 0;
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
/*!
\file spi_if.h
\brief spi接收发送统一接口函数
\version firmware for GD32H7xx
*/
#ifndef _SPI_IF_H_
#define _SPI_IF_H_
#include <stdint.h>
typedef struct {
uint32_t spi; // MCU对应的SPI口
uint32_t rstPort; // 复位GPIO引脚
uint16_t rstPin; // 复位pin引脚
uint32_t csPort; // 片选GPIO引脚
uint16_t csPin; // 片选pin引脚
uint8_t initialized; // 初始化状态
} spi_dev_t;
// 兼容旧结构体名称
typedef spi_dev_t spi_t;
/* 对外接口函数 */
void spi_transmit_byte(uint32_t spi_periph, uint8_t data);
uint8_t spi_receive_byte(uint32_t spi_periph);
uint8_t spi_transmit_receive_byte(uint32_t spi_periph, uint8_t tx_data);
// GPIO控制函数
void spi_cs_control(spi_dev_t *dev, uint8_t enable);
void spi_rst_control(spi_dev_t *dev, uint8_t enable);
// SPI初始化函数
int spi_init_dev(spi_dev_t *dev, uint32_t spi_periph, uint32_t csPort, uint16_t csPin, uint32_t rstPort, uint16_t rstPin);
int spi_deinit_dev(spi_dev_t *dev);
#endif
+333
View File
@@ -0,0 +1,333 @@
/**
* @file app_fatfs.c
* @brief FATFS 文件系统 API 桩实现(功能待实现)
*
* 本文件为 app_fatfs.h 中声明的所有函数提供桩实现。
* 所有函数返回"未实现"错误状态,待 FATFS 功能完整移植后再替换为真实实现。
*/
#include "app_fatfs.h"
#include <string.h>
/* 文件系统基础操作 */
FS_Status fs_init(void)
{
return FS_STATUS_INIT_FAILED;
}
FS_Status fs_deinit(void)
{
return FS_STATUS_OK;
}
FS_Status fs_mount(const char *path)
{
(void)path;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_unmount(const char *path)
{
(void)path;
return FS_STATUS_OK;
}
FS_Status fs_check_space(void)
{
return FS_STATUS_NO_SPACE;
}
bool fs_get_is_mounted(void)
{
return false;
}
/* 文件系统格式化 */
FS_Status fs_format_drive(const char *drive_path)
{
(void)drive_path;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_format_drive_fat16(const char *drive_path)
{
(void)drive_path;
return FS_STATUS_FS_ERROR;
}
/* 目录操作 */
FS_Status fs_mkdir(const char *path)
{
(void)path;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_dir_exists(const char *path, bool *exists)
{
(void)path;
if (exists != NULL) {
*exists = false;
}
return FS_STATUS_OK;
}
FS_Status fs_list_dir(const char *path, FS_FileInfo *file_list, uint32_t *count, uint32_t max_count)
{
(void)path;
(void)file_list;
(void)max_count;
if (count != NULL) {
*count = 0;
}
return FS_STATUS_OK;
}
/* 文件操作 */
FS_Status fs_create_file(const char *path)
{
(void)path;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_delete_file(const char *path)
{
(void)path;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_file_exists(const char *path, bool *exists)
{
(void)path;
if (exists != NULL) {
*exists = false;
}
return FS_STATUS_OK;
}
FS_Status fs_get_file_size(const char *path, uint32_t *size)
{
(void)path;
if (size != NULL) {
*size = 0;
}
return FS_STATUS_FILE_NOT_FOUND;
}
FS_Status fs_get_file_info(const char *path, FS_FileInfo *info)
{
(void)path;
if (info != NULL) {
memset(info, 0, sizeof(*info));
}
return FS_STATUS_FILE_NOT_FOUND;
}
FS_Status fs_rename_file(const char *old_path, const char *new_path)
{
(void)old_path;
(void)new_path;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_copy_file(const char *src_path, const char *dst_path)
{
(void)src_path;
(void)dst_path;
return FS_STATUS_FS_ERROR;
}
/* 文件读写 */
FS_Status fs_open(void *file, const char *path, uint8_t mode)
{
(void)file;
(void)path;
(void)mode;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_close(void *file)
{
(void)file;
return FS_STATUS_OK;
}
FS_Status fs_read(void *file, void *buffer, uint32_t size, uint32_t *bytes_read)
{
(void)file;
(void)buffer;
(void)size;
if (bytes_read != NULL) {
*bytes_read = 0;
}
return FS_STATUS_READ_FAILED;
}
FS_Status fs_write(void *file, const void *buffer, uint32_t size, uint32_t *bytes_written)
{
(void)file;
(void)buffer;
(void)size;
if (bytes_written != NULL) {
*bytes_written = 0;
}
return FS_STATUS_WRITE_FAILED;
}
FS_Status fs_seek(void *file, uint32_t offset)
{
(void)file;
(void)offset;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_truncate(void *file)
{
(void)file;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_sync(void *file)
{
(void)file;
return FS_STATUS_OK;
}
/* 文件内容操作 */
FS_Status fs_read_file_content(const char *path, char *buffer, uint32_t buffer_size, FS_ReadRange *range)
{
(void)path;
(void)buffer;
(void)buffer_size;
if (range != NULL) {
range->actual_read = 0;
}
return FS_STATUS_READ_FAILED;
}
FS_Status fs_write_file_content(const char *path, const void *data, uint32_t size, bool append)
{
(void)path;
(void)data;
(void)size;
(void)append;
return FS_STATUS_WRITE_FAILED;
}
FS_Status fs_write_string(const char *path, const char *str, bool append)
{
(void)path;
(void)str;
(void)append;
return FS_STATUS_WRITE_FAILED;
}
/* 文件系统信息 */
FS_Status fs_get_volume_info(const char *path, uint32_t *total_kb, uint32_t *free_kb, float *usage_percent)
{
(void)path;
if (total_kb != NULL) *total_kb = 0;
if (free_kb != NULL) *free_kb = 0;
if (usage_percent != NULL) *usage_percent = 0.0f;
return FS_STATUS_FS_ERROR;
}
/* 通用文件管理 */
FS_Status fs_file_manager_check_and_roll_ex(const FileManagerConfig *config, const char *current_file,
uint32_t pending_write_size, char *new_filename, uint32_t new_filename_len)
{
(void)config;
(void)current_file;
(void)pending_write_size;
(void)new_filename;
(void)new_filename_len;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_file_manager_create_new(const FileManagerConfig *config, char *new_filename, uint32_t max_len)
{
(void)config;
if (new_filename != NULL && max_len > 0) new_filename[0] = '\0';
return FS_STATUS_FS_ERROR;
}
FS_Status fs_file_manager_ensure_dir_exists(const FileManagerConfig *config)
{
(void)config;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_file_manager_get_count(const FileManagerConfig *config, uint32_t *count)
{
(void)config;
if (count != NULL) *count = 0;
return FS_STATUS_OK;
}
FS_Status fs_file_manager_delete_oldest(const FileManagerConfig *config)
{
(void)config;
return FS_STATUS_FS_ERROR;
}
FS_Status fs_file_manager_find_latest(const FileManagerConfig *config, char *filename, uint32_t max_len)
{
(void)config;
if (filename != NULL && max_len > 0) filename[0] = '\0';
return FS_STATUS_FILE_NOT_FOUND;
}
FS_Status fs_file_manager_find_oldest(const FileManagerConfig *config, char *filename, uint32_t max_len)
{
(void)config;
if (filename != NULL && max_len > 0) filename[0] = '\0';
return FS_STATUS_FILE_NOT_FOUND;
}
FS_Status fs_file_manager_resolve_log_append_path(const FileManagerConfig *config, char *filename, uint32_t max_len)
{
(void)config;
if (filename != NULL && max_len > 0) filename[0] = '\0';
return FS_STATUS_FS_ERROR;
}
void fs_log_set_filename_start_seq(uint8_t start_seq)
{
(void)start_seq;
}
FS_Status fs_generate_timestamp_filename(const FileManagerConfig *config, char *filename, uint32_t max_len)
{
(void)config;
if (filename != NULL && max_len > 0) filename[0] = '\0';
return FS_STATUS_FS_ERROR;
}
bool fs_is_filename_valid(const char *filename, const FileManagerConfig *config)
{
(void)filename;
(void)config;
return false;
}
FS_Status fs_cleanup_invalid_files(const FileManagerConfig *config)
{
(void)config;
return FS_STATUS_OK;
}
uint32_t fs_get_current_fat_time(void)
{
return 0;
}
FS_Status fs_get_current_time_string(char *buffer, uint32_t size)
{
if (buffer != NULL && size > 0) buffer[0] = '\0';
return FS_STATUS_FS_ERROR;
}
FS_Status fs_get_formatted_time_string(char *buffer, uint32_t size)
{
if (buffer != NULL && size > 0) buffer[0] = '\0';
return FS_STATUS_FS_ERROR;
}
+156
View File
@@ -0,0 +1,156 @@
/**
* @file app_fatfs.h
* @brief FATFS 文件系统兼容头文件(整合自 CCU601E_D)
*
* CCU621_M 平台:FATFS 功能由 flash_file_mgr + externalflash + sdmmc 共同实现。
* 本头文件为从 CCU601E_D 移植的应用层代码提供兼容的类型和 API 声明。
*/
#ifndef APP_FATFS_H
#define APP_FATFS_H
#include <stdint.h>
#include <stdbool.h>
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#define FS_PATH_LEN 64
#define FS_FILE_NAME_LEN 32
#define FS_READ_BUF_SIZE 512
#define LOG_DIR_PATH "0:/log"
#define LOG_FILE_MAX_SIZE (2*1024*1024)
#define LOG_FILE_MAX_COUNT 3
#define LOG_MAX_FILES 20
#if FATFS_ENABLE_FAULT
#define FAULT_DIR_PATH "0:/fault"
#define FAULT_FILE_MAX_SIZE (1024*500)
#define FAULT_FILE_MAX_COUNT 2
#define FAULT_MAX_FILES 20
#endif
#if FATFS_ENABLE_ORDER
#define ORDER_DIR_PATH "0:/order"
#define ORDER_FILE_MAX_SIZE (1024*500)
#define ORDER_FILE_MAX_COUNT 2
#define ORDER_MAX_FILES 20
#endif
#define LOG_SYNC_INTERVAL 10
typedef enum {
FS_STATUS_OK = 0,
FS_STATUS_INIT_FAILED,
FS_STATUS_FS_ERROR,
FS_STATUS_FILE_FULL,
FS_STATUS_WRITE_FAILED,
FS_STATUS_READ_FAILED,
FS_STATUS_FILE_NOT_FOUND,
FS_STATUS_PARAM_ERROR,
FS_STATUS_NO_SPACE
} FS_Status;
typedef struct {
char name[FS_FILE_NAME_LEN];
uint32_t size;
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} FS_FileInfo;
typedef struct {
uint32_t start_pos;
uint32_t end_pos;
uint32_t actual_read;
} FS_ReadRange;
typedef enum {
FILE_TYPE_LOG = 0,
FILE_TYPE_FAULT,
FILE_TYPE_ORDER,
FILE_TYPE_MAX
} FileType;
typedef struct {
FileType type;
const char *dir_path;
const char *file_prefix;
const char *file_extension;
uint32_t max_file_size;
uint32_t max_file_count;
} FileManagerConfig;
/* 文件系统基础操作 */
FS_Status fs_init(void);
FS_Status fs_deinit(void);
FS_Status fs_mount(const char *path);
FS_Status fs_unmount(const char *path);
FS_Status fs_check_space(void);
bool fs_get_is_mounted(void);
/* 文件系统格式化 */
FS_Status fs_format_drive(const char *drive_path);
FS_Status fs_format_drive_fat16(const char *drive_path);
/* 目录操作 */
FS_Status fs_mkdir(const char *path);
FS_Status fs_dir_exists(const char *path, bool *exists);
FS_Status fs_list_dir(const char *path, FS_FileInfo *file_list, uint32_t *count, uint32_t max_count);
/* 文件操作 */
FS_Status fs_create_file(const char *path);
FS_Status fs_delete_file(const char *path);
FS_Status fs_file_exists(const char *path, bool *exists);
FS_Status fs_get_file_size(const char *path, uint32_t *size);
FS_Status fs_get_file_info(const char *path, FS_FileInfo *info);
FS_Status fs_rename_file(const char *old_path, const char *new_path);
FS_Status fs_copy_file(const char *src_path, const char *dst_path);
/* 文件读写(FatFs FIL 类型用 void* 代理,避免引入 ff.h */
FS_Status fs_open(void *file, const char *path, uint8_t mode);
FS_Status fs_close(void *file);
FS_Status fs_read(void *file, void *buffer, uint32_t size, uint32_t *bytes_read);
FS_Status fs_write(void *file, const void *buffer, uint32_t size, uint32_t *bytes_written);
FS_Status fs_seek(void *file, uint32_t offset);
FS_Status fs_truncate(void *file);
FS_Status fs_sync(void *file);
/* 文件内容操作 */
FS_Status fs_read_file_content(const char *path, char *buffer, uint32_t buffer_size, FS_ReadRange *range);
FS_Status fs_write_file_content(const char *path, const void *data, uint32_t size, bool append);
FS_Status fs_write_string(const char *path, const char *str, bool append);
/* 文件系统信息 */
FS_Status fs_get_volume_info(const char *path, uint32_t *total_kb, uint32_t *free_kb, float *usage_percent);
/* 通用文件管理 */
FS_Status fs_file_manager_check_and_roll_ex(const FileManagerConfig *config, const char *current_file,
uint32_t pending_write_size, char *new_filename, uint32_t new_filename_len);
FS_Status fs_file_manager_create_new(const FileManagerConfig *config, char *new_filename, uint32_t max_len);
FS_Status fs_file_manager_ensure_dir_exists(const FileManagerConfig *config);
FS_Status fs_file_manager_get_count(const FileManagerConfig *config, uint32_t *count);
FS_Status fs_file_manager_delete_oldest(const FileManagerConfig *config);
FS_Status fs_file_manager_find_latest(const FileManagerConfig *config, char *filename, uint32_t max_len);
FS_Status fs_file_manager_find_oldest(const FileManagerConfig *config, char *filename, uint32_t max_len);
FS_Status fs_file_manager_resolve_log_append_path(const FileManagerConfig *config, char *filename, uint32_t max_len);
void fs_log_set_filename_start_seq(uint8_t start_seq);
FS_Status fs_generate_timestamp_filename(const FileManagerConfig *config, char *filename, uint32_t max_len);
bool fs_is_filename_valid(const char *filename, const FileManagerConfig *config);
FS_Status fs_cleanup_invalid_files(const FileManagerConfig *config);
uint32_t fs_get_current_fat_time(void);
FS_Status fs_get_current_time_string(char *buffer, uint32_t size);
FS_Status fs_get_formatted_time_string(char *buffer, uint32_t size);
extern const FileManagerConfig g_log_file_config;
#if FATFS_ENABLE_FAULT
extern const FileManagerConfig g_fault_file_config;
#endif
#if FATFS_ENABLE_ORDER
extern const FileManagerConfig g_order_file_config;
#endif
#endif /* APP_FATFS_H */
+57
View File
@@ -0,0 +1,57 @@
/**
* @file fatfs_card.h
* @brief 卡号白名单兼容头文件(整合自 CCU601E_D)
*
* 实际存储由 flash_file_mgr/card_flash_impl 通过 EEPROM + SPI Flash 实现。
*/
#ifndef FATFS_CARD_H
#define FATFS_CARD_H
#include <stdint.h>
#include <stdbool.h>
#define CARD_DIR_PATH "0:/card"
#define CARD_FILE_NAME "card_data.csv"
#define CARD_NUM_MAX_LEN 32
#define CARD_MAX_COUNT 595
typedef struct {
char card_num[CARD_NUM_MAX_LEN];
uint32_t balance;
uint32_t create_time;
uint32_t last_use_time;
uint16_t status;
uint8_t reserved[8];
} CardInfo;
typedef enum {
CARD_STATUS_OK = 0,
CARD_STATUS_INIT_FAILED,
CARD_STATUS_FS_ERROR,
CARD_STATUS_FILE_FULL,
CARD_STATUS_WRITE_FAILED,
CARD_STATUS_READ_FAILED,
CARD_STATUS_FILE_NOT_FOUND,
CARD_STATUS_PARAM_ERROR,
CARD_STATUS_NO_SPACE,
CARD_STATUS_CARD_EXIST,
CARD_STATUS_CARD_NOT_FOUND,
CARD_STATUS_ALREADY_INIT
} CardStatus;
CardStatus card_init(void);
void card_deinit(void);
CardStatus card_add(const CardInfo *card_info);
CardStatus card_update(const CardInfo *card_info);
CardStatus card_delete(const char *card_num);
CardStatus card_find(const char *card_num, CardInfo *card_info);
CardStatus card_get_count(uint32_t *count);
CardStatus card_get_all(CardInfo *card_list, uint32_t *count, uint32_t max_count);
CardStatus card_check_exist(const char *card_num, bool *exist);
CardStatus card_backup(void);
CardStatus card_restore(void);
CardStatus card_clear_all(void);
#endif /* FATFS_CARD_H */
+12
View File
@@ -0,0 +1,12 @@
/**
* @file fatfs_fault.h
* @brief FATFS 故障记录模块兼容头文件
*/
#ifndef FATFS_FAULT_H
#define FATFS_FAULT_H
#include <stdint.h>
/* Fault module placeholder — fault storage is handled by flash_file_mgr/fault_flash_impl */
#endif /* FATFS_FAULT_H */
+100
View File
@@ -0,0 +1,100 @@
/**
* @file fatfs_init.c
* @brief FATFS 初始化模块桩实现(功能待实现)
*
* 本文件为 fatfs_init.h 中声明的函数提供桩实现。
* 所有函数提供安全默认行为,待 FATFS 功能完整移植后再替换为真实实现。
*/
#include "fatfs_init.h"
#include <stddef.h>
void fatfs_init(void)
{
/* FATFS 尚未初始化 */
}
void fatfs_action(void)
{
/* 无操作 */
}
#if FATFS_SHELL_RM_CAT_EN
U8_T u8_file_clear_action(U8_T u8_type, U8_T u8_action, U8_T u8_flag)
{
(void)u8_type;
(void)u8_action;
(void)u8_flag;
return 0;
}
U8_T u8_file_set_cat_cmd(char *path)
{
(void)path;
return 0;
}
U8_T u8_file_card_action(U8_T action, const char *card_num, U32_T balance)
{
(void)action;
(void)card_num;
(void)balance;
return 0;
}
#endif
U8_T cache_add_log(const char *format, ...)
{
(void)format;
return 0;
}
U8_T cache_add_card(const char *card_num, U32_T balance)
{
(void)card_num;
(void)balance;
return 0;
}
U8_T cache_add_fault(const void *fault_data)
{
(void)fault_data;
return 0;
}
U8_T cache_add_order(const void *order_data)
{
(void)order_data;
return 0;
}
void cache_process_all(void)
{
/* 无缓存数据待处理 */
}
void cache_clear_all(void)
{
/* 无缓存数据待清除 */
}
U8_T fatfs_set_write_disable_flag(U8_T flag_mask, U8_T enable)
{
(void)flag_mask;
(void)enable;
return 0;
}
U8_T fatfs_get_write_disable_flag(U8_T flag_mask)
{
(void)flag_mask;
return 0;
}
void v_app_fatfs_log_real_file_name(char *fileName, int len, int pathEn)
{
if (fileName != NULL && len > 0) {
fileName[0] = '\0';
}
(void)pathEn;
}
+99
View File
@@ -0,0 +1,99 @@
/**
* @file fatfs_init.h
* @brief FATFS 模块初始化兼容头文件(整合自 CCU601E_D)
*
* CCU621_M 的 BSP 没有独立的 app_fatfs 模块,FATFS 功能通过
* flash_file_mgr + externalflash + sdmmc 实现。
* 本头文件为从 CCU601E_D 移植的应用层代码提供兼容的 API 声明。
*/
#ifndef FATFS_INIT_H
#define FATFS_INIT_H
#ifdef __cplusplus
extern "C" {
#endif
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#define USE_FREERTOS_HEAP
#define USE_CACHE_MUTEX
#ifdef USE_FREERTOS_HEAP
#include "FreeRTOS.h"
#include "task.h"
#define CACHE_MALLOC(size) pvPortMalloc(size)
#define CACHE_FREE(ptr) vPortFree(ptr)
#else
#define CACHE_MALLOC(size) malloc(size)
#define CACHE_FREE(ptr) free(ptr)
#endif
#ifdef USE_CACHE_MUTEX
#include "semphr.h"
#define CACHE_MUTEX_CREATE() xSemaphoreCreateMutex()
#define CACHE_MUTEX_TAKE(mutex) xSemaphoreTake(mutex, portMAX_DELAY)
#define CACHE_MUTEX_GIVE(mutex) xSemaphoreGive(mutex)
#define CACHE_MUTEX_DELETE(mutex) vSemaphoreDelete(mutex)
#else
#define CACHE_MUTEX_CREATE() NULL
#define CACHE_MUTEX_TAKE(mutex) (void)0
#define CACHE_MUTEX_GIVE(mutex) (void)0
#define CACHE_MUTEX_DELETE(mutex) (void)0
#endif
#define FATFS_SHELL_RM_CAT_EN (0)
#define FATFS_FILE_ACTION_FLAG (0xFF)
#define FATFS_WRITE_DISABLE_TCP_DEBUG 0x10
#define FATFS_PRINT_EN (0)
#if FATFS_PRINT_EN
#define FATFS_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define FATFS_PRINT(fmt, ...) do {} while(0)
#endif
typedef enum {
CACHE_TYPE_NONE = 0,
#if FATFS_ENABLE_LOG
CACHE_TYPE_LOG,
#endif
#if FATFS_ENABLE_CARD
CACHE_TYPE_CARD,
#endif
#if FATFS_ENABLE_FAULT
CACHE_TYPE_FAULT,
#endif
#if FATFS_ENABLE_ORDER
CACHE_TYPE_ORDER,
#endif
} CacheType;
void fatfs_init(void);
void fatfs_action(void);
#if FATFS_SHELL_RM_CAT_EN
U8_T u8_file_clear_action(U8_T u8_type, U8_T u8_action, U8_T u8_flag);
U8_T u8_file_set_cat_cmd(char *path);
U8_T u8_file_card_action(U8_T action, const char *card_num, U32_T balance);
#endif
U8_T cache_add_log(const char *format, ...);
U8_T cache_add_card(const char *card_num, U32_T balance);
U8_T cache_add_fault(const void *fault_data);
U8_T cache_add_order(const void *order_data);
void cache_process_all(void);
void cache_clear_all(void);
U8_T fatfs_set_write_disable_flag(U8_T flag_mask, U8_T enable);
U8_T fatfs_get_write_disable_flag(U8_T flag_mask);
void v_app_fatfs_log_real_file_name(char *fileName, int len, int pathEn);
#ifdef __cplusplus
}
#endif
#endif /* FATFS_INIT_H */
+14
View File
@@ -0,0 +1,14 @@
/**
* @file fatfs_log.h
* @brief FATFS 日志模块兼容头文件
*/
#ifndef FATFS_LOG_H
#define FATFS_LOG_H
#include <stdint.h>
/* Log module placeholder — log storage is handled by flash_file_mgr */
void fatfs_log_init(void);
void fatfs_log_write(const char *format, ...);
#endif /* FATFS_LOG_H */
+12
View File
@@ -0,0 +1,12 @@
/**
* @file fatfs_order.h
* @brief FATFS 充电订单模块兼容头文件
*/
#ifndef FATFS_ORDER_H
#define FATFS_ORDER_H
#include <stdint.h>
/* Order module placeholder — order storage is handled by flash_file_mgr/unsettled_order_mng */
#endif /* FATFS_ORDER_H */
+485
View File
@@ -0,0 +1,485 @@
#include "gd32h7xx.h"
#include "app_rtc/app_rtc.h"
#include "publicdata/publicdata.h"
#include <stdio.h>
#include <string.h>
/* 秒换算常量(用于时间戳转换函数)。 */
#define xMINUTE (60U)
#define xHOUR (60U * xMINUTE)
#define xDAY (24U * xHOUR)
#define xYEAR (365U * xDAY)
/* 备份寄存器标记值:用于判断 RTC 是否已经做过首次配置。 */
#define BKP_VALUE 0x32F0U
/* RTC 首次默认时间(未配置/备份域失效时使用,BCD 格式)。 */
#define RTC_DEFAULT_YEAR_BCD 0x26U
#define RTC_DEFAULT_MONTH_BCD 0x01U
#define RTC_DEFAULT_DAY_BCD 0x01U
#define RTC_DEFAULT_HOUR_BCD 0x12U
#define RTC_DEFAULT_MINUTE_BCD 0x00U
#define RTC_DEFAULT_SECOND_BCD 0x00U
#define RTC_DEFAULT_WEEKDAY 0x04U /* 1=周一 ... 7=周日;2026-01-01 为周四 */
/* RTC 预分频配置(LXTAL=32.768kHz 的标准配置)。 */
static __IO uint32_t s_prescaler_a = 0U;
static __IO uint32_t s_prescaler_s = 0U;
/* RTC 时钟源选择标志(来自 RCU_BDCTL[9:8])。 */
static uint32_t s_rtcsrc_flag = 0U;
/* 前置声明:RTC 初始化与校验辅助函数。 */
static void rtc_pre_config(void);
static void rtc_setup_default_if_needed(void);
static uint8_t rtc_is_leap_year_u16(uint16_t year);
static uint8_t rtc_days_in_month_u16(uint16_t year, uint8_t month);
static uint8_t rtc_calc_weekday(uint16_t year, uint8_t month, uint8_t day);
static uint8_t rtc_validate_comm_time(const Comm_Time *time);
/**
* @brief RTC 模块初始化入口。
* @details
* - 打开 PMU 与备份域写保护;
* - 选择并配置 RTC 时钟源(当前默认 LXTAL);
* - 首次上电或备份标记缺失时写入默认时间。
*/
void v_rtc_init(void)
{
rcu_periph_clock_enable(RCU_PMU);
pmu_backup_write_enable();
rtc_tamper_disable(RTC_TAMPER0);
s_rtcsrc_flag = GET_BITS(RCU_BDCTL, 8, 9);
rtc_pre_config();
rtc_setup_default_if_needed();
}
/**
* @brief 配置 RTC 时钟源与预分频。
* @details 默认使用 LXTAL32.768kHz),对应 A=127/S=255。
*/
static void rtc_pre_config(void)
{
rcu_osci_on(RCU_LXTAL);
(void)rcu_osci_stab_wait(RCU_LXTAL);
rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
s_prescaler_s = 0xFFU;
s_prescaler_a = 0x7FU;
rcu_periph_clock_enable(RCU_RTC);
(void)rtc_register_sync_wait();
}
/**
* @brief 首次配置 RTC 默认时间并写入备份标记。
*/
static void rtc_setup_default_if_needed(void)
{
rtc_parameter_struct rtc_initpara;
if ((RTC_BKP0 == BKP_VALUE) && (s_rtcsrc_flag != 0x00U)) {
return;
}
memset(&rtc_initpara, 0, sizeof(rtc_parameter_struct));
rtc_initpara.year = RTC_DEFAULT_YEAR_BCD;
rtc_initpara.day_of_week = RTC_DEFAULT_WEEKDAY;
rtc_initpara.month = RTC_DEFAULT_MONTH_BCD;
rtc_initpara.date = RTC_DEFAULT_DAY_BCD;
rtc_initpara.factor_asyn = s_prescaler_a;
rtc_initpara.factor_syn = s_prescaler_s;
rtc_initpara.display_format = RTC_24HOUR;
rtc_initpara.am_pm = RTC_AM;
rtc_initpara.hour = RTC_DEFAULT_HOUR_BCD;
rtc_initpara.minute = RTC_DEFAULT_MINUTE_BCD;
rtc_initpara.second = RTC_DEFAULT_SECOND_BCD;
if (rtc_init(&rtc_initpara) == SUCCESS) {
RTC_BKP0 = BKP_VALUE;
}
rcu_all_reset_flag_clear();
}
/**
* @brief 判断闰年(完整年份,如 2026)。
*/
static uint8_t rtc_is_leap_year_u16(uint16_t year)
{
return (uint8_t)(((year % 4U == 0U) && (year % 100U != 0U)) || (year % 400U == 0U));
}
/**
* @brief 获取指定年月的天数。
*/
static uint8_t rtc_days_in_month_u16(uint16_t year, uint8_t month)
{
static const uint8_t days_in_month[] = {31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U};
if ((month < 1U) || (month > 12U)) {
return 0U;
}
if ((month == 2U) && (rtc_is_leap_year_u16(year) != 0U)) {
return 29U;
}
return days_in_month[month - 1U];
}
/**
* @brief 计算星期(返回 1~7,周一~周日)。
*/
static uint8_t rtc_calc_weekday(uint16_t year, uint8_t month, uint8_t day)
{
static const uint8_t t[] = {0U, 3U, 2U, 5U, 0U, 3U, 5U, 1U, 4U, 6U, 2U, 4U};
uint16_t y = year;
uint8_t w;
if (month < 3U) {
y--;
}
w = (uint8_t)((y + y / 4U - y / 100U + y / 400U + t[month - 1U] + day) % 7U);
return (w == 0U) ? 7U : w;
}
/**
* @brief 校验 Comm_Time 时间合法性。
*/
static uint8_t rtc_validate_comm_time(const Comm_Time *time)
{
uint8_t max_day;
if (time == NULL) {
return 0U;
}
if ((time->iYear < 2000U) || (time->iYear > 2099U)) {
return 0U;
}
if ((time->ucMonth < 1U) || (time->ucMonth > 12U)) {
return 0U;
}
max_day = rtc_days_in_month_u16(time->iYear, time->ucMonth);
if ((time->ucDay < 1U) || (time->ucDay > max_day)) {
return 0U;
}
if ((time->ucHour > 23U) || (time->ucMin > 59U) || (time->ucSec > 59U)) {
return 0U;
}
return 1U;
}
/**
* @brief 读取 RTC 当前时间(转换到 Comm_Time)。
*/
void GetCurrentTime(Comm_Time *curTime)
{
rtc_parameter_struct rtc_initpara;
if (curTime == NULL) {
return;
}
memset(&rtc_initpara, 0, sizeof(rtc_parameter_struct));
rtc_current_time_get(&rtc_initpara);
curTime->iYear = (U16_T)(2000U + bcd_to_dec(rtc_initpara.year));
curTime->ucMonth = bcd_to_dec(rtc_initpara.month);
curTime->ucDay = bcd_to_dec(rtc_initpara.date);
curTime->ucHour = bcd_to_dec(rtc_initpara.hour);
curTime->ucMin = bcd_to_dec(rtc_initpara.minute);
curTime->ucSec = bcd_to_dec(rtc_initpara.second);
}
/**
* @brief 设置 RTC 时间(输入为 Comm_Time)。
*/
void v_rtc_set_time(Comm_Time *curTime)
{
uint8_t weekday;
rtc_parameter_struct rtc_initpara;
if (rtc_validate_comm_time(curTime) == 0U) {
return;
}
/* Ensure backup domain write access is enabled before touching RTC registers. */
rcu_periph_clock_enable(RCU_PMU);
pmu_backup_write_enable();
weekday = rtc_calc_weekday(curTime->iYear, curTime->ucMonth, curTime->ucDay);
memset(&rtc_initpara, 0, sizeof(rtc_initpara));
rtc_initpara.year = dec_to_bcd((uint8_t)(curTime->iYear - 2000U));
rtc_initpara.month = dec_to_bcd(curTime->ucMonth);
rtc_initpara.date = dec_to_bcd(curTime->ucDay);
rtc_initpara.day_of_week = weekday;
rtc_initpara.hour = dec_to_bcd(curTime->ucHour);
rtc_initpara.minute = dec_to_bcd(curTime->ucMin);
rtc_initpara.second = dec_to_bcd(curTime->ucSec);
rtc_initpara.display_format = RTC_24HOUR;
rtc_initpara.am_pm = RTC_AM;
rtc_initpara.factor_asyn = s_prescaler_a;
rtc_initpara.factor_syn = s_prescaler_s;
if (rtc_init(&rtc_initpara) != SUCCESS) {
return;
}
(void)rtc_register_sync_wait();
}
/**
* @brief 本地时间(UTC+8)转 Unix 秒(UTC)。
*/
unsigned int xDate2Seconds(Comm_Time *time)
{
static const unsigned int month_sec[12] = {
0U * xDAY, 31U * xDAY, 59U * xDAY, 90U * xDAY, 120U * xDAY, 151U * xDAY,
181U * xDAY, 212U * xDAY, 243U * xDAY, 273U * xDAY, 304U * xDAY, 334U * xDAY
};
unsigned int year;
unsigned int seconds;
if ((time == NULL) || (rtc_validate_comm_time(time) == 0U) || (time->iYear < 1970U)) {
return 0U;
}
year = (unsigned int)time->iYear - 1970U;
seconds = xYEAR * year + xDAY * ((year + 1U) / 4U);
seconds += month_sec[time->ucMonth - 1U];
if ((time->ucMonth > 2U) && (((unsigned int)time->iYear % 4U) == 0U)) {
seconds += xDAY;
}
seconds += xDAY * ((unsigned int)time->ucDay - 1U);
seconds += xHOUR * (unsigned int)time->ucHour;
seconds += xMINUTE * (unsigned int)time->ucMin;
seconds += (unsigned int)time->ucSec;
if (seconds >= (8U * xHOUR)) {
seconds -= (8U * xHOUR);
}
return seconds;
}
/**
* @brief Unix 秒(UTC)转本地时间(UTC+8)。
*/
void xSeconds2Date(unsigned long seconds, Comm_Time *time)
{
static const unsigned int month_day[12] = {31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U};
unsigned int days;
unsigned short leap_y_count;
if (time == NULL) {
return;
}
time->ucSec = (U8_T)(seconds % 60UL);
seconds /= 60UL;
time->ucMin = (U8_T)(seconds % 60UL);
seconds += 8UL * 60UL;
seconds /= 60UL;
time->ucHour = (U8_T)(seconds % 24UL);
days = (unsigned int)(seconds / 24UL);
leap_y_count = (unsigned short)((days + 365U) / 1461U);
if (((days + 366U) % 1461U) == 0U) {
time->iYear = (U16_T)(1970U + (days / 366U));
time->ucMonth = 12U;
time->ucDay = 31U;
return;
}
days -= leap_y_count;
time->iYear = (U16_T)(1970U + (days / 365U));
days %= 365U;
days += 1U;
if ((time->iYear % 4U) == 0U) {
if (days > 60U) {
--days;
} else if (days == 60U) {
time->ucMonth = 2U;
time->ucDay = 29U;
return;
}
}
for (time->ucMonth = 0U; month_day[time->ucMonth] < days; time->ucMonth++) {
days -= month_day[time->ucMonth];
}
time->ucMonth += 1U;
time->ucDay = (U8_T)days;
}
/**
* @brief 根据时区偏移调整时间(支持跨天/月/年)。
*/
void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset)
{
const int minutes_per_day = 24 * 60;
int total_minutes;
int days_adjust;
int year;
U8_T month;
int day;
U8_T max_days;
if (ctTime == NULL) {
return;
}
total_minutes = (int)ctTime->ucHour * 60 + (int)ctTime->ucMin;
total_minutes += hour_offset * 60 + min_offset;
days_adjust = total_minutes / minutes_per_day;
total_minutes %= minutes_per_day;
if (total_minutes < 0) {
total_minutes += minutes_per_day;
days_adjust--;
}
ctTime->ucHour = (U8_T)(total_minutes / 60);
ctTime->ucMin = (U8_T)(total_minutes % 60);
if (days_adjust == 0) {
return;
}
year = (int)ctTime->iYear;
month = ctTime->ucMonth;
day = (int)ctTime->ucDay + days_adjust;
while (day > (max_days = rtc_days_in_month_u16((uint16_t)year, month))) {
day -= max_days;
month++;
if (month > 12U) {
month = 1U;
year++;
}
}
while (day < 1) {
month--;
if (month < 1U) {
month = 12U;
year--;
}
day += rtc_days_in_month_u16((uint16_t)year, month);
}
ctTime->ucDay = (U8_T)day;
ctTime->ucMonth = month;
ctTime->iYear = (U16_T)year;
}
/**
* @brief 解析 OCPP 时间戳字符串到 Comm_Time。
* @param ctTime 解析结果输出。
* @param time_str 输入字符串,支持 `Z` 与 `+/-HH:MM`。
* @param time_type 0=输出 UTC1=输出北京时间(UTC+8)。
* @return 0 成功,1 失败。
*/
U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type)
{
char time_copy[64];
char *time_part;
int hour;
int min;
int sec;
int millisec = 0;
char tz_sign = 0;
int tz_hour = 0;
int tz_min = 0;
int parsed;
if ((ctTime == NULL) || (time_str == NULL) || (strlen(time_str) >= sizeof(time_copy))) {
return 1U;
}
strcpy(time_copy, time_str);
time_part = strchr(time_copy, 'T');
if (time_part == NULL) {
return 1U;
}
*time_part = '\0';
time_part++;
if (sscanf(time_copy, "%hu-%hhu-%hhu", &ctTime->iYear, &ctTime->ucMonth, &ctTime->ucDay) != 3) {
return 1U;
}
parsed = sscanf(time_part, "%d:%d:%d.%d%c%d:%d", &hour, &min, &sec, &millisec, &tz_sign, &tz_hour, &tz_min);
if (parsed != 7) {
parsed = sscanf(time_part, "%d:%d:%d.%dZ", &hour, &min, &sec, &millisec);
if (parsed == 4) {
tz_sign = 'Z';
} else {
parsed = sscanf(time_part, "%d:%d:%d%c%d:%d", &hour, &min, &sec, &tz_sign, &tz_hour, &tz_min);
if (parsed != 6) {
parsed = sscanf(time_part, "%d:%d:%dZ", &hour, &min, &sec);
if (parsed == 3) {
tz_sign = 'Z';
} else {
return 1U;
}
}
}
}
ctTime->ucHour = (U8_T)hour;
ctTime->ucMin = (U8_T)min;
ctTime->ucSec = (U8_T)sec;
if (time_type == 1U) {
if ((tz_sign == 'Z') || (tz_sign == 0)) {
v_adjust_time_with_timezone(ctTime, 8, 0);
} else {
int off_h = (tz_sign == '+') ? tz_hour : -tz_hour;
int off_m = (tz_sign == '+') ? tz_min : -tz_min;
v_adjust_time_with_timezone(ctTime, -off_h, -off_m);
v_adjust_time_with_timezone(ctTime, 8, 0);
}
} else if ((tz_sign != 'Z') && (tz_sign != 0)) {
int off_h = (tz_sign == '+') ? tz_hour : -tz_hour;
int off_m = (tz_sign == '+') ? tz_min : -tz_min;
v_adjust_time_with_timezone(ctTime, -off_h, -off_m);
}
return (rtc_validate_comm_time(ctTime) != 0U) ? 0U : 1U;
}
/**
* @brief RTC 自测:读取当前时间并打印。
*/
void test_rtc(void)
{
Comm_Time now_time;
GetCurrentTime(&now_time);
printf("[RTC] now: %04u-%02u-%02u %02u:%02u:%02u\r\n",
now_time.iYear,
now_time.ucMonth,
now_time.ucDay,
now_time.ucHour,
now_time.ucMin,
now_time.ucSec);
}
//获取两个时间相差多少分钟,忽略年月日
int CalculateTimeDiffInMinutes(const Comm_Time time1, const Comm_Time time2) {
// 步骤1: 将时间转换为分钟数 (忽略秒)
int minutes1 = time1.ucHour * 60 + time1.ucMin;
int minutes2 = time2.ucHour * 60 + time2.ucMin;
// 步骤2: 计算绝对差值
int diff = abs(minutes1 - minutes2);
// 步骤3: 处理跨天情况 (一天共1440分钟)
if (diff > 720) { // 超过720分钟则跨天更短
diff = 1440 - diff;
}
return diff;
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef APP_RTC_H
#define APP_RTC_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
#include "main.h"
#include "publicdata/type.h"
/* 对外接口函数声明 */
void v_rtc_init(void);
/* app_rtc 兼容接口(对业务层使用 Comm_Time) */
void GetCurrentTime(Comm_Time *curTime);
void v_rtc_set_time(Comm_Time *curTime);
unsigned int xDate2Seconds(Comm_Time *time);
void xSeconds2Date(unsigned long seconds, Comm_Time *time);
U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type);
void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset);
int CalculateTimeDiffInMinutes(const Comm_Time time1, const Comm_Time time2);
void test_rtc(void);
#ifdef __cplusplus
}
#endif
#endif /* APP_RTC_H */
+247
View File
@@ -0,0 +1,247 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "publicdata/type.h"
typedef unsigned char U8_T;
typedef unsigned short U16_T;
// 闰年检查
static U8_T u8_is_leap_year(U16_T year)
{
return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
}
// 获取月份天数
static U8_T u8_get_days_in_month(U16_T year, U8_T month)
{
static const U8_T days_in_month[] = {31,28,31,30,31,30,31,31,30,31,30,31};
if (month < 1 || month > 12) return 0;
U8_T days = days_in_month[month-1];
// 闰年二月特殊处理
if (month == 2 && u8_is_leap_year(year)) {
days = 29;
}
return days;
}
// 日期时间验证
static U8_T u8_validate_datetime(const Comm_Time *ctTime)
{
// 年份范围检查
if (ctTime->iYear < 2020 || ctTime->iYear > 2100) {
return 0;
}
// 月份检查
if (ctTime->ucMonth < 1 || ctTime->ucMonth > 12) {
return 0;
}
// 日期检查
U8_T max_days = u8_get_days_in_month(ctTime->iYear, ctTime->ucMonth);
if (ctTime->ucDay < 1 || ctTime->ucDay > max_days) {
return 0;
}
// 时间检查
if (ctTime->ucHour > 23 || ctTime->ucMin > 59 || ctTime->ucSec > 59) {
return 0;
}
return 1;
}
#if 0 /* CCU621_M: duplicate of app_rtc.c — disabled to avoid L6200E */
/*******************************************************************************
* 名称: v_adjust_time_with_timezone
* 功能: 按给定时区偏移量调整 Comm_Time 中的时刻与日期,用于 OCPP 时间戳与本地/UTC 转换。
* 先将“时+分”与偏移量合并为总分钟数,归一化到 [0, 24*60) 并得到日进退数,
* 再按日进退数正确跨月、跨年调整日期(含闰年与各月天数)。
*
* 参数:
* ctTime - 输入输出,待调整的日期时间结构(会直接修改 ucHour/ucMin/ucDay/ucMonth/iYear
* hour_offset - 时区偏移小时数,可为负(如 UTC+8 传入 8,UTC-5 传入 -5
* min_offset - 时区偏移分钟数,可为负(如 +08:30 则 hour_offset=8, min_offset=30
*
* 说明:
* - 仅修改时、分、日、月、年;秒(ucSec)不参与计算也不改动。
* - 日期跨月、跨年时按公历规则处理(含闰年二月 28/29 天)。
* - 若调整后年份超出合理范围,由调用方 u8_validate_datetime() 等做校验或裁剪。
******************************************************************************/
void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset)
{
const int minutes_per_day = 24 * 60;
int total_minutes;
int days_adjust;
int year;
U8_T month;
int day;
U8_T max_days;
if (ctTime == NULL) {
return;
}
/* 1) 将“当前时+分”与偏移量合并为总分钟数 */
total_minutes = ctTime->ucHour * 60 + ctTime->ucMin;
total_minutes += hour_offset * 60 + min_offset;
/* 2) 归一化到 [0, minutes_per_day),并得到需要进退的天数 */
days_adjust = total_minutes / minutes_per_day;
total_minutes = total_minutes % minutes_per_day;
if (total_minutes < 0) {
total_minutes += minutes_per_day;
days_adjust--;
}
/* 3) 写回时、分 */
ctTime->ucHour = (U8_T)(total_minutes / 60);
ctTime->ucMin = (U8_T)(total_minutes % 60);
/* 4) 按日进退数调整日期,并正确处理跨月、跨年 */
if (days_adjust == 0) {
return;
}
year = (int)ctTime->iYear;
month = ctTime->ucMonth;
day = (int)ctTime->ucDay + days_adjust;
/* 向后推日期:day 超出当月则进入下月/下年 */
while (day > (max_days = u8_get_days_in_month((U16_T)year, month))) {
day -= max_days;
month++;
if (month > 12) {
month = 1;
year++;
}
}
/* 向前推日期:day 小于 1 则进入上月/上年 */
while (day < 1) {
month--;
if (month < 1) {
month = 12;
year--;
}
day += u8_get_days_in_month((U16_T)year, month);
}
ctTime->ucDay = (U8_T)day;
ctTime->ucMonth = month;
ctTime->iYear = (U16_T)year;
}
// 主函数:OCPP时间戳解析
// time_type: 0-存入国际标准时间(UTC), 1-存入北京时间(UTC+8)
// 返回:0-成功,非0-失败
U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type)
{
if (ctTime == NULL || time_str == NULL) {
return 1; // 参数错误
}
// 复制输入字符串,避免修改原始数据
char time_copy[64];
if (strlen(time_str) >= sizeof(time_copy)) {
return 1; // 输入过长
}
strcpy(time_copy, time_str);
// 分割日期时间部分和时区部分
char *time_part = strchr(time_copy, 'T');
if (time_part == NULL) {
return 1; // 格式错误,缺少'T'
}
*time_part = '\0';
time_part++;
// 解析日期部分 (YYYY-MM-DD)
if (sscanf(time_copy, "%hu-%hhu-%hhu",
&ctTime->iYear, &ctTime->ucMonth, &ctTime->ucDay) != 3) {
return 1; // 日期解析失败
}
int hour, min, sec, millisec = 0;
char tz_sign = 0;
int tz_hour = 0, tz_min = 0;
// 尝试解析带毫秒的格式
int parsed = sscanf(time_part, "%d:%d:%d.%d%c%d:%d",
&hour, &min, &sec, &millisec, &tz_sign, &tz_hour, &tz_min);
if (parsed == 7) {
// Case 1: 带毫秒和时区偏移(如 "10:00:28.990+08:00"
} else {
// 尝试解析带毫秒的UTC格式(Z结尾)
parsed = sscanf(time_part, "%d:%d:%d.%dZ", &hour, &min, &sec, &millisec);
if (parsed == 4) {
// Case 2: 带毫秒的UTC时间(如 "10:00:28.990Z"
tz_sign = 'Z';
} else {
// 尝试无毫秒的格式
parsed = sscanf(time_part, "%d:%d:%d%c%d:%d",
&hour, &min, &sec, &tz_sign, &tz_hour, &tz_min);
if (parsed == 6) {
// Case 3: 无毫秒带时区偏移(如 "10:14:14+08:00"
} else {
parsed = sscanf(time_part, "%d:%d:%dZ", &hour, &min, &sec);
if (parsed == 3) {
// Case 4: 无毫秒UTC时间(如 "15:09:18Z"
tz_sign = 'Z';
} else {
return 1; // 时间格式解析失败
}
}
}
}
// 填充时间部分
ctTime->ucHour = hour;
ctTime->ucMin = min;
ctTime->ucSec = sec;
// 时区处理逻辑
if (time_type == 1) {
// 需要转换为北京时间 (UTC+8)
if (tz_sign == 'Z' || tz_sign == 0) {
// UTC时间直接加8小时
v_adjust_time_with_timezone(ctTime, 8, 0);
} else {
// 处理带时区偏移的情况
// 先将输入时间转换为UTC,再转换为北京时间
int original_offset_hour = (tz_sign == '+') ? tz_hour : -tz_hour;
int original_offset_min = (tz_sign == '+') ? tz_min : -tz_min;
// 转换为UTC时间(减去原始偏移)
v_adjust_time_with_timezone(ctTime, -original_offset_hour, -original_offset_min);
// 再转换为北京时间(加上8小时)
v_adjust_time_with_timezone(ctTime, 8, 0);
}
} else {
// time_type == 0,存入国际标准时间(UTC)
if (tz_sign != 'Z' && tz_sign != 0) {
// 非UTC时间需要转换为UTC
int original_offset_hour = (tz_sign == '+') ? tz_hour : -tz_hour;
int original_offset_min = (tz_sign == '+') ? tz_min : -tz_min;
// 转换为UTC时间(减去原始偏移)
v_adjust_time_with_timezone(ctTime, -original_offset_hour, -original_offset_min);
}
// 如果已经是UTC时间(Z结尾),则不需要转换
}
// 最终数据验证
if (!u8_validate_datetime(ctTime)) {
return 1; // 数据验证失败
}
return 0; // 成功
}
#endif /* CCU621_M: duplicate disabled */
+93
View File
@@ -0,0 +1,93 @@
# RTC 模块说明
## 1. 模块位置
- `BSP/rtc/rtc.c`
- `BSP/rtc/rtc.h`
## 2. 模块职责
- 完成 MCU 内部 RTC 外设初始化(默认 `LXTAL 32.768kHz`
- 提供统一时间读写接口(业务层使用 `Comm_Time`
- 提供时间算法工具:
- 本地时间(UTC+8)与 Unix 时间戳互转
- OCPP 时间戳解析
- 按时区偏移调整时间(支持跨天/月/年)
## 3. 硬件与掉电行为说明
### 3.1 `RTC_BKP0` 是什么
- `RTC_BKP0` 是 RTC 备份寄存器中的一个槽位(软件可读写)
- 本项目中它作为“RTC 是否已初始化”的标记位使用
- 典型逻辑:
- `RTC_BKP0 == BKP_VALUE`:认为已配置过,不覆盖当前时间
- `RTC_BKP0 != BKP_VALUE`:按默认时间初始化并写入标记
### 3.2 掉电后时间会不会继续走
- `RTC_BKP0` 只是存储标记,不会自动计时
- 时间能否在掉电期间继续增加,取决于 RTC 域是否持续供电(VBAT)以及 RTC 时钟是否持续工作
- 若掉电导致备份域失电,RTC 计时会停止;下次上电会按“首次初始化”逻辑处理
## 4. 默认时间策略
- 当前默认时间由 `rtc.c` 宏定义控制:
- `RTC_DEFAULT_YEAR_BCD`
- `RTC_DEFAULT_MONTH_BCD`
- `RTC_DEFAULT_DAY_BCD`
- `RTC_DEFAULT_HOUR_BCD`
- `RTC_DEFAULT_MINUTE_BCD`
- `RTC_DEFAULT_SECOND_BCD`
- `RTC_DEFAULT_WEEKDAY`
- 默认值仅在“未初始化/备份标记无效”时生效,不会每次上电都覆盖已有时间
## 5. 对外接口
- `void v_rtc_init(void);`
- RTC 初始化入口(由 `BSP/sys_drv_init.c` 调用)
- `void GetCurrentTime(Comm_Time *curTime);`
- 读取当前时间到 `Comm_Time`
- `void v_rtc_set_time(Comm_Time *curTime);`
- 写 RTC 时间(内含时间合法性检查)
- `unsigned int xDate2Seconds(Comm_Time *time);`
- 本地时间(UTC+8)转 Unix 秒(UTC
- `void xSeconds2Date(unsigned long seconds, Comm_Time *time);`
- Unix 秒(UTC)转本地时间(UTC+8
- `U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type);`
- 解析 OCPP 时间戳(支持 `Z``+08:00``-05:30` 等)
- `time_type=0`:输出 UTC
- `time_type=1`:输出北京时间(UTC+8
- `void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset);`
-`Comm_Time` 按时区偏移修正
- `void test_rtc(void);`
- RTC 自测接口(打印当前时间)
## 6. 启动与调用链
1. `main.c` -> `v_sys_hardware_init()`
2. `BSP/sys_drv_init.c` -> `v_rtc_init()`
3. 业务层通过 `GetCurrentTime/v_rtc_set_time` 访问时间
4. `app/app_init/app_test.c` 在周期测试中可触发 RTC 读写测试
## 7. 常见问题
- **Q: 修改默认时间后,为什么上电看到的不是默认值?**
A: 因为备份标记仍有效,模块判断为“已初始化”,不会覆盖当前时间。
- **Q: 想强制重新走默认时间怎么办?**
A: 清空备份标记(例如清 `RTC_BKP0`)或清备份域后重启。
## 8. 相关文档
- [返回主说明 README](../../README.md)
- [externalflash 模块说明](../externalflash/externalflash模块说明.md)
+29
View File
@@ -0,0 +1,29 @@
#ifndef BSP_INCLUDE_H
#define BSP_INCLUDE_H
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
#include "iic_eeprom/fm24cl16b.h" /* [CCU601E_D] */
#include "spi_Flash/flash_external_data.h"
#include "spi_Flash/flash_upgrade.h"
#include "net_lwip/ethernetif.h" /* is_eth_link_up */
#include "fdcan/app_fdcan.h"
#include "can_spi/CANSPI.h"
#define BMS_CAN_ID_1 CAN_1
#define BMS_CAN_ID_2 CAN_2
#define MDU_CAN_ID CAN_3
#define CAN_RECV_BUF_SIZE CAN_BUFFER_CNT
#elif SYSTEM_TYPE == SYSTEM_TYPE_CCU621_M
#include "eeprom/fm24cl16.h" /* [CCU621_M] */
#include "externalflash/flash_external_data.h"
#include "externalflash/flash_upgrade.h"
#include "net_lwip/eth_link.h" /* netif_is_link_up */
#include "can.h"
#define BMS_CAN_ID_1 E_CAN_1
#define BMS_CAN_ID_2 E_CAN_0
#define MDU_CAN_ID E_CAN_2
#define CAN_RECV_BUF_SIZE CAN_RX_BUFFER_SIZE
#endif
#endif
+521
View File
@@ -0,0 +1,521 @@
/*!
\file can.c
\brief CAN统一接口实现(整合 CAN0/CAN1/CAN2
*/
#include "gd32h7xx.h"
#include "can.h"
#include <string.h>
/* RX strategy: TX on MB0, RX on MB1 (validated on all 3 CAN ports). */
/* 支持的 CAN 端口数量(CAN0/CAN1/CAN2 */
#define CAN_PORT_COUNT 3U
/* 每个端口的软件接收环形缓存深度(单位:帧) */
/* 单帧最大数据长度(Classic CAN,8 字节) */
#define CAN_DATA_MAX_LEN 8U
/**
* @brief 单帧接收缓存条目
*/
typedef struct {
uint32_t id; /* 帧 ID(标准/扩展均可) */
uint8_t data[CAN_DATA_MAX_LEN]; /* 帧数据 */
uint8_t dlc; /* 数据长度 */
uint8_t ide; /* 帧类型:0 标准帧,1 扩展帧 */
uint8_t rtr; /* 帧类型:0 数据帧,1 远程帧 */
uint8_t used; /* 条目是否有效:0 无效,1 有效 */
} can_rx_item_t;
/**
* @brief 单端口运行态(软件接收环形缓存)
*/
typedef struct {
can_rx_item_t rx_buffer[CAN_RX_BUFFER_SIZE]; /* 缓存数组 */
volatile uint8_t head; /* 写指针(中断侧推进) */
volatile uint8_t tail; /* 读指针(任务侧推进) */
} can_port_state_t;
/**
* @brief 端口硬件静态配置表项
*/
typedef struct {
uint32_t canx; /* CAN 外设基址(CAN0/CAN1/CAN2 */
can_idx_enum idx; /* 时钟源索引(IDX_CANx */
rcu_periph_enum can_clock; /* CAN 外设时钟 */
rcu_periph_enum gpio_clock; /* GPIO 端口时钟 */
uint32_t gpio_port; /* GPIO 端口 */
uint32_t gpio_af; /* 复用功能编号 */
uint32_t tx_pin; /* TX 引脚 */
uint32_t rx_pin; /* RX 引脚 */
IRQn_Type irqn; /* 对应中断号 */
} can_port_cfg_t;
/* 各端口运行态(接收缓存和读写索引) */
static can_port_state_t g_can_state[CAN_PORT_COUNT];
static uint8_t g_can_initialized = 0U;
/* 各端口硬件配置映射表 */
static const can_port_cfg_t g_can_cfg[CAN_PORT_COUNT] = {
{ CAN0, IDX_CAN0, RCU_CAN0, RCU_GPIOB, GPIOB, GPIO_AF_9, GPIO_PIN_9, GPIO_PIN_8, CAN0_Message_IRQn },
{ CAN1, IDX_CAN1, RCU_CAN1, RCU_GPIOB, GPIOB, GPIO_AF_9, GPIO_PIN_6, GPIO_PIN_5, CAN1_Message_IRQn },
{ CAN2, IDX_CAN2, RCU_CAN2, RCU_GPIOF, GPIOF, GPIO_AF_2, GPIO_PIN_7, GPIO_PIN_6, CAN2_Message_IRQn }
};
/**
* @brief 获取指定端口的配置项
* @param port 端口号(0/1/2
* @return 配置项指针,非法端口返回 NULL
*/
static const can_port_cfg_t *can_get_cfg(uint8_t port)
{
if (port >= CAN_PORT_COUNT) {
return NULL;
}
return &g_can_cfg[port];
}
/**
* @brief 初始化指定端口的软件接收缓存
* @param port 端口号(0/1/2
*/
static void can_buffer_init(uint8_t port)
{
memset(&g_can_state[port], 0, sizeof(g_can_state[port]));
}
/**
* @brief 配置指定端口 CAN GPIO 和时钟
* @param port 端口号(0/1/2
*/
static void can_gpio_config(uint8_t port)
{
const can_port_cfg_t *cfg = can_get_cfg(port);
if (cfg == NULL) {
return;
}
rcu_can_clock_config(cfg->idx, RCU_CANSRC_APB2);
rcu_periph_clock_enable(cfg->can_clock);
rcu_periph_clock_enable(cfg->gpio_clock);
gpio_af_set(cfg->gpio_port, cfg->gpio_af, cfg->rx_pin);
gpio_mode_set(cfg->gpio_port, GPIO_MODE_AF, GPIO_PUPD_NONE, cfg->rx_pin);
gpio_output_options_set(cfg->gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, cfg->rx_pin);
gpio_af_set(cfg->gpio_port, cfg->gpio_af, cfg->tx_pin);
gpio_mode_set(cfg->gpio_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, cfg->tx_pin);
gpio_output_options_set(cfg->gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, cfg->tx_pin);
}
/**
* @brief 配置指定端口 CAN 协议参数和中断
* @param port 端口号(0/1/2
* @note CAN0/CAN1: 250kbps, CAN2: 125kbps,采样点约 87.5%
*/
static void can_parameter_config(uint8_t port)
{
const can_port_cfg_t *cfg = can_get_cfg(port);
can_parameter_struct can_parameter;
uint32_t prescaler;
if (cfg == NULL) {
return;
}
/* 根据端口选择预分频器:CAN2使用125kbps,其他使用250kbps */
if (port == 2) { /* CAN2 */
prescaler = 150U; /* 125 kbps */
} else {
prescaler = 75U; /* 250 kbps */
}
can_deinit(cfg->canx);
can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
can_parameter.internal_counter_source = CAN_TIMER_SOURCE_BIT_CLOCK;
can_parameter.self_reception = DISABLE;
can_parameter.mb_tx_order = CAN_TX_HIGH_PRIORITY_MB_FIRST;
can_parameter.mb_tx_abort_enable = ENABLE;
can_parameter.local_priority_enable = DISABLE;
can_parameter.mb_rx_ide_rtr_type = CAN_IDE_RTR_FILTERED;
can_parameter.mb_remote_frame = CAN_STORE_REMOTE_REQUEST_FRAME;
can_parameter.rx_private_filter_queue_enable = DISABLE;
can_parameter.edge_filter_enable = DISABLE;
can_parameter.protocol_exception_enable = DISABLE;
can_parameter.rx_filter_order = CAN_RX_FILTER_ORDER_MAILBOX_FIRST;
can_parameter.memory_size = CAN_MEMSIZE_32_UNIT;
/* Follow official examples mailbox filter setting. */
can_parameter.mb_public_filter = 0U;
can_parameter.resync_jump_width = 1U;
can_parameter.prop_time_segment = 2U;
can_parameter.time_segment_1 = 11U;
can_parameter.time_segment_2 = 2U;
can_parameter.prescaler = prescaler;
can_init(cfg->canx, &can_parameter);
// nvic_irq_enable(cfg->irqn, 0U, 0U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
/* Mailbox path: TX on MB0, RX on MB1. */
can_interrupt_enable(cfg->canx, CAN_INT_MB0); /* TX complete/abort events */
can_interrupt_enable(cfg->canx, CAN_INT_MB1);
/* Enable error-related interrupts to avoid "silent dead" and help recovery. */
can_interrupt_enable(cfg->canx, CAN_INT_ERR_SUMMARY);
can_interrupt_enable(cfg->canx, CAN_INT_BUSOFF);
can_interrupt_enable(cfg->canx, CAN_INT_BUSOFF_RECOVERY);
can_interrupt_enable(cfg->canx, CAN_INT_RX_WARNING);
can_interrupt_enable(cfg->canx, CAN_INT_TX_WARNING);
/* Real bus communication mode. */
can_operation_mode_enter(cfg->canx, CAN_NORMAL_MODE);
}
/**
* @brief 配置指定端口接收过滤器(当前放行标准帧和扩展帧)
* @param port 端口号(0/1/2
*/
static void can_filter_config(uint8_t port)
{
if (can_get_cfg(port) == NULL) {
return;
}
/* Mailbox RX uses mb_public_filter configured in can_parameter_config(). */
}
/**
* @brief 指定端口发送一帧 CAN 数据
* @param port 端口号(0/1/2
* @param id 帧 ID
* @param data 数据指针
* @param dlc 数据长度(1~8
* @param ide 帧类型:0 标准帧,1 扩展帧
* @param rtr 帧类型:0 数据帧,1 远程帧
* @return 0 成功,1 参数错误
*/
static uint8_t can_port_send_data(uint8_t port, uint32_t id, const uint8_t *data, uint8_t dlc, uint8_t ide, uint8_t rtr)
{
can_mailbox_descriptor_struct tx_mailbox; /* 发送邮箱描述符 */
uint32_t tx_buffer[CAN_DATA_MAX_LEN] = {0}; /* 局部发送缓冲(驱动要求 32bit 对齐) */
const can_port_cfg_t *cfg = can_get_cfg(port); /* 端口配置 */
if ((cfg == NULL) || (data == NULL) || (dlc == 0U) || (dlc > CAN_DATA_MAX_LEN)) {
return 1U;
}
can_struct_para_init(CAN_MDSC_STRUCT, &tx_mailbox);
tx_mailbox.id = id;
tx_mailbox.dlc = dlc;
tx_mailbox.ide = ide;
tx_mailbox.rtr = rtr;
tx_mailbox.srr = (ide == 1U) ? 1U : 0U;
tx_mailbox.code = CAN_MB_TX_STATUS_DATA;
tx_mailbox.data_bytes = dlc;
memcpy(tx_buffer, data, dlc);
tx_mailbox.data = tx_buffer;
can_mailbox_config(cfg->canx, 0U, &tx_mailbox);
return 0U;
}
/**
* @brief 从指定端口的软件接收缓存读取一帧数据
* @param port 端口号(0/1/2
* @param id 输出:帧 ID
* @param data 输出:帧数据
* @param dlc 输出:数据长度
* @param ide 输出:帧类型(标准/扩展)
* @param rtr 输出:帧类型(数据/远程)
* @return 0 成功,1 无数据,2 参数错误
*/
static uint8_t can_port_receive_data(uint8_t port, uint32_t *id, uint8_t *data, uint8_t *dlc, uint8_t *ide, uint8_t *rtr)
{
can_port_state_t *state; /* 指向端口运行态 */
if ((port >= CAN_PORT_COUNT) || (id == NULL) || (data == NULL) || (dlc == NULL) || (ide == NULL) || (rtr == NULL)) {
return 2U;
}
state = &g_can_state[port];
if (state->rx_buffer[state->tail].used == 0U) {
return 1U;
}
*id = state->rx_buffer[state->tail].id;
*dlc = state->rx_buffer[state->tail].dlc;
*ide = state->rx_buffer[state->tail].ide;
*rtr = state->rx_buffer[state->tail].rtr;
memcpy(data, state->rx_buffer[state->tail].data, *dlc);
state->rx_buffer[state->tail].used = 0U;
state->tail = (uint8_t)((state->tail + 1U) % CAN_RX_BUFFER_SIZE);
return 0U;
}
/**
* @brief 统一中断处理逻辑(按端口复用)
* @param port 端口号(0/1/2
* @note 在中断上下文执行:从硬件邮箱读帧并入软件环形缓存
*/
static void can_irq_handler_common(uint8_t port)
{
can_port_state_t *state; /* 端口运行态 */
const can_port_cfg_t *cfg = can_get_cfg(port); /* 端口配置 */
uint8_t next_head; /* 计算后的下一写指针 */
if (cfg == NULL) {
return;
}
{
can_mailbox_descriptor_struct rx_mb;
uint32_t rx_data_u32[2] = {0U, 0U}; /* 8 bytes aligned buffer */
if (RESET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_MB1)) {
return;
}
can_struct_para_init(CAN_MDSC_STRUCT, &rx_mb);
rx_mb.data = rx_data_u32;
rx_mb.data_bytes = 8U;
if (SUCCESS != can_mailbox_receive_data_read(cfg->canx, 1U, &rx_mb)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB1);
return;
}
/* Only accept: extended + data frame */
if ((rx_mb.ide == 0U) || (rx_mb.rtr != 0U)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB1);
return;
}
state = &g_can_state[port];
next_head = (uint8_t)((state->head + 1U) % CAN_RX_BUFFER_SIZE);
if ((next_head != state->tail) && (state->rx_buffer[state->head].used == 0U)) {
uint8_t dlc = (uint8_t)rx_mb.dlc;
if (dlc > CAN_DATA_MAX_LEN) {
dlc = CAN_DATA_MAX_LEN;
}
state->rx_buffer[state->head].id = rx_mb.id;
state->rx_buffer[state->head].dlc = dlc;
state->rx_buffer[state->head].ide = (uint8_t)rx_mb.ide;
state->rx_buffer[state->head].rtr = (uint8_t)rx_mb.rtr;
memcpy(state->rx_buffer[state->head].data, (uint8_t *)rx_data_u32, dlc);
state->rx_buffer[state->head].used = 1U;
state->head = next_head;
}
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB1);
}
}
/**
* @brief CAN 中断统一入口(消息/错误/BusOff/告警等均可调用)
* @param can_port 端口号(0/1/2
*
* 说明:
* - GD32H7 的 CAN 中断线有 Message/Busoff/Error/TEC/REC 多条,
* 不同中断源触发哪一条与库/配置有关。这里统一收敛到一个入口,保证不中断“漏接”。
*/
void v_can_irq_handler(uint8_t can_port)
{
const can_port_cfg_t *cfg = can_get_cfg(can_port);
if (cfg == NULL) {
return;
}
/* Message path dispatch (MB1 RX mailbox). */
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_MB1)) {
can_irq_handler_common(can_port);
}
/* MB0 indicates TX mailbox event, clear it to avoid stale status. */
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_MB0)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB0);
}
/* 清理可能导致反复进入的错误/BusOff 标志(不依赖业务侧处理) */
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_BUSOFF)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_BUSOFF);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_BUSOFF_RECOVERY)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_BUSOFF_RECOVERY);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_ERR_SUMMARY)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_ERR_SUMMARY);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_RX_WARNING)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_RX_WARNING);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_TX_WARNING)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_TX_WARNING);
}
}
/**
* @brief 初始化全部 CAN 端口(GPIO/参数/过滤器/缓存)
*/
void v_can_interface_init(void)
{
uint8_t port; /* 端口遍历索引 */
for (port = 0U; port < CAN_PORT_COUNT; port++) {
can_mailbox_descriptor_struct rx_mb;
uint32_t dummy_data[2] = {0U, 0U};
can_gpio_config(port);
can_parameter_config(port);
can_filter_config(port);
can_buffer_init(port);
/* Configure MB1 as RX mailbox. */
can_struct_para_init(CAN_MDSC_STRUCT, &rx_mb);
rx_mb.rtr = 0U;
rx_mb.ide = 1U; /* expect extended */
rx_mb.code = CAN_MB_RX_STATUS_EMPTY;
/* No ID filtering in mailbox setup; extension/data filtering is done in software path. */
rx_mb.id = 0U;
rx_mb.data = dummy_data;
rx_mb.data_bytes = 8U;
can_mailbox_config(g_can_cfg[port].canx, 1U, &rx_mb);
}
g_can_initialized = 1U;
}
uint8_t u8_can_is_initialized(void)
{
return g_can_initialized;
}
/**
* @brief 统一 CAN 发送接口(兼容旧接口)
* @param can_port 端口号(0/1/2
* @param id 帧 ID
* @param data 数据指针
* @param dlc 数据长度(1~8
* @param ide 0 标准帧,1 扩展帧
* @param rtr 0 数据帧,1 远程帧
* @return 0 成功,1 参数错误
*/
uint8_t can_send_data(uint8_t can_port, uint32_t id, uint8_t *data, uint8_t dlc, uint8_t ide, uint8_t rtr)
{
return can_port_send_data(can_port, id, data, dlc, ide, rtr);
}
/**
* @brief 统一 CAN 接收接口(兼容旧接口)
* @param can_port 端口号(0/1/2
* @param id 输出:帧 ID
* @param data 输出:帧数据
* @param dlc 输出:数据长度
* @param ide 输出:帧类型(标准/扩展)
* @param rtr 输出:帧类型(数据/远程)
* @return 0 成功,1 无数据,2 参数错误
*/
uint8_t can_receive_data(uint8_t can_port, uint32_t *id, uint8_t *data, uint8_t *dlc, uint8_t *ide, uint8_t *rtr)
{
return can_port_receive_data(can_port, id, data, dlc, ide, rtr);
}
/**
* @brief 对外统一发送接口(与参考工程接口风格一致)
* @param can_id 端口枚举
* @param can_data 发送数据结构
*/
void v_can_tx(E_CAN_ID can_id, const CAN_DATA *can_data)
{
if ((can_data == NULL) || (can_data->Len == 0U) || (can_data->Len > CAN_DATA_MAX_LEN)) {
return;
}
(void)can_send_data((uint8_t)can_id,
can_data->ID,
(uint8_t *)can_data->Data,
can_data->Len,
1U,
0U);
}
/**
* @brief 对外统一接收接口(仿照参考工程:一次取出当前软件缓存内全部CAN报文)
* @param can_id 端口枚举
* @param can_data 输出:接收数据结构体数组
* @return 返回本次拷出的报文数量;0 表示无数据或参数错误
*/
uint8_t u8_can_rx(E_CAN_ID can_id, CAN_DATA *can_data)
{
can_port_state_t *state;
const can_port_cfg_t *cfg;
uint8_t count = 0U;
if ((can_id >= E_CAN_0 + CAN_PORT_COUNT) || (can_data == NULL)) {
return 0U;
}
cfg = can_get_cfg((uint8_t)can_id);
if (cfg == NULL) {
return 0U;
}
state = &g_can_state[(uint8_t)can_id];
NVIC_DisableIRQ(cfg->irqn);
while ((count < CAN_RX_BUFFER_SIZE) && (state->rx_buffer[state->tail].used != 0U)) {
can_data[count].ID = state->rx_buffer[state->tail].id;
can_data[count].Len = state->rx_buffer[state->tail].dlc;
memcpy(can_data[count].Data, state->rx_buffer[state->tail].data, can_data[count].Len);
state->rx_buffer[state->tail].used = 0U;
state->tail = (uint8_t)((state->tail + 1U) % CAN_RX_BUFFER_SIZE);
count++;
}
if (state->head == state->tail) {
state->head = 0U;
state->tail = 0U;
}
NVIC_EnableIRQ(cfg->irqn);
return count;
}
/**
* @brief 打包扩展帧 ID(协议字段组装)
* @param prior 优先级字段
* @param cmd 命令字段
* @param desAddr 目的地址字段
* @param oriAddr 源地址字段
* @return 29 位扩展帧 ID
*/
uint32_t u32_get_can_ID(uint8_t prior, uint8_t cmd, uint8_t desAddr, uint8_t oriAddr)
{
return ((uint32_t)prior << 26)
| ((uint32_t)cmd << 16)
| ((uint32_t)desAddr << 8)
| (uint32_t)oriAddr;
}
/**
* @brief 解析扩展帧 ID(协议字段拆解)
* @param u32_id 待解析 ID
* @param prior 输出:优先级(可为 NULL)
* @param cmd 输出:命令(可为 NULL
* @param desAddr 输出:目的地址(可为 NULL)
* @param oriAddr 输出:源地址(可为 NULL)
* @return 1 成功,0 参数 ID 非法
*/
uint8_t u8_can_ID_unpack(uint32_t u32_id, uint8_t *prior, uint8_t *cmd, uint8_t *desAddr, uint8_t *oriAddr)
{
if ((u32_id == 0U) || (u32_id > 0x1FFFFFFFU)) {
return 0U;
}
if (prior != NULL) {
*prior = (uint8_t)((u32_id >> 26) & 0xFFU);
}
if (cmd != NULL) {
*cmd = (uint8_t)((u32_id >> 16) & 0xFFU);
}
if (desAddr != NULL) {
*desAddr = (uint8_t)((u32_id >> 8) & 0xFFU);
}
if (oriAddr != NULL) {
*oriAddr = (uint8_t)(u32_id & 0xFFU);
}
return 1U;
}
+55
View File
@@ -0,0 +1,55 @@
/*!
\file can.h
\brief CAN配置头文件
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
#ifndef CAN_H
#define CAN_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define CAN_RX_BUFFER_SIZE 32U
typedef enum {
E_CAN_0 = 0,
E_CAN_1 = 1,
E_CAN_2 = 2
} E_CAN_ID;
typedef struct {
uint32_t ID;
uint8_t Len;
uint8_t Data[8U];
} CAN_DATA;
/* CAN统一初始化接口 */
void v_can_interface_init(void);
uint8_t u8_can_is_initialized(void);
/* CAN 中断分发(供 gd32h7xx_it.c 复用) */
void v_can_irq_handler(uint8_t can_port);
/* 对外统一发送/接收接口(扩展帧、数据帧) */
void v_can_tx(E_CAN_ID can_id, const CAN_DATA *can_data);
uint8_t u8_can_rx(E_CAN_ID can_id, CAN_DATA *can_data);
/* 兼容当前工程的原统一接口 */
uint8_t can_send_data(uint8_t can_port, uint32_t id, uint8_t *data, uint8_t dlc, uint8_t ide, uint8_t rtr);
uint8_t can_receive_data(uint8_t can_port, uint32_t *id, uint8_t *data, uint8_t *dlc, uint8_t *ide, uint8_t *rtr);
/* 扩展帧ID打包/解析工具 */
uint32_t u32_get_can_ID(uint8_t prior, uint8_t cmd, uint8_t desAddr, uint8_t oriAddr);
uint8_t u8_can_ID_unpack(uint32_t u32_id, uint8_t *prior, uint8_t *cmd, uint8_t *desAddr, uint8_t *oriAddr);
#ifdef __cplusplus
}
#endif
#endif /* CAN_H */
+715
View File
@@ -0,0 +1,715 @@
/*!
\file usart.c
\brief 串口(USART/UART)统一驱动入口(集中式)
目标:把 `BSP\com\` 下分散的串口实现整理为“单入口/统一API”,形态上尽量贴近
`CCU601E_RUN\BSP\ST_HAL\usart.c`(集中管理各串口初始化、发送、接收缓存、中断)。
*/
#include "usart.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "gd32h7xx.h"
#include "publicdata/public_define.h"
#include "ringfifo/ringfifo.h"
#ifndef DEBUG_SHELL_USART_NULL
#define DEBUG_SHELL_USART_NULL (0xFFU)
#endif
#ifndef DEBUG_SHELL_USART_ID
#define DEBUG_SHELL_USART_ID DEBUG_SHELL_USART_NULL
#endif
/* ===== 通用:ringfifo 接收缓存(每路一套)===== */
#define UART0_RX_BUFFER_SIZE 256U
#define UART2_RX_BUFFER_SIZE 256U
#define UART3_RX_BUFFER_SIZE 256U
#define UART4_RX_BUFFER_SIZE 256U
#define UART5_RX_BUFFER_SIZE 256U
#define UART6_RX_BUFFER_SIZE 4096U
#define UART7_RX_BUFFER_SIZE 256U
#define UART0_BAUDRATE 115200U
#define UART2_BAUDRATE 115200U
#define UART3_BAUDRATE 2400U
#define UART4_BAUDRATE 2400U
#define UART5_BAUDRATE 115200U
#define UART6_BAUDRATE 115200U
#define UART7_BAUDRATE 115200U
static _fifo_t s_uart0_rx;
static _fifo_t s_uart2_rx;
static _fifo_t s_uart3_rx;
static _fifo_t s_uart4_rx;
static _fifo_t s_uart5_rx;
static _fifo_t s_uart6_rx;
static _fifo_t s_uart7_rx;
static uint8_t s_uart0_rx_mem[UART0_RX_BUFFER_SIZE];
static uint8_t s_uart2_rx_mem[UART2_RX_BUFFER_SIZE];
static uint8_t s_uart3_rx_mem[UART3_RX_BUFFER_SIZE];
static uint8_t s_uart4_rx_mem[UART4_RX_BUFFER_SIZE];
static uint8_t s_uart5_rx_mem[UART5_RX_BUFFER_SIZE];
static uint8_t s_uart6_rx_mem[UART6_RX_BUFFER_SIZE];
static uint8_t s_uart7_rx_mem[UART7_RX_BUFFER_SIZE];
/* 前置声明:避免 usart6_init 中调用时出现隐式声明 */
void uart6_init(void);
/* 与 CCU601E_D 一致:按 slot 0~6 对应 7 路物理串口(实际编号 0/2/3/4/5/6/7),Shell 用 usart_printf 配置。 */
U8_T u8_uasrt_print[USART_NUM_PRINT_CNT] = {0};
static uint8_t usart_hw_num_to_slot(uint8_t usart_num)
{
switch (usart_num) {
case 0: return 0U;
case 2: return 1U;
case 3: return 2U;
case 4: return 3U;
case 5: return 4U;
case 6: return 5U;
case 7: return 6U;
default: return 0xFFU;
}
}
static void usart_dump_line(const char *tag, uint8_t usart_num, const uint8_t *data, uint16_t len)
{
uint16_t i;
if (data == NULL || len == 0U) {
return;
}
(void)printf("\r\n--------------usart(%u) %s :\r\n", (unsigned)usart_num, tag);
for (i = 0U; i < len; i++) {
(void)printf("%02X ", (unsigned int)data[i]);
}
(void)printf("\r\n---------------%s end\r\n", tag);
}
/**
* @brief 根据串口编号返回对应 ringfifo 句柄
* @param usart_num 串口编号
* @retval 对应的 _fifo_t 指针;不支持时返回 NULL
*/
static _fifo_t *get_fifo_by_id(uint8_t usart_num)
{
switch (usart_num) {
case 0: return &s_uart0_rx;
case 2: return &s_uart2_rx;
case 3: return &s_uart3_rx;
case 4: return &s_uart4_rx;
case 5: return &s_uart5_rx;
case 6: return &s_uart6_rx;
case 7: return &s_uart7_rx;
default: return NULL;
}
}
/**
* @brief 注册全部串口接收 FIFO(仅执行一次)
* @retval none
*/
static void usart_fifo_register_all(void)
{
static uint8_t registered = 0U;
if (registered) {
return;
}
fifo_register(&s_uart0_rx, s_uart0_rx_mem, UART0_RX_BUFFER_SIZE, NULL, NULL);
fifo_register(&s_uart2_rx, s_uart2_rx_mem, UART2_RX_BUFFER_SIZE, NULL, NULL);
fifo_register(&s_uart3_rx, s_uart3_rx_mem, UART3_RX_BUFFER_SIZE, NULL, NULL);
fifo_register(&s_uart4_rx, s_uart4_rx_mem, UART4_RX_BUFFER_SIZE, NULL, NULL);
fifo_register(&s_uart5_rx, s_uart5_rx_mem, UART5_RX_BUFFER_SIZE, NULL, NULL);
fifo_register(&s_uart6_rx, s_uart6_rx_mem, UART6_RX_BUFFER_SIZE, NULL, NULL);
fifo_register(&s_uart7_rx, s_uart7_rx_mem, UART7_RX_BUFFER_SIZE, NULL, NULL);
registered = 1U;
}
/* ===== 通用:阻塞式发送 ===== */
/**
* @brief 阻塞发送指定长度数据
* @param periph 串口外设基地址
* @param data 待发送数据指针
* @param len 待发送长度
* @retval none
*/
static void usart_send_blocking(uint32_t periph, const uint8_t *data, uint16_t len)
{
for (uint16_t i = 0; i < len; i++) {
while (RESET == usart_flag_get(periph, USART_FLAG_TBE)) {}
usart_data_transmit(periph, data[i]);
}
while (RESET == usart_flag_get(periph, USART_FLAG_TC)) {}
}
static uint32_t usart_periph_from_id(uint8_t usart_id)
{
switch (usart_id) {
case E_USART_0: return USART0;
case E_USART_2: return USART2;
case E_USART_3: return UART3;
case E_USART_4: return UART4;
case E_USART_5: return USART5;
case E_USART_6: return UART6;
case E_USART_7: return UART7;
default: return 0U;
}
}
/* ===== 各串口硬件初始化(集中在同一个文件里)===== */
/**
* @brief USART0 初始化:PF4/PF51152008N1
* @retval none
*/
void usart0_init(void)
{
rcu_periph_clock_enable(RCU_GPIOF);
rcu_periph_clock_enable(RCU_USART0);
gpio_af_set(GPIOF, GPIO_AF_4, GPIO_PIN_4);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_4);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_4);
gpio_af_set(GPIOF, GPIO_AF_4, GPIO_PIN_5);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5);
usart_deinit(USART0);
usart_word_length_set(USART0, USART_WL_8BIT);
usart_stop_bit_set(USART0, USART_STB_1BIT);
usart_parity_config(USART0, USART_PM_NONE);
usart_baudrate_set(USART0, UART0_BAUDRATE);
usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
// nvic_irq_enable(USART0_IRQn, 3U, 1U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
usart_interrupt_enable(USART0, USART_INT_RBNE);
usart_enable(USART0);
}
/**
* @brief USART2 初始化:PD8/PD91152008N1
* @retval none
*/
void usart2_init(void)
{
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_USART2);
gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_8);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_8);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_8);
gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_9);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_9);
usart_deinit(USART2);
usart_word_length_set(USART2, USART_WL_8BIT);
usart_stop_bit_set(USART2, USART_STB_1BIT);
usart_parity_config(USART2, USART_PM_NONE);
usart_baudrate_set(USART2, UART2_BAUDRATE);
usart_receive_config(USART2, USART_RECEIVE_ENABLE);
usart_transmit_config(USART2, USART_TRANSMIT_ENABLE);
// nvic_irq_enable(USART2_IRQn, 3U, 2U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
usart_interrupt_enable(USART2, USART_INT_RBNE);
usart_enable(USART2);
}
/**
* @brief UART3 初始化:PC10/PC1124008E1
* @retval none
*/
void uart3_init(void)
{
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_UART3);
gpio_af_set(GPIOC, GPIO_AF_8, GPIO_PIN_10);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_10);
gpio_af_set(GPIOC, GPIO_AF_8, GPIO_PIN_11);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
usart_deinit(UART3);
/* 8E1: data(8) + parity(1) -> word length must be 9-bit on this peripheral */
usart_word_length_set(UART3, USART_WL_9BIT);
usart_stop_bit_set(UART3, USART_STB_1BIT);
usart_parity_config(UART3, USART_PM_EVEN);
usart_baudrate_set(UART3, UART3_BAUDRATE);
usart_hardware_flow_rts_config(UART3, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(UART3, USART_CTS_DISABLE);
usart_receive_config(UART3, USART_RECEIVE_ENABLE);
usart_transmit_config(UART3, USART_TRANSMIT_ENABLE);
// nvic_irq_enable(UART3_IRQn, 3U, 0U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
usart_interrupt_enable(UART3, USART_INT_RBNE);
usart_enable(UART3);
}
/**
* @brief UART4 初始化:PC12/PD224008E1
* @retval none
*/
void uart4_init(void)
{
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_UART4);
gpio_af_set(GPIOC, GPIO_AF_8, GPIO_PIN_12);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_12);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_12);
gpio_af_set(GPIOD, GPIO_AF_8, GPIO_PIN_2);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_2);
usart_deinit(UART4);
/* 8E1: data(8) + parity(1) -> word length must be 9-bit on this peripheral */
usart_word_length_set(UART4, USART_WL_9BIT);
usart_stop_bit_set(UART4, USART_STB_1BIT);
usart_parity_config(UART4, USART_PM_EVEN);
usart_baudrate_set(UART4, UART4_BAUDRATE);
usart_hardware_flow_rts_config(UART4, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(UART4, USART_CTS_DISABLE);
usart_receive_config(UART4, USART_RECEIVE_ENABLE);
usart_transmit_config(UART4, USART_TRANSMIT_ENABLE);
// nvic_irq_enable(UART4_IRQn, 2U, 0U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
usart_interrupt_enable(UART4, USART_INT_RBNE);
usart_enable(UART4);
}
/**
* @brief USART5 初始化:PC6/PC71152008N1
* @retval none
*/
void usart5_init(void)
{
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_USART5);
gpio_af_set(GPIOC, GPIO_AF_7, GPIO_PIN_6);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_6);
gpio_af_set(GPIOC, GPIO_AF_7, GPIO_PIN_7);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
usart_deinit(USART5);
usart_oversample_config(USART5, USART_OVSMOD_16);
usart_word_length_set(USART5, USART_WL_8BIT);
usart_stop_bit_set(USART5, USART_STB_1BIT);
usart_parity_config(USART5, USART_PM_NONE);
usart_baudrate_set(USART5, UART5_BAUDRATE);
usart_hardware_flow_rts_config(USART5, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(USART5, USART_CTS_DISABLE);
usart_receive_config(USART5, USART_RECEIVE_ENABLE);
usart_transmit_config(USART5, USART_TRANSMIT_ENABLE);
// nvic_irq_enable(USART5_IRQn, 1U, 0U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
usart_interrupt_enable(USART5, USART_INT_RBNE);
usart_enable(USART5);
}
/**
* @brief 兼容接口:旧代码调用 usart6_init,内部转到 uart6_init
* @retval none
*/
void usart6_init(void)
{
/* 兼容旧头文件声明:usart6_init -> uart6_init */
uart6_init();
}
/**
* @brief UART6 初始化:PB4/PB31152008N1
* @retval none
*/
void uart6_init(void)
{
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_UART6);
gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_4);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_4);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_4);
gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_3);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);
usart_deinit(UART6);
usart_word_length_set(UART6, USART_WL_8BIT);
usart_stop_bit_set(UART6, USART_STB_1BIT);
usart_parity_config(UART6, USART_PM_NONE);
usart_baudrate_set(UART6, UART6_BAUDRATE);
usart_hardware_flow_rts_config(UART6, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(UART6, USART_CTS_DISABLE);
usart_receive_config(UART6, USART_RECEIVE_ENABLE);
usart_transmit_config(UART6, USART_TRANSMIT_ENABLE);
// nvic_irq_enable(UART6_IRQn, 0U, 0U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
usart_interrupt_enable(UART6, USART_INT_RBNE);
usart_enable(UART6);
}
/**
* @brief UART7 初始化:PE1/PE01152008N1
* @retval none
*/
void uart7_init(void)
{
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_UART7);
gpio_af_set(GPIOE, GPIO_AF_8, GPIO_PIN_1);
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_1);
gpio_af_set(GPIOE, GPIO_AF_8, GPIO_PIN_0);
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0);
usart_deinit(UART7);
usart_word_length_set(UART7, USART_WL_8BIT);
usart_stop_bit_set(UART7, USART_STB_1BIT);
usart_parity_config(UART7, USART_PM_NONE);
usart_baudrate_set(UART7, UART7_BAUDRATE);
usart_hardware_flow_rts_config(UART7, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(UART7, USART_CTS_DISABLE);
usart_receive_config(UART7, USART_RECEIVE_ENABLE);
usart_transmit_config(UART7, USART_TRANSMIT_ENABLE);
// nvic_irq_enable(UART7_IRQn, 3U, 7U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
usart_interrupt_enable(UART7, USART_INT_RBNE);
usart_enable(UART7);
}
/* ===== 中断服务程序(集中管理)===== */
/**
* @brief USART0 中断处理函数,接收数据压入 FIFO
* @retval none
*/
void USART0_IRQHandler(void)
{
if (RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {
uint8_t b = usart_data_receive(USART0);
fifo_write(&s_uart0_rx, &b, 1U);
usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);
}
}
/**
* @brief USART2 中断处理函数,接收数据压入 FIFO
* @retval none
*/
void USART2_IRQHandler(void)
{
if (RESET != usart_interrupt_flag_get(USART2, USART_INT_FLAG_RBNE)) {
uint8_t b = usart_data_receive(USART2);
fifo_write(&s_uart2_rx, &b, 1U);
usart_interrupt_flag_clear(USART2, USART_INT_FLAG_RBNE);
}
}
/**
* @brief UART3 中断处理函数,接收数据压入 FIFO
* @retval none
*/
void UART3_IRQHandler(void)
{
if (RESET != usart_flag_get(UART3, USART_FLAG_RBNE)) {
uint8_t b = usart_data_receive(UART3);
fifo_write(&s_uart3_rx, &b, 1U);
}
}
/**
* @brief UART4 中断处理函数,接收数据压入 FIFO
* @retval none
*/
void UART4_IRQHandler(void)
{
if (RESET != usart_flag_get(UART4, USART_FLAG_RBNE)) {
uint8_t b = usart_data_receive(UART4);
fifo_write(&s_uart4_rx, &b, 1U);
}
}
/**
* @brief USART5 中断处理函数,接收数据压入 FIFO
* @retval none
*/
void USART5_IRQHandler(void)
{
if (RESET != usart_interrupt_flag_get(USART5, USART_INT_FLAG_RBNE)) {
uint8_t b = usart_data_receive(USART5);
fifo_write(&s_uart5_rx, &b, 1U);
}
}
/**
* @brief UART6 中断处理函数,接收数据压入 FIFO
* @retval none
*/
void UART6_IRQHandler(void)
{
if (RESET != usart_interrupt_flag_get(UART6, USART_INT_FLAG_RBNE)) {
uint8_t b = usart_data_receive(UART6);
fifo_write(&s_uart6_rx, &b, 1U);
}
}
/**
* @brief UART7 中断处理函数,接收数据压入 FIFO
* @retval none
*/
void UART7_IRQHandler(void)
{
if (RESET != usart_interrupt_flag_get(UART7, USART_INT_FLAG_RBNE)) {
uint8_t b = usart_data_receive(UART7);
fifo_write(&s_uart7_rx, &b, 1U);
}
}
/* ===== 对外:兼容现有统一API ===== */
/**
* @brief 初始化指定串口
* @param usart_num 串口编号(0/2/3/4/5/6/7
* @retval 0 初始化成功;-1 参数不支持
*/
int v_usart_init(uint8_t usart_num)
{
usart_fifo_register_all();
switch (usart_num) {
case 0: usart0_init(); return 0;
case 2: usart2_init(); return 0;
case 3: uart3_init(); return 0;
case 4: uart4_init(); return 0;
case 5: usart5_init(); return 0;
case 6: uart6_init(); return 0;
case 7: uart7_init(); return 0;
default: return -1;
}
}
/**
* @brief 批量初始化全部串口
* @retval 0
*/
int v_usart_init_all(void)
{
usart_fifo_register_all();
usart0_init();
usart2_init();
uart3_init();
uart4_init();
usart5_init();
uart6_init();
uart7_init();
return 0;
}
/**
* @brief 统一发送二进制数据接口
* @param usart_num 串口编号
* @param data 发送缓冲区
* @param len 发送长度
* @retval 0 发送成功;-1 参数或编号不支持
*/
int v_usart_send_data(uint8_t usart_num, uint8_t *data, uint16_t len)
{
if (data == NULL || len == 0U) return -1;
switch (usart_num) {
case 0: usart_send_blocking(USART0, data, len); return 0;
case 2: usart_send_blocking(USART2, data, len); return 0;
case 3: usart_send_blocking(UART3, data, len); return 0;
case 4: usart_send_blocking(UART4, data, len); return 0;
case 5: usart_send_blocking(USART5, data, len); return 0;
case 6: usart_send_blocking(UART6, data, len); return 0;
case 7: usart_send_blocking(UART7, data, len); return 0;
default: return -1;
}
}
/**
* @brief 查询指定串口接收 FIFO 的有效数据长度
* @param usart_num 串口编号
* @retval >=0 有效数据长度;-1 编号不支持
*/
int v_usart_get_rx_data_count(uint8_t usart_num)
{
_fifo_t *f = get_fifo_by_id(usart_num);
if (f == NULL) return -1;
return (int)fifo_get_occupy_size(f);
}
/**
* @brief 从指定串口 FIFO 读取数据
* @param usart_num 串口编号
* @param data 读取目标缓冲区
* @param len 期望读取长度
* @retval >=0 实际读取长度;-1 参数或编号不支持
*/
int v_usart_read_rx_buffer(uint8_t usart_num, uint8_t *data, uint16_t len)
{
if (data == NULL || len == 0U) return -1;
_fifo_t *f = get_fifo_by_id(usart_num);
if (f == NULL) return -1;
return (int)fifo_read(f, data, len);
}
/**
* @brief 快速读取接口(当前复用普通读取实现)
* @param usart_num 串口编号
* @param data 读取目标缓冲区
* @param len 期望读取长度
* @retval >=0 实际读取长度;-1 参数或编号不支持
*/
int v_usart_read_rx_buffer_fast(uint8_t usart_num, uint8_t *data, uint16_t len)
{
return v_usart_read_rx_buffer(usart_num, data, len);
}
/**
* @brief 清空指定串口接收 FIFO
* @param usart_num 串口编号
* @retval 0 清空成功;-1 编号不支持
*/
int v_usart_clear_rx_buffer(uint8_t usart_num)
{
_fifo_t *f = get_fifo_by_id(usart_num);
if (f == NULL) return -1;
fifo_release(f);
switch (usart_num) {
case 0: fifo_register(f, s_uart0_rx_mem, UART0_RX_BUFFER_SIZE, NULL, NULL); break;
case 2: fifo_register(f, s_uart2_rx_mem, UART2_RX_BUFFER_SIZE, NULL, NULL); break;
case 3: fifo_register(f, s_uart3_rx_mem, UART3_RX_BUFFER_SIZE, NULL, NULL); break;
case 4: fifo_register(f, s_uart4_rx_mem, UART4_RX_BUFFER_SIZE, NULL, NULL); break;
case 5: fifo_register(f, s_uart5_rx_mem, UART5_RX_BUFFER_SIZE, NULL, NULL); break;
case 6: fifo_register(f, s_uart6_rx_mem, UART6_RX_BUFFER_SIZE, NULL, NULL); break;
case 7: fifo_register(f, s_uart7_rx_mem, UART7_RX_BUFFER_SIZE, NULL, NULL); break;
default: return -1;
}
return 0;
}
/**
* @brief 对外统一二进制接收接口
* @param usart_id 串口ID
* @param u8_recvBuf 接收缓存
* @param u16_len 期望读取长度
* @retval 实际读取长度,失败返回0
*/
uint16_t u16_usart_recv(E_USART_ID usart_id ,uint8_t* u8_recvBuf ,uint16_t u16_len)
{
if(usart_id == E_USART_3)
{
//printf("\r\n");
}
int ret = v_usart_read_rx_buffer((uint8_t)usart_id, u8_recvBuf, u16_len);
if (ret <= 0) {
return 0U;
}
#if 1
{
uint8_t slot = usart_hw_num_to_slot((uint8_t)usart_id);
if ((ret > 0) && (slot < USART_NUM_PRINT_CNT) && (u8_uasrt_print[slot] != 0U)) {
usart_dump_line("recv", (uint8_t)usart_id, u8_recvBuf, (uint16_t)ret);
}
}
#endif
return (uint16_t)ret;
}
/**
* @brief 对外统一二进制发送接口
* @param usart_id 串口ID
* @param u8_sendBuf 发送缓存
* @param u16_len 待发送长度
* @retval 实际发送长度,失败返回0
*/
uint16_t u16_usart_send(E_USART_ID usart_id ,uint8_t* u8_sendBuf ,uint16_t u16_len)
{
int ret;
#if 1
{
uint8_t slot = usart_hw_num_to_slot((uint8_t)usart_id);
if ((u16_len > 0U) && (slot < USART_NUM_PRINT_CNT) && (u8_uasrt_print[slot] != 0U)) {
usart_dump_line("send", (uint8_t)usart_id, u8_sendBuf, u16_len);
}
}
#endif
ret = v_usart_send_data((uint8_t)usart_id, u8_sendBuf, u16_len);
if (ret < 0) {
return 0U;
}
return u16_len;
}
/**
* @brief 按串口编号打印格式化字符串
* @param usart_num 串口编号
* @param format 格式化字符串
* @param ... 可变参数
* @retval >0 输出长度;<=0 表示失败或无输出
*/
int v_usart_printf(uint8_t usart_num, const char *format, ...)
{
char buf[256];
va_list args;
va_start(args, format);
int n = vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
if (n <= 0) return n;
if ((size_t)n >= sizeof(buf)) n = (int)(sizeof(buf) - 1U);
v_usart_send_data(usart_num, (uint8_t *)buf, (uint16_t)n);
return n;
}
void printf_hex(const char *mag , U8_T *data , U16_T len)
{
U16_T i;
const char *tag = (mag != NULL) ? mag : "";
if (data == NULL) {
printf("%s (00): <null>\r\n", tag);
return;
}
printf("%s (%02u): ", tag, (unsigned int)len);
for (i = 0U; i < len; i++) {
printf("%02X ", (unsigned int)data[i]);
}
printf("\r\n");
}
/**
* @brief printf 重定向接口,默认输出到 USART5
* @param ch 字符
* @param f 文件指针(未使用)
* @retval 原字符 ch
*/
int fputc(int ch, FILE *f)
{
(void)f;
uint8_t b = (uint8_t)ch;
uint32_t debug_periph;
if ((uint8_t)DEBUG_SHELL_USART_ID == (uint8_t)DEBUG_SHELL_USART_NULL) {
return ch;
}
debug_periph = usart_periph_from_id((uint8_t)DEBUG_SHELL_USART_ID);
if (debug_periph != 0U) {
usart_send_blocking(debug_periph, &b, 1U);
}
return ch;
}
+68
View File
@@ -0,0 +1,68 @@
/*!
\file usart.h
\brief the header file of USART
\version 2025-01-24, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef USART_H
#define USART_H
#include <stdint.h>
#include "main.h"
#include "publicdata/public_define.h"
typedef enum{
E_USART_0 = 0,
E_USART_2 = 2,
E_USART_3 = 3,
E_USART_4 = 4,
E_USART_5 = 5,
E_USART_6 = 6,
E_USART_7 = 7
}E_USART_ID;
/* 批量初始化所有串口 */
int v_usart_init_all(void);
/* 兼容层原子接口(meter_coll_task 等模块直接调用) */
int v_usart_send_data(uint8_t usart_num, uint8_t *data, uint16_t len);
int v_usart_read_rx_buffer(uint8_t usart_num, uint8_t *data, uint16_t len);
/* 对外二进制收发统一接口 */
uint16_t u16_usart_recv(E_USART_ID usart_id ,uint8_t* u8_recvBuf ,uint16_t u16_len);
uint16_t u16_usart_send(E_USART_ID usart_id ,uint8_t* u8_sendBuf ,uint16_t u16_len);
void printf_hex(const char *mag , U8_T *data , U16_T len);
/* Shell: usart_printf <slot 0~6> <0|1> 对应物理口 USART0,USART2,3,4,5,6,7;与 CCU601E_D 行为一致,按 slot 下标。 */
#define USART_NUM_PRINT_CNT (7U)
extern U8_T u8_uasrt_print[USART_NUM_PRINT_CNT];
#endif /* USART_H */
+13
View File
@@ -0,0 +1,13 @@
/**
* @file eeprom_map.h
* @brief Compatibility shim for legacy includes.
*
* EEPROM/FRAM capacity and logical partition macros are now defined in
* `fm24cl16.h`. Keep this header to avoid breaking old include paths.
*/
#ifndef EEPROM_MAP_H
#define EEPROM_MAP_H
#include "fm24cl16.h"
#endif /* EEPROM_MAP_H */
+102
View File
@@ -0,0 +1,102 @@
# eeprom 模块说明(i2c1 / fm24cl16
> 本文档描述 `CCU621_M` 工程中 `BSP/eeprom` 的职责、接口、初始化方式与测试方法。
[返回主说明](./../../README.md)
## 快速跳转
- [1. 模块职责](#1-模块职责)
- [2. 涉及文件](#2-涉及文件)
- [3. 初始化流程](#3-初始化流程)
- [4. 对外接口](#4-对外接口)
- [5. 已接入测试](#5-已接入测试)
- [6. 维护注意事项](#6-维护注意事项)
- [返回主说明 README](./../../README.md)
---
## 1. 模块职责
`BSP/eeprom` 负责 FM24CL16I2C FRAM)基础驱动能力,当前职责为:
- I2C1 引脚与时序配置;
- FRAM 地址初始化;
- FRAM 指定地址写入与读出;
- 提供上层测试调用接口(由 `app_test` 验证读写一致性)。
---
## 2. 涉及文件
| 文件 | 说明 |
|---|---|
| `BSP/eeprom/i2c1.h` | I2C1 宏配置与函数声明 |
| `BSP/eeprom/i2c1.c` | I2C1 GPIO/I2C 初始化与总线复位 |
| `BSP/eeprom/fm24cl16.h` | FM24CL16 对外接口声明 + 容量/地址分区宏 |
| `BSP/eeprom/fm24cl16.c` | FM24CL16 读写实现(`eeprom_buffer_write/read` 等) |
| `BSP/eeprom/eeprom_map.h` | 兼容头(转发到 `fm24cl16.h` |
> 说明:历史冗余文件 `eeprom_I2C_fm24cl16.c/.h` 已移除,避免双实现并存导致维护混乱。
---
## 3. 初始化流程
当前已在 `BSP/sys_drv_init.c``v_sys_hardware_init()` 中接入:
1. `gpio_config()`:配置 I2C1 对应 GPIO
2. `i2c_config()`:配置 I2C1 时序并使能;
3. `i2c_eeprom_init()`:初始化 FM24CL16 设备地址等参数。
调用位置:系统硬件初始化阶段(通信外设初始化流程中)。
---
## 4. 对外接口
### 4.1 I2C 基础接口(`i2c1.c`
- `void gpio_config(void);`
- `void i2c_config(void);`
- `void i2c_bus_reset(void);`
### 4.2 EEPROM 接口与地址分区(`fm24cl16.h` + `fm24cl16.c`
- `void i2c_eeprom_init(void);`
- `void eeprom_buffer_write(uint8_t *p_buffer, uint16_t write_address, uint16_t number_of_byte);`
- `void eeprom_buffer_read(uint8_t *p_buffer, uint16_t read_address, uint16_t number_of_byte);`
- `void eeprom_driver_init(void);`
- 容量/范围宏:`FRAM_SIZE_BYTES``FRAM_START_ADDR``FRAM_END_ADDR` 等;
- 分区宏:`EEPROM_ADDR_CHGRCD_MNG``EEPROM_ADDR_HISALARM_MNG``EEPROM_ADDR_UNSETTLED_MNG``EEPROM_ADDR_CARD_MNG``EEPROM_ADDR_A_LOG_DATA``EEPROM_ADDR_B_LOG_DATA`
> 说明:`eeprom_page_write()` 为驱动内部函数,已收敛为 `fm24cl16.c` 内部 `static`,不再对外暴露。
---
## 5. 已接入测试
`app/app_init/app_test.c` 中,测试流程已统一由 `v_app_test_periodic()` 调度,并受总开关控制:
- `APP_TEST_ENABLE`:0 时不编译测试实现,仅保留空函数桩;
- EEPROM 基础读写:固定地址 32B;
- EEPROM 多地址测试:覆盖分区起点与测试窗口地址,执行备份-写读校验-恢复;
- EEPROM 速度测试:在测试窗口执行循环读写并统计吞吐;
- Flash 测试:固定地址测速 + 1MB 步长 32MB 全空间校验。
---
## 6. 维护注意事项
1. 保持 EEPROM 仅一套实现,避免同名接口重复定义;
2. 修改 I2C 引脚/时序时,同步更新 `i2c1.h` 与硬件原理图;
3. 增加上层功能前,先通过 `u8_app_test_eeprom_rw_once()` 回归;
4. EEPROM 驱动已引入互斥访问,新增调用方需避免在中断上下文直接调用阻塞式接口;
5. 建议业务数据区与测试区分离(例如测试使用 `0x0600` 起始窗口),避免互相覆盖。
---
## 关联文档
- 主索引:[`CCU621_M/README.md`](./../../README.md)
- 本文位置:`BSP/eeprom/eeprom模块说明.md`
+631
View File
@@ -0,0 +1,631 @@
/*!
\file fm24cl16.c
\brief the read and write function file
\version 2025-01-24, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "fm24cl16.h"
#include "i2c1.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#define EEPROM_BLOCK0_ADDRESS 0xA0
#define MAX_RELOAD_SIZE 255
#define EEPROM_WRITE_DELAY_MS 5U
#define EEPROM_STATE_LOOP_MAX 64U
/* Stable mode on this board: fixed slave address + 16-bit internal address */
#define EEPROM_MEM_ADDR_16BIT 1U
#define EEPROM_DEVADDR_FIXED 1U
static uint8_t eeprom_address;
static volatile uint32_t g_eeprom_last_error = 0U;
static SemaphoreHandle_t g_eeprom_mutex = NULL;
/* forward declarations for internal helpers */
static void eeprom_i2c_clear_error_flags(void);
uint8_t eeprom_probe(uint16_t mem_address);
static void eeprom_lock(void)
{
if (g_eeprom_mutex != NULL) {
(void)xSemaphoreTake(g_eeprom_mutex, portMAX_DELAY);
}
}
static void eeprom_unlock(void)
{
if (g_eeprom_mutex != NULL) {
(void)xSemaphoreGive(g_eeprom_mutex);
}
}
enum {
EEPROM_ERR_NONE = 0U,
EEPROM_ERR_WR_BUSY_TIMEOUT = 1U,
EEPROM_ERR_WR_ADDR_TBE_TIMEOUT = 2U,
EEPROM_ERR_WR_DATA_TBE_TIMEOUT = 3U,
EEPROM_ERR_WR_STOP_TIMEOUT = 4U,
EEPROM_ERR_RD_BUSY_TIMEOUT = 5U,
EEPROM_ERR_RD_ADDR_TBE_TIMEOUT = 6U,
EEPROM_ERR_RD_RESTART_TC_TIMEOUT = 7U,
EEPROM_ERR_RD_RELOAD_TCR_TIMEOUT = 8U,
EEPROM_ERR_RD_DATA_RBNE_TIMEOUT = 9U,
EEPROM_ERR_RD_STOP_TIMEOUT = 10U,
EEPROM_ERR_LOOP_GUARD = 11U,
EEPROM_ERR_I2C_NACK = 12U,
EEPROM_ERR_I2C_BUS = 13U
};
static uint8_t eeprom_i2c_error_pending(void)
{
if(i2c_flag_get(I2CX, I2C_FLAG_NACK)) {
g_eeprom_last_error = EEPROM_ERR_I2C_NACK;
i2c_flag_clear(I2CX, I2C_FLAG_NACK);
return 1U;
}
if(i2c_flag_get(I2CX, I2C_FLAG_BERR) || i2c_flag_get(I2CX, I2C_FLAG_LOSTARB) ||
i2c_flag_get(I2CX, I2C_FLAG_OUERR)) {
g_eeprom_last_error = EEPROM_ERR_I2C_BUS;
i2c_flag_clear(I2CX, I2C_FLAG_BERR);
i2c_flag_clear(I2CX, I2C_FLAG_LOSTARB);
i2c_flag_clear(I2CX, I2C_FLAG_OUERR);
return 1U;
}
return 0U;
}
static void eeprom_i2c_clear_error_flags(void)
{
i2c_flag_clear(I2CX, I2C_FLAG_NACK);
i2c_flag_clear(I2CX, I2C_FLAG_BERR);
i2c_flag_clear(I2CX, I2C_FLAG_LOSTARB);
i2c_flag_clear(I2CX, I2C_FLAG_OUERR);
i2c_flag_clear(I2CX, I2C_FLAG_TIMEOUT);
}
static uint8_t eeprom_make_dev_addr(uint16_t mem_addr)
{
#if (EEPROM_DEVADDR_FIXED == 1U)
(void)mem_addr;
return (uint8_t)EEPROM_BLOCK0_ADDRESS;
#else
return (uint8_t)(EEPROM_BLOCK0_ADDRESS | ((mem_addr >> 7U) & 0x0EU));
#endif
}
static void eeprom_page_write(uint8_t *p_buffer, uint16_t write_address, uint8_t number_of_byte);
static uint8_t eeprom_read_chunk(uint8_t *p_buffer, uint16_t read_address, uint8_t number_of_byte);
uint32_t eeprom_get_last_error(void)
{
return g_eeprom_last_error;
}
uint8_t eeprom_probe(uint16_t mem_address)
{
uint32_t timeout = 0U;
uint8_t dev_addr = eeprom_make_dev_addr(mem_address);
eeprom_lock();
eeprom_i2c_clear_error_flags();
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_master_addressing(I2CX, dev_addr, I2C_MASTER_TRANSMIT);
i2c_transfer_byte_number_config(I2CX,
#if (EEPROM_MEM_ADDR_16BIT == 1U)
2U
#else
1U
#endif
);
i2c_automatic_end_disable(I2CX);
while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_BUSY_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_start_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_stop_on_bus(I2CX);
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
/* send memory address bytes to validate addressing scheme */
#if (EEPROM_MEM_ADDR_16BIT == 1U)
i2c_data_transmit(I2CX, (uint8_t)((mem_address >> 8U) & 0xFFU));
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_stop_on_bus(I2CX);
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
#endif
i2c_data_transmit(I2CX, (uint8_t)(mem_address & 0xFFU));
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TC)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_stop_on_bus(I2CX);
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_DATA_TBE_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_stop_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_STPDET)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_STOP_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
g_eeprom_last_error = EEPROM_ERR_NONE;
eeprom_unlock();
return I2C_OK;
}
/*!
\brief unified init entry for EEPROM driver
\param[in] none
\param[out] none
\retval none
*/
void eeprom_driver_init(void)
{
gpio_config();
i2c_config();
i2c_eeprom_init();
if (g_eeprom_mutex == NULL) {
g_eeprom_mutex = xSemaphoreCreateMutex();
}
}
/*!
\brief initialize peripherals used by the I2C EEPROM driver
\param[in] none
\param[out] none
\retval none
*/
void i2c_eeprom_init(void)
{
eeprom_address = EEPROM_BLOCK0_ADDRESS;
}
/*!
\brief write buffer of data to the I2C EEPROM
\param[in] p_buffer: pointer to the buffer containing the data to be written to the EEPROM
\param[in] write_address: EEPROM's internal address to write to
\param[in] number_of_byte: number of bytes to write to the EEPROM
\param[out] none
\retval none
*/
void eeprom_buffer_write(uint8_t *p_buffer, uint16_t write_address, uint16_t number_of_byte)
{
uint16_t remain;
uint16_t off;
uint8_t chunk;
uint8_t page_left;
uint8_t retry;
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
g_eeprom_last_error = EEPROM_ERR_NONE;
return;
}
eeprom_lock();
g_eeprom_last_error = EEPROM_ERR_NONE;
remain = number_of_byte;
off = 0U;
while (remain > 0U) {
/* Do not cross page boundary (AT24/FRAM compatible safe strategy). */
page_left = (uint8_t)(I2C_PAGE_SIZE - (((uint16_t)(write_address + off)) % I2C_PAGE_SIZE));
if (page_left == 0U) {
page_left = I2C_PAGE_SIZE;
}
chunk = (remain > (uint16_t)page_left) ? page_left : (uint8_t)remain;
if (chunk > I2C_PAGE_SIZE) {
chunk = I2C_PAGE_SIZE;
}
retry = 0U;
while (retry < 3U) {
eeprom_page_write(&p_buffer[off], (uint16_t)(write_address + off), chunk);
if (g_eeprom_last_error == EEPROM_ERR_NONE) {
break;
}
/* retry after simple bus recovery */
i2c_bus_reset();
vTaskDelay(pdMS_TO_TICKS(1U));
retry++;
}
if (g_eeprom_last_error != EEPROM_ERR_NONE) {
eeprom_unlock();
return;
}
off = (uint16_t)(off + (uint16_t)chunk);
remain = (uint16_t)(remain - (uint16_t)chunk);
vTaskDelay(pdMS_TO_TICKS(EEPROM_WRITE_DELAY_MS));
}
eeprom_unlock();
}
/*!
\brief write more than one byte to the EEPROM with a single write cycle
\param[in] p_buffer: pointer to the buffer containing the data to be written to the EEPROM
\param[in] write_address: EEPROM's internal address to write to
\param[in] number_of_byte: number of bytes to write to the EEPROM
\param[out] none
\retval none
*/
static void eeprom_page_write(uint8_t *p_buffer, uint16_t write_address, uint8_t number_of_byte)
{
uint32_t timeout = 0U;
uint8_t bytes_sent = 0;
uint8_t mem_high_addr = (uint8_t)((write_address >> 8U) & 0xFFU);
uint8_t mem_low_addr = (uint8_t)(write_address & 0xFFU);
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
return;
}
eeprom_address = eeprom_make_dev_addr(write_address);
eeprom_i2c_clear_error_flags();
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_BUSY_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_master_addressing(I2CX, eeprom_address, I2C_MASTER_TRANSMIT);
i2c_transfer_byte_number_config(I2CX,
#if (EEPROM_MEM_ADDR_16BIT == 1U)
(uint32_t)number_of_byte + 2U
#else
(uint32_t)number_of_byte + 1U
#endif
);
/* Use explicit TC->STOP sequence to avoid partial writes */
i2c_automatic_end_disable(I2CX);
i2c_reload_disable(I2CX);
i2c_start_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
#if (EEPROM_MEM_ADDR_16BIT == 1U)
i2c_data_transmit(I2CX, mem_high_addr);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
#endif
i2c_data_transmit(I2CX, mem_low_addr);
/* wait address byte(s) accepted before data */
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
while(bytes_sent < number_of_byte) {
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_DATA_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_data_transmit(I2CX, *p_buffer);
p_buffer++;
bytes_sent++;
}
/* wait transfer complete then send stop */
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TC)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_DATA_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_stop_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_STPDET)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_STOP_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
}
static uint8_t eeprom_read_chunk(uint8_t *p_buffer, uint16_t read_address, uint8_t number_of_byte)
{
uint32_t timeout = 0U;
uint8_t mem_high_addr = (uint8_t)((read_address >> 8U) & 0xFFU);
uint8_t mem_low_addr = (uint8_t)(read_address & 0xFFU);
uint8_t dev_addr = eeprom_make_dev_addr(read_address);
uint8_t i = 0U;
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
return I2C_FAIL;
}
eeprom_i2c_clear_error_flags();
while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_BUSY_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
/* phase 1: set memory low address */
i2c_master_addressing(I2CX, dev_addr, I2C_MASTER_TRANSMIT);
i2c_transfer_byte_number_config(I2CX,
#if (EEPROM_MEM_ADDR_16BIT == 1U)
2U
#else
1U
#endif
);
i2c_automatic_end_disable(I2CX);
i2c_start_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
#if (EEPROM_MEM_ADDR_16BIT == 1U)
i2c_data_transmit(I2CX, mem_high_addr);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
#endif
i2c_data_transmit(I2CX, mem_low_addr);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TC)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_RESTART_TC_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
/* phase 2: repeated start + read bytes */
i2c_master_addressing(I2CX, dev_addr, I2C_MASTER_RECEIVE);
i2c_transfer_byte_number_config(I2CX, number_of_byte);
i2c_automatic_end_enable(I2CX);
i2c_start_on_bus(I2CX);
for(i = 0U; i < number_of_byte; i++) {
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_RBNE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_DATA_RBNE_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
p_buffer[i] = (uint8_t)i2c_data_receive(I2CX);
}
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_STPDET)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_STOP_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
return I2C_OK;
}
/*!
\brief read data from the EEPROM
\param[in] p_buffer: pointer to the buffer that receives the data read from the EEPROM
\param[in] read_address: EEPROM's internal address to start reading from
\param[in] number_of_byte: number of bytes to reads from the EEPROM
\param[out] none
\retval none
*/
void eeprom_buffer_read(uint8_t *p_buffer, uint16_t read_address, uint16_t number_of_byte)
{
uint16_t remain;
uint16_t off;
uint8_t chunk;
uint8_t retry;
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
g_eeprom_last_error = EEPROM_ERR_NONE;
return;
}
eeprom_lock();
g_eeprom_last_error = EEPROM_ERR_NONE;
remain = number_of_byte;
off = 0U;
while (remain > 0U) {
/* For read, allow larger chunk but keep it reasonable */
chunk = (remain > 128U) ? 128U : (uint8_t)remain;
retry = 0U;
while (retry < 3U) {
if (eeprom_read_chunk(&p_buffer[off], (uint16_t)(read_address + off), chunk) == I2C_OK) {
break;
}
i2c_bus_reset();
vTaskDelay(pdMS_TO_TICKS(1U));
retry++;
}
if (g_eeprom_last_error != EEPROM_ERR_NONE) {
eeprom_unlock();
return;
}
off = (uint16_t)(off + (uint16_t)chunk);
remain = (uint16_t)(remain - (uint16_t)chunk);
vTaskDelay(pdMS_TO_TICKS(1U));
}
eeprom_unlock();
}
+120
View File
@@ -0,0 +1,120 @@
/*!
\file fm24cl16.h
\brief the header file of AT24Cxx
\version 2025-01-24, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef FM24CL16_H
#define FM24CL16_H
#include "gd32h7xx_it.h"
typedef enum {
I2C_START = 0,
I2C_SEND_ADDRESS,
I2C_RESTART,
I2C_TRANSMIT_DATA,
I2C_RELOAD,
I2C_STOP,
I2C_END
} i2c_process_enum;
#define I2C_TIME_OUT (uint32_t)(20000)
#define EEP_FIRST_PAGE 0x00
#define I2C_OK 0
#define I2C_FAIL 1
/* FM24xx logical capacity/address map */
#define FRAM_SIZE_KBIT (16U)
#define FRAM_SIZE_BYTES (2048U)
#define FRAM_ADDR_BITS (11U)
#define FRAM_START_ADDR (0x0000U)
#define FRAM_END_ADDR (0x07FFU)
#define FRAM_TOTAL_ADDR (0x0800U)
#define FRAM_PAGE_SIZE (256U)
#define FRAM_PAGES_COUNT (8U)
/* Logical partitions (ported from CCU601E_D) */
#define EEPROM_ADDR_CHGRCD_MNG (0x0000U) /* charge record manager */
#define EEPROM_ADDR_HISALARM_MNG (0x0020U) /* history alarm manager */
#define EEPROM_ADDR_UNSETTLED_MNG (0x0040U) /* unsettled order manager */
#define EEPROM_ADDR_CARD_MNG (0x00A0U) /* card whitelist manager */
#define EEPROM_ADDR_A_LOG_DATA (0x0100U) /* gun A temporary order data */
#define EEPROM_ADDR_B_LOG_DATA (0x0400U) /* gun B temporary order data */
#define EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL 0x0700U
#define EEPROM_OCPP_MV_OFFLINE_CTRL_SIZE 128u
#define EEPROM_ADDR_OCPP_FW_INSTALLED_PENDING 0x0780U
#define EEPROM_OCPP_FW_INSTALLED_PENDING_VAL 0x55U
#define EEPROM_OCPP_FW_INSTALLED_PENDING_CLEAR 0x00U
#define EEPROM_ADDR_IN_RANGE(addr) ((uint16_t)(addr) <= (uint16_t)FRAM_END_ADDR)
/* function declarations */
/* unified EEPROM init: GPIO + I2C + device address */
void eeprom_driver_init(void);
/* initialize peripherals used by the I2C EEPROM driver */
void i2c_eeprom_init(void);
/* write buffer of data to the I2C EEPROM */
void eeprom_buffer_write(uint8_t *p_buffer, uint16_t write_address, uint16_t number_of_byte);
/* read data from the EEPROM */
void eeprom_buffer_read(uint8_t *p_buffer, uint16_t read_address, uint16_t number_of_byte);
/* read latest driver error code (0 means no error) */
uint32_t eeprom_get_last_error(void);
/* probe one EEPROM block address and return I2C_OK/I2C_FAIL */
uint8_t eeprom_probe(uint16_t mem_address);
/* --------------------------------------------------------------------------
* Compatibility layer for migrated modules from CCU601E_D
* - old API: eeprom_write/eeprom_read(...) returns HAL_OK on success
* - local API: eeprom_buffer_write/eeprom_buffer_read(...) with error queried
* -------------------------------------------------------------------------- */
#ifndef HAL_OK
#define HAL_OK (0)
#endif
#ifndef HAL_ERROR
#define HAL_ERROR (1)
#endif
static inline int eeprom_write(uint16_t addr, uint8_t *buf, uint16_t len)
{
eeprom_buffer_write(buf, addr, len);
return (eeprom_get_last_error() == 0U) ? HAL_OK : HAL_ERROR;
}
static inline int eeprom_read(uint16_t addr, uint8_t *buf, uint16_t len)
{
eeprom_buffer_read(buf, addr, len);
return (eeprom_get_last_error() == 0U) ? HAL_OK : HAL_ERROR;
}
#endif /* AT24CXX_H */
+128
View File
@@ -0,0 +1,128 @@
/*!
\file i2c.c
\brief I2C configuration file
\version 2025-01-24, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "i2c1.h"
#include <stdio.h>
#include "gd32h7xx.h"
/*!
\brief configure the GPIO ports
\param[in] none
\param[out] none
\retval none
*/
void gpio_config(void)
{
/* enable GPIO clock */
rcu_periph_clock_enable(RCU_GPIO_I2C);
/* enable I2C clock */
rcu_periph_clock_enable(RCU_I2C);
/* connect I2C_SCL_PIN to I2C_SCL */
gpio_af_set(I2C_SCL_PORT, I2C_GPIO_AF, I2C_SCL_PIN);
/* connect I2C_SDA_PIN to I2C_SDA */
gpio_af_set(I2C_SDA_PORT, I2C_GPIO_AF, I2C_SDA_PIN);
/* configure GPIO pins of I2C */
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_60MHZ, I2C_SCL_PIN);
gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SDA_PIN);
gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_60MHZ, I2C_SDA_PIN);
}
/*!
\brief configure the I2C interface
\param[in] none
\param[out] none
\retval none
*/
void i2c_config(void)
{
/* configure I2C timing */
i2c_timing_config(I2CX, 0x3, 0x7, 0);
i2c_master_clock_config(I2CX, 0x32, 0x32);
/* enable analog filter and disable digital filter for bus robustness */
i2c_analog_noise_filter_enable(I2CX);
i2c_digital_noise_filter_config(I2CX, FILTER_DISABLE);
/* enable I2C */
i2c_enable(I2CX);
}
/*!
\brief reset I2C gpio configure
\param[in] none
\param[out] none
\retval none
*/
void i2c_gpio_reset(void)
{
/* reset I2C_SCL_PIN and I2C_SDA_PIN */
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, I2C_SCL_PIN);
gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, I2C_SCL_PIN);
gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, I2C_SDA_PIN);
gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, I2C_SDA_PIN);
}
/*!
\brief reset i2c bus
\param[in] none
\param[out] none
\retval none
*/
void i2c_bus_reset()
{
/* configure SDA/SCL for GPIO */
GPIO_BC(I2C_SCL_PORT) |= I2C_SCL_PIN;
GPIO_BC(I2C_SDA_PORT) |= I2C_SDA_PIN;
/* reset I2C_SCL_PIN and I2C_SDA_PIN */
i2c_gpio_reset();
__NOP();
__NOP();
__NOP();
__NOP();
__NOP();
/* stop signal */
GPIO_BOP(I2C_SCL_PORT) |= I2C_SCL_PIN;
__NOP();
__NOP();
__NOP();
__NOP();
__NOP();
GPIO_BOP(I2C_SDA_PORT) |= I2C_SDA_PIN;
/* connect I2C_SCL_PIN to I2C_SCL */
/* connect I2C_SDA_PIN to I2C_SDA */
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_60MHZ, I2C_SCL_PIN);
gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SDA_PIN);
gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_60MHZ, I2C_SDA_PIN);
}
+60
View File
@@ -0,0 +1,60 @@
/*!
\file i2c.h
\brief the header file of I2C
\version 2025-01-24, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef I2C_H
#define I2C_H
#define I2C_SPEED 400000
#define I2C_PAGE_SIZE 8
#define I2CX I2C1
#define RCU_GPIO_I2C RCU_GPIOF
#define RCU_I2C RCU_I2C1
#define I2C_SCL_PORT GPIOF
#define I2C_SDA_PORT GPIOF
#define I2C_SCL_PIN GPIO_PIN_1
#define I2C_SDA_PIN GPIO_PIN_0
#define I2C_GPIO_AF GPIO_AF_4
/* function declarations */
/* configure the GPIO ports */
void gpio_config(void);
/* configure the I2C interface */
void i2c_config(void);
/* reset I2C gpio configure */
void i2c_gpio_reset(void);
/* reset i2c bus */
void i2c_bus_reset(void);
#endif /* I2C_H */
+550
View File
@@ -0,0 +1,550 @@
#include "exflash_spi4_gd25qxx.h"
#include "gd32h7xx_dma.h"
#include <stddef.h>
/* NOR Flash 标准指令 */
#define EXFLASH_CMD_WRITE (0x02u) /* Page Program */
#define EXFLASH_CMD_WREN (0x06u) /* Write Enable */
#define EXFLASH_CMD_READ (0x03u) /* Read Data */
#define EXFLASH_CMD_RDSR (0x05u) /* Read Status Register */
#define EXFLASH_CMD_RDID (0x9Fu) /* Read JEDEC ID */
#define EXFLASH_CMD_SE (0x20u) /* 4KB Sector Erase */
#define EXFLASH_CMD_BE (0xC7u) /* Bulk Erase */
#define EXFLASH_CMD_EN4B (0xB7u) /* Enter 4-byte address mode */
/* 状态寄存器位定义 */
#define EXFLASH_SR_WIP_MASK (0x01u) /* WIP=1 表示写/擦进行中 */
/* 读操作发送的占位字节 */
#define EXFLASH_DUMMY_BYTE (0xA5u)
#define EXFLASH_DMA_PERIPH (DMA0)
#define EXFLASH_DMA_RX_CH (DMA_CH1)
#define EXFLASH_DMA_TX_CH (DMA_CH2)
#define EXFLASH_DMA_WAIT_MAX_LOOP (0x00FFFFFFu)
/* Cortex-M7 D-Cache line size = 32 bytes */
#define EXFLASH_DCACHE_LINE_SIZE (32u)
/* DMA 中断置位标志:1=该通道传输完成 */
static volatile uint8_t s_spi4_dma_rx_done = 0u;
static volatile uint8_t s_spi4_dma_tx_done = 0u;
/* 读阶段 TX 使用的固定哑字节;DMA 配置为 memory_inc disable */
static uint8_t s_spi4_dma_dummy_tx = EXFLASH_DUMMY_BYTE;
/* 写阶段 RX 丢弃字节;DMA 配置为 memory_inc disable */
static uint8_t s_spi4_dma_dummy_rx = 0u;
static uint32_t cache_addr_down_align(uint32_t addr, uint32_t align)
{
return addr & ~(align - 1u);
}
static uint32_t cache_len_up_align(uint32_t addr, uint32_t len, uint32_t align)
{
uint32_t end = addr + len;
uint32_t end_aligned = (end + (align - 1u)) & ~(align - 1u);
return end_aligned - cache_addr_down_align(addr, align);
}
static void dcache_clean_by_addr(uint32_t addr, uint32_t len)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t a = cache_addr_down_align(addr, EXFLASH_DCACHE_LINE_SIZE);
uint32_t l = cache_len_up_align(addr, len, EXFLASH_DCACHE_LINE_SIZE);
SCB_CleanDCache_by_Addr((uint32_t *)a, (int32_t)l);
#else
(void)addr;
(void)len;
#endif
}
static void dcache_invalidate_by_addr(uint32_t addr, uint32_t len)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t a = cache_addr_down_align(addr, EXFLASH_DCACHE_LINE_SIZE);
uint32_t l = cache_len_up_align(addr, len, EXFLASH_DCACHE_LINE_SIZE);
SCB_InvalidateDCache_by_Addr((uint32_t *)a, (int32_t)l);
#else
(void)addr;
(void)len;
#endif
}
/**
* @brief 发送 32bit 地址(A31~A0)。
* @param addr 线性地址。
*/
static void spi_flash_send_addr(uint32_t addr)
{
spi_flash_send_byte((uint8_t)((addr >> 24) & 0xFFu));
spi_flash_send_byte((uint8_t)((addr >> 16) & 0xFFu));
spi_flash_send_byte((uint8_t)((addr >> 8) & 0xFFu));
spi_flash_send_byte((uint8_t)(addr & 0xFFu));
}
/**
* @brief 进入 4-byte 地址模式(支持 32MB 地址空间访问)。
*/
static void spi_flash_enter_4byte_addr_mode(void)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_EN4B);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待 DMA 收发双通道完成。
* @return 1 成功,0 超时。
*/
static uint8_t spi4_dma_wait_done(void)
{
uint32_t timeout = EXFLASH_DMA_WAIT_MAX_LOOP;
while (timeout-- > 0u) {
if ((s_spi4_dma_rx_done == 1u) && (s_spi4_dma_tx_done == 1u)) {
return 1u;
}
}
return 0u;
}
/**
* @brief 启动 SPI4 DMA 全双工传输(同时配置 RX/TX)。
* @param tx_buf TX 源地址。
* @param tx_inc TX 地址是否自增。
* @param rx_buf RX 目标地址。
* @param rx_inc RX 地址是否自增。
* @param length 传输字节数。
* @return 1 启动并完成成功,0 超时/失败。
*/
static uint8_t spi4_dma_transfer(uint8_t *tx_buf,
uint32_t tx_inc,
uint8_t *rx_buf,
uint32_t rx_inc,
uint16_t length)
{
dma_single_data_parameter_struct dma_init_struct;
if ((tx_buf == NULL) || (rx_buf == NULL) || (length == 0u)) {
return 0u;
}
s_spi4_dma_rx_done = 0u;
s_spi4_dma_tx_done = 0u;
dma_deinit(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_deinit(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
dma_single_data_para_struct_init(&dma_init_struct);
/* DMA 读 TX 前清 cacheDMA 写 RX 后需要 invalidate cache */
if (tx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_clean_by_addr((uint32_t)tx_buf, (uint32_t)length);
}
if (rx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_invalidate_by_addr((uint32_t)rx_buf, (uint32_t)length);
}
/* RX: SPI4_RDATA -> memory */
dma_init_struct.request = DMA_REQUEST_SPI4_RX;
dma_init_struct.periph_addr = (uint32_t)&SPI_RDATA(SPI4);
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE;
dma_init_struct.memory0_addr = (uint32_t)rx_buf;
dma_init_struct.memory_inc = rx_inc;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init_struct.number = length;
dma_single_data_mode_init(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, &dma_init_struct);
dma_interrupt_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FTF);
/* TX: memory -> SPI4_TDATA */
dma_init_struct.request = DMA_REQUEST_SPI4_TX;
dma_init_struct.periph_addr = (uint32_t)&SPI_TDATA(SPI4);
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE;
dma_init_struct.memory0_addr = (uint32_t)tx_buf;
dma_init_struct.memory_inc = tx_inc;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init_struct.number = length;
dma_single_data_mode_init(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, &dma_init_struct);
dma_interrupt_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FTF);
spi_dma_enable(SPI4, SPI_DMA_RECEIVE);
spi_dma_enable(SPI4, SPI_DMA_TRANSMIT);
/* 显式启动一次主机传输,保证时钟输出 */
spi_master_transfer_start(SPI4, SPI_TRANS_START);
dma_channel_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
if (spi4_dma_wait_done() == 0u) {
spi_dma_disable(SPI4, SPI_DMA_RECEIVE);
spi_dma_disable(SPI4, SPI_DMA_TRANSMIT);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
return 0u;
}
spi_dma_disable(SPI4, SPI_DMA_RECEIVE);
spi_dma_disable(SPI4, SPI_DMA_TRANSMIT);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
/* DMA 写完 RX 后,CPU 读取前再次 invalidate,避免 cache 仍是旧数据 */
if (rx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_invalidate_by_addr((uint32_t)rx_buf, (uint32_t)length);
}
return 1u;
}
/**
* @brief 初始化 SPI4 与外部 Flash 片选 GPIO。
* @details
* - CS: PJ9(软件控制)
* - SCK: PK0MOSI: PJ10MISO: PJ11
* - SPI Mode08bitMSB first
*/
void spi_flash_init(void)
{
spi_parameter_struct spi_init_struct;
/* 1) 时钟使能 */
rcu_periph_clock_enable(RCU_GPIOJ);
rcu_periph_clock_enable(RCU_GPIOK);
rcu_periph_clock_enable(RCU_SPI4);
rcu_periph_clock_enable(RCU_DMA0);
rcu_periph_clock_enable(RCU_DMAMUX);
rcu_spi_clock_config(IDX_SPI4, RCU_SPISRC_APB2);
/* DMA 中断配置放在 DMA 时钟就绪后,避免早期访问 DMA 寄存器引发异常 */
nvic_irq_enable(DMA0_Channel1_IRQn, 4U, 0U);
nvic_irq_enable(DMA0_Channel2_IRQn, 4U, 0U);
/* 2) 片选脚初始化:默认拉高(未选中) */
gpio_mode_set(GPIOJ, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_9);
SPI_FLASH_CS_HIGH();
/* 3) SPI4 复用脚初始化 */
gpio_af_set(GPIOK, GPIO_AF_5, GPIO_PIN_0); /* SCK */
gpio_af_set(GPIOJ, GPIO_AF_5, GPIO_PIN_10 | GPIO_PIN_11); /* MOSI/MISO */
gpio_mode_set(GPIOK, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_output_options_set(GPIOK, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_0);
gpio_mode_set(GPIOJ, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_11);
gpio_mode_set(GPIOJ, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_10);
/* 4) SPI4 参数初始化 */
spi_i2s_deinit(SPI4);
spi_struct_para_init(&spi_init_struct);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; /* 全双工 */
spi_init_struct.device_mode = SPI_MASTER; /* 主机 */
spi_init_struct.data_size = SPI_DATASIZE_8BIT; /* 8bit 帧 */
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /* Mode 0 */
spi_init_struct.nss = SPI_NSS_SOFT; /* 软件 NSS */
spi_init_struct.prescale = SPI_PSC_32; /* 分频,保证稳定 */
spi_init_struct.endian = SPI_ENDIAN_MSB; /* 高位先发 */
spi_init(SPI4, &spi_init_struct);
/* 5) 使能 SPI 外设 */
spi_byte_access_enable(SPI4);
spi_nss_output_enable(SPI4);
spi_enable(SPI4);
/* 清理可能残留的 DMA 中断完成标志,防止首次传输误触发 */
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF);
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF);
/* 启用 4-byte 地址模式,支持访问 0x00000000~0x01FFFFFF32MB */
spi_flash_enter_4byte_addr_mode();
}
/**
* @brief 擦除指定地址所在扇区(4KB)。
* @param sector_addr 32bit 扇区地址(建议 4KB 对齐)。
*/
void spi_flash_sector_erase(uint32_t sector_addr)
{
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_SE);
spi_flash_send_addr(sector_addr);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 全片擦除。
* @warning 全片擦除耗时很长,仅在必要时调用。
*/
void spi_flash_bulk_erase(void)
{
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_BE);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 页内编程(不跨页)。
* @param pbuffer 写入源缓冲区。
* @param write_addr 写起始地址。
* @param num_byte_to_write 页内写入字节数(建议 <=256 且不跨页)。
*/
void spi_flash_page_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
if ((pbuffer == NULL) || (num_byte_to_write == 0u)) {
return;
}
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_WRITE);
spi_flash_send_addr(write_addr);
(void)spi4_dma_transfer(pbuffer,
DMA_MEMORY_INCREASE_ENABLE,
&s_spi4_dma_dummy_rx,
DMA_MEMORY_INCREASE_DISABLE,
num_byte_to_write);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 连续写(自动按页拆分)。
* @param pbuffer 写入源缓冲区。
* @param write_addr 写起始地址。
* @param num_byte_to_write 写入总字节数。
* @details
* 原始实现将分页计数变量定义为 uint8_t,在数据量 >255 页时会溢出;
* 此处统一使用 uint32_t,避免分页计算错误。
*/
void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
uint32_t addr_offset;
uint32_t first_page_space;
uint32_t remain;
uint32_t page_bytes;
if ((pbuffer == NULL) || (num_byte_to_write == 0u)) {
return;
}
addr_offset = write_addr % SPI_FLASH_PAGE_SIZE;
first_page_space = SPI_FLASH_PAGE_SIZE - addr_offset;
remain = num_byte_to_write;
/* 若首地址非页对齐,先补齐当前页 */
if ((addr_offset != 0u) && (remain != 0u)) {
page_bytes = (remain < first_page_space) ? remain : first_page_space;
spi_flash_page_write(pbuffer, write_addr, (uint16_t)page_bytes);
write_addr += page_bytes;
pbuffer += page_bytes;
remain -= page_bytes;
}
/* 整页写 */
while (remain >= SPI_FLASH_PAGE_SIZE) {
spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);
write_addr += SPI_FLASH_PAGE_SIZE;
pbuffer += SPI_FLASH_PAGE_SIZE;
remain -= SPI_FLASH_PAGE_SIZE;
}
/* 尾包写 */
if (remain > 0u) {
spi_flash_page_write(pbuffer, write_addr, (uint16_t)remain);
}
}
/**
* @brief 连续读取数据。
* @param pbuffer 读出目标缓冲区。
* @param read_addr 读取起始地址。
* @param num_byte_to_read 读取字节数。
*/
void spi_flash_buffer_read(uint8_t *pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
if ((pbuffer == NULL) || (num_byte_to_read == 0u)) {
return;
}
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_READ);
spi_flash_send_addr(read_addr);
(void)spi4_dma_transfer(&s_spi4_dma_dummy_tx,
DMA_MEMORY_INCREASE_DISABLE,
pbuffer,
DMA_MEMORY_INCREASE_ENABLE,
num_byte_to_read);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 读取 JEDEC ID(三字节)。
* @return 24bit IDManufacturer[23:16] | MemoryType[15:8] | Capacity[7:0]。
*/
uint32_t spi_flash_read_id(void)
{
uint32_t id0;
uint32_t id1;
uint32_t id2;
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_RDID);
id0 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
id1 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
id2 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
SPI_FLASH_CS_HIGH();
return (id0 << 16) | (id1 << 8) | id2;
}
/**
* @brief 发起连续读序列(保持片选有效)。
* @param read_addr 读取起始地址。
*/
void spi_flash_start_read_sequence(uint32_t read_addr)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_READ);
spi_flash_send_addr(read_addr);
}
/**
* @brief 在连续读序列中读取 1 字节。
* @return 读到的数据字节。
*/
uint8_t spi_flash_read_byte(void)
{
return spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
}
/**
* @brief SPI4 全双工收发一个字节。
* @param byte 发送字节。
* @return 同步接收字节。
*/
uint8_t spi_flash_send_byte(uint8_t byte)
{
uint32_t timeout = 0x00FFFFFFu;
/* GD32H7 SPI 主机在部分场景需要显式启动传输,否则时钟不输出,RP 永远不置位 */
spi_master_transfer_start(SPI4, SPI_TRANS_START);
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TP)) && (timeout-- > 0u)) {
/* 等待发送缓冲区可写 */
}
if (timeout == 0u) {
return 0xFFu;
}
spi_i2s_data_transmit(SPI4, byte);
timeout = 0x00FFFFFFu;
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RP)) && (timeout-- > 0u)) {
/* 等待接收缓冲区有数据 */
}
if (timeout == 0u) {
return 0xFFu;
}
return (uint8_t)spi_i2s_data_receive(SPI4);
}
/**
* @brief SPI4 全双工收发半字。
* @param half_word 发送半字。
* @return 同步接收半字。
*/
uint16_t spi_flash_send_halfword(uint16_t half_word)
{
uint32_t timeout = 0x00FFFFFFu;
spi_master_transfer_start(SPI4, SPI_TRANS_START);
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TP)) && (timeout-- > 0u)) {
/* 等待发送缓冲区可写 */
}
if (timeout == 0u) {
return 0xFFFFu;
}
spi_i2s_data_transmit(SPI4, half_word);
timeout = 0x00FFFFFFu;
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RP)) && (timeout-- > 0u)) {
/* 等待接收缓冲区有数据 */
}
if (timeout == 0u) {
return 0xFFFFu;
}
return (uint16_t)spi_i2s_data_receive(SPI4);
}
/**
* @brief 发送 Write Enable 指令(WREN)。
*/
void spi_flash_write_enable(void)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_WREN);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待写/擦操作结束(轮询 SR.WIP)。
*/
void spi_flash_wait_for_write_end(void)
{
uint8_t flash_status;
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_RDSR);
do {
flash_status = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
} while ((flash_status & EXFLASH_SR_WIP_MASK) != 0u);
SPI_FLASH_CS_HIGH();
}
/**
* @brief DMA0 Channel1 IRQSPI4 RX 完成中断。
*/
void DMA0_Channel1_IRQHandler(void)
{
if (SET == dma_interrupt_flag_get(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF);
s_spi4_dma_rx_done = 1u;
}
}
/**
* @brief DMA0 Channel2 IRQSPI4 TX 完成中断。
*/
void DMA0_Channel2_IRQHandler(void)
{
if (SET == dma_interrupt_flag_get(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF);
s_spi4_dma_tx_done = 1u;
}
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef EXFLASH_SPI4_GD25QXX_H
#define EXFLASH_SPI4_GD25QXX_H
#include "gd32h7xx.h"
/* GD25QXX 页大小:每页 256 字节 */
#define SPI_FLASH_PAGE_SIZE (0x100u)
/* 片选脚由 PJ9 软件控制 */
#define SPI_FLASH_CS_LOW() gpio_bit_reset(GPIOJ, GPIO_PIN_9)
#define SPI_FLASH_CS_HIGH() gpio_bit_set(GPIOJ, GPIO_PIN_9)
/* 初始化 SPI4 与片选 GPIO */
void spi_flash_init(void);
/* 擦除 4KB 扇区 */
void spi_flash_sector_erase(uint32_t sector_addr);
/* 全片擦除(耗时较长) */
void spi_flash_bulk_erase(void);
/* 页内写(不处理跨页) */
void spi_flash_page_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write);
/* 连续写(自动处理跨页) */
void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write);
/* 连续读 */
void spi_flash_buffer_read(uint8_t *pbuffer, uint32_t read_addr, uint16_t num_byte_to_read);
/* 读取 JEDEC ID */
uint32_t spi_flash_read_id(void);
/* 发起连续读命令序列(随后可重复 spi_flash_read_byte */
void spi_flash_start_read_sequence(uint32_t read_addr);
/* 连续读阶段读取 1 字节 */
uint8_t spi_flash_read_byte(void);
/* SPI 收发 1 字节 */
uint8_t spi_flash_send_byte(uint8_t byte);
/* SPI 收发 1 半字 */
uint16_t spi_flash_send_halfword(uint16_t half_word);
/* 置写使能(WREN */
void spi_flash_write_enable(void);
/* 轮询 WIP 位,等待写/擦完成 */
void spi_flash_wait_for_write_end(void);
#endif
@@ -0,0 +1,75 @@
# externalflash 模块说明(SPI4 外部 Flash
> 本文档描述 `CCU621_M/BSP/externalflash` 模块的职责、接口、初始化位置以及 DMA + D-Cache 场景下的使用注意事项。
[返回主说明](./../../README.md)
## 1. 模块组成
- **底层驱动(SPI4 + GD25QXX**
- `exflash_spi4_gd25qxx.c`
- `exflash_spi4_gd25qxx.h`
- **统一对外接口(业务层推荐使用)**
- `flash_external_data.c`
- `flash_external_data.h`
## 2. 初始化入口
- 系统硬件初始化入口:`BSP/sys_drv_init.c`
- `v_sys_hardware_init()` 中调用 `v_flash_dataflash_init()`,完成 SPI4/GPIO/DMA 初始化。
> 约束:业务层/测试层不要重复 init,避免初始化时序与资源重复配置。
## 3. 推荐对外接口(统一接口)
业务层请优先使用以下接口(位于 `flash_external_data.c/.h`):
- `U8_T v_flash_dataflash_init(void)`
- `U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len)`
- `U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len)`
- `U8_T s32_flash_dataflash_erase_sector(U32_T addr)`
- 兼容旧命名:`unsigned int SPI_flsh_ReadID(void)`
### 3.1 地址规划(摘自 `flash_external_data.h`
- 全空间(4-byte 地址模式):`0x00000000 ~ 0x01FFFFFF`32MB
- OTA 区:`0x00000000 ~ 0x001FFFFF`
- 参数区:`0x210000 ~ ...`
- 业务记录区:`0x220000 ~ ...`
- FatFs 映射区:`0xA00000 ~ 0x01FFFFFF`
> 说明:以上为“线性地址”规划,调用统一接口时直接传该地址即可。
## 4. DMA + D-Cache 使用注意事项(非常重要)
当前 SPI4 外部 Flash 读写采用 **DMA** 方式提升性能;在 Cortex-M7 **开启 D-Cache** 的情况下,DMA 与 CPU 会出现数据一致性问题:
- DMA 写入内存后,CPU 可能仍读取到旧 cache 数据;
- DMA 从内存读取 TX 数据时,可能读不到 CPU 刚写入但未 clean 的 cache 内容。
### 4.1 当前工程的处理策略
- 底层 SPI4 DMA 传输在驱动内部做了 D-Cache clean/invalidate(按 32B cache line 对齐)。
- 统一接口 `s32_flash_dataflash_read/write` 额外做了一层保护:
- 如果业务传入的 `buf` **不是 32B 对齐**,内部会自动使用 **对齐 bounce buffer** 完成 DMA 传输,再 `memcpy` 到业务缓冲。
### 4.2 业务层建议
- 业务层可直接使用 `s32_flash_dataflash_read/write`,无需自行对齐或自行做 cache 维护。
- 若业务存在高频读写,可考虑在模块内复用一块“32B 对齐的工作缓冲”,减少 malloc/free 次数(后续可优化)。
## 5. 测试入口
- `app/app_init/app_test.c`
- 周期执行 flash 擦写读回校验
- 输出擦除/读写耗时与速度统计
- 新增 `u8_app_test_flash_32m_stride_rw()`:以 1MB 步长扫描 32MB 地址空间做擦写读回校验
## 6. 常见问题排查
- **读回校验失败**
- 首先确认是否走统一接口(避免绕过 bounce buffer
- 其次检查 D-Cache 是否开启、MPU 区域属性是否覆盖了 DMA buffer
- **卡死在 SPI_FLAG_RP 等待**
- 通常表示 SPI 主机传输未启动或时钟未输出,需要检查 `spi_master_transfer_start` 是否调用、片选是否正确、外设时钟是否使能
+149
View File
@@ -0,0 +1,149 @@
#include "flash_external_data.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "app_init/app_init.h"
#include "exflash_spi4_gd25qxx.h"
#include "FreeRTOS.h"
#include "portable.h"
#define FLASH_IO_ALIGN (32u)
#define FLASH_IO_GUARD_SIZE (32u)
static uint8_t *align_up_u8(uint8_t *p, uint32_t align)
{
uintptr_t v = (uintptr_t)p;
v = (v + (align - 1U)) & ~(uintptr_t)(align - 1U);
return (uint8_t *)v;
}
static uint8_t need_bounce_buf(const void *p)
{
return (((uintptr_t)p) & (FLASH_IO_ALIGN - 1U)) ? 1U : 0U;
}
/**
* @brief 外部 Flash 初始化(统一接口)。
* @return 0 成功。
*/
U8_T v_flash_dataflash_init(void)
{
spi_flash_init();
printf("[EXFLASH] JEDEC ID: 0x%06X\r\n", (unsigned int)spi_flash_read_id());
return 0U;
}
/**
* @brief 外部 Flash 写接口(统一接口)。
* @param addr 24bit 线性地址。
* @param buf 写入数据缓冲区。
* @param len 写入长度(字节)。
* @return 0 成功,1 参数错误,2 长度超驱动接口上限。
*/
U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len)
{
uint8_t *raw;
uint8_t *aligned;
uint32_t alloc_size;
if ((buf == NULL) || (len == 0U)) {
return 1U;
}
/* 底层驱动 write 接口长度参数为 uint16_t。 */
if (len > 0xFFFFu) {
return 2U;
}
/* 若用户缓冲未按 32B 对齐,使用 bounce buffer,避免 DMA/DCache 维护影响堆头或触发一致性问题 */
if (need_bounce_buf(buf) == 0U) {
spi_flash_buffer_write(buf, addr, (U16_T)len);
return 0U;
}
alloc_size = len + FLASH_IO_ALIGN + FLASH_IO_GUARD_SIZE;
raw = (uint8_t *)pvPortMalloc(alloc_size);
if (raw == NULL) {
return 3U;
}
aligned = align_up_u8(raw, FLASH_IO_ALIGN);
memcpy(aligned, buf, len);
spi_flash_buffer_write(aligned, addr, (U16_T)len);
vPortFree(raw);
return 0U;
}
/**
* @brief 外部 Flash 读接口(统一接口)。
* @param addr 24bit 线性地址。
* @param buf 读出数据缓冲区。
* @param len 读取长度(字节)。
* @return 0 成功,1 参数错误,2 长度超驱动接口上限。
*/
U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len)
{
uint8_t *raw;
uint8_t *aligned;
uint32_t alloc_size;
if ((buf == NULL) || (len == 0U)) {
return 1U;
}
if (len > 0xFFFFu) {
return 2U;
}
if (need_bounce_buf(buf) == 0U) {
spi_flash_buffer_read(buf, addr, (U16_T)len);
return 0U;
}
alloc_size = len + FLASH_IO_ALIGN + FLASH_IO_GUARD_SIZE;
raw = (uint8_t *)pvPortMalloc(alloc_size);
if (raw == NULL) {
return 3U;
}
aligned = align_up_u8(raw, FLASH_IO_ALIGN);
spi_flash_buffer_read(aligned, addr, (U16_T)len);
memcpy(buf, aligned, len);
vPortFree(raw);
return 0U;
}
/**
* @brief 擦除指定地址所在扇区。
* @param addr 扇区起始地址(建议 4KB 对齐)。
* @return 0 成功。
*/
U8_T s32_flash_dataflash_erase_sector(U32_T addr)
{
spi_flash_sector_erase(addr);
return 0U;
}
/**
* @brief 读取外部 Flash JEDEC ID(兼容旧命名)。
*/
unsigned int SPI_flsh_ReadID(void)
{
return (unsigned int)spi_flash_read_id();
}
/**
* @brief 擦除 FatFs 映射区全部扇区。
*/
void v_flash_dataflash_fatfs_esaes_sectot(void)
{
U32_T i;
U32_T sector_addr;
for (i = 0U; i < FATFS_TOTAL_SECTORS; i++) {
sector_addr = FATFS_PHYSICAL_START_ADDR + (i * SPI_SECTOR_SIZE);
printf("SPI flash erase sector: 0x%08X\r\n", (unsigned int)sector_addr);
spi_flash_sector_erase(sector_addr);
mSleep(1U);
}
}
+100
View File
@@ -0,0 +1,100 @@
#ifndef FLASH_EXTERNAL_DATA_H
#define FLASH_EXTERNAL_DATA_H
#include "publicdata/type.h"
#include "meter_calculate/meter_calculate_impl.h"
/* ============================================================================
* 外部 Flash 容量与地址说明
*
* 当前上电识别 JEDEC ID: 0xC84019
* - Manufacturer: 0xC8GigaDevice
* - Capacity code: 0x19(典型为 256Mbit = 32MB
*
* 当前底层驱动已启用 4-byte 地址模式(4 字节地址):
* - 可直接访问线性地址范围:0x00000000 ~ 0x01FFFFFF32MB
* - 总容量:0x02000000 bytes
* ============================================================================ */
#define EXFLASH_ADDR_MIN (0x00000000u)
#define EXFLASH_ADDR_MAX (0x01FFFFFFu)
#define EXFLASH_TOTAL_SIZE (0x02000000u)
/* SPI NOR Flash 物理扇区大小(字节) */
#define SPI_SECTOR_SIZE (4096u)
#ifndef RECORD_ZERO_SAVE
#define RECORD_ZERO_SAVE (0x7FFFu)
#endif
#ifndef RECORD_HEADER
#define RECORD_HEADER (0x55u)
#endif
/* OTA 区(升级保留区) */
#define UPGRADE_START_ADDR (0x00000000u)
#define UPGRADE_END_ADDR (0x001FFFFFu)
#define UPGRADE_TOTAL_SIZE (UPGRADE_END_ADDR - UPGRADE_START_ADDR + 1u)
/* 配置区 */
#define DATAFLASH_VAR_CFG_DATA1_ADDR (0x210000u)
#define DATAFLASH_VAR_CFG_DATA2_ADDR (0x211000u)
#define DATAFLASH_VAR_CFG_DATA3_ADDR (0x212000u)
#define DATAFLASH_FIX_CFG_DATA1_ADDR (0x213000u)
#define DATAFLASH_FIX_CFG_DATA2_ADDR (0x214000u)
#define DATAFLASH_FIX_CFG_DATA3_ADDR (0x215000u)
#define DATAFLASH_OCPP_CFG_DATA_ADDR (0x216000u)
/* 故障、卡号、历史记录区 */
#define DATAFLASH_FAULT_ADDR (0x220000u)
#define DATAFLASH_FAULT_SECTOR_CNT (2u)
#define DATAFLASH_FAULT_END_ADDR (DATAFLASH_FAULT_ADDR + (DATAFLASH_FAULT_SECTOR_CNT * SPI_SECTOR_SIZE) - 1u)
#define DATAFLASH_OCPP_MV_OFFLINE_ADDR (0x2C0000u)
#define DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE (0x8000u)
#define DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT (16u)
#define DATAFLASH_OCPP_MV_OFFLINE_TOTAL_SIZE (DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE * 2u)
#define DATAFLASH_CARD_ADDR (0x222000u)
#define DATAFLASH_CARD_SECTOR_CNT (7u)
#define DATAFLASH_CARD_END_ADDR (DATAFLASH_CARD_ADDR + (DATAFLASH_CARD_SECTOR_CNT * SPI_SECTOR_SIZE) - 1u)
#define DATAFLASH_HIS_RECORD_ADDR (0x230000u)
#define DATAFLASH_HIS_RECORD_END_ADDR (0x2BFFFFu)
#define DATAFLASH_HIS_RECORD_SIZE (DATAFLASH_HIS_RECORD_END_ADDR - DATAFLASH_HIS_RECORD_ADDR + 1u)
#define DATAFLASH_HIS_RECORD_SECTOR_CNT (DATAFLASH_HIS_RECORD_SIZE / SPI_SECTOR_SIZE)
/* Charge-order storage layout (compatible with migrated flash_file_mgr module) */
#ifndef CHG_ORDER_RECORD_SIZE
#define CHG_ORDER_RECORD_SIZE (sizeof(S_LOG_DATA))
#endif
#ifndef CHG_ORDER_FULL_RECORD_SIZE
#define CHG_ORDER_FULL_RECORD_SIZE (1u + CHG_ORDER_RECORD_SIZE + 1u)
#endif
#ifndef CHG_ORDER_PER_SECTOR_CNT
#define CHG_ORDER_PER_SECTOR_CNT (SPI_SECTOR_SIZE / CHG_ORDER_FULL_RECORD_SIZE)
#endif
#ifndef CHG_ORDER_MAX_CNT
#define CHG_ORDER_MAX_CNT (200u)
#endif
#ifndef TEMP_CHG_RECORD_SIZE
#define TEMP_CHG_RECORD_SIZE (sizeof(S_LOG_DATA))
#endif
/* FatFs 映射区 */
#define FATFS_PHYSICAL_START_ADDR (0xA00000u)
#define FATFS_PHYSICAL_STOP_ADDR (EXFLASH_ADDR_MAX)
#define FATFS_PARTITION_SIZE (FATFS_PHYSICAL_STOP_ADDR - FATFS_PHYSICAL_START_ADDR + 1u)
#define FATFS_TOTAL_SECTORS (FATFS_PARTITION_SIZE / SPI_SECTOR_SIZE)
/* 统一对外接口:0=成功,非0=失败 */
U8_T v_flash_dataflash_init(void);
U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len);
U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len);
U8_T s32_flash_dataflash_erase_sector(U32_T addr);
/* 兼容旧接口命名 */
unsigned int SPI_flsh_ReadID(void);
void v_flash_dataflash_fatfs_esaes_sectot(void);
#endif
+189
View File
@@ -0,0 +1,189 @@
/**
* @file flash_upgrade.c
* @brief 外置 Flash 固件升级区读写(与 CCU601E_D / CCU621_IAP 布局一致)
*/
#include "flash_upgrade.h"
#include "app_init/app_init.h"
#include "wdt_task/wdt_task.h"
#include "exflash_spi4_gd25qxx.h"
#include <stdio.h>
#include <string.h>
extern U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len);
extern U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len);
extern U8_T s32_flash_dataflash_erase_sector(U32_T addr);
/* 0: 按包擦扇区;1: upgrade_erase_program_area 已整体擦除 */
static int g_upgrade_program_pre_erased = 0;
static uint8_t s_upgrade_header_buf[sizeof(upgrade_header_t)]
__attribute__((aligned(32)));
__attribute__((weak)) void upgrade_erase_yield(void)
{
}
int upgrade_init(void)
{
upgrade_header_t header;
if (upgrade_read_header(&header) == 0) {
if (header.upgrade_flag == UPGRADE_FLAG_NEED_UP) {
return 1;
}
if (header.upgrade_flag == UPGRADE_FLAG_DONE) {
return 2;
}
}
g_upgrade_program_pre_erased = 0;
return 0;
}
int upgrade_write_header(const upgrade_header_t *header)
{
if (header == NULL) {
return -1;
}
spi_flash_wait_for_write_end();
v_wdt_dog_toggle();
s32_flash_dataflash_erase_sector(HEADER_SECTOR_ADDR);
v_wdt_dog_toggle();
memcpy(s_upgrade_header_buf, header, sizeof(upgrade_header_t));
if (s32_flash_dataflash_write(HEADER_SECTOR_ADDR, s_upgrade_header_buf,
sizeof(upgrade_header_t)) != 0U) {
printf("[FW_UPGRADE] header write failed\r\n");
return -1;
}
spi_flash_wait_for_write_end();
v_wdt_dog_toggle();
return 0;
}
int upgrade_read_header(upgrade_header_t *header)
{
if (header == NULL) {
return -1;
}
if (s32_flash_dataflash_read(HEADER_SECTOR_ADDR, (U8_T *)header, sizeof(upgrade_header_t)) != 0U) {
return -1;
}
return 0;
}
int upgrade_success_update_header(upgrade_header_t *header)
{
return upgrade_write_header(header);
}
int upgrade_write_packet(uint32_t packet_index, const uint8_t *data, uint32_t data_len)
{
uint32_t packet_addr;
uint32_t sector_start;
if ((data == NULL) || (data_len == 0U) || (data_len > PACKET_SIZE)) {
return -1;
}
packet_addr = PROGRAM_START_ADDR + (packet_index * PACKET_SIZE);
if (packet_addr + data_len > UPGRADE_END_ADDR) {
return -1;
}
if (!g_upgrade_program_pre_erased) {
sector_start = (packet_addr / SECTOR_SIZE) * SECTOR_SIZE;
if ((packet_index * PACKET_SIZE) % SECTOR_SIZE == 0U) {
s32_flash_dataflash_erase_sector(sector_start);
v_wdt_dog_toggle();
mSleep(1);
}
}
if (s32_flash_dataflash_write(packet_addr, (U8_T *)data, data_len) != 0U) {
return -1;
}
return 0;
}
int upgrade_read_packet(uint32_t packet_index, uint8_t *data, uint32_t *data_len)
{
uint32_t packet_addr;
uint32_t max_read_size;
if ((data == NULL) || (data_len == NULL)) {
return -1;
}
packet_addr = PROGRAM_START_ADDR + (packet_index * PACKET_SIZE);
if (packet_addr > UPGRADE_END_ADDR) {
return -1;
}
if (s32_flash_dataflash_read(packet_addr, data, PACKET_SIZE) != 0U) {
return -1;
}
max_read_size = UPGRADE_END_ADDR - packet_addr + 1U;
if (max_read_size < PACKET_SIZE) {
*data_len = max_read_size;
} else {
*data_len = PACKET_SIZE;
}
return 0;
}
int upgrade_erase_program_area(void)
{
return upgrade_erase_program_area_for_size(PROGRAM_MAX_SIZE);
}
int upgrade_erase_program_area_for_size(uint32_t firmware_bytes)
{
uint32_t sector_addr;
uint32_t last_byte;
uint32_t last_sector;
if (firmware_bytes == 0U) {
return -1;
}
if (firmware_bytes > PROGRAM_MAX_SIZE) {
return -1;
}
last_byte = PROGRAM_START_ADDR + firmware_bytes - 1U;
if (last_byte > UPGRADE_END_ADDR) {
return -1;
}
last_sector = (last_byte / SECTOR_SIZE) * SECTOR_SIZE;
printf("[FW_UPGRADE] erase start size=%lu sectors=0x%08lX..0x%08lX\r\n",
(unsigned long)firmware_bytes,
(unsigned long)PROGRAM_START_ADDR,
(unsigned long)last_sector);
for (sector_addr = PROGRAM_START_ADDR; sector_addr <= last_sector; sector_addr += SECTOR_SIZE) {
s32_flash_dataflash_erase_sector(sector_addr);
v_wdt_dog_toggle();
mSleep(1);
upgrade_erase_yield();
if (((sector_addr - PROGRAM_START_ADDR) / SECTOR_SIZE) % 10U == 9U) {
printf("[FW_UPGRADE] erase progress at 0x%08lX\r\n",
(unsigned long)sector_addr);
}
}
printf("[FW_UPGRADE] erase done\r\n");
g_upgrade_program_pre_erased = 1;
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
/**
* @file flash_upgrade.h
* @brief 固件升级兼容头文件(整合自 CCU601E_D BSP/spi_Flash/flash_upgrade.h
*
* CCU621_M 平台:升级相关常量、类型与接口与 CCU601E_D 保持一致。
*/
#ifndef __FLASH_UPGRADE_H__
#define __FLASH_UPGRADE_H__
#include <stdint.h>
#include "main.h"
#include "externalflash/flash_external_data.h"
/* 扇区大小 (4KB) */
#define SECTOR_SIZE 4096
/* 数据包大小 */
#define PACKET_SIZE (1024)
/* 头部信息扇区 (第一个扇区) */
#define HEADER_SECTOR_ADDR UPGRADE_START_ADDR
#define HEADER_SECTOR_SIZE SECTOR_SIZE
/* 程序数据起始地址 (第二个扇区开始) */
#define PROGRAM_START_ADDR (UPGRADE_START_ADDR + HEADER_SECTOR_SIZE)
#define PROGRAM_MAX_SIZE (UPGRADE_TOTAL_SIZE - HEADER_SECTOR_SIZE)
/* 升级标志定义 */
#define UPGRADE_FLAG_IDLE 0x00
#define UPGRADE_FLAG_NEED_UP 0x55AA
#define UPGRADE_FLAG_DONE 0xAA55
#define UPGRADE_FLAG_ERROR 0xEEFF
/* 升级头部信息结构 */
#pragma pack(push, 1)
typedef struct {
uint16_t upgrade_flag;
uint32_t file_size;
uint32_t packet_count;
uint32_t crc32;
uint32_t timestamp;
uint8_t version[16];
uint8_t reserved[32];
} upgrade_header_t;
#pragma pack(pop)
/* 函数声明 */
int upgrade_init(void);
int upgrade_read_header(upgrade_header_t *header);
int upgrade_write_packet(uint32_t packet_index, const uint8_t *data, uint32_t data_len);
int upgrade_read_packet(uint32_t packet_index, uint8_t *data, uint32_t *data_len);
int upgrade_success_update_header(upgrade_header_t *header);
int upgrade_erase_program_area(void);
int upgrade_erase_program_area_for_size(uint32_t firmware_bytes);
/** 擦除循环中让出 CPU,读 BLE/TCP 入站(默认 weak 空实现,tcp_server 覆盖) */
void upgrade_erase_yield(void);
#endif /* __FLASH_UPGRADE_H__ */
+732
View File
@@ -0,0 +1,732 @@
/**
* @file card_flash_impl.c
* @brief 卡号白名单:EEPROM `S_CARD_MNG` + SPI Flash 定长槽(48B/槽,与 fault 相同的帧头+CRC 低字节)
*
* 单槽布局:RECORD_HEADER + CARD_FLASH_BODY_T(46) + crc8
* 写任意槽需对该槽所在扇区做读-合并-擦除-整扇区写回(NOR Flash 不可原地改已写位)。
*/
#include "publicdata/public_define.h"
#include "publicdata/publicdata.h"
#include "app_fatfs/fatfs_card.h"
#include "app_fatfs/fatfs_init.h"
#include "card_flash_impl.h"
#include "eeprom/fm24cl16.h"
#include "externalflash/flash_external_data.h"
#include <string.h>
/* 动态内存抽象:默认使用 FreeRTOS 堆接口,可按需覆盖 */
#ifndef FLASH_MGR_MALLOC
#define FLASH_MGR_MALLOC(sz) pvPortMalloc((sz))
#endif
#ifndef FLASH_MGR_FREE
#define FLASH_MGR_FREE(ptr) vPortFree((ptr))
#endif
#if FATFS_EN
#if 1
#define CARD_FLASH_MAX_CNT CARD_MAX_COUNT
#define CARD_BODY_SIZE 46u
#define CARD_SLOT_SIZE 48u
#define CARD_SLOTS_PER_SECTOR ((U32_T)SPI_SECTOR_SIZE / (U32_T)CARD_SLOT_SIZE)
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(sizeof(CARD_FLASH_BODY_T) == 46u, "CARD_FLASH_BODY_T must be 46 bytes");
#endif
static bool g_card_init;
static S_CARD_MNG s_cardMng;
/**
* @brief z_strnlen。
*/
static size_t z_strnlen(const char *s, size_t max)
{
size_t i;
for (i = 0; i < max && s[i] != '\0'; i++) {
}
return i;
}
/**
* @brief v_pack_card_num。
*/
static void v_pack_card_num(const char *src, U8_T dst[32])
{
memset(dst, 0, 32u);
if (src == NULL) {
return;
}
size_t n = z_strnlen(src, (size_t)CARD_NUM_MAX_LEN);
memcpy(dst, src, n);
}
/**
* @brief b_card_num_eq。
*/
static bool b_card_num_eq(const char *src, const U8_T dst[32])
{
U8_T a[32];
v_pack_card_num(src, a);
return memcmp(a, dst, 32u) == 0;
}
/**
* @brief v_read_eeprom_card_mng。
*/
static void v_read_eeprom_card_mng(S_CARD_MNG *m)
{
int i;
S_CARD_MNG tmp;
for (i = 0; i < 3; i++) {
if (eeprom_read(EEPROM_ADDR_CARD_MNG, (U8_T *)&tmp, (S32_T)sizeof(S_CARD_MNG)) == HAL_OK) {
m->u16_head = tmp.u16_head;
m->u16_cardCnt = tmp.u16_cardCnt;
m->u16_reserved = tmp.u16_reserved;
return;
}
}
m->u16_head = 0;
m->u16_cardCnt = 0;
m->u16_reserved = 0;
}
/**
* @brief v_write_eeprom_card_mng。
*/
static void v_write_eeprom_card_mng(const S_CARD_MNG *m)
{
S_CARD_MNG out;
out.u16_head = CARD_MNG_HEAD;
out.u16_cardCnt = m->u16_cardCnt;
out.u16_reserved = m->u16_reserved;
eeprom_write(EEPROM_ADDR_CARD_MNG, (U8_T *)&out, (S32_T)sizeof(S_CARD_MNG));
}
/**
* @brief u32_slot_addr。
*/
static U32_T u32_slot_addr(U16_T slot)
{
U32_T sec = (U32_T)slot / CARD_SLOTS_PER_SECTOR;
U32_T j = (U32_T)slot % CARD_SLOTS_PER_SECTOR;
return (U32_T)DATAFLASH_CARD_ADDR + sec * (U32_T)SPI_SECTOR_SIZE + j * (U32_T)CARD_SLOT_SIZE;
}
/**
* @brief v_read_slot_raw。
*/
static void v_read_slot_raw(U16_T slot, U8_T *out)
{
(void)s32_flash_dataflash_read(u32_slot_addr(slot), out, CARD_SLOT_SIZE);
}
/**
* @brief b_slot_buf_valid。
*/
static bool b_slot_buf_valid(const U8_T *buf)
{
U8_T crc;
if (buf[0] != RECORD_HEADER) {
return false;
}
crc = (U8_T)u16_crc_checksum((U8_T *)(buf + 1), (U16_T)CARD_BODY_SIZE);
return buf[CARD_SLOT_SIZE - 1u] == crc;
}
/**
* @brief v_body_to_card_info。
*/
static void v_body_to_card_info(const CARD_FLASH_BODY_T *b, CardInfo *ci)
{
uint32_t ct;
uint32_t lt;
memset(ci, 0, sizeof(*ci));
memcpy(ci->card_num, b->card_num, sizeof(b->card_num));
ci->card_num[CARD_NUM_MAX_LEN - 1u] = '\0';
ci->balance = b->amount_milli / 10u;
memcpy(&ct, &b->rsv[0], 4u);
memcpy(&lt, &b->rsv[4], 4u);
ci->create_time = ct;
ci->last_use_time = lt;
ci->status = b->status;
memcpy(ci->reserved, b->rsv, sizeof(b->rsv));
}
/**
* @brief v_card_info_to_body。
*/
static void v_card_info_to_body(const CardInfo *ci, CARD_FLASH_BODY_T *b)
{
memset(b, 0, sizeof(*b));
v_pack_card_num(ci->card_num, b->card_num);
b->amount_milli = ci->balance * 10u;
b->status = ci->status;
memcpy(&b->rsv[0], &ci->create_time, 4u);
memcpy(&b->rsv[4], &ci->last_use_time, 4u);
}
/**
* @brief u16_count_valid_slots。
*/
static U16_T u16_count_valid_slots(void)
{
U16_T n = 0;
U16_T i;
U8_T buf[CARD_SLOT_SIZE];
for (i = 0; i < CARD_FLASH_MAX_CNT; i++) {
v_read_slot_raw(i, buf);
if (b_slot_buf_valid(buf)) {
n++;
}
}
return n;
}
/**
* @brief s_find_slot_by_num。
*/
static S16_T s_find_slot_by_num(const char *num)
{
U16_T i;
U8_T buf[CARD_SLOT_SIZE];
if (num == NULL) {
return -1;
}
for (i = 0; i < CARD_FLASH_MAX_CNT; i++) {
v_read_slot_raw(i, buf);
if (!b_slot_buf_valid(buf)) {
continue;
}
if (b_card_num_eq(num, buf + 1)) {
return (S16_T)i;
}
}
return -1;
}
/**
* @brief s_find_free_slot。
*/
static S16_T s_find_free_slot(void)
{
U16_T i;
U8_T buf[CARD_SLOT_SIZE];
for (i = 0; i < CARD_FLASH_MAX_CNT; i++) {
v_read_slot_raw(i, buf);
if (!b_slot_buf_valid(buf)) {
return (S16_T)i;
}
}
return -1;
}
/**
* @brief v_erase_all_card_sectors。
*/
static void v_erase_all_card_sectors(void)
{
U8_T s;
for (s = 0; s < (U8_T)DATAFLASH_CARD_SECTOR_CNT; s++) {
(void)s32_flash_dataflash_erase_sector((U32_T)DATAFLASH_CARD_ADDR + (U32_T)s * (U32_T)SPI_SECTOR_SIZE);
}
}
/**
* @brief 写单槽:读扇区内其它有效槽,合并后擦除扇区再写回整扇区
* @param body_or_null 非 NULL 写入/更新;NULL 表示删除该槽(置为擦除态)
*/
static CardStatus e_flush_slot(U16_T slot, const CARD_FLASH_BODY_T *body_or_null)
{
U32_T sec = (U32_T)slot / CARD_SLOTS_PER_SECTOR;
U32_T j = (U32_T)slot % CARD_SLOTS_PER_SECTOR;
U32_T sector_base = (U32_T)DATAFLASH_CARD_ADDR + sec * (U32_T)SPI_SECTOR_SIZE;
U32_T k;
U8_T tmp[CARD_SLOT_SIZE];
U8_T *sector_rw = (U8_T *)FLASH_MGR_MALLOC((size_t)SPI_SECTOR_SIZE);
if (sector_rw == NULL) {
return CARD_STATUS_WRITE_FAILED;
}
memset(sector_rw, 0xFF, SPI_SECTOR_SIZE);
for (k = 0; k < CARD_SLOTS_PER_SECTOR; k++) {
U16_T g = (U16_T)(sec * CARD_SLOTS_PER_SECTOR + k);
if (g >= CARD_FLASH_MAX_CNT) {
break;
}
if (g == slot) {
continue;
}
v_read_slot_raw(g, tmp);
if (b_slot_buf_valid(tmp)) {
memcpy(&sector_rw[k * CARD_SLOT_SIZE], tmp, CARD_SLOT_SIZE);
}
}
if (body_or_null != NULL) {
U8_T *dst = &sector_rw[j * CARD_SLOT_SIZE];
dst[0] = RECORD_HEADER;
memcpy(dst + 1, body_or_null, sizeof(CARD_FLASH_BODY_T));
dst[CARD_SLOT_SIZE - 1u] =
(U8_T)u16_crc_checksum(dst + 1, (U16_T)CARD_BODY_SIZE);
}
(void)s32_flash_dataflash_erase_sector(sector_base);
if (s32_flash_dataflash_write(sector_base, sector_rw, (U32_T)SPI_SECTOR_SIZE) != 0u) {
FLASH_MGR_FREE(sector_rw);
return CARD_STATUS_WRITE_FAILED;
}
FLASH_MGR_FREE(sector_rw);
return CARD_STATUS_OK;
}
/**
* @brief card_init。
*/
CardStatus card_init(void)
{
U16_T actual;
if (g_card_init) {
return CARD_STATUS_ALREADY_INIT;
}
v_read_eeprom_card_mng(&s_cardMng);
actual = u16_count_valid_slots();
if (s_cardMng.u16_head != CARD_MNG_HEAD) {
s_cardMng.u16_head = CARD_MNG_HEAD;
s_cardMng.u16_cardCnt = actual;
s_cardMng.u16_reserved = 0;
v_write_eeprom_card_mng(&s_cardMng);
} else if (s_cardMng.u16_cardCnt != actual) {
s_cardMng.u16_cardCnt = actual;
v_write_eeprom_card_mng(&s_cardMng);
}
g_card_init = true;
FATFS_PRINT("card flash: init ok, cnt=%u\r\n", (unsigned)s_cardMng.u16_cardCnt);
return CARD_STATUS_OK;
}
/**
* @brief card_deinit。
*/
void card_deinit(void)
{
g_card_init = false;
}
/**
* @brief card_add。
*/
CardStatus card_add(const CardInfo *card_info)
{
CARD_FLASH_BODY_T body;
S16_T free_slot;
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
if (card_info == NULL) {
return CARD_STATUS_PARAM_ERROR;
}
if (s_find_slot_by_num(card_info->card_num) >= 0) {
return CARD_STATUS_CARD_EXIST;
}
free_slot = s_find_free_slot();
if (free_slot < 0) {
return CARD_STATUS_NO_SPACE;
}
v_card_info_to_body(card_info, &body);
if (e_flush_slot((U16_T)free_slot, &body) != CARD_STATUS_OK) {
return CARD_STATUS_WRITE_FAILED;
}
s_cardMng.u16_cardCnt++;
v_write_eeprom_card_mng(&s_cardMng);
return CARD_STATUS_OK;
}
/**
* @brief card_update。
*/
CardStatus card_update(const CardInfo *card_info)
{
CARD_FLASH_BODY_T body;
S16_T idx;
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
if (card_info == NULL) {
return CARD_STATUS_PARAM_ERROR;
}
idx = s_find_slot_by_num(card_info->card_num);
if (idx < 0) {
return CARD_STATUS_CARD_NOT_FOUND;
}
v_card_info_to_body(card_info, &body);
if (e_flush_slot((U16_T)idx, &body) != CARD_STATUS_OK) {
return CARD_STATUS_WRITE_FAILED;
}
return CARD_STATUS_OK;
}
/**
* @brief card_delete。
*/
CardStatus card_delete(const char *card_num)
{
S16_T idx;
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
if (card_num == NULL) {
return CARD_STATUS_PARAM_ERROR;
}
idx = s_find_slot_by_num(card_num);
if (idx < 0) {
return CARD_STATUS_CARD_NOT_FOUND;
}
if (e_flush_slot((U16_T)idx, NULL) != CARD_STATUS_OK) {
return CARD_STATUS_WRITE_FAILED;
}
if (s_cardMng.u16_cardCnt > 0) {
s_cardMng.u16_cardCnt--;
}
v_write_eeprom_card_mng(&s_cardMng);
return CARD_STATUS_OK;
}
/**
* @brief card_find。
*/
CardStatus card_find(const char *card_num, CardInfo *card_info)
{
S16_T idx;
U8_T buf[CARD_SLOT_SIZE];
CARD_FLASH_BODY_T body;
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
if (card_num == NULL || card_info == NULL) {
return CARD_STATUS_PARAM_ERROR;
}
idx = s_find_slot_by_num(card_num);
if (idx < 0) {
return CARD_STATUS_CARD_NOT_FOUND;
}
v_read_slot_raw((U16_T)idx, buf);
if (!b_slot_buf_valid(buf)) {
return CARD_STATUS_READ_FAILED;
}
memcpy(&body, buf + 1, sizeof(CARD_FLASH_BODY_T));
v_body_to_card_info(&body, card_info);
return CARD_STATUS_OK;
}
/**
* @brief card_get_count。
*/
CardStatus card_get_count(uint32_t *count)
{
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
if (count == NULL) {
return CARD_STATUS_PARAM_ERROR;
}
*count = (uint32_t)s_cardMng.u16_cardCnt;
return CARD_STATUS_OK;
}
/**
* @brief card_get_all。
*/
CardStatus card_get_all(CardInfo *card_list, uint32_t *count, uint32_t max_count)
{
U16_T i;
U32_T n;
U8_T buf[CARD_SLOT_SIZE];
CARD_FLASH_BODY_T body;
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
if (count == NULL) {
return CARD_STATUS_PARAM_ERROR;
}
n = 0;
for (i = 0; i < CARD_FLASH_MAX_CNT && n < max_count; i++) {
v_read_slot_raw(i, buf);
if (!b_slot_buf_valid(buf)) {
continue;
}
if (card_list != NULL) {
memcpy(&body, buf + 1, sizeof(CARD_FLASH_BODY_T));
v_body_to_card_info(&body, &card_list[n]);
}
n++;
}
*count = n;
return CARD_STATUS_OK;
}
/**
* @brief card_check_exist。
*/
CardStatus card_check_exist(const char *card_num, bool *exist)
{
if (card_num == NULL || exist == NULL) {
return CARD_STATUS_PARAM_ERROR;
}
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
*exist = (s_find_slot_by_num(card_num) >= 0);
return CARD_STATUS_OK;
}
/**
* @brief card_backup。
*/
CardStatus card_backup(void)
{
/* 无独立备份区;保留接口兼容旧 CSV 备份语义 */
return CARD_STATUS_OK;
}
/**
* @brief card_restore。
*/
CardStatus card_restore(void)
{
return CARD_STATUS_OK;
}
/**
* @brief card_clear_all。
*/
CardStatus card_clear_all(void)
{
if (!g_card_init) {
return CARD_STATUS_INIT_FAILED;
}
v_erase_all_card_sectors();
s_cardMng.u16_head = CARD_MNG_HEAD;
s_cardMng.u16_cardCnt = 0;
s_cardMng.u16_reserved = 0;
v_write_eeprom_card_mng(&s_cardMng);
FATFS_PRINT("card flash: cleared\r\n");
return CARD_STATUS_OK;
}
#else /* !FATFS_ENABLE_CARD */
CardStatus card_init(void)
{
return CARD_STATUS_INIT_FAILED;
}
void card_deinit(void) {}
/**
* @brief card_add。
*/
CardStatus card_add(const CardInfo *card_info)
{
(void)card_info;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_update。
*/
CardStatus card_update(const CardInfo *card_info)
{
(void)card_info;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_delete。
*/
CardStatus card_delete(const char *card_num)
{
(void)card_num;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_find。
*/
CardStatus card_find(const char *card_num, CardInfo *card_info)
{
(void)card_num;
(void)card_info;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_get_count。
*/
CardStatus card_get_count(uint32_t *count)
{
if (count) {
*count = 0;
}
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_get_all。
*/
CardStatus card_get_all(CardInfo *card_list, uint32_t *count, uint32_t max_count)
{
(void)card_list;
(void)max_count;
if (count) {
*count = 0;
}
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_check_exist。
*/
CardStatus card_check_exist(const char *card_num, bool *exist)
{
(void)card_num;
if (exist) {
*exist = false;
}
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_backup。
*/
CardStatus card_backup(void)
{
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_restore。
*/
CardStatus card_restore(void)
{
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_clear_all。
*/
CardStatus card_clear_all(void)
{
return CARD_STATUS_INIT_FAILED;
}
#endif /* FATFS_ENABLE_CARD */
#else /* !FATFS_EN */
CardStatus card_init(void)
{
return CARD_STATUS_INIT_FAILED;
}
void card_deinit(void) {}
/**
* @brief card_add。
*/
CardStatus card_add(const CardInfo *card_info)
{
(void)card_info;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_update。
*/
CardStatus card_update(const CardInfo *card_info)
{
(void)card_info;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_delete。
*/
CardStatus card_delete(const char *card_num)
{
(void)card_num;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_find。
*/
CardStatus card_find(const char *card_num, CardInfo *card_info)
{
(void)card_num;
(void)card_info;
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_get_count。
*/
CardStatus card_get_count(uint32_t *count)
{
if (count) {
*count = 0;
}
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_get_all。
*/
CardStatus card_get_all(CardInfo *card_list, uint32_t *count, uint32_t max_count)
{
(void)card_list;
(void)max_count;
if (count) {
*count = 0;
}
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_check_exist。
*/
CardStatus card_check_exist(const char *card_num, bool *exist)
{
(void)card_num;
if (exist) {
*exist = false;
}
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_backup。
*/
CardStatus card_backup(void)
{
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_restore。
*/
CardStatus card_restore(void)
{
return CARD_STATUS_INIT_FAILED;
}
/**
* @brief card_clear_all。
*/
CardStatus card_clear_all(void)
{
return CARD_STATUS_INIT_FAILED;
}
#endif /* FATFS_EN */
+46
View File
@@ -0,0 +1,46 @@
/**
* @file card_flash_impl.h
* @brief 卡号白名单:EEPROM 管理信息 + 外部 SPI Flash 槽位存储(与 fault_flash_impl 同类策略)
*
* 对外业务接口见 `app_fatfs/fatfs_card.h`card_init / card_add / card_find 等)。
* 本头文件提供介质层结构说明,便于调试或扩展。
*/
#ifndef CARD_FLASH_IMPL_H
#define CARD_FLASH_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "publicdata/type.h"
#define CARD_MNG_HEAD 0x5AA8u
/** EEPROM 中的卡管理块(地址 EEPROM_ADDR_CARD_MNG */
typedef struct
{
U16_T u16_head;
U16_T u16_cardCnt;
U16_T u16_reserved;
} S_CARD_MNG;
/**
* Flash 单槽有效载荷 46 字节(槽位总占用 481 字节 RECORD_HEADER + 46 + 1 字节 CRC 低 8 位)
* - amount_milli:金额,单位 0.001 元(与 API 中 balance「分」换算:毫元 = 分 * 10)
* - rsv[8]:前 4 字节 create_time,后 4 字节 last_use_timeUnix 秒,小端),与 CardInfo 时间字段对应
*/
typedef struct __attribute__((packed))
{
U8_T card_num[32];
U32_T amount_milli;
U16_T status;
U8_T rsv[8];
} CARD_FLASH_BODY_T;
#ifdef __cplusplus
}
#endif
#endif /* CARD_FLASH_IMPL_H */
+241
View File
@@ -0,0 +1,241 @@
/**
* @file fault_flash_impl.c
* @brief 历史故障记录:EEPROM 索引 + 外部 Flash 数据区实现。
*
* 迁移说明(参考 CCU601E_D):
* - EEPROMfm24cl16)仅存储 S_HIS_FAULT_MNG 索引信息,减少擦写与上电恢复成本。
* - 外部 Flashexternalflash)按扇区保存 HIS_FAULT_DATA_T 记录内容。
* - 每条记录格式:1字节 RECORD_HEADER + 数据 + 1字节CRC(低8位)。
*/
#include "flash_file_mgr/fault_flash_impl.h"
#include <string.h>
#include "publicdata/publicdata.h"
#include "publicdata/public_define.h"
#include "eeprom/fm24cl16.h"
#include "externalflash/flash_external_data.h"
/* 兼容抽象:默认使用 FreeRTOS 堆 */
#ifndef FLASH_MGR_MALLOC
#define FLASH_MGR_MALLOC(sz) pvPortMalloc((sz))
#endif
#ifndef FLASH_MGR_FREE
#define FLASH_MGR_FREE(ptr) vPortFree((ptr))
#endif
#define HIS_FAULT_MNG_HEADER (0x5AA5u)
#define FAULT_MNG_EEPROM_ADDR ((U16_T)EEPROM_ADDR_HISALARM_MNG)
#define FAULT_RECORD_RETRY (3u)
/* 来自故障任务:全局管理索引 */
extern S_HIS_FAULT_MNG s_hisFaultMng;
void v_fault_flash_init(void)
{
/* 上电恢复 EEPROM 中的历史故障管理索引。 */
v_read_eeprom_hisFaultMng(&s_hisFaultMng);
/* 管理信息异常时回退到安全默认值。 */
if ((s_hisFaultMng.u16_head != HIS_FAULT_MNG_HEADER) ||
(s_hisFaultMng.u16_hisFaultCnt > HIS_FAULT_MAX_CNT) ||
(s_hisFaultMng.u16_currIndex > HIS_FAULT_MAX_CNT)) {
v_fault_clear_his_fault();
}
}
static U16_T fault_sector_record_count(void)
{
return (U16_T)(SPI_SECTOR_SIZE / (sizeof(HIS_FAULT_DATA_T) + 2u));
}
void v_write_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng)
{
S_HIS_FAULT_MNG tmp;
if (hisFaultMng == NULL) {
return;
}
tmp.u16_head = HIS_FAULT_MNG_HEADER;
tmp.u16_hisFaultCnt = hisFaultMng->u16_hisFaultCnt;
tmp.u16_currIndex = hisFaultMng->u16_currIndex;
eeprom_buffer_write((U8_T *)&tmp, FAULT_MNG_EEPROM_ADDR, (U16_T)sizeof(S_HIS_FAULT_MNG));
}
void v_read_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng)
{
S_HIS_FAULT_MNG tmp;
U8_T i;
if (hisFaultMng == NULL) {
return;
}
memset(&tmp, 0, sizeof(tmp));
for (i = 0u; i < FAULT_RECORD_RETRY; i++) {
eeprom_buffer_read((U8_T *)&tmp, FAULT_MNG_EEPROM_ADDR, (U16_T)sizeof(S_HIS_FAULT_MNG));
if ((eeprom_get_last_error() == 0u) && (tmp.u16_head == HIS_FAULT_MNG_HEADER)) {
hisFaultMng->u16_head = HIS_FAULT_MNG_HEADER;
hisFaultMng->u16_hisFaultCnt = tmp.u16_hisFaultCnt;
hisFaultMng->u16_currIndex = tmp.u16_currIndex;
return;
}
}
/* 失败时给出安全默认值,避免野数据参与索引计算。 */
hisFaultMng->u16_head = 0u;
hisFaultMng->u16_hisFaultCnt = 0u;
hisFaultMng->u16_currIndex = RECORD_ZERO_SAVE;
}
void v_fault_clear_his_fault(void)
{
s_hisFaultMng.u16_head = HIS_FAULT_MNG_HEADER;
s_hisFaultMng.u16_hisFaultCnt = 0u;
s_hisFaultMng.u16_currIndex = RECORD_ZERO_SAVE;
v_write_eeprom_hisFaultMng(&s_hisFaultMng);
}
U16_T u16_fault_get_his_count(void)
{
S_HIS_FAULT_MNG mng;
memset(&mng, 0, sizeof(mng));
v_read_eeprom_hisFaultMng(&mng);
if (mng.u16_head != HIS_FAULT_MNG_HEADER) {
return 0u;
}
if (mng.u16_hisFaultCnt > HIS_FAULT_MAX_CNT) {
return HIS_FAULT_MAX_CNT;
}
return mng.u16_hisFaultCnt;
}
U8_T u8_fault_read_his_by_newest(U16_T newest_pos, HIS_FAULT_DATA_T *pt_hisFault)
{
S_HIS_FAULT_MNG mng;
U16_T total;
U16_T slot;
if (pt_hisFault == NULL) {
return 0u;
}
memset(pt_hisFault, 0, sizeof(*pt_hisFault));
total = u16_fault_get_his_count();
if ((total == 0u) || (newest_pos >= total)) {
return 0u;
}
memset(&mng, 0, sizeof(mng));
v_read_eeprom_hisFaultMng(&mng);
if ((mng.u16_head != HIS_FAULT_MNG_HEADER) || (mng.u16_currIndex == RECORD_ZERO_SAVE)) {
return 0u;
}
slot = (U16_T)((mng.u16_currIndex + HIS_FAULT_MAX_CNT - newest_pos) % HIS_FAULT_MAX_CNT);
v_read_hisFault_record(pt_hisFault, slot);
return (pt_hisFault->e_fault_info == E_FAULT_NULL) ? 0u : 1u;
}
void v_read_hisFault_record(HIS_FAULT_DATA_T *pt_hisFault, U16_T u16_index)
{
U16_T rec_size = (U16_T)sizeof(HIS_FAULT_DATA_T);
U16_T one_sector_cnt = fault_sector_record_count();
U32_T addr;
U16_T sector_idx;
U16_T sector_rec_idx;
U8_T retry;
U8_T *buf;
if ((pt_hisFault == NULL) || (one_sector_cnt == 0u)) {
return;
}
buf = (U8_T *)FLASH_MGR_MALLOC((size_t)rec_size + 2u);
if (buf == NULL) {
return;
}
memset(pt_hisFault, 0, sizeof(*pt_hisFault));
for (retry = 0u; retry < FAULT_RECORD_RETRY; retry++) {
sector_idx = (U16_T)(u16_index / one_sector_cnt);
sector_rec_idx = (U16_T)(u16_index % one_sector_cnt);
addr = (U32_T)(DATAFLASH_FAULT_ADDR +
(U32_T)sector_idx * SPI_SECTOR_SIZE +
(U32_T)sector_rec_idx * (rec_size + 2u));
if (s32_flash_dataflash_read(addr, buf, (U32_T)(rec_size + 2u)) != 0u) {
continue;
}
if ((buf[0] == RECORD_HEADER) &&
(buf[rec_size + 1u] == (U8_T)u16_crc_checksum(buf + 1u, rec_size))) {
memcpy(pt_hisFault, buf + 1u, rec_size);
break;
}
}
FLASH_MGR_FREE(buf);
}
static void v_write_hisFault_record(HIS_FAULT_DATA_T *pt_hisFault, U16_T u16_index)
{
U16_T rec_size = (U16_T)sizeof(HIS_FAULT_DATA_T);
U16_T one_sector_cnt = fault_sector_record_count();
U16_T sector_idx;
U16_T sector_rec_idx;
U32_T sector_addr;
U32_T addr;
U8_T *buf;
if ((pt_hisFault == NULL) || (one_sector_cnt == 0u)) {
return;
}
buf = (U8_T *)FLASH_MGR_MALLOC((size_t)rec_size + 2u);
if (buf == NULL) {
return;
}
buf[0] = RECORD_HEADER;
memcpy(buf + 1u, pt_hisFault, rec_size);
buf[rec_size + 1u] = (U8_T)u16_crc_checksum((U8_T *)pt_hisFault, rec_size);
sector_idx = (U16_T)(u16_index / one_sector_cnt);
sector_rec_idx = (U16_T)(u16_index % one_sector_cnt);
sector_addr = (U32_T)(DATAFLASH_FAULT_ADDR + (U32_T)sector_idx * SPI_SECTOR_SIZE);
addr = (U32_T)(sector_addr + (U32_T)sector_rec_idx * (rec_size + 2u));
/* 扇区首条记录写入前先擦除,避免旧数据残留影响 CRC。 */
if (sector_rec_idx == 0u) {
(void)s32_flash_dataflash_erase_sector(sector_addr);
}
(void)s32_flash_dataflash_write(addr, buf, (U32_T)(rec_size + 2u));
FLASH_MGR_FREE(buf);
}
void v_hisFault_save_record(HIS_FAULT_DATA_T *pt_hisFault_data)
{
if (pt_hisFault_data == NULL) {
return;
}
if (s_hisFaultMng.u16_currIndex == RECORD_ZERO_SAVE) {
s_hisFaultMng.u16_currIndex = 0u;
} else {
s_hisFaultMng.u16_currIndex =
(U16_T)((s_hisFaultMng.u16_currIndex + 1u) % HIS_FAULT_MAX_CNT);
}
if (s_hisFaultMng.u16_hisFaultCnt < HIS_FAULT_MAX_CNT) {
s_hisFaultMng.u16_hisFaultCnt++;
}
v_write_hisFault_record(pt_hisFault_data, s_hisFaultMng.u16_currIndex);
v_write_eeprom_hisFaultMng(&s_hisFaultMng);
}
+58
View File
@@ -0,0 +1,58 @@
/**
* @file fault_flash_impl.h
* @brief 历史故障记录持久化接口(EEPROM索引 + 外部Flash数据)。
*/
#ifndef FAULT_FLASH_IMPL_H
#define FAULT_FLASH_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "fault_cheak/faultcheck_task.h"
#include "fault_cheak/fault_interface.h"
#define FAULT_INFO_TEXT_MAX_LEN (64u)
/* 故障历史存储功能开关:
* - 0:先屏蔽(避免影响 EEPROM 可靠性排查)
* - 1:使能(恢复 EEPROM 索引 + Flash 记录读写)
*/
#ifndef FAULT_FLASH_MGR_ENABLE
#define FAULT_FLASH_MGR_ENABLE (1u)
#endif
/**
* @brief 历史故障管理信息(存放在 EEPROM)。
*/
typedef struct
{
U16_T u16_head; /* 固定头 0x5AA5 */
U16_T u16_hisFaultCnt; /* 历史记录总数(上限 HIS_FAULT_MAX_CNT */
U16_T u16_currIndex; /* 当前最新记录逻辑索引,初值 RECORD_ZERO_SAVE */
} S_HIS_FAULT_MNG;
typedef struct
{
E_FAULT_INFO e_mag;
char info[FAULT_INFO_TEXT_MAX_LEN];
} S_FAULT_INFO;
void v_fault_flash_init(void);
void v_write_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng);
void v_read_eeprom_hisFaultMng(S_HIS_FAULT_MNG *hisFaultMng);
void v_fault_clear_his_fault(void);
U16_T u16_fault_get_his_count(void);
U8_T u8_fault_read_his_by_newest(U16_T newest_pos, HIS_FAULT_DATA_T *pt_hisFault);
void v_hisFault_save_record(HIS_FAULT_DATA_T *pt_hisFault_data);
void v_read_hisFault_record(HIS_FAULT_DATA_T *pt_hisFault, U16_T u16_index);
#ifdef __cplusplus
}
#endif
#endif /* FAULT_FLASH_IMPL_H */
@@ -0,0 +1,72 @@
# flash_file_mgr 模块说明
本文档用于说明 `BSP/flash_file_mgr` 目录下各文件职责、数据落盘位置与调用关系,便于定位问题和后续扩展。
## 目录内文件与功能
### 1) `fault_flash_impl.h` / `fault_flash_impl.c`
- **功能**:历史故障记录持久化(EEPROM 管理信息 + SPI Flash 记录体)。
- **管理信息**`S_HIS_FAULT_MNG`,存放在 EEPROM `EEPROM_ADDR_HISALARM_MNG`
- **记录数据**:从 `DATAFLASH_FAULT_ADDR` 开始,单条记录格式:
- `RECORD_HEADER(1B)` + `HIS_FAULT_DATA_T` + `CRC低8位(1B)`
- **核心接口**
- `v_hisFault_save_record()`:保存一条故障记录。
- `v_read_hisFault_record()`:按逻辑索引读取记录。
- `v_fault_clear_his_fault()`:清空故障管理信息。
### 2) `card_flash_impl.h` / `card_flash_impl.c`
- **功能**:卡号白名单持久化(替代 CSV/FAT 文件实现),由 `fatfs_card.h` 对外暴露业务接口。
- **管理信息**`S_CARD_MNG`,存放在 EEPROM `EEPROM_ADDR_CARD_MNG`
- **记录数据**:从 `DATAFLASH_CARD_ADDR` 开始,单槽格式:
- `RECORD_HEADER(1B)` + `CARD_FLASH_BODY_T` + `CRC低8位(1B)`
- **关键特性**
- 按卡号查重/查找。
- 写入采用“读扇区 -> 合并 -> 擦除 -> 整扇区回写”策略,避免 NOR Flash 原地改写问题。
- **对外接口(由 `fatfs_card.h` 声明)**
- `card_init/card_add/card_update/card_delete/card_find/...`
### 3) `meter_calculate_flash_impl.h` / `meter_calculate_flash_impl.c`
- **功能**:充电订单历史记录 + 临时订单(断电续传)持久化。
- **管理信息**`S_CHG_ORDER_MNG`,存放在 EEPROM `EEPROM_ADDR_CHGRCD_MNG`
- **历史订单数据**:从 `DATAFLASH_HIS_RECORD_ADDR` 开始,记录体为 `S_LOG_DATA`,单条格式:
- `RECORD_HEADER(1B)` + `S_LOG_DATA` + `CRC低8位(1B)`
- **临时订单数据**:A/B 枪分别存放在 EEPROM `EEPROM_ADDR_A_LOG_DATA` / `EEPROM_ADDR_B_LOG_DATA`
- **核心接口**
- `u32_chg_order_save_record()`:保存并分配唯一索引。
- `v_read_chg_order_record()`:按索引读取。
- `v_temp_chg_data_save/v_temp_chg_data_read/...`:临时订单保存/恢复。
### 4) `unsettled_order_mng.h` / `unsettled_order_mng.c`
- **功能**:未结算订单索引列表管理(仅存索引,不存订单正文)。
- **存储位置**EEPROM `EEPROM_ADDR_UNSETTLED_MNG`
- **数据结构**`S_UNSETTLED_ORDER_MNG`,包含头标志、数量、CRC 和 3 字节压缩索引数组。
- **核心接口**
- `u8_add_unsettled_order()`:新增未结订单索引。
- `u8_remove_unsettled_order()`:删除已结索引。
- `u8_check_order_settled()`:检查是否仍未结。
### 5) `ocpp_mv_offline_flash_impl.h` / `ocpp_mv_offline_flash_impl.c`
- **功能**:平台离线时周期缓存 MeterValuesFlash 为 **整池定长帧数组**(仿 `fault_flash_impl`:帧头+32B 负载+CRC),**全局序号**与物理槽 `seq % POOL_MAX` 一一对应;FRAM 存 `u32_next_seq`**流水号→(seq_start,count)** 映射表(最多 6 笔并发),上送时按序号直接读 Flash。
- **容量**`OCPP_MV_OFFLINE_POOL_MAX` = 每扇区 `floor(4096/帧长)` × `DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT`(扇区尾截断不用)。
- **滚动**`u32_next_seq` 递增写满池后自然覆盖最旧物理槽;映射行上送后应 `u8_ocpp_mv_offline_map_remove`
- **核心接口**`v_ocpp_mv_offline_init``u32_ocpp_mv_offline_pool_max``u8_ocpp_mv_offline_mv_save``u8_ocpp_mv_offline_mv_read_by_seq``u8_ocpp_mv_offline_map_get``u8_ocpp_mv_offline_map_remove``v_ocpp_mv_offline_flash_erase_pool``u8_ocpp_mv_offline_fram_read` / `u8_ocpp_mv_offline_fram_write`
- **RAM 镜像**`s_ocppMvOfflineFramMng`;上电 `v_ocpp_mv_offline_init()`(兼容宏 `v_ocpp_mv_offline_flash_init()`)。
## 地址与容量宏来源
`flash_file_mgr` 模块统一依赖 `BSP/spi_Flash/flash_external_data.h` 中定义的地址与容量宏(如 `DATAFLASH_FAULT_ADDR``DATAFLASH_CARD_ADDR``DATAFLASH_HIS_RECORD_ADDR``DATAFLASH_OCPP_MV_OFFLINE_ADDR``CHG_ORDER_*` 等)。
建议后续若调整分区地址,仅修改 `flash_external_data.h`,并回归验证:
- 故障记录读写;
- 卡号增删查;
- 订单保存与按索引回读;
- 未结算订单索引增删。
## 模块关系(简版)
- `faultcheck_task` -> `fault_flash_impl`:故障历史。
- `fatfs_init / bs_public_impl` -> `fatfs_card.h` -> `card_flash_impl`:本地卡鉴权与白名单。
- `meter_calculate_impl` -> `meter_calculate_flash_impl`:订单持久化。
- `meter_calculate_flash_impl` + `bs_public_impl` -> `unsettled_order_mng`:未结算订单索引维护。
- (规划)`BS_ocpp_ctrl` / OCPP 离线路径 -> `ocpp_mv_offline_flash_impl`:离线 MeterValues 采样落盘与联网后补发。
@@ -0,0 +1,108 @@
# flash_file_mgr 模块说明
> 路径:`BSP/flash_file_mgr/`
> 作用:提供历史故障记录的掉电持久化能力,采用“EEPROM 管理索引 + 外部 Flash 存储内容”的双存储架构。
## 1. 模块文件
- `fault_flash_impl.h`
- 历史故障持久化对外接口声明
- `S_HIS_FAULT_MNG` 管理结构定义
- `fault_flash_impl.c`
- EEPROM 索引读写实现
- 外部 Flash 记录读写实现
- 历史记录计数、按“最新N条”读取实现
- `meter_calculate_flash_impl.c`
- 充电订单记录持久化实现(EEPROM 管理块 + 外部 Flash 订单正文)
- 提供订单保存/按索引读取/按位置读取、临时订单 EEPROM 缓存接口
- 支持双枪临时订单标志管理(用于掉电恢复)
- `unsettled_order_mng.c`
- 未结算订单列表管理(EEPROM 持久化)
- 支持未结订单添加、移除、查询、清空与 CRC 校验
## 2. 设计目标
- 将高频变化的小数据(计数、索引)放 EEPROM,降低 Flash 管理复杂度;
- 将体积较大的历史故障记录放 externalflash,便于顺序存储与扩展;
- 提供统一接口给 `fault_cheak``meter_calculate`,业务层只关心“保存/读取故障或订单记录”。
## 3. 存储布局
### 3.1 EEPROM(索引区)
- 地址:`EEPROM_ADDR_HISALARM_MNG`
- 数据:`S_HIS_FAULT_MNG`
- `u16_head`:固定头 `0x5AA5`,用于有效性校验
- `u16_hisFaultCnt`:历史记录数
- `u16_currIndex`:当前最新逻辑索引(环形)
### 3.2 externalflash(数据区)
- 基址:`DATAFLASH_FAULT_ADDR`
- 扇区大小:`SPI_SECTOR_SIZE`
- 单条记录格式:
- `1 byte RECORD_HEADER`
- `sizeof(HIS_FAULT_DATA_T) bytes 数据体`
- `1 byte CRC``u16_crc_checksum` 低 8 位)
## 4. 核心接口说明
- `v_hisFault_save_record(HIS_FAULT_DATA_T *pt_hisFault_data)`
- 保存一条历史故障
- 更新环形索引与计数
- 写 externalflash 记录后更新 EEPROM 管理信息
- `u8_fault_read_his_by_newest(U16_T newest_pos, HIS_FAULT_DATA_T *pt_hisFault)`
- 按“最新优先”读取历史记录
- `newest_pos=0` 表示最新一条
- `u16_fault_get_his_count(void)`
- 返回历史记录数量(带上限保护)
- `v_fault_clear_his_fault(void)`
- 清空管理信息(不逐条清除 Flash 区内容)
### 4.1 计量订单相关接口(meter_calculate
- `v_chg_order_flash_init(void)`
- 初始化充电订单管理信息(读取 EEPROM 管理块,必要时恢复默认)
- `u32_chg_order_save_record(S_LOG_DATA *order_data)`
- 保存订单到外部 Flash,并返回单调递增订单索引
- `v_read_chg_order_record(S_LOG_DATA *order_data, U32_T index)`
- 按订单索引读取订单记录
- `v_save_temp_chg_record_to_eeprom(...)` / `v_read_temp_chg_record_from_eeprom(...)`
- 充电过程临时订单缓存/恢复(EEPROM)
- `u8_add_unsettled_order(U32_T orderIndex)` / `u8_remove_unsettled_order(U32_T orderIndex)`
- 未结算订单添加/移除
- `u8_check_order_settled(U32_T orderIndex)`
- 查询订单是否仍处于未结算列表
## 5. 写入策略
- 采用环形索引写入,达到上限后覆盖最老记录;
- 每个扇区的第 1 条记录写入前先擦除该扇区,避免旧数据干扰;
- 记录读取时校验:
- 头字节必须是 `RECORD_HEADER`
- CRC 必须匹配
- 否则判定为无效记录。
## 6. 依赖关系
- EEPROM 驱动:`BSP/eeprom/fm24cl16.c`
- 外部 Flash 驱动:`BSP/externalflash/flash_external_data.c`
- CRC 工具:`app/publicdata/public_func.c``u16_crc_checksum`
- 故障数据结构:`app/fault_cheak/faultcheck_task.h`
- 计量订单结构:`app/meter_calculate/meter_calculate_impl.h``S_LOG_DATA`
## 7. 注意事项
- 请保证 `DATAFLASH_FAULT_ADDR` 区域不与其他业务区重叠;
- `HIS_FAULT_MAX_CNT` 与扇区分配需协同评估,避免有效容量不足;
- 计量订单区(`DATAFLASH_HIS_RECORD_ADDR`)与故障区地址需避免重叠;
- 未结算订单列表与订单管理块均依赖 EEPROM,建议控制写频率并保留 CRC 校验;
- 该模块默认在任务上下文调用,若多任务并发写入建议增加互斥保护。
## 8. 相关文档
- [主说明 README](../../README.md)
- [fault_cheak 模块说明](../../app/fault_cheak/fault_cheak模块说明.md)
- [eeprom 模块说明](../eeprom/eeprom模块说明.md)
- [externalflash 模块说明](../externalflash/externalflash模块说明.md)
@@ -0,0 +1,526 @@
/**
* @file meter_calculate_flash_impl.c
* @brief 充电订单与临时订单的 EEPROM + 外部 SPI Flash 持久化实现
*
* 功能说明:
* - 订单管理块 `S_CHG_ORDER_MNG` 存于 EEPROM`EEPROM_ADDR_CHGRCD_MNG`):头标志、已存条数、
* 环形槽当前位置、单调递增的 `u32_lastIndex`(对外唯一订单号)。
* - 订单正文 `S_LOG_DATA` 存于 Flash 区 `DATAFLASH_HIS_RECORD_ADDR` 起;单条布局为
* 1 字节 `RECORD_HEADER` + 数据 + 1 字节 CRC`u16_crc_checksum` 低 8 位),按扇区擦除后顺序写入。
* - `u32_chg_order_save_record`:分配新 `u32_lastIndex`,按 `(lastIndex-1)%CHG_ORDER_MAX_CNT` 映射槽位并写 Flash。
* - `v_read_chg_order_record(index)`:用 `(index-1)%CHG_ORDER_MAX_CNT` 定位槽,并校验记录内 `u32_index` 与请求一致,防止覆盖残留。
* - `v_read_chg_order_by_position`:在未满/已满环形缓冲下,按“第几条历史”语义换算槽位。
* - 双枪临时订单:标志字节 + `S_LOG_DATA` 存于 EEPROM`EEPROM_ADDR_A_LOG_DATA` / `B`),用于断电续传;清除时仅写无效标志以省擦写。
*
* 注意:`v_chg_order_clear_all` 仅复位管理元数据,不整片擦除 Flash(由后续写入覆盖)。
*/
#include "meter_calculate_flash_impl.h"
#include <string.h>
#include <stdlib.h>
#include "mylog/mylog.h"
/* 动态内存抽象:默认使用 FreeRTOS 堆接口,可按需覆盖 */
#ifndef FLASH_MGR_MALLOC
#define FLASH_MGR_MALLOC(sz) pvPortMalloc((sz))
#endif
#ifndef FLASH_MGR_FREE
#define FLASH_MGR_FREE(ptr) vPortFree((ptr))
#endif
/* 模块内部全局变量 */
S_CHG_ORDER_MNG s_chgOrderMng;
/* 内部函数声明 */
static void v_write_chg_order_record(S_LOG_DATA *order_data, U16_T position);
static U8_T v_read_chg_order_record_internal(S_LOG_DATA *order_data, U16_T position);
/***************************************************************
* EEPROM管理函数
***************************************************************/
/**
* @brief 写入充电订单管理信息到EEPROM
*/
void v_write_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng)
{
S_CHG_ORDER_MNG tempMng;
// 设置头标志
tempMng.u16_head = 0x5AA5;
tempMng.u16_chgOrderCnt = chgOrderMng->u16_chgOrderCnt;
tempMng.u16_currIndex = chgOrderMng->u16_currIndex;
tempMng.u32_lastIndex = chgOrderMng->u32_lastIndex;
// 写入EEPROM
eeprom_write(EEPROM_ADDR_CHGRCD_MNG, (U8_T *)&tempMng, sizeof(S_CHG_ORDER_MNG));
}
/**
* @brief 从EEPROM读取充电订单管理信息
*/
void v_read_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng)
{
S_CHG_ORDER_MNG tempMng;
int i;
for (i = 0; i < 3; i++) {
// eeprom_read返回HAL_OK(0)表示成功,非0表示失败
if (eeprom_read(EEPROM_ADDR_CHGRCD_MNG, (U8_T *)&tempMng, sizeof(S_CHG_ORDER_MNG)) == HAL_OK) {
// 验证头标志
if (tempMng.u16_head == 0x5AA5) {
chgOrderMng->u16_head = 0x5AA5;
chgOrderMng->u16_chgOrderCnt = tempMng.u16_chgOrderCnt;
chgOrderMng->u16_currIndex = tempMng.u16_currIndex;
chgOrderMng->u32_lastIndex = tempMng.u32_lastIndex;
return;
}
}
}
// 读取失败或数据无效,初始化默认值
chgOrderMng->u16_head = 0x5AA5;
chgOrderMng->u16_chgOrderCnt = 0;
chgOrderMng->u16_currIndex = RECORD_ZERO_SAVE;
chgOrderMng->u32_lastIndex = 0;
}
/***************************************************************
* Flash记录读写函数(内部)
***************************************************************/
/**
* @brief 向Flash写充电订单记录(内部函数)
*
* @param order_data 充电订单数据指针
* @param position 循环缓冲区中的位置(1-N范围)
*/
static void v_write_chg_order_record(S_LOG_DATA *order_data, U16_T position)
{
U16_T u16_oneRcdSize = CHG_ORDER_RECORD_SIZE; // 一条记录大小(可能大于255
U32_T u32_addr; // 存储地址
U16_T SectorIndex = 0; // 扇区偏移索引
U16_T SectorRcdIndex = 0; // 扇区内记录索引号
U16_T OneSectorRcdCnt = CHG_ORDER_PER_SECTOR_CNT; // 一个扇区内最多存储记录数
U32_T SectorAddr = 0; // 扇区地址
U16_T CurSectorRcdCnt = 0; // 当前扇区内应存放最大数目
// position参数范围是1-N,转换为0-N范围用于内部计算
U16_T internal_position = position - 1;
U8_T *tmpbuf = (U8_T *)FLASH_MGR_MALLOC(CHG_ORDER_FULL_RECORD_SIZE);
if (tmpbuf == NULL) {
return;
}
// 填充待存储数据缓存
tmpbuf[0] = RECORD_HEADER;
memcpy(tmpbuf + 1, order_data, u16_oneRcdSize); // 充电订单记录
// 计算并存储CRC
U16_T calculated_crc = u16_crc_checksum((U8_T *)order_data, u16_oneRcdSize);
tmpbuf[u16_oneRcdSize + 1] = (U8_T)calculated_crc; // 存储CRC低字节
#ifdef DEBUG_FLASH_WRITE
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] Write to addr 0x%08X (position %u, internal %u):", u32_addr, position, internal_position);
MYLOG_MSG(TASK_ID_Meterfee, " Header: 0x%02X", tmpbuf[0]);
MYLOG_MSG(TASK_ID_Meterfee, " Calculated CRC: 0x%04X -> stored low byte: 0x%02X", calculated_crc, tmpbuf[u16_oneRcdSize + 1]);
#endif
// 确定存储位置(使用internal_position,范围0-N
SectorIndex = internal_position / OneSectorRcdCnt;
SectorRcdIndex = internal_position % OneSectorRcdCnt;
SectorAddr = DATAFLASH_HIS_RECORD_ADDR + SectorIndex * SPI_SECTOR_SIZE;
// 如果是扇区内第一条记录,需要擦除整个扇区
if (SectorRcdIndex == 0) {
s32_flash_dataflash_erase_sector(SectorAddr);
// 如果记录数超过最大限制,需要调整计数
if (s_chgOrderMng.u16_chgOrderCnt > CHG_ORDER_MAX_CNT) {
// 如果待存储扇区为最后一个扇区
U16_T totalSectors = (CHG_ORDER_MAX_CNT + OneSectorRcdCnt - 1) / OneSectorRcdCnt;
if ((SectorIndex + 1) == totalSectors) {
CurSectorRcdCnt = CHG_ORDER_MAX_CNT % OneSectorRcdCnt;
if (CurSectorRcdCnt == 0) {
CurSectorRcdCnt = OneSectorRcdCnt;
}
} else {
CurSectorRcdCnt = OneSectorRcdCnt;
}
s_chgOrderMng.u16_chgOrderCnt = CHG_ORDER_MAX_CNT - CurSectorRcdCnt + 1;
}
}
// 计算具体地址并写入
u32_addr = SectorAddr + SectorRcdIndex * CHG_ORDER_FULL_RECORD_SIZE;
s32_flash_dataflash_write(u32_addr, tmpbuf, CHG_ORDER_FULL_RECORD_SIZE);
FLASH_MGR_FREE(tmpbuf);
}
/**
* @brief 从Flash读充电订单记录(内部函数)
*
* @param order_data 充电订单数据指针(用于存储读取到的数据)
* @param position 循环缓冲区中的位置(1-N范围)
* @return U8_T 读取结果:0-成功,1-失败
*/
static U8_T v_read_chg_order_record_internal(S_LOG_DATA *order_data, U16_T position)
{
U16_T u16_oneRcdSize = CHG_ORDER_RECORD_SIZE; // 一条记录大小(可能大于255
U32_T u32_addr; // 读取地址
U16_T SectorIndex = 0; // 扇区偏移索引
U16_T SectorRcdIndex = 0; // 扇区内记录索引号
U16_T OneSectorRcdCnt = CHG_ORDER_PER_SECTOR_CNT; // 一个扇区内最多存储记录数
// position参数范围是1-N,转换为0-N范围用于内部计算
U16_T internal_position = position - 1;
U8_T *tmpbuf = (U8_T *)FLASH_MGR_MALLOC(CHG_ORDER_FULL_RECORD_SIZE);
if (tmpbuf == NULL) {
return 1;
}
// 确定读取位置(使用internal_position,范围0-N
SectorIndex = internal_position / OneSectorRcdCnt;
SectorRcdIndex = internal_position % OneSectorRcdCnt;
u32_addr = DATAFLASH_HIS_RECORD_ADDR + SectorIndex * SPI_SECTOR_SIZE +
SectorRcdIndex * CHG_ORDER_FULL_RECORD_SIZE;
// 读取数据
s32_flash_dataflash_read(u32_addr, tmpbuf, CHG_ORDER_FULL_RECORD_SIZE);
// 调试信息:打印读取到的数据
#ifdef DEBUG_FLASH_READ
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] Read from addr 0x%08X (position %u, internal %u):", u32_addr, position, internal_position);
MYLOG_MSG(TASK_ID_Meterfee, " Header: 0x%02X (expected: 0x%02X)", tmpbuf[0], RECORD_HEADER);
// 计算CRC
U16_T calculated_crc = u16_crc_checksum(tmpbuf + 1, u16_oneRcdSize);
U8_T stored_crc = tmpbuf[u16_oneRcdSize + 1];
MYLOG_MSG(TASK_ID_Meterfee, " Stored CRC: 0x%02X", stored_crc);
MYLOG_MSG(TASK_ID_Meterfee, " Calculated CRC: 0x%04X -> low byte: 0x%02X", calculated_crc, (U8_T)calculated_crc);
#endif
// 验证数据完整性
if ((tmpbuf[0] == RECORD_HEADER) &&
(tmpbuf[u16_oneRcdSize + 1] == (U8_T)u16_crc_checksum(tmpbuf + 1, u16_oneRcdSize))) {
#ifdef DEBUG_FLASH_READ
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] v_read_chg_order_record_internal: data ok, memcpy dst=0x%08X, size=%u",
(U32_T)order_data, u16_oneRcdSize);
#endif
memcpy(order_data, tmpbuf + 1, u16_oneRcdSize);
FLASH_MGR_FREE(tmpbuf);
return 0;
}
#ifdef DEBUG_FLASH_READ
MYLOG_MSG(TASK_ID_Meterfee, "[DEBUG] CRC check failed!");
MYLOG_MSG(TASK_ID_Meterfee, " Header match: %s", (tmpbuf[0] == RECORD_HEADER) ? "YES" : "NO");
MYLOG_MSG(TASK_ID_Meterfee, " CRC match: %s", (tmpbuf[u16_oneRcdSize + 1] == (U8_T)u16_crc_checksum(tmpbuf + 1, u16_oneRcdSize)) ? "YES" : "NO");
#endif
FLASH_MGR_FREE(tmpbuf);
return 1;
}
/***************************************************************
* 公共接口函数实现
***************************************************************/
/**
* @brief 初始化充电订单Flash存储模块
*/
void v_chg_order_flash_init(void)
{
// 从EEPROM读取管理信息
v_read_eeprom_chgOrderMng(&s_chgOrderMng);
// 如果头标志无效,初始化默认值
if (s_chgOrderMng.u16_head != 0x5AA5)
{
s_chgOrderMng.u16_head = 0x5AA5;
s_chgOrderMng.u16_chgOrderCnt = 0;
s_chgOrderMng.u16_currIndex = RECORD_ZERO_SAVE;
s_chgOrderMng.u32_lastIndex = 0;
// 写入EEPROM
v_write_eeprom_chgOrderMng(&s_chgOrderMng);
}
// MYLOG_MSG(TASK_ID_Meterfee, "OrderMng: init ok, cnt=%u",
// s_chgOrderMng.u16_chgOrderCnt);
}
/**
* @brief 清除所有充电订单记录
*/
void v_chg_order_clear_all(void)
{
// 重置管理信息
s_chgOrderMng.u16_head = 0x5AA5;
s_chgOrderMng.u16_chgOrderCnt = 0;
s_chgOrderMng.u16_currIndex = RECORD_ZERO_SAVE;
s_chgOrderMng.u32_lastIndex = 0;
// 写入EEPROM
v_write_eeprom_chgOrderMng(&s_chgOrderMng);
// 注意:这里不清除Flash中的实际数据,因为Flash擦除成本高
// 实际数据会在后续写入时被覆盖
}
/**
* @brief 保存充电订单记录到Flash
*
* 新设计:索引号直接对应Flash物理位置
* 索引号 = u32_lastIndex + 1
* 存储位置 = (u32_lastIndex % CHG_ORDER_MAX_CNT)
* 注意:此函数会修改order_data中的u32_index字段
*/
U32_T u32_chg_order_save_record(S_LOG_DATA *order_data)
{
U32_T assignedIndex;
// 分配唯一递增索引
s_chgOrderMng.u32_lastIndex++;
assignedIndex = s_chgOrderMng.u32_lastIndex;
// 计算存储位置(基于索引号的循环缓冲区)
U16_T storagePosition = (s_chgOrderMng.u32_lastIndex - 1) % CHG_ORDER_MAX_CNT;
// 更新当前索引位置(存储0-N范围)
s_chgOrderMng.u16_currIndex = storagePosition;
// 记录条数递增(不超过最大限制)
if (s_chgOrderMng.u16_chgOrderCnt < CHG_ORDER_MAX_CNT) {
s_chgOrderMng.u16_chgOrderCnt++;
}
// 直接修改传入的订单数据,设置索引
order_data->u32_index = assignedIndex;
// 写入Flash记录,传递1-N范围的position
v_write_chg_order_record(order_data, storagePosition + 1);
// 更新管理信息到EEPROM
v_write_eeprom_chgOrderMng(&s_chgOrderMng);
return assignedIndex;
}
/**
* @brief 从Flash读取指定索引的充电订单记录
*
* 新设计:根据索引号直接计算存储位置,无需遍历查找
* 存储位置 = (index - 1) % CHG_ORDER_MAX_CNT
*/
U8_T v_read_chg_order_record(S_LOG_DATA *order_data, U32_T index)
{
// 检查索引号有效性
if (index == 0 || index > s_chgOrderMng.u32_lastIndex) {
return 1; // 索引号无效
}
// 如果没有记录,直接返回失败
if (s_chgOrderMng.u16_chgOrderCnt == 0) {
return 1;
}
// 计算存储位置(基于索引号的循环缓冲区)
U16_T storagePosition = (index - 1) % CHG_ORDER_MAX_CNT;
// 直接读取到传入的缓冲区,传递1-N范围的position
if (v_read_chg_order_record_internal(order_data, storagePosition + 1) == 0) {
// 检查索引是否匹配(防止数据被覆盖)
if (order_data->u32_index == index) {
return 0; // 成功
}
}
return 1; // 读取失败或索引不匹配
}
/**
* @brief 读取指定位置的充电订单记录(基于循环缓冲区位置)
*/
U8_T v_read_chg_order_by_position(S_LOG_DATA *order_data, U16_T position)
{
// 检查位置是否有效
if (position >= s_chgOrderMng.u16_chgOrderCnt) {
return 1;
}
// 计算实际Flash位置(考虑循环缓冲区)
U16_T actual_position;
if (s_chgOrderMng.u16_chgOrderCnt < CHG_ORDER_MAX_CNT) {
// 缓冲区未满,直接按顺序读取
actual_position = position;
} else {
// 缓冲区已满,需要计算循环位置
actual_position = (s_chgOrderMng.u16_currIndex + 1 + position) % CHG_ORDER_MAX_CNT;
}
// 传递1-N范围的position给内部函数
return v_read_chg_order_record_internal(order_data, actual_position + 1);
}
/**
* @brief 获取充电订单记录总数
*/
U16_T v_get_chg_order_count(void)
{
return s_chgOrderMng.u16_chgOrderCnt;
}
/**
* @brief 获取当前索引位置
*/
U16_T v_get_chg_order_curr_index(void)
{
return s_chgOrderMng.u16_currIndex;
}
/**
* @brief 获取最后分配的索引值
*/
U32_T v_get_chg_order_last_index(void)
{
return s_chgOrderMng.u32_lastIndex;
}
/***************************************************************
* EEPROM临时充电记录存储函数
***************************************************************/
/**
* @brief 获取指定枪号的EEPROM地址
*/
static U32_T v_get_eeprom_addr_for_gun(U8_T gunNo)
{
if (gunNo == 0) {
return EEPROM_ADDR_A_LOG_DATA;
} else {
return EEPROM_ADDR_B_LOG_DATA;
}
}
/**
* @brief 保存临时充电记录到EEPROM(用于断电续传)
*/
U8_T v_save_temp_chg_record_to_eeprom(U8_T gunNo, const S_LOG_DATA *order_data, E_TEMP_CHG_FLAG flag)
{
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
U8_T *buffer = NULL;
U8_T ret = 1;
// 检查参数有效性
if (order_data == NULL || flag >= TEMP_CHG_FLAG_INVALID) {
return 1;
}
// 动态分配内存
buffer = (U8_T *)FLASH_MGR_MALLOC(TEMP_CHG_RECORD_SIZE + 1);
if (buffer == NULL) {
return 1; // 内存分配失败
}
// 构建缓冲区:标志位 + 订单数据
buffer[0] = (U8_T)flag;
memcpy(buffer + 1, order_data, TEMP_CHG_RECORD_SIZE);
// 写入EEPROMeeprom_write返回HAL_OK(0)表示成功
if (eeprom_write(eeprom_addr, buffer, TEMP_CHG_RECORD_SIZE + 1) == HAL_OK) {
ret = 0; // 成功
}
// 释放内存
FLASH_MGR_FREE(buffer);
return ret;
}
/**
* @brief 从EEPROM读取临时充电记录(用于断电续传恢复)
*/
U8_T v_read_temp_chg_record_from_eeprom(U8_T gunNo, S_LOG_DATA *order_data, E_TEMP_CHG_FLAG *flag)
{
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
U8_T *buffer = NULL;
// 检查参数有效性
if (order_data == NULL) {
return 1;
}
// 动态分配内存
buffer = (U8_T *)FLASH_MGR_MALLOC(TEMP_CHG_RECORD_SIZE + 1);
if (buffer == NULL) {
return 1; // 内存分配失败
}
// 从EEPROM读取,eeprom_read返回HAL_OK(0)表示成功
if (eeprom_read(eeprom_addr, buffer, TEMP_CHG_RECORD_SIZE + 1) != HAL_OK) {
FLASH_MGR_FREE(buffer);
return 1; // 读取失败
}
// 检查标志位有效性
U8_T read_flag = buffer[0];
if (read_flag >= TEMP_CHG_FLAG_INVALID) {
FLASH_MGR_FREE(buffer);
return 1; // 无效标志位
}
// 返回标志位(如果提供了指针)
if (flag != NULL) {
*flag = (E_TEMP_CHG_FLAG)read_flag;
}
// 复制订单数据
memcpy(order_data, buffer + 1, TEMP_CHG_RECORD_SIZE);
// 释放内存
FLASH_MGR_FREE(buffer);
return 0; // 成功
}
/**
* @brief 清除EEPROM中的临时充电记录
*/
U8_T v_clear_temp_chg_record_in_eeprom(U8_T gunNo)
{
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
U8_T invalid_flag = TEMP_CHG_FLAG_INVALID;
// 只写入无效标志位,不清除整个区域(节省EEPROM写寿命)
// eeprom_write返回HAL_OK(0)表示成功
if (eeprom_write(eeprom_addr, &invalid_flag, 1) == HAL_OK) {
return 0; // 成功
}
return 1; // 失败
}
/**
* @brief 检查EEPROM中是否有有效的临时充电记录
*/
U8_T v_check_temp_chg_record_valid(U8_T gunNo)
{
U32_T eeprom_addr = v_get_eeprom_addr_for_gun(gunNo);
U8_T flag_byte;
// 读取标志位,eeprom_read返回HAL_OK(0)表示成功
if (eeprom_read(eeprom_addr, &flag_byte, 1) != HAL_OK) {
return 0; // 读取失败,视为无效
}
// 检查标志位是否有效
if (flag_byte == TEMP_CHG_FLAG_UNSETTLED || flag_byte == TEMP_CHG_FLAG_SETTLED) {
return 1; // 有有效记录
}
return 0; // 无效记录
}
@@ -0,0 +1,185 @@
/**
* @file meter_calculate_flash_impl.h
* @brief 充电订单 / 临时订单 EEPROM+SPI Flash 存储接口声明
*
* 与 `meter_calculate_flash_impl.c` 配套:管理元数据在 EEPROM,订单记录在 `DATAFLASH_HIS_RECORD_ADDR`
* 区域;并提供双枪临时订单 EEPROM 备份与恢复接口。具体地址与记录格式见 `public_define.h`、`flash_external_data.h`。
*/
#ifndef METER_CALCULATE_FLASH_IMPL_H
#define METER_CALCULATE_FLASH_IMPL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "meter_calculate/meter_calculate_impl.h" /* S_LOG_DATA 等 */
#include "eeprom/fm24cl16.h"
#include "externalflash/flash_external_data.h"
/*------ Exported macro(宏定义) -------------------------*/
// 调试宏定义
// #define DEBUG_FLASH_READ 0 // 启用Flash读取调试
// #define DEBUG_FLASH_WRITE 0 // 启用Flash写入调试
/* 充电订单记录容量相关宏(CHG_ORDER_* / TEMP_CHG_RECORD_SIZE
* 统一由 `BSP/spi_Flash/flash_external_data.h` 提供,避免多处重复定义。
*/
// 断电续传标志位定义
typedef enum
{
TEMP_CHG_FLAG_UNSETTLED = 0x00, // 未结算状态(充电中)
TEMP_CHG_FLAG_SETTLED = 0x01, // 已结算状态(充电完成)
TEMP_CHG_FLAG_INVALID = 0xFF // 无效状态
} E_TEMP_CHG_FLAG;
/*------ Exported struct(结构体定义) --------------------*/
/**
* @brief 充电订单管理信息结构体
*
* 存储在EEPROM中,地址:EEPROM_ADDR_CHGRCD_MNG (0x0000)
* 用于管理充电订单的索引和计数信息
*/
typedef struct
{
U16_T u16_head; // 头标志 (0x5AA5)
U16_T u16_chgOrderCnt; // 充电订单总条数
U16_T u16_currIndex; // 当前索引(循环缓冲区中的位置)
U32_T u32_lastIndex; // 最后分配的索引值(保证索引唯一性递增)
} S_CHG_ORDER_MNG;
/*------ Exported variables(变量定义) -------------------*/
// 充电订单管理信息全局变量
extern S_CHG_ORDER_MNG s_chgOrderMng;
/*------ Exported function prototypes (函数声明) -------*/
/**
* @brief 初始化充电订单Flash存储模块
*
* 从EEPROM读取管理信息,如果不存在则初始化默认值
*/
void v_chg_order_flash_init(void);
/**
* @brief 写入充电订单管理信息到EEPROM
*
* @param chgOrderMng 充电订单管理信息指针
*/
void v_write_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng);
/**
* @brief 从EEPROM读取充电订单管理信息
*
* @param chgOrderMng 充电订单管理信息指针(用于存储读取到的数据)
*/
void v_read_eeprom_chgOrderMng(S_CHG_ORDER_MNG *chgOrderMng);
/**
* @brief 清除所有充电订单记录
*
* 重置管理信息并清除Flash中的记录
*/
void v_chg_order_clear_all(void);
/**
* @brief 保存充电订单记录到Flash
*
* 自动分配索引并保存记录,更新管理信息
* 注意:此函数会修改order_data中的u32_index字段
*
* @param order_data 充电订单数据指针(会被修改)
* @return U32_T 分配的索引值(唯一标识)
*/
U32_T u32_chg_order_save_record(S_LOG_DATA *order_data);
/**
* @brief 从Flash读取指定索引的充电订单记录
*
* @param order_data 充电订单数据指针(用于存储读取到的数据)
* @param index 要读取的记录索引
* @return U8_T 读取结果:0-成功,1-失败
*/
U8_T v_read_chg_order_record(S_LOG_DATA *order_data, U32_T index);
/**
* @brief 获取充电订单记录总数
*
* @return U16_T 充电订单记录总数
*/
U16_T v_get_chg_order_count(void);
/**
* @brief 获取当前索引位置
*
* @return U16_T 当前索引位置
*/
U16_T v_get_chg_order_curr_index(void);
/**
* @brief 获取最后分配的索引值
*
* @return U32_T 最后分配的索引值(唯一递增)
*/
U32_T v_get_chg_order_last_index(void);
/**
* @brief 读取指定位置的充电订单记录(基于循环缓冲区位置)
*
* @param order_data 充电订单数据指针(用于存储读取到的数据)
* @param position 循环缓冲区中的位置(0-based)
* @return U8_T 读取结果:0-成功,1-失败
*/
U8_T v_read_chg_order_by_position(S_LOG_DATA *order_data, U16_T position);
/**
* @brief 保存临时充电记录到EEPROM(用于断电续传)
*
* 在充电过程中周期更新实时充电信息,第一个字节为断电续传标志位
*
* @param gunNo 枪号(0-A枪,1-B枪)
* @param order_data 充电订单数据指针
* @param flag 断电续传标志位(0-未结算,1-已结算)
* @return U8_T 保存结果:0-成功,1-失败
*/
U8_T v_save_temp_chg_record_to_eeprom(U8_T gunNo, const S_LOG_DATA *order_data, E_TEMP_CHG_FLAG flag);
/**
* @brief 从EEPROM读取临时充电记录(用于断电续传恢复)
*
* @param gunNo 枪号(0-A枪,1-B枪)
* @param order_data 充电订单数据指针(用于存储读取到的数据)
* @param flag 读取到的断电续传标志位指针(可选)
* @return U8_T 读取结果:0-成功,1-失败
*/
U8_T v_read_temp_chg_record_from_eeprom(U8_T gunNo, S_LOG_DATA *order_data, E_TEMP_CHG_FLAG *flag);
/**
* @brief 清除EEPROM中的临时充电记录
*
* 将断电续传标志位设置为无效状态
*
* @param gunNo 枪号(0-A枪,1-B枪)
* @return U8_T 清除结果:0-成功,1-失败
*/
U8_T v_clear_temp_chg_record_in_eeprom(U8_T gunNo);
/**
* @brief 检查EEPROM中是否有有效的临时充电记录
*
* @param gunNo 枪号(0-A枪,1-B枪)
* @return U8_T 检查结果:0-无有效记录,1-有有效记录
*/
U8_T v_check_temp_chg_record_valid(U8_T gunNo);
#ifdef __cplusplus
}
#endif
#endif /* METER_CALCULATE_FLASH_IMPL_H */
@@ -0,0 +1,570 @@
/**
* @file ocpp_mv_offline_flash_impl.c
* @brief OCPP 离线 MeterValuesSPI Flash 线性池 + FRAM 流水号映射(实现仿 fault_flash_impl
*
* =============================================================================
* 本文件按功能划分为以下模块(自上而下阅读):
* -----------------------------------------------------------------------------
* [1] 编译期检查、全局变量与文件内静态资源
* [2] FRAM 管理块:CRC 计算、默认值、读/写 EEPROM、上电初始化
* [3] Flash 池寻址:枪半区基址、该枪序号 ↔ 槽、槽 ↔ 线性字节地址
* [4] Flash 单槽访问:组帧/校验、按物理槽读一条、按物理槽写一条(含扇区擦除策略)
* [5] FRAM 映射表:按流水号查找行、查找空行
* [6] 对外接口:池容量、负载合法性、保存/按序号读取 MV
* [7] 对外:映射表查询/删除;整池擦除仅本文件 static
* =============================================================================
*/
#include "ocpp_mv_offline_flash_impl.h"
#include "unsettled_order_mng.h"
#include "publicdata/publicdata.h"
#include <string.h>
/* -------------------------------------------------------------------------- */
/* [1] 编译期检查、全局变量与文件内静态资源 */
/* -------------------------------------------------------------------------- */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(OCPP_MV_OFFLINE_RECORDS_PER_SECTOR > 0u, "records per sector");
_Static_assert(OCPP_MV_OFFLINE_POOL_MAX_PER_GUN > 0u, "pool max per gun");
#else
typedef int __ocpp_mv_check_c1[(OCPP_MV_OFFLINE_RECORDS_PER_SECTOR > 0u) ? 1 : -1];
typedef int __ocpp_mv_check_c2[(OCPP_MV_OFFLINE_POOL_MAX_PER_GUN > 0u) ? 1 : -1];
#endif
/**
* @var s_ocppMvOfflineFramMng
* @brief OCPP 离线 MV 的 FRAM 管理块在 RAM 中的镜像
* @details
* - 与 FM24 地址 `EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL`0x0700)对应,长度为 128 字节。
* - 含 `u32_next_seq[2]`(每枪下一条单调序号)及最多 `OCPP_MV_OFFLINE_MAP_ROWS` 行流水号+枪映射。
* - 上电由 `v_ocpp_mv_offline_init` 通过 `u8_ocpp_mv_offline_fram_read` 装载;业务写入 MV 后
* 由 `u8_ocpp_mv_offline_fram_write` 回写 FRAM,掉电保持序号与映射连续性。
*/
static S_OCPP_MV_OFFLINE_FRAM_MNG s_ocppMvOfflineFramMng;
/**
* @var s_ocpp_mv_frame_buf
* @brief 单条 Flash 记录在 RAM 中的组帧缓冲(静态、本文件内可见)
* @details 布局与 `fault_flash_impl` 一致:`[0]` 帧头、`[1..64]` 负载、`[65]` CRC 低 8 位,
* 总长 `OCPP_MV_OFFLINE_FRAME_BYTES`34 = 1+32+1)。供底层按物理槽读/写时复用,非重入。
*/
static U8_T s_ocpp_mv_frame_buf[OCPP_MV_OFFLINE_FRAME_BYTES];
/* -------------------------------------------------------------------------- */
/* [2] FRAM 管理块:CRC、默认值、读/写 EEPROM、上电初始化 */
/* -------------------------------------------------------------------------- */
/**
* @fn u16_ocpp_mv_fram_crc
* @brief 计算 FRAM 管理块整结构的 CRC-16(算法与未结算模块 `u16_calculate_crc` 一致)
* @param[in] p 待计算的管理块指针(只读)
* @return 16 位 CRC 值
* @note 计算前将副本中 `u16_crc` 置 0,再对整块字节流做 CRC,与写入 EEPROM 时的规则一致。
*/
static U16_T u16_ocpp_mv_fram_crc(const S_OCPP_MV_OFFLINE_FRAM_MNG *p)
{
S_OCPP_MV_OFFLINE_FRAM_MNG z;
(void)memcpy(&z, p, sizeof(z));
z.u16_crc = 0;
return u16_calculate_crc((const U8_T *)&z, (U16_T)sizeof(z));
}
/**
* @fn v_ocpp_mv_offline_fram_default
* @brief 将 FRAM 管理块置为出厂/安全默认并填入合法 CRC(不写 EEPROM)
* @param[in,out] mng 目标结构体指针;为 NULL 时直接返回
* @details 清零后设置 `u16_head`、`u16_version``u32_next_seq[]` 与映射表全 0。
*/
static void v_ocpp_mv_offline_fram_default(S_OCPP_MV_OFFLINE_FRAM_MNG *mng)
{
if (mng == NULL) {
return;
}
(void)memset(mng, 0, sizeof(*mng));
mng->u16_head = OCPP_MV_OFFLINE_FRAM_HEAD;
mng->u16_version = (U16_T)OCPP_MV_OFFLINE_FRAM_MNG_VER;
mng->u16_crc = u16_ocpp_mv_fram_crc(mng);
}
/**
* @fn u8_ocpp_mv_offline_fram_read
* @brief 从 FRAM(FM24)读取管理块到调用方缓冲区,并校验头与 CRC
* @param[out] out 输出缓冲区,不可为 NULL
* @return 0 读取且校验成功;1 空指针;2 I2C 读失败(已写入默认块到 out);3 自愈写 EEPROM 失败
* @details
* - 若头标记、版本号或 CRC 不匹配,则调用 `v_ocpp_mv_offline_fram_default` 填充 out
* 并尝试写回 EEPROM 以自愈坏块。
*/
static U8_T u8_ocpp_mv_offline_fram_read(S_OCPP_MV_OFFLINE_FRAM_MNG *out)
{
S_OCPP_MV_OFFLINE_FRAM_MNG tmp;
U16_T crc_expect;
if (out == NULL) {
return 1u;
}
if (eeprom_read((S32_T)EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL, (U8_T *)&tmp, (S32_T)sizeof(tmp)) != HAL_OK) {
v_ocpp_mv_offline_fram_default(out);
return 2u;
}
crc_expect = u16_ocpp_mv_fram_crc(&tmp);
if (tmp.u16_head != OCPP_MV_OFFLINE_FRAM_HEAD || tmp.u16_version != (U16_T)OCPP_MV_OFFLINE_FRAM_MNG_VER
|| tmp.u16_crc != crc_expect) {
v_ocpp_mv_offline_fram_default(out);
if (eeprom_write((S32_T)EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL, (U8_T *)out, (S32_T)sizeof(*out)) != HAL_OK) {
return 3u;
}
return 0u;
}
(void)memcpy(out, &tmp, sizeof(tmp));
return 0u;
}
/**
* @fn u8_ocpp_mv_offline_fram_write
* @brief 将管理块写入 FRAM,并自动重算 `u16_crc` 后落盘
* @param[in] mng 待写入的管理块指针;为 NULL 时返回错误
* @return 0 成功;1 空指针;2 EEPROM 写失败
*/
static U8_T u8_ocpp_mv_offline_fram_write(const S_OCPP_MV_OFFLINE_FRAM_MNG *mng)
{
S_OCPP_MV_OFFLINE_FRAM_MNG tmp;
if (mng == NULL) {
return 1u;
}
(void)memcpy(&tmp, mng, sizeof(tmp));
tmp.u16_crc = u16_ocpp_mv_fram_crc(&tmp);
if (eeprom_write((S32_T)EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL, (U8_T *)&tmp, (S32_T)sizeof(tmp)) != HAL_OK) {
return 2u;
}
return 0u;
}
/**
* @fn v_ocpp_mv_offline_init
* @brief 模块上电初始化:从 FRAM 恢复 `s_ocppMvOfflineFramMng`
* @details 若读失败或数据非法,则写入默认管理块,保证后续 `mv_save` 有合法序号与空映射表。
*/
void v_ocpp_mv_offline_init(void)
{
if (u8_ocpp_mv_offline_fram_read(&s_ocppMvOfflineFramMng) != 0u) {
v_ocpp_mv_offline_fram_default(&s_ocppMvOfflineFramMng);
(void)u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
}
}
/* -------------------------------------------------------------------------- */
/* [3] Flash 池寻址:枪半区基址、该枪单调序号 ↔ 槽、槽 ↔ 线性地址 */
/* -------------------------------------------------------------------------- */
/**
* @fn u32_ocpp_mv_gun_half_base
* @brief 指定枪在 SPI Flash 上的半区首字节地址(枪0=前半 32KB,枪1=后半 32KB
*/
static U32_T u32_ocpp_mv_gun_half_base(U8_T gun)
{
return DATAFLASH_OCPP_MV_OFFLINE_ADDR + (U32_T)gun * DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE;
}
/**
* @fn u32_gun_seq_to_slot
* @brief 该枪单调序号映射为该枪半区内物理槽(环形覆盖)
*/
static U32_T u32_gun_seq_to_slot(U32_T seq)
{
return (U32_T)(seq % (U32_T)OCPP_MV_OFFLINE_POOL_MAX_PER_GUN);
}
/**
* @fn u32_gun_slot_to_addr
* @brief 枪号 + 该枪半区内槽索引 → 线性字节地址
*/
static U32_T u32_gun_slot_to_addr(U8_T gun, U32_T slot_in_gun)
{
U32_T rps = (U32_T)OCPP_MV_OFFLINE_RECORDS_PER_SECTOR;
U32_T sec_in_gun = slot_in_gun / rps;
U32_T pos = slot_in_gun % rps;
U32_T sec_base = u32_ocpp_mv_gun_half_base(gun) + sec_in_gun * SPI_SECTOR_SIZE;
return sec_base + pos * (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES;
}
/* -------------------------------------------------------------------------- */
/* [4] Flash 单槽访问:读一条、写一条(扇区擦除策略同 fault) */
/* -------------------------------------------------------------------------- */
/**
* @fn u8_ocpp_mv_read_at_gun_slot
* @brief 从指定枪半区内槽读取一条 MV 负载(校验帧头与 CRC)
*/
static U8_T u8_ocpp_mv_read_at_gun_slot(U8_T gun, U32_T slot_in_gun, S_OCPP_MV_OFFLINE_MV *mv)
{
U32_T addr = u32_gun_slot_to_addr(gun, slot_in_gun);
if (mv == NULL) {
return 1u;
}
if (s32_flash_dataflash_read(addr, s_ocpp_mv_frame_buf, (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES) != 0u) {
return 2u;
}
if (s_ocpp_mv_frame_buf[0] != RECORD_HEADER) {
return 3u;
}
if (s_ocpp_mv_frame_buf[1u + sizeof(S_OCPP_MV_OFFLINE_MV)] !=
(U8_T)u16_crc_checksum(s_ocpp_mv_frame_buf + 1u, (U16_T)sizeof(S_OCPP_MV_OFFLINE_MV))) {
return 3u;
}
(void)memcpy(mv, s_ocpp_mv_frame_buf + 1u, sizeof(S_OCPP_MV_OFFLINE_MV));
return 0u;
}
/**
* @fn u8_ocpp_mv_write_at_gun_slot
* @brief 向指定枪半区内槽写入一条 MV(组帧、按扇区擦除后写入)
* @details 扇区首槽擦扇区;非首槽若本扇区 0 槽无效则先擦整扇区(同 fault 策略)。
*/
static U8_T u8_ocpp_mv_write_at_gun_slot(U8_T gun, U32_T slot_in_gun, const S_OCPP_MV_OFFLINE_MV *mv)
{
U32_T rps = (U32_T)OCPP_MV_OFFLINE_RECORDS_PER_SECTOR;
U32_T sec_in_gun = slot_in_gun / rps;
U32_T pos = slot_in_gun % rps;
U32_T sec_base = u32_ocpp_mv_gun_half_base(gun) + sec_in_gun * SPI_SECTOR_SIZE;
U32_T addr = sec_base + pos * (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES;
U32_T slot0_in_sector = sec_in_gun * rps;
S_OCPP_MV_OFFLINE_MV dummy;
if (mv == NULL) {
return 1u;
}
/* 非扇区首槽:仅在本扇区尚无其它有效记录时擦除(避免 seq 20 写入后 seq 21 因 slot0 空再次擦扇区抹掉 20) */
if (pos != 0u) {
if (u8_ocpp_mv_read_at_gun_slot(gun, slot0_in_sector, &dummy) != 0u) {
U32_T s;
U8_T sector_has_data = 0u;
for (s = slot0_in_sector + 1u; s < slot_in_gun; s++) {
if (u8_ocpp_mv_read_at_gun_slot(gun, s, &dummy) == 0u) {
sector_has_data = 1u;
break;
}
}
if (sector_has_data == 0u) {
(void)s32_flash_dataflash_erase_sector(sec_base);
}
}
}
s_ocpp_mv_frame_buf[0] = RECORD_HEADER;
(void)memcpy(s_ocpp_mv_frame_buf + 1u, mv, sizeof(S_OCPP_MV_OFFLINE_MV));
s_ocpp_mv_frame_buf[1u + sizeof(S_OCPP_MV_OFFLINE_MV)] =
(U8_T)u16_crc_checksum((U8_T *)mv, (U16_T)sizeof(S_OCPP_MV_OFFLINE_MV));
if (pos == 0u) {
(void)s32_flash_dataflash_erase_sector(sec_base);
}
return s32_flash_dataflash_write(addr, s_ocpp_mv_frame_buf, (U32_T)OCPP_MV_OFFLINE_FRAME_BYTES);
}
/* -------------------------------------------------------------------------- */
/* [5] FRAM 映射表:按会话键(当前为开始充电秒 + hi=0)查找、查找空行 */
/* -------------------------------------------------------------------------- */
/**
* @fn p_ocpp_mv_map_find_row
* @brief 在映射表中查找与给定会话键(`u32_tx_sn_lo` / `u32_tx_sn_hi`)及枪号完全匹配的一行
*/
static S_OCPP_MV_OFFLINE_MAP_ROW *p_ocpp_mv_map_find_row(U32_T tx_sn_lo, U32_T tx_sn_hi, U8_T gun)
{
U32_T i;
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u32_tx_sn_lo == tx_sn_lo && r->u32_tx_sn_hi == tx_sn_hi && r->u8_gun_no == gun) {
return r;
}
}
return NULL;
}
/**
* @fn p_ocpp_mv_map_find_empty
* @brief 在映射表中查找第一个空闲行(约定:`u32_tx_sn_lo` 与 `u32_tx_sn_hi` 均为 0 表示未使用)
* @return 空闲行指针;若无空行返回 NULL(表示已达映射行上限)
*/
static S_OCPP_MV_OFFLINE_MAP_ROW *p_ocpp_mv_map_find_empty(void)
{
U32_T i;
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u32_tx_sn_lo == 0u && r->u32_tx_sn_hi == 0u) {
return r;
}
}
return NULL;
}
/**
* @brief 映射表满时,复用指定枪最旧一行(seq_start 最小)
* @details 起充不再批量 clear_gun;满表时仅在新会话首条 MV 前淘汰一行,避免 “map full” 丢采样
*/
static S_OCPP_MV_OFFLINE_MAP_ROW *p_ocpp_mv_map_evict_oldest_gun(U8_T gun)
{
U32_T i;
S_OCPP_MV_OFFLINE_MAP_ROW *best = NULL;
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u8_gun_no != gun || (r->u32_tx_sn_lo == 0u && r->u32_tx_sn_hi == 0u)) {
continue;
}
if (best == NULL || r->u32_seq_start < best->u32_seq_start) {
best = r;
}
}
if (best != NULL) {
(void)memset(best, 0, sizeof(*best));
}
return best;
}
/* -------------------------------------------------------------------------- */
/* [6] 对外:负载校验、保存 MV、按枪序号读取 MV */
/* -------------------------------------------------------------------------- */
/**
* @fn u8_ocpp_mv_offline_mv_is_valid
* @brief 判断内存中的一条 MV 负载是否带有合法魔数与版本号(不读 Flash)
* @param[in] mv 负载指针;NULL 视为无效
* @return 1 有效;0 无效
*/
U8_T u8_ocpp_mv_offline_mv_is_valid(const S_OCPP_MV_OFFLINE_MV *mv)
{
if (mv == NULL) {
return 0u;
}
if (mv->u32_magic != OCPP_MV_OFFLINE_FLASH_MAGIC) {
return 0u;
}
if (mv->u16_version != (U16_T)OCPP_MV_OFFLINE_FLASH_VER) {
return 0u;
}
return 1u;
}
/**
* @brief 为指定会话键在该枪半区追加保存一条离线 MV:分配序号、写 Flash、更新 FRAM
* @param[in] tx_sn_lo 会话键:当前为 `xDate2Seconds(StartChargeTime)`(单路 U32
* @param[in] tx_sn_hi 当前恒 0(与 FRAM 行 `u32_tx_sn_hi` 一致)
* @param[in] u8_gun_no 枪号 0 或 1
* @param[out] out_seq 本次在该枪下的单调序号(与物理槽 `seq % POOL_MAX_PER_GUN` 对应)
* @details
* - `seq` 取当前 `u32_next_seq[u8_gun_no]`;已存在映射行时要求 `seq == seq_start + count`。
* - Flash 写失败时回滚 RAM 中对映射行的修改,不推进该枪的 `u32_next_seq`。
*/
U8_T u8_ocpp_mv_offline_mv_save(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, const S_OCPP_MV_OFFLINE_MV *mv,
U32_T *out_seq)
{
S_OCPP_MV_OFFLINE_MV wr;
S_OCPP_MV_OFFLINE_MAP_ROW *row;
S_OCPP_MV_OFFLINE_MAP_ROW *empty;
U32_T seq;
U32_T slot;
U8_T ret;
if (mv == NULL || out_seq == NULL) {
return 1u;
}
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
(void)memcpy(&wr, mv, sizeof(wr));
if (wr.u32_magic != OCPP_MV_OFFLINE_FLASH_MAGIC) {
wr.u32_magic = OCPP_MV_OFFLINE_FLASH_MAGIC;
}
if (wr.u16_version != (U16_T)OCPP_MV_OFFLINE_FLASH_VER) {
wr.u16_version = (U16_T)OCPP_MV_OFFLINE_FLASH_VER;
}
seq = s_ocppMvOfflineFramMng.u32_next_seq[u8_gun_no];
row = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (row != NULL) {
if (seq != row->u32_seq_start + (U32_T)row->u16_count) {
return 5u;
}
row->u16_count++;
} else {
empty = p_ocpp_mv_map_find_empty();
if (empty == NULL) {
empty = p_ocpp_mv_map_evict_oldest_gun(u8_gun_no);
if (empty == NULL) {
return 2u;
}
}
empty->u32_tx_sn_lo = tx_sn_lo;
empty->u32_tx_sn_hi = tx_sn_hi;
empty->u32_seq_start = seq;
empty->u16_count = 1u;
empty->u8_gun_no = u8_gun_no;
empty->u8_reserved = 0u;
}
slot = u32_gun_seq_to_slot(seq);
ret = u8_ocpp_mv_write_at_gun_slot(u8_gun_no, slot, &wr);
if (ret != 0u) {
if (row != NULL) {
row->u16_count--;
} else {
empty = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (empty != NULL) {
(void)memset(empty, 0, sizeof(*empty));
}
}
return 3u;
}
s_ocppMvOfflineFramMng.u32_next_seq[u8_gun_no] = seq + 1u;
*out_seq = seq;
ret = u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
if (ret != 0u) {
return 4u;
}
return 0u;
}
/**
* @brief 按该枪单调序号读取一条 MV`seq % POOL_MAX_PER_GUN`
*/
U8_T u8_ocpp_mv_offline_mv_read_by_seq(U8_T u8_gun_no, U32_T seq, S_OCPP_MV_OFFLINE_MV *out)
{
U32_T slot;
if (out == NULL) {
return 1u;
}
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
slot = u32_gun_seq_to_slot(seq);
return u8_ocpp_mv_read_at_gun_slot(u8_gun_no, slot, out);
}
/* -------------------------------------------------------------------------- */
/* [7] 对外:映射表查询/删除、整池擦除 */
/* -------------------------------------------------------------------------- */
/**
* @fn u8_ocpp_mv_offline_map_get
* @brief 根据会话键查询其在池中的连续序号区间(供上送循环 `seq_start .. seq_start+count-1`
* @param[in] tx_sn_lo 会话键(当前为开始充电秒)
* @param[in] tx_sn_hi 当前恒 0
* @param[out] seq_start 输出该区间的首条全局序号
* @param[out] count 输出连续条数
* @return 0 找到;1 空指针;2 未找到对应键
*/
U8_T u8_ocpp_mv_offline_map_get(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, U32_T *seq_start, U16_T *count)
{
S_OCPP_MV_OFFLINE_MAP_ROW *row;
if (seq_start == NULL || count == NULL) {
return 1u;
}
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
row = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (row == NULL) {
return 2u;
}
*seq_start = row->u32_seq_start;
*count = row->u16_count;
return 0u;
}
/**
* @fn u8_ocpp_mv_offline_map_remove
* @brief 上送完成后删除指定会话键的映射行(不自动擦除 Flash 池中对应物理数据)
* @param[in] tx_sn_lo 会话键(当前为开始充电秒)
* @param[in] tx_sn_hi 当前恒 0
* @return 0 成功删除并已写回 FRAM;1 未找到该键;2 FRAM 写失败
* @note 若需同时清空整池 Flash,可调用本文件内静态函数 `v_ocpp_mv_offline_flash_erase_pool`(维护/调试用)。
*/
U8_T u8_ocpp_mv_offline_map_remove(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi)
{
S_OCPP_MV_OFFLINE_MAP_ROW *row;
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
row = p_ocpp_mv_map_find_row(tx_sn_lo, tx_sn_hi, u8_gun_no);
if (row == NULL) {
return 1u;
}
(void)memset(row, 0, sizeof(*row));
return u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
}
/**
* @fn u8_ocpp_mv_offline_map_clear_gun
* @brief 新一笔充电开始前,清除指定枪在 FRAM 中的全部离线 MV 映射行
*
* @details
* - **作用对象**:仅 `s_ocppMvOfflineFramMng.aMap[]`(会话键 → Flash 全局序号区间),**不擦除** SPI Flash 池内已写入的 MV 物理记录;池区仍按环形序号覆盖写入。
* - **调用时机**:维护/调试;正常起充不再批量调用(同枪多笔离线充须保留各行映射)。映射满时由 `mv_save` 按枪淘汰最旧一行。
* - **与 `u8_ocpp_mv_offline_map_remove` 区别**`map_remove` 在**单笔**离线 MV 上送完成后按会话键删一行;本函数在**起充边沿**按枪批量清空,不区分 `tx_sn_lo`/`tx_sn_hi`。
* - **上送侧**:清空后本枪需重新落盘才会建立新映射;进行中的 `session` 补发状态机在桥接层会随起充一并 `memset` 复位。
*
* @param[in] u8_gun_no 枪索引 `0 .. OCPP_MV_OFFLINE_GUN_CNT-1`
* @return 0 成功(含本枪本无映射行、无需写 FRAM);1 枪号非法;2 有行被清但 FRAM 写回失败
*/
U8_T u8_ocpp_mv_offline_map_clear_gun(U8_T u8_gun_no)
{
U32_T i;
U8_T changed = 0u;
if (u8_gun_no >= OCPP_MV_OFFLINE_GUN_CNT) {
return 1u;
}
for (i = 0u; i < (U32_T)OCPP_MV_OFFLINE_MAP_ROWS; i++) {
S_OCPP_MV_OFFLINE_MAP_ROW *r = &s_ocppMvOfflineFramMng.aMap[i];
if (r->u8_gun_no == u8_gun_no && (r->u32_tx_sn_lo != 0u || r->u32_tx_sn_hi != 0u)) {
(void)memset(r, 0, sizeof(*r));
changed = 1u;
}
}
if (changed == 0u) {
return 0u;
}
if (u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng) != 0u) {
return 2u;
}
return 0u;
}
/**
* @fn v_ocpp_mv_offline_flash_erase_pool
* @brief 擦除离线 MV 专用分区内的全部物理扇区(整池 Flash 清空)
* @param[in] also_reset_fram 非 0 时同时将 `s_ocppMvOfflineFramMng` 置默认并写回 FRAM(序号与映射清零)
* @details 遍历 `DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT`,对每扇区基址调用 `s32_flash_dataflash_erase_sector`。
*/
static void v_ocpp_mv_offline_flash_erase_pool(U8_T also_reset_fram)
{
U32_T i;
for (i = 0u; i < DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT; i++) {
(void)s32_flash_dataflash_erase_sector(DATAFLASH_OCPP_MV_OFFLINE_ADDR + i * SPI_SECTOR_SIZE);
}
if (also_reset_fram != 0u) {
v_ocpp_mv_offline_fram_default(&s_ocppMvOfflineFramMng);
(void)u8_ocpp_mv_offline_fram_write(&s_ocppMvOfflineFramMng);
}
}
@@ -0,0 +1,195 @@
/**
* @file ocpp_mv_offline_flash_impl.h
* @brief OCPP 离线 MeterValues:仿 `fault_flash_impl` — SPI Flash 线性池 + FRAM 流水号→序号映射
*
* **为何更简单**
* - Flash 侧为「定长记录数组」:每条在 **该枪半区** 内逻辑序号与物理槽一一对应(`seq` → 地址由宏公式算出)。
* - FRAM 侧存:**每枪下一单调序号** `u32_next_seq[2]`、以及若干行「流水号(tx)+枪号 ↔ 起始序号 + 条数」;
* 上送时 `u8_ocpp_mv_offline_map_get(gun, tx...)` 得 `seq_start,count`,再
* `for (k=0;k<count;k++) u8_ocpp_mv_offline_mv_read_by_seq(gun, seq_start+k, &buf)`。
*
* **Flash 分区(双枪互不穿插)**
* - 区间 `0x2C0000`~`0x2CFFFF`64KB)见 `flash_external_data.h`**前半 32KB 仅枪0、后半 32KB 仅枪1**
* 保证连续 Flash 地址均为同一把枪的环形池;双枪同时充电时不会在物理上交错两条订单的 MV。
*
* **Flash 格式(与故障记录一致思路)**
* - 每条:`RECORD_HEADER`(1B) + `S_OCPP_MV_OFFLINE_MV`(32B) + CRC8低字节(1B),总长 `OCPP_MV_OFFLINE_FRAME_BYTES`。
* - 扇区内可容纳条数 = `SPI_SECTOR_SIZE / OCPP_MV_OFFLINE_FRAME_BYTES`(向下取整,尾部截断不用)。
* - **每枪**最大条数 = `OCPP_MV_OFFLINE_POOL_MAX_PER_GUN`(每枪 8 扇区 × 每扇区条数)。
*
* **环形滚动(按枪)**
* - 每枪单调序号 `u32_next_seq[gun]`(存 FRAM):`seq = next_seq[gun]++`;物理槽 `idx = seq % POOL_MAX_PER_GUN`(仅在该枪半区内取模)。
* - 池满后自然覆盖该枪最旧物理槽;上送完成后应 `u8_ocpp_mv_offline_map_remove(gun, tx...)` 删除对应行。
*/
#ifndef OCPP_MV_OFFLINE_FLASH_IMPL_H
#define OCPP_MV_OFFLINE_FLASH_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "externalflash/flash_external_data.h"
#include "eeprom/fm24cl16.h"
/** @brief 单包 MeterValues 负载长度(字节),与结构体 `S_OCPP_MV_OFFLINE_MV` 一致(与 BS_ocpp_ctrl 已实现 measurand 对齐) */
#define OCPP_MV_OFFLINE_PAYLOAD_BYTES 32u
/** @brief 单条落盘帧长:帧头 + 负载 + CRC1(与 `fault_flash_impl` 相同思路) */
#define OCPP_MV_OFFLINE_FRAME_BYTES (1u + OCPP_MV_OFFLINE_PAYLOAD_BYTES + 1u)
/** @brief 每个物理扇区内可容纳的 MV 条数(4096 / FRAME,向下取整,尾部截断) */
#define OCPP_MV_OFFLINE_RECORDS_PER_SECTOR (SPI_SECTOR_SIZE / OCPP_MV_OFFLINE_FRAME_BYTES)
/** @brief 枪号:0 = 前半区(枪1),1 = 后半区(枪2) */
#define OCPP_MV_OFFLINE_GUN_CNT 2u
/** @brief 每个枪在 Flash 内占用的连续扇区数(半区 32KB / 4096 */
#define OCPP_MV_OFFLINE_SECTORS_PER_GUN (DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE / SPI_SECTOR_SIZE)
/** @brief 单枪环形池最大条数(每枪 8 扇区 × 每扇区条数) */
#define OCPP_MV_OFFLINE_POOL_MAX_PER_GUN (OCPP_MV_OFFLINE_RECORDS_PER_SECTOR * OCPP_MV_OFFLINE_SECTORS_PER_GUN)
/** @brief FRAM 中可同时保存的「流水号→序号区间」映射行数(受 128B 控制块限制) */
#define OCPP_MV_OFFLINE_MAP_ROWS 6u
/** @brief 负载内魔数(小端可视 OCMV) */
#define OCPP_MV_OFFLINE_FLASH_MAGIC 0x564D434Fu
/** @brief 负载格式版本(32B 布局为 4;与旧 64B 不兼容,升级后建议整池擦除) */
#define OCPP_MV_OFFLINE_FLASH_VER 4u
/** @brief FRAM 管理块头 */
#define OCPP_MV_OFFLINE_FRAM_HEAD 0x5AA9u
/** @brief FRAM 管理块布局版本(变更字段时递增;5:每枪 next_seq + 映射行含枪号) */
#define OCPP_MV_OFFLINE_FRAM_MNG_VER 5u
/**
* @brief 一条待补发的 MeterValues「测量快照」负载(固定 32B)
*
* 字段与 `BS_ocpp_ctrl.c` 中 `u8_get_ocpp_measurand_data` **已实现** 的 measurand 对齐,便于离线存盘与联网后按同口径组 SampledValue
* - `ENUM_EnergyActiveImportRegister`:累计 Wh(源码为 u64/10,此处存 u32,超高 Wh 截断)
* - `ENUM_EnergyActiveImportInterval`:事务段增量 Wh → `u32_energy_interval_wh`
* - `ENUM_Voltage` / `ENUM_CurrentImport`:电表电压 0.1V、电流 0.01A(原 0.1A×10
* - `ENUM_PowerActiveImport`:有功功率 W(饱和 65535,极大桩可再约定用比例)
* - `ENUM_PowerOffered`:额定功率,单位 **0.1 kW**(与 `E_BS_GET_SYS_DATA_RATED_POWER` 一致)
* - `ENUM_CurrentOffered`:最大电流 0.01A
* - `ENUM_SoC` + `u8_soc_data_type`SOC% 与 0当前/1起始/2结束(与函数入参 `u8_data_type` 一致)
*
* 流水号 **tx** 不在本结构重复保存,以 FRAM 映射 `aMap[]` 为准。
*/
typedef struct
{
U32_T u32_magic; /**< 须为 OCPP_MV_OFFLINE_FLASH_MAGIC */
U32_T u32_timestamp_unix; /**< 取样时刻日历秒:`xDate2Seconds(GetCurrentTime)`,与补发 JSON 中 timestamp 一致(勿存上电单调秒) */
U32_T u32_meter_import_wh; /**< Energy.Active.Import.Register:累计 Whu64/10 截断 u32 */
U32_T u32_energy_interval_wh; /**< Energy.Active.Import.Interval:事务段增量 Wh */
U16_T u16_voltage_v_x10; /**< Voltage:输出电压 0.1V */
U16_T u16_current_import_a_x100; /**< Current.Import:电流 0.01A */
U16_T u16_power_active_w; /**< Power.Active.Import:功率 W(饱和) */
U16_T u16_power_offered_01kw; /**< Power.Offered:额定功率 0.1kW 单位 */
U16_T u16_current_offered_a_x100; /**< Current.Offered:最大电流 0.01A */
U8_T u8_soc; /**< SoC0~100 */
U8_T u8_soc_data_type; /**< SoC 上下文:0 当前 / 1 Transaction.Begin / 2 Transaction.End */
U8_T u8_connector_id; /**< 连接器号 */
U8_T u8_evse_id; /**< EVSE/枪逻辑号 */
U16_T u16_version; /**< 须为 OCPP_MV_OFFLINE_FLASH_VER */
} S_OCPP_MV_OFFLINE_MV;
/**
* @brief FRAM 中一行:会话键(当前实现为开始充电秒)↔ 在 Flash 池中的 **连续序号区间**
*
* 第 k 条(0<=k<count)在该枪下的单调序号为 `u32_seq_start + k`,读 Flash 时用
* `idx = (u32_seq_start + k) % OCPP_MV_OFFLINE_POOL_MAX_PER_GUN`(与本模块 `mv_read_by_seq` 一致)。
*/
typedef struct
{
U32_T u32_tx_sn_lo; /**< 当前:`xDate2Seconds(StartChargeTime)`;字段名历史兼容 */
U32_T u32_tx_sn_hi; /**< 当前约定恒 0(保留);非 64 位键的高半字 */
U32_T u32_seq_start; /**< 该流水号在该枪下第一条 MV 的单调序号 */
U16_T u16_count; /**< 连续条数 */
U8_T u8_gun_no; /**< 0=前半区枪11=后半区枪2(与 `u8_ocpp_mv_offline_mv_save` 入参一致) */
U8_T u8_reserved; /**< 预留 */
} S_OCPP_MV_OFFLINE_MAP_ROW;
/**
* @brief FRAM 管理块(128B`EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL`
*
* CRC:整结构 `u16_crc` 置 0 后按 `u16_calculate_crc` 计算(与未结算模块相同)。
*/
typedef struct
{
U16_T u16_head; /**< OCPP_MV_OFFLINE_FRAM_HEAD */
U16_T u16_version; /**< OCPP_MV_OFFLINE_FRAM_MNG_VER */
U16_T u16_crc; /**< CRC-16 */
U8_T u8_flags; /**< 预留标志位 */
U8_T u8_reserved1; /**< 预留 */
U32_T u32_next_seq[OCPP_MV_OFFLINE_GUN_CNT]; /**< 每枪下一条将分配的单调序号(掉电连续) */
S_OCPP_MV_OFFLINE_MAP_ROW aMap[OCPP_MV_OFFLINE_MAP_ROWS]; /**< 会话键+枪→序号区间 */
U8_T u8_reserved2[16]; /**< 补齐至 128 字节 */
} S_OCPP_MV_OFFLINE_FRAM_MNG;
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_MV) == OCPP_MV_OFFLINE_PAYLOAD_BYTES, "MV payload 32B");
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) == 128u, "FRAM mng 128B");
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) <= EEPROM_OCPP_MV_OFFLINE_CTRL_SIZE, "FRAM fit");
#else
/* ARMCC v5 不支持 _Static_assert,使用编译期断言替代 */
typedef int __ocpp_mv_size_check_1[(sizeof(S_OCPP_MV_OFFLINE_MV) == OCPP_MV_OFFLINE_PAYLOAD_BYTES) ? 1 : -1];
typedef int __ocpp_mv_size_check_2[(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) == 128u) ? 1 : -1];
typedef int __ocpp_mv_size_check_3[(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) <= EEPROM_OCPP_MV_OFFLINE_CTRL_SIZE) ? 1 : -1];
#endif
/** @brief 上电:从 FRAM 恢复管理块 RAM 镜像,非法则默认并写回 */
void v_ocpp_mv_offline_init(void);
/**
* @brief 保存一条离线 MV:分配该枪单调序号、写 Flash、更新/追加映射行
* @param u8_gun_no 枪号:0=前半区(枪1),1=后半区(枪2)
* @param tx_sn_lo 会话键:当前为 `xDate2Seconds(StartChargeTime)`(单路 U32
* @param tx_sn_hi 当前恒 0(与 `u32_tx_sn_hi` 一致;接口双参仅为与 FRAM 行字段对齐)
* @param out_seq 输出本次在该枪下的单调序号
* @return 0 成功;1 参数非法(含枪号越界);2 映射表满;3 Flash 失败;4 FRAM 失败;
* 5 流水号已存在但本次序号不连续(调用顺序错误)
*/
U8_T u8_ocpp_mv_offline_mv_save(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, const S_OCPP_MV_OFFLINE_MV *mv,
U32_T *out_seq);
/**
* @brief 按该枪单调序号读一条 MV(物理槽:`seq % POOL_MAX_PER_GUN`,落在该枪半区)
* @return 0 成功;1 参数非法;2 校验/帧头失败
*/
U8_T u8_ocpp_mv_offline_mv_read_by_seq(U8_T u8_gun_no, U32_T seq, S_OCPP_MV_OFFLINE_MV *out);
/** @brief 判断负载是否为当前版本有效数据 */
U8_T u8_ocpp_mv_offline_mv_is_valid(const S_OCPP_MV_OFFLINE_MV *mv);
/**
* @brief 查询指定枪上会话键(`tx_sn_lo` + `tx_sn_hi`,当前 hi 恒 0)对应的序号区间(用于上送循环)
* @return 0 找到;1 空指针;2 未找到
*/
U8_T u8_ocpp_mv_offline_map_get(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, U32_T *seq_start, U16_T *count);
/**
* @brief 上送完成后删除该枪上映射行(不擦整池 Flash;若需整池清空在 `ocpp_mv_offline_flash_impl.c` 内维护调用)
* @return 0 成功;1 未找到;2 FRAM 写失败
*/
U8_T u8_ocpp_mv_offline_map_remove(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi);
/**
* @brief 新一笔充电开始前:清除该枪在 FRAM 中的全部 MV 映射行(释放 map 槽位;不擦 SPI Flash 池)
* @param[in] u8_gun_no 枪索引
* @return 0 成功;1 枪号非法;2 FRAM 写失败
* @see u8_ocpp_mv_offline_mv_save(映射满时按枪淘汰最旧一行)
*/
U8_T u8_ocpp_mv_offline_map_clear_gun(U8_T u8_gun_no);
/* 整池擦除、FRAM 读写默认块、池容量查询等为模块内部实现,无对外声明。 */
#ifdef __cplusplus
}
#endif
#endif /* OCPP_MV_OFFLINE_FLASH_IMPL_H */
+441
View File
@@ -0,0 +1,441 @@
/**
* @file unsettled_order_mng.c
* @brief 未结算订单列表:EEPROM 持久化与 CRC 校验
*
* 功能说明:
* - 在 EEPROM `EEPROM_ADDR_UNSETTLED_MNG` 保存 `S_UNSETTLED_ORDER_MNG`:头标志 `UNSETTLED_HEADER`、
* 未结条数、以及除 CRC 字段外的 CRC-16 校验(与 `u16_calculate_crc` 一致)。
* - 每条未结订单仅保存其 Flash 订单号(`u32_chg_order_save_record` 返回的 `u32_index`)的 24 位压缩形式,
* 最多 `UNSETTLED_MAX_CNT` 条;列表按顺序追加,删除时前移尾部元素。
* - `u8_check_order_settled`:返回 1 表示该索引仍在未结列表中(未结算),0 表示不在列表中(视为已结或从未登记)。
* 命名与注释以头文件为准。
*/
#include "unsettled_order_mng.h"
#include "mylog/mylog.h"
/* 本文件打印使能:0=不编译打印,1=编译打印 */
#ifndef UNSETTLED_MNG_LOG_EN
#define UNSETTLED_MNG_LOG_EN (1)
#endif
#if UNSETTLED_MNG_LOG_EN
#define UNSETTLED_MNG_LOG(fmt, ...) MYLOG_MSG(TASK_ID_Meterfee, fmt, ##__VA_ARGS__)
#else
#define UNSETTLED_MNG_LOG(fmt, ...) ((void)0)
#endif
/*------ Local variables(局部变量) ----------------------*/
// 未结算订单管理信息全局变量
S_UNSETTLED_ORDER_MNG s_unsettledOrderMng;
/*------ Local function prototypes(局部函数声明) ---------*/
static void v_write_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng);
static void v_read_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng);
static U16_T u16_calculate_unsettled_crc(S_UNSETTLED_ORDER_MNG *unsettledMng);
static U8_T u8_find_order_index(U32_T orderIndex, U16_T *position);
static void v_uint32_to_3bytes(U32_T value, U8_T *bytes);
static U32_T u32_3bytes_to_uint32(const U8_T *bytes);
/*------ Exported functions(导出函数实现) ----------------*/
/**
* @brief 初始化未结算订单管理模块
*/
void v_unsettled_order_init(void)
{
// 读取EEPROM中的管理信息
v_read_eeprom_unsettledMng(&s_unsettledOrderMng);
// 检查数据完整性
if (s_unsettledOrderMng.u16_head != UNSETTLED_HEADER)
{
// 头标志错误,初始化默认值
s_unsettledOrderMng.u16_head = UNSETTLED_HEADER;
s_unsettledOrderMng.u16_unsettledCnt = 0;
// 清空索引数组(3字节数组)
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
{
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = 0;
}
// 计算并保存CRC
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
UNSETTLED_MNG_LOG("UnsettledMng: init default");
}
else
{
// 验证CRC
U16_T calculatedCrc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
if (calculatedCrc != s_unsettledOrderMng.u16_crc)
{
// CRC校验失败,恢复默认值
UNSETTLED_MNG_LOG("UnsettledMng: CRC fail, reset default");
s_unsettledOrderMng.u16_unsettledCnt = 0;
// 清空索引数组(3字节数组)
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
{
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = 0;
}
// 重新计算CRC并保存
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
}
else
{
#if 0 // 测试读取
s_unsettledOrderMng.u16_unsettledCnt = 1;
s_unsettledOrderMng.u8_unsettledIndexes[0][0] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[0][1] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[0][2] = 1;
s_unsettledOrderMng.u8_unsettledIndexes[1][0] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[1][1] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[1][2] = 0;
#endif
UNSETTLED_MNG_LOG("UnsettledMng: init ok, cnt=%u",
s_unsettledOrderMng.u16_unsettledCnt);
}
}
}
/**
* @brief 添加未结算订单索引
*/
U8_T u8_add_unsettled_order(U32_T orderIndex)
{
// 检查订单索引是否有效
if (orderIndex == 0)
{
UNSETTLED_MNG_LOG("UnsettledMng: invalid index");
return 1;
}
// 检查索引是否超过3字节范围(最大16,777,215
if (orderIndex > 0xFFFFFF)
{
UNSETTLED_MNG_LOG("UnsettledMng: index >24b, idx=%u", orderIndex);
return 1;
}
// 检查是否已存在
U16_T position;
if (u8_find_order_index(orderIndex, &position) == 0)
{
UNSETTLED_MNG_LOG("UnsettledMng: index exists, idx=%u", orderIndex);
return 0; // 已存在,不算失败
}
// 检查列表是否已满
if (s_unsettledOrderMng.u16_unsettledCnt >= UNSETTLED_MAX_CNT)
{
UNSETTLED_MNG_LOG("UnsettledMng: list full, add reject");
return 1;
}
// 添加到列表末尾(使用3字节存储)
v_uint32_to_3bytes(orderIndex, s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt]);
s_unsettledOrderMng.u16_unsettledCnt++;
// 更新CRC并保存到EEPROM
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
UNSETTLED_MNG_LOG("UnsettledMng: add ok, idx=%u, cnt=%u",
orderIndex, s_unsettledOrderMng.u16_unsettledCnt);
return 0;
}
/**
* @brief 移除已结算订单索引
*/
U8_T u8_remove_unsettled_order(U32_T orderIndex)
{
// 查找订单索引位置
U16_T position;
if (u8_find_order_index(orderIndex, &position) != 0)
{
UNSETTLED_MNG_LOG("UnsettledMng: idx not found, idx=%u", orderIndex);
return 1;
}
// 从列表中移除(将后面的元素前移)
for (U16_T i = position; i < s_unsettledOrderMng.u16_unsettledCnt - 1; i++)
{
// 复制3字节索引
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = s_unsettledOrderMng.u8_unsettledIndexes[i + 1][0];
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = s_unsettledOrderMng.u8_unsettledIndexes[i + 1][1];
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = s_unsettledOrderMng.u8_unsettledIndexes[i + 1][2];
}
// 清空最后一个元素
s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt - 1][0] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt - 1][1] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[s_unsettledOrderMng.u16_unsettledCnt - 1][2] = 0;
s_unsettledOrderMng.u16_unsettledCnt--;
// 更新CRC并保存到EEPROM
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
UNSETTLED_MNG_LOG("UnsettledMng: rm ok, idx=%u, cnt=%u",
orderIndex, s_unsettledOrderMng.u16_unsettledCnt);
return 0;
}
/**
* @brief 检查订单是否已结算
*/
U8_T u8_check_order_settled(U32_T orderIndex)
{
U16_T position;
// 在未结算列表中查找
if (u8_find_order_index(orderIndex, &position) == 0)
{
return 1; // 找到,说明未结算
}
return 0; // 未找到,说明已结算
}
/**
* @brief 获取未结算订单数量
*/
U16_T u16_get_unsettled_order_count(void)
{
return s_unsettledOrderMng.u16_unsettledCnt;
}
/**
* @brief 获取所有未结算订单索引
*/
void v_get_unsettled_order_indexes(U32_T *indexArray, U16_T *count)
{
if (indexArray == NULL || count == NULL)
{
return;
}
*count = s_unsettledOrderMng.u16_unsettledCnt;
for (U16_T i = 0; i < s_unsettledOrderMng.u16_unsettledCnt; i++)
{
// 将3字节索引转换为32位整数
indexArray[i] = u32_3bytes_to_uint32(s_unsettledOrderMng.u8_unsettledIndexes[i]);
}
}
/**
* @brief 清除所有未结算订单记录
*/
void v_clear_all_unsettled_orders(void)
{
s_unsettledOrderMng.u16_unsettledCnt = 0;
// 清空索引数组(3字节数组)
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
{
s_unsettledOrderMng.u8_unsettledIndexes[i][0] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[i][1] = 0;
s_unsettledOrderMng.u8_unsettledIndexes[i][2] = 0;
}
// 更新CRC并保存到EEPROM
s_unsettledOrderMng.u16_crc = u16_calculate_unsettled_crc(&s_unsettledOrderMng);
v_write_eeprom_unsettledMng(&s_unsettledOrderMng);
UNSETTLED_MNG_LOG("UnsettledMng: clear all");
}
/**
* @brief 打印未结算订单状态(调试用)
*/
void v_print_unsettled_order_status(void)
{
UNSETTLED_MNG_LOG("=== Unsettled status ===");
UNSETTLED_MNG_LOG("Unsettled cnt: %u", s_unsettledOrderMng.u16_unsettledCnt);
if (s_unsettledOrderMng.u16_unsettledCnt > 0)
{
UNSETTLED_MNG_LOG("Unsettled idx list:");
for (U16_T i = 0; i < s_unsettledOrderMng.u16_unsettledCnt; i++)
{
U32_T orderIndex = u32_3bytes_to_uint32(s_unsettledOrderMng.u8_unsettledIndexes[i]);
UNSETTLED_MNG_LOG(" [%u] index=%u", i, orderIndex);
}
}
else
{
UNSETTLED_MNG_LOG("No unsettled order");
}
UNSETTLED_MNG_LOG("=====================");
}
/**
* @brief 计算CRC校验值
*/
U16_T u16_calculate_crc(const U8_T *data, U16_T length)
{
U16_T crc = 0xFFFF;
if (data == NULL || length == 0)
{
return crc;
}
for (U16_T i = 0; i < length; i++)
{
crc ^= (U16_T)data[i] << 8;
for (U8_T j = 0; j < 8; j++)
{
if (crc & 0x8000)
{
crc = (crc << 1) ^ UNSETTLED_CRC_POLY;
}
else
{
crc <<= 1;
}
}
}
return crc;
}
/*------ Local functions(局部函数实现) -------------------*/
/**
* @brief 写入未结算订单管理信息到EEPROM
*/
static void v_write_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng)
{
if (unsettledMng == NULL)
{
return;
}
// 先计算CRC
unsettledMng->u16_crc = u16_calculate_unsettled_crc(unsettledMng);
// 写入到EEPROM
eeprom_write(EEPROM_ADDR_UNSETTLED_MNG, (U8_T *)unsettledMng, sizeof(S_UNSETTLED_ORDER_MNG));
}
/**
* @brief 从EEPROM读取未结算订单管理信息
*/
static void v_read_eeprom_unsettledMng(S_UNSETTLED_ORDER_MNG *unsettledMng)
{
if (unsettledMng == NULL)
{
return;
}
// 从EEPROM读取,eeprom_read返回HAL_OK(0)表示成功
if (eeprom_read(EEPROM_ADDR_UNSETTLED_MNG, (U8_T *)unsettledMng, sizeof(S_UNSETTLED_ORDER_MNG)) != HAL_OK)
{
// 读取失败,初始化默认值
unsettledMng->u16_head = 0;
unsettledMng->u16_unsettledCnt = 0;
unsettledMng->u16_crc = 0;
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
{
unsettledMng->u8_unsettledIndexes[i][0] = 0;
unsettledMng->u8_unsettledIndexes[i][1] = 0;
unsettledMng->u8_unsettledIndexes[i][2] = 0;
}
}
}
/**
* @brief 计算未结算订单管理信息的CRC
*/
static U16_T u16_calculate_unsettled_crc(S_UNSETTLED_ORDER_MNG *unsettledMng)
{
if (unsettledMng == NULL)
{
return 0;
}
// 手动计算结构体大小,避免结构体对齐问题
// 结构体布局:u16_head(2) + u16_unsettledCnt(2) + u16_crc(2) + u8_unsettledIndexes[30][3](90)
// 总大小:2 + 2 + 2 + 90 = 96字节
// 计算除CRC字段外的数据:u16_head + u16_unsettledCnt + u8_unsettledIndexes
U8_T tempBuffer[94]; // 2 + 2 + 90 = 94字节
U16_T offset = 0;
// 复制头标志
tempBuffer[offset++] = (unsettledMng->u16_head >> 8) & 0xFF;
tempBuffer[offset++] = unsettledMng->u16_head & 0xFF;
// 复制未结算订单数量
tempBuffer[offset++] = (unsettledMng->u16_unsettledCnt >> 8) & 0xFF;
tempBuffer[offset++] = unsettledMng->u16_unsettledCnt & 0xFF;
// 复制索引数组(3字节数组)
for (U16_T i = 0; i < UNSETTLED_MAX_CNT; i++)
{
for (U8_T j = 0; j < 3; j++)
{
tempBuffer[offset++] = unsettledMng->u8_unsettledIndexes[i][j];
}
}
// 计算CRC
return u16_calculate_crc(tempBuffer, offset);
}
/**
* @brief 在未结算订单列表中查找订单索引
*/
static U8_T u8_find_order_index(U32_T orderIndex, U16_T *position)
{
for (U16_T i = 0; i < s_unsettledOrderMng.u16_unsettledCnt; i++)
{
U32_T storedIndex = u32_3bytes_to_uint32(s_unsettledOrderMng.u8_unsettledIndexes[i]);
if (storedIndex == orderIndex)
{
if (position != NULL)
{
*position = i;
}
return 0; // 找到
}
}
return 1; // 未找到
}
/**
* @brief 将32位整数转换为3字节数组
*/
static void v_uint32_to_3bytes(U32_T value, U8_T *bytes)
{
bytes[0] = (value >> 16) & 0xFF; // 最高字节
bytes[1] = (value >> 8) & 0xFF; // 中间字节
bytes[2] = value & 0xFF; // 最低字节
}
/**
* @brief 将3字节数组转换为32位整数
*/
static U32_T u32_3bytes_to_uint32(const U8_T *bytes)
{
return ((U32_T)bytes[0] << 16) | ((U32_T)bytes[1] << 8) | (U32_T)bytes[2];
}
+138
View File
@@ -0,0 +1,138 @@
/**
* @file unsettled_order_mng.h
* @brief 未结算订单索引列表(EEPROM 持久化)
*
* 与充电订单 Flash 模块配合:订单保存成功后可将 `u32_index` 加入本列表;平台确认结算后移除。
* 数据区独立 EEPROM 段,结构含头、计数、CRC 与 3 字节压缩索引表,详见 `S_UNSETTLED_ORDER_MNG`。
*/
#ifndef UNSETTLED_ORDER_MNG_H
#define UNSETTLED_ORDER_MNG_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "eeprom/fm24cl16.h"
/*------ Exported macro(宏定义) -------------------------*/
// 最大未结算订单数(基于可用空间计算)
// 地址范围:0x0040 - 0x00FF = 192字节
// 结构体大小:头标志(2) + 计数(2) + CRC(2) = 6字节
// 剩余空间:192 - 6 = 186字节
// 每个索引使用3字节压缩存储(支持最大索引值16,777,215)
// 最大存储数量:186 / 3 = 62个(向下取整)
// 用户要求最大数量为60,满足要求
#define UNSETTLED_MAX_CNT 30 // 最大未结算订单数
// 头标志定义
#define UNSETTLED_HEADER 0x5AA6 // 未结算订单管理信息头标志
// CRC多项式(简单校验)
#define UNSETTLED_CRC_POLY 0x1021 // CRC-16-CCITT多项式
/*------ Exported struct(结构体定义) --------------------*/
/**
* @brief 未结算订单管理信息结构体
*
* 存储在EEPROM地址:EEPROM_ADDR_UNSETTLED_MNG (0x0040)
* 用于管理未结算订单的索引列表
* 使用3字节压缩存储索引,支持最大60个未结算订单
*/
typedef struct
{
U16_T u16_head; // 头标志 (0x5AA6)
U16_T u16_unsettledCnt; // 未结算订单数量
U16_T u16_crc; // CRC校验值
U8_T u8_unsettledIndexes[UNSETTLED_MAX_CNT][3]; // 未结算订单索引数组(每个索引3字节)
} S_UNSETTLED_ORDER_MNG;
/*------ Exported variables(变量定义) -------------------*/
// 未结算订单管理信息全局变量
extern S_UNSETTLED_ORDER_MNG s_unsettledOrderMng;
/*------ Exported function prototypes (函数声明) -------*/
/**
* @brief 初始化未结算订单管理模块
*
* 从EEPROM读取管理信息,如果不存在则初始化默认值
* 验证数据完整性(CRC校验)
*/
void v_unsettled_order_init(void);
/**
* @brief 添加未结算订单索引
*
* 当新的充电订单保存到Flash时调用此函数
*
* @param orderIndex 订单索引(由u32_chg_order_save_record返回)
* @return U8_T 添加结果:0-成功,1-失败(列表已满)
*/
U8_T u8_add_unsettled_order(U32_T orderIndex);
/**
* @brief 移除已结算订单索引
*
* 当平台确认结算后调用此函数
*
* @param orderIndex 要移除的订单索引
* @return U8_T 移除结果:0-成功,1-失败(未找到该索引)
*/
U8_T u8_remove_unsettled_order(U32_T orderIndex);
/**
* @brief 检查订单是否已结算
*
* @param orderIndex 要检查的订单索引
* @return U8_T 检查结果:0-已结算,1-未结算
*/
U8_T u8_check_order_settled(U32_T orderIndex);
/**
* @brief 获取未结算订单数量
*
* @return U16_T 未结算订单数量
*/
U16_T u16_get_unsettled_order_count(void);
/**
* @brief 获取所有未结算订单索引
*
* @param indexArray 索引数组指针(用于存储读取到的数据)
* @param count 实际读取到的索引数量指针
*/
void v_get_unsettled_order_indexes(U32_T *indexArray, U16_T *count);
/**
* @brief 清除所有未结算订单记录
*
* 用于系统重置或测试
*/
void v_clear_all_unsettled_orders(void);
/**
* @brief 打印未结算订单状态(调试用)
*/
void v_print_unsettled_order_status(void);
/**
* @brief 计算CRC校验值
*
* @param data 数据指针
* @param length 数据长度(字节)
* @return U16_T CRC校验值
*/
U16_T u16_calculate_crc(const U8_T *data, U16_T length);
#ifdef __cplusplus
}
#endif
#endif /* UNSETTLED_ORDER_MNG_H */
+126
View File
@@ -0,0 +1,126 @@
/*
* FreeRTOS Kernel V10.3.1
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__TASKING__) || defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 32 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 150 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_MALLOC_FAILED_HOOK 1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configGENERATE_RUN_TIME_STATS 0
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 priority levels */
#endif
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
#endif /* FREERTOS_CONFIG_H */
+282
View File
@@ -0,0 +1,282 @@
/**
* @file app_freertos.c
* @brief FreeRTOS 运行信息查看(任务/堆/CPU占用)
*
* 参考实现:`CCU601E_RUN/BSP/freertos/app_freertos.c`
*
* 本工程适配说明:
* - 任务“栈大小/周期”从 `app_init/app_init.c` 的任务表 `my_task_data[]` 获取
* - 看门狗计数 `wdtCount` 通过 `u16_get_wdt_cnt()` 获取实时值
* - 通过 letter_shell 导出 `ps` 命令打印任务信息
*/
#include "freertos/app_freertos.h"
#include <string.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "app_init/app_init.h"
#include "wdt_task/wdt_task.h"
#include "publicdata/public_define.h"
#if (MY_SHELL_EN)
#include "letter_shell/shell_port.h"
#define APP_RTOS_OUTPUT(str_) shell_send_String((str_))
#else
static void app_rtos_output(const char *str)
{
(void)str;
}
#define APP_RTOS_OUTPUT(str_) app_rtos_output((str_))
#endif
/* 按任务名查表:type=0 栈大小(字节)type=1 周期(ms) */
static uint32_t get_task_cfg_by_name(const char *name, uint8_t type)
{
if (name == NULL) {
return 0U;
}
for (uint32_t i = 0U; i < (uint32_t)MY_TASK_NUM; i++) {
if (strcmp(name, my_task_data[i].task_name) == 0) {
return (type == 0U) ? (uint32_t)my_task_data[i].task_size
: (uint32_t)my_task_data[i].task_timer;
}
}
return 0U;
}
/* 按任务名查任务ID:成功返回1,并通过 task_id 输出;失败返回0 */
static uint8_t get_task_id_by_name(const char *name, TASK_ID *task_id)
{
if ((name == NULL) || (task_id == NULL)) {
return 0U;
}
for (uint32_t i = 0U; i < (uint32_t)MY_TASK_NUM; i++) {
if (strcmp(name, my_task_data[i].task_name) == 0) {
*task_id = (TASK_ID)i;
return 1U;
}
}
return 0U;
}
/**
* @brief 按优先级对任务状态数组进行排序,返回排序后的原始数组下标
* @param pxTaskStatusArray 任务状态数组指针
* @param uxArraySize 数组长度
* @param rtos_pri_index 输出缓冲区(最大30个元素),存储排序后的原始数组下标
* @return 实际存储数量(≤uxArraySize
*/
static UBaseType_t SortTasksByPriorityIndex(const TaskStatus_t *pxTaskStatusArray,
UBaseType_t uxArraySize,
UBaseType_t rtos_pri_index[30])
{
if (pxTaskStatusArray == NULL || rtos_pri_index == NULL) {
return 0U;
}
UBaseType_t valid_size = (uxArraySize > 30U) ? 30U : uxArraySize;
UBaseType_t indexes[30];
for (UBaseType_t i = 0U; i < valid_size; i++) {
indexes[i] = i;
}
for (UBaseType_t i = 0U; i + 1U < valid_size; i++) {
for (UBaseType_t j = 0U; j + 1U < (valid_size - i); j++) {
if (pxTaskStatusArray[indexes[j]].uxCurrentPriority <=
pxTaskStatusArray[indexes[j + 1U]].uxCurrentPriority) {
UBaseType_t tmp = indexes[j];
indexes[j] = indexes[j + 1U];
indexes[j + 1U] = tmp;
}
}
}
for (UBaseType_t i = 0U; i < valid_size; i++) {
rtos_pri_index[i] = indexes[i];
}
return valid_size;
}
SystemInfo_t *getSystemInfo(void)
{
TaskStatus_t *pxTaskStatusArray = NULL;
UBaseType_t uxArraySize;
UBaseType_t rtos_pri[30] = {0};
const char states[] = {'X', 'R', 'B', 'S', 'D'};
uint32_t ulTotalTime = 0U;
uxArraySize = uxTaskGetNumberOfTasks();
pxTaskStatusArray = (TaskStatus_t *)pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
if (pxTaskStatusArray == NULL) {
return NULL;
}
uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, &ulTotalTime);
UBaseType_t sorted_count = SortTasksByPriorityIndex(pxTaskStatusArray, uxArraySize, rtos_pri);
SystemInfo_t *sysInfo = (SystemInfo_t *)pvPortMalloc(sizeof(SystemInfo_t));
if (sysInfo == NULL) {
vPortFree(pxTaskStatusArray);
return NULL;
}
memset(sysInfo, 0, sizeof(SystemInfo_t));
sysInfo->tasks = (TaskInfo_t *)pvPortMalloc(sorted_count * sizeof(TaskInfo_t));
if (sysInfo->tasks == NULL) {
vPortFree(pxTaskStatusArray);
vPortFree(sysInfo);
return NULL;
}
memset(sysInfo->tasks, 0, sorted_count * sizeof(TaskInfo_t));
sysInfo->taskCount = (unsigned int)sorted_count;
for (UBaseType_t i = 0U; i < sorted_count; i++) {
UBaseType_t x = rtos_pri[i];
if (x >= uxArraySize) {
break;
}
TaskInfo_t *task = &sysInfo->tasks[i];
strncpy(task->taskName, pxTaskStatusArray[x].pcTaskName, sizeof(task->taskName) - 1U);
task->taskName[sizeof(task->taskName) - 1U] = '\0';
if (pxTaskStatusArray[x].eCurrentState <= eDeleted) {
task->state = states[pxTaskStatusArray[x].eCurrentState];
} else {
task->state = '?';
}
task->id = (unsigned int)i;
task->priority = (unsigned int)pxTaskStatusArray[x].uxCurrentPriority;
task->allocatedStack = (unsigned int)get_task_cfg_by_name(pxTaskStatusArray[x].pcTaskName, 0U);
task->cycle = (unsigned int)get_task_cfg_by_name(pxTaskStatusArray[x].pcTaskName, 1U);
{
TASK_ID task_id;
task->wdtCount = (get_task_id_by_name(pxTaskStatusArray[x].pcTaskName, &task_id) == 1U)
? (unsigned int)u16_get_wdt_cnt((uint8_t)task_id)
: 0U;
}
task->minFreeStack = (unsigned int)pxTaskStatusArray[x].usStackHighWaterMark * 4U;
sysInfo->totalAllocatedStack += task->allocatedStack;
if (task->allocatedStack > 0U) {
unsigned int freePct = (task->minFreeStack * 100U) / task->allocatedStack;
task->stackUsagePercentage = (freePct == 0U) ? 0U : (100U - freePct);
} else {
task->stackUsagePercentage = 0U;
}
task->cpuUsagePercentage = (ulTotalTime > 0U)
? (unsigned int)((pxTaskStatusArray[x].ulRunTimeCounter * 100UL) / ulTotalTime)
: 0U;
}
sysInfo->freeHeapSize = (unsigned int)xPortGetFreeHeapSize();
sysInfo->minEverFreeHeapSize = (unsigned int)xPortGetMinimumEverFreeHeapSize();
sysInfo->totalHeapSize = (unsigned int)configTOTAL_HEAP_SIZE;
vPortFree(pxTaskStatusArray);
return sysInfo;
}
void freeSystemInfo(SystemInfo_t *sysInfo)
{
if (sysInfo != NULL) {
if (sysInfo->tasks != NULL) {
vPortFree(sysInfo->tasks);
}
vPortFree(sysInfo);
}
}
void printSystemInfo(void)
{
SystemInfo_t *sysInfo = getSystemInfo();
if (sysInfo == NULL) {
APP_RTOS_OUTPUT("Error: Failed to get system information!\r\n");
return;
}
char buffer[256];
(void)snprintf(buffer, sizeof(buffer),
"%-3s %-12s %-2s %-4s %-6s %-6s %-6s %-6s %-5s %-5s\r\n",
"ID", "TaskName", "St", "Pri", "Cycle", "Stack", "Wdt", "Free", "Use%", "CPU%");
APP_RTOS_OUTPUT(buffer);
for (unsigned int i = 0U; i < sysInfo->taskCount; i++) {
TaskInfo_t *task = &sysInfo->tasks[i];
const char *cpuStr = (task->cpuUsagePercentage > 0U) ? "" : "<1";
if (task->cpuUsagePercentage > 0U) {
(void)snprintf(buffer, sizeof(buffer),
"%-3u %-12.12s %-2c %-4u %-6u %-6u %-6u %-6u %-5u %-5u%%\r\n",
task->id,
task->taskName,
task->state,
task->priority,
task->cycle,
task->allocatedStack,
task->wdtCount,
task->minFreeStack,
task->stackUsagePercentage,
task->cpuUsagePercentage);
} else {
(void)snprintf(buffer, sizeof(buffer),
"%-3u %-12.12s %-2c %-4u %-6u %-6u %-6u %-6u %-5u %-5s%%\r\n",
task->id,
task->taskName,
task->state,
task->priority,
task->cycle,
task->allocatedStack,
task->wdtCount,
task->minFreeStack,
task->stackUsagePercentage,
cpuStr);
}
APP_RTOS_OUTPUT(buffer);
}
float free_percent = (sysInfo->totalHeapSize > 0U) ? ((float)sysInfo->freeHeapSize * 100.0f) / (float)sysInfo->totalHeapSize : 0.0f;
float min_percent = (sysInfo->totalHeapSize > 0U) ? ((float)sysInfo->minEverFreeHeapSize * 100.0f) / (float)sysInfo->totalHeapSize : 0.0f;
(void)snprintf(buffer, sizeof(buffer),
"\r\nHeap:\r\n"
"Free: %u bytes (%.1f%%)\r\n"
"Min: %u bytes (%.1f%%)\r\n"
"Stacks: %u bytes\r\n"
"Total: %u bytes\r\n"
"----------------------------------------\r\n",
sysInfo->freeHeapSize, free_percent,
sysInfo->minEverFreeHeapSize, min_percent,
sysInfo->totalAllocatedStack,
sysInfo->totalHeapSize);
APP_RTOS_OUTPUT(buffer);
freeSystemInfo(sysInfo);
}
#if (MY_SHELL_EN)
/* 兼容入口:对齐参考工程函数名 */
static void printTaskStackUsage(void)
{
printSystemInfo();
}
/* Shell 命令:ps */
static void v_task_show(void)
{
printTaskStackUsage();
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
ps, v_task_show, myshlle-- task runing state show);
#endif
+44
View File
@@ -0,0 +1,44 @@
/**
* @file app_freertos.h
* @brief FreeRTOS 运行信息查看(任务/堆/CPU占用)
*
* 说明:
* - 参考 `CCU601E_RUN/BSP/freertos/app_freertos.c` 的用途,在 Shell 中提供 `ps` 命令
* - 本工程接入任务看门狗统计,`wdtCount` 为各任务实时看门狗计数值
*/
#ifndef APP_FREERTOS_H
#define APP_FREERTOS_H
#include "FreeRTOS.h"
/* 单任务信息快照 */
typedef struct {
char taskName[configMAX_TASK_NAME_LEN];
char state;
unsigned int id;
unsigned int priority;
unsigned int cycle; /* 任务周期(ms) - 来自任务表 */
unsigned int allocatedStack; /* 分配堆栈大小(字节) - 来自任务表 */
unsigned int wdtCount; /* 看门狗计数(秒级累加,实时读取) */
unsigned int minFreeStack; /* 最小剩余堆栈(字节) */
unsigned int stackUsagePercentage; /* 堆栈使用百分比 */
unsigned int cpuUsagePercentage; /* CPU使用百分比 */
} TaskInfo_t;
/* 系统信息快照 */
typedef struct {
TaskInfo_t *tasks; /* 任务数组 */
unsigned int taskCount; /* 任务数量 */
unsigned int freeHeapSize; /* 当前剩余堆空间 */
unsigned int minEverFreeHeapSize; /* 历史最小剩余堆 */
unsigned int totalAllocatedStack; /* 已知分配任务空间(字节) */
unsigned int totalHeapSize; /* 总堆空间大小(字节) */
} SystemInfo_t;
SystemInfo_t *getSystemInfo(void);
void freeSystemInfo(SystemInfo_t *sysInfo);
void printSystemInfo(void);
#endif /* APP_FREERTOS_H */
+48
View File
@@ -0,0 +1,48 @@
/**
* @file app_myshell.c
* @brief 扩展 Shell 命令(与 CCU601E_D\BSP\letter_shell\app_myshell.c 对齐的串口打印开关等)
*/
#include "publicdata/public_define.h"
#if (MY_SHELL_EN)
#include "letter_shell/shell_port.h"
#include "usart.h"
#include "shell.h"
#include "publicdata/publicdata.h"
/* slot 0~6 对应:USART0, USART2, UART3, UART4, USART5, UART6, UART7(与 u16_usart_* 的 E_USART_ID 一致) */
static const uint8_t s_usart_slot_hw_num[USART_NUM_PRINT_CNT] = { 0U, 2U, 3U, 4U, 5U, 6U, 7U };
void usart_printf_Func(int id, int on)
{
if ((id < 0) || (id >= (int)USART_NUM_PRINT_CNT)) {
v_shell_print("usart_printf: id must be 0~6 (0=USART0,1=USART2,2=UART3,...)\r\n");
return;
}
if ((on < 0) || (on > 1)) {
v_shell_print("usart_printf: on must be 0 or 1\r\n");
return;
}
u8_uasrt_print[(uint8_t)id] = (U8_T)on;
v_shell_print("usart slot[%d] hw=USART%u print=%d\r\n", id, (unsigned int)s_usart_slot_hw_num[(uint8_t)id], on);
v_shell_print("flags [%d][%d][%d][%d] [%d][%d][%d]\r\n",
(int)u8_uasrt_print[0], (int)u8_uasrt_print[1], (int)u8_uasrt_print[2], (int)u8_uasrt_print[3],
(int)u8_uasrt_print[4], (int)u8_uasrt_print[5], (int)u8_uasrt_print[6]);
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
usart_printf,
usart_printf_Func,
shell-- usart_printf slot(0-6) on(0/1) dump u16 send/recv);
// 命令
static void v_reboot_func(void)
{
v_shell_print("Execute system reboot command\r\n");
v_sys_reboot();
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), reboot, v_reboot_func, myshlle-- reboot sys);
#endif /* MY_SHELL_EN */
+179
View File
@@ -0,0 +1,179 @@
/**
* @file shell_cfg_user.h
* @author Letter (nevermindzzt@gmail.com)
* @brief shell config
* @version 3.0.0
* @date 2019-12-31
*
* @copyright (c) 2019 Letter
*
*/
#ifndef __SHELL_CFG_USER_H__
#define __SHELL_CFG_USER_H__
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
/**
* @brief 是否使用默认shell任务while循环,使能宏`SHELL_USING_TASK`后此宏有意义
* 使能此宏,则`shellTask()`函数会一直循环读取输入,一般使用操作系统建立shell
* 任务时使能此宏,关闭此宏的情况下,一般适用于无操作系统,在主循环中调用`shellTask()`
*/
#define SHELL_TASK_WHILE 1
/**
* @brief 是否使用命令导出方式
* 使能此宏后,可以使用`SHELL_EXPORT_CMD()`等导出命令
* 定义shell命令,关闭此宏的情况下,需要使用命令表的方式
*/
#define SHELL_USING_CMD_EXPORT 1
/**
* @brief 是否使用shell伴生对象
* 一些扩展的组件(文件系统支持,日志工具等)需要使用伴生对象
*/
#define SHELL_USING_COMPANION 0
/**
* @brief 支持shell尾行模式
*/
#define SHELL_SUPPORT_END_LINE 1
/**
* @brief 是否在输出命令列表中列出用户
*/
#define SHELL_HELP_LIST_USER 0
/**
* @brief 是否在输出命令列表中列出变量
*/
#define SHELL_HELP_LIST_VAR 0
/**
* @brief 是否在输出命令列表中列出按键
*/
#define SHELL_HELP_LIST_KEY 0
/**
* @brief 是否在输出命令列表中展示命令权限
*/
#define SHELL_HELP_SHOW_PERMISSION 1
/**
* @brief 使用LF作为命令行回车触发
* 可以和SHELL_ENTER_CR同时开启
*/
#define SHELL_ENTER_LF 0
/**
* @brief 使用CR作为命令行回车触发
* 可以和SHELL_ENTER_LF同时开启
*/
#define SHELL_ENTER_CR 1
/**
* @brief 使用CRLF作为命令行回车触发
* 不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同时开启
*/
#define SHELL_ENTER_CRLF 0
/**
* @brief 使用执行未导出函数的功能
* 启用后,可以通过`exec [addr] [args]`直接执行对应地址的函数
* @attention 如果地址错误,可能会直接引起程序崩溃
*/
#define SHELL_EXEC_UNDEF_FUNC 0
/**
* @brief shell命令参数最大数量
* 包含命令名在内,超过16个参数并且使用了参数自动转换的情况下,需要修改源码
*/
#define SHELL_PARAMETER_MAX_NUMBER 4
/**
* @brief 历史命令记录数量
*/
#define SHELL_HISTORY_MAX_NUMBER 5
/**
* @brief 双击间隔(ms)
* 使能宏`SHELL_LONG_HELP`后此宏生效,定义双击tab补全help的时间间隔
*/
#define SHELL_DOUBLE_CLICK_TIME 200
/**
* @brief 管理的最大shell数量 --允许最多 2 个 Shell 实例
*/
#define SHELL_MAX_NUMBER 1
/**
* @brief shell格式化输出的缓冲大小
* 为0时不使用shell格式化输出
*/
#define SHELL_PRINT_BUFFER 128
/**
* @brief shell格式化输入的缓冲大小
* 为0时不使用shell格式化输入
* @note shell格式化输入会阻塞shellTask, 仅适用于在有操作系统的情况下使用
*/
#define SHELL_SCAN_BUFFER 0
/**
* @brief 获取系统时间(ms)
* 定义此宏为获取系统Tick,如`HAL_GetTick()`
* @note 此宏不定义时无法使用双击tab补全命令help,无法使用shell超时锁定
*/
#define SHELL_GET_TICK() ((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS))
/**
* @brief 使用锁
* @note 使用shell锁时,需要对加锁和解锁进行实现
*/
#define SHELL_USING_LOCK 1
/**
* @brief shell内存分配
* shell本身不需要此接口,若使用shell伴生对象,需要进行定义
*/
#define SHELL_MALLOC(size) 0
/**
* @brief shell内存释放
* shell本身不需要此接口,若使用shell伴生对象,需要进行定义
*/
#define SHELL_FREE(obj) 0
/**
* @brief 是否显示shell信息
*/
#define SHELL_SHOW_INFO 1
/**
* @brief 是否在登录后清除命令行
*/
#define SHELL_CLS_WHEN_LOGIN 1
/**
* @brief shell默认用户
*/
#define SHELL_DEFAULT_USER "jiankalka"
/**
* @brief shell默认用户密码
* 若默认用户不需要密码,设为""
*/
#define SHELL_DEFAULT_USER_PASSWORD ""//"ieszt1234"
/**
* @brief shell自动锁定超时
* shell当前用户密码有效的时候生效,超时后会自动重新锁定shell
* 设置为0时关闭自动锁定功能,时间单位为`SHELL_GET_TICK()`单位
* @note 使用超时锁定必须保证`SHELL_GET_TICK()`有效
*/
#define SHELL_LOCK_TIMEOUT 0 * 60 * 1000
#endif
+184
View File
@@ -0,0 +1,184 @@
/**
* @file shell_port.c
* @author Letter (NevermindZZT@gmail.com)
* @brief
* @version 0.1
* @date 2019-02-22
*
* @copyright (c) 2019 Letter
*
*/
#include "publicdata/public_define.h"
#if (MY_SHELL_EN)
#include "shell.h"
#include <stdarg.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#include "usart.h"
#include "wdt_task/wdt_task.h"
#include "app_init/app_init.h"
Shell shell;
char shellBuffer[512];
#define SHELL_USART_ID ((E_USART_ID)DEBUG_SHELL_USART_ID)
static SemaphoreHandle_t shellMutex;
/**
* @brief 用户shell写
*
* @param data 数据
* @param len 数据长度
*
* @return short 实际写入的数据长度
*/
short userShellWrite(char *data, unsigned short len)
{
if ((uint8_t)DEBUG_SHELL_USART_ID == (uint8_t)DEBUG_SHELL_USART_NULL) {
return 0;
}
u16_usart_send(SHELL_USART_ID,(uint8_t *)data,len);
return len;
}
/**
* @brief 用户shell读
*
* @param data 数据
* @param len 数据长度
*
* @return short 实际读取到
*/
short userShellRead(char *data, unsigned short len)
{
short recv_len;
if ((uint8_t)DEBUG_SHELL_USART_ID == (uint8_t)DEBUG_SHELL_USART_NULL) {
return 0;
}
recv_len = u16_usart_recv(SHELL_USART_ID,(uint8_t *)data,len);
return recv_len;
}
/**
* @brief 用户shell上锁
*
* @param shell shell
*
* @return int 0
*/
int userShellLock(Shell *shell)
{
(void)shell;
if (shellMutex != NULL) {
(void)xSemaphoreTakeRecursive(shellMutex, portMAX_DELAY);
}
return 0;
}
/**
* @brief 用户shell解锁
*
* @param shell shell
*
* @return int 0
*/
int userShellUnlock(Shell *shell)
{
(void)shell;
if (shellMutex != NULL) {
(void)xSemaphoreGiveRecursive(shellMutex);
}
return 0;
}
/**
* @brief 用户shell初始化
*
*/
void userShellInit(void)
{
if ((uint8_t)DEBUG_SHELL_USART_ID == (uint8_t)DEBUG_SHELL_USART_NULL) {
return;
}
shellMutex = xSemaphoreCreateRecursiveMutex();
shell.write = userShellWrite;
shell.read = userShellRead;
shell.lock = userShellLock;
shell.unlock = userShellUnlock;
shellInit(&shell, shellBuffer, 512);
}
//CEVENT_EXPORT(EVENT_INIT_STAGE2, userShellInit);
void v_myshell_task(void *argument)
{
for (;;) {
v_wdt_setFlag(TASK_ID_MyShell);
shellTask(argument);
/* shellTask 内部有 while(1) + vTaskDelay(10ms)
* 正常情况下不会执行到这里;若返回则重新进入 */
mSleep(10U);
}
}
#else /* MY_SHELL_EN */
/* 关闭 Shell 时,该文件不产生任何可执行代码 */
#endif /* MY_SHELL_EN */
/************************************************************************************* */
#if (MY_SHELL_EN)
unsigned short shell_send_String(const char *string)
{
unsigned short count = 0;
const char *p = string;
SHELL_ASSERT(shell.write, return 0);
while(*p++)
{
count ++;
}
return shell.write((char *)string, count);
}
#define SHELL_PRINT_BUFFER_SIZE 256 // 可调整缓冲区大小
// 类似printf的格式化输出函数
void v_shell_print(const char *format, ...)
{
va_list args;
char buffer[SHELL_PRINT_BUFFER_SIZE];
int len;
// 1. 处理可变参数
va_start(args, format);
// 2. 安全格式化字符串
len = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
// 3. 校验长度有效性
if(len <= 0) return; // 格式化失败
if(len >= (int)sizeof(buffer)) {
// 截断到最大长度
buffer[sizeof(buffer) - 1] = '\0';
len = sizeof(buffer) - 1;
}
// 4. 发送数据到串口
if(len > 0) {
if ((uint8_t)DEBUG_SHELL_USART_ID == (uint8_t)DEBUG_SHELL_USART_NULL) {
return;
}
u16_usart_send(SHELL_USART_ID, (uint8_t *)buffer, (uint16_t)len);
}
}
#endif /* MY_SHELL_EN */
+33
View File
@@ -0,0 +1,33 @@
/**
* @file shell_port.h
* @author Letter (NevermindZZT@gmail.com)
* @brief
* @version 0.1
* @date 2019-02-22
*
* @copyright (c) 2019 Letter
*
*/
#ifndef __SHELL_PORT_H__
#define __SHELL_PORT_H__
#include "publicdata/public_define.h"
#if (MY_SHELL_EN)
#include "shell.h"
#endif
/* MY_SHELL_EN=0 时提供空桩,避免链接进 shell 代码 */
#if (MY_SHELL_EN)
extern Shell shell;
void userShellInit(void);
void v_myshell_task(void *argument);
#else
static inline void userShellInit(void) {}
static inline void v_myshell_task(void *argument) { (void)argument; }
#endif
unsigned short shell_send_String(const char *string);
void v_shell_print(const char *format, ...);
#endif
+940
View File
@@ -0,0 +1,940 @@
/*!
\file exmc_nandflash_fixed.c
\brief improved NAND Flash driver with complete GPIO configuration
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#if 0
#include "gd32h7xx.h"
#include "exmc_nandflash_fixed.h"
#include <stdio.h>
/* NAND Flash操作区域定义 */
#define NAND_CMD_AREA *(__IO uint8_t *)(BANK_NAND_ADDR | EXMC_CMD_AREA)
#define NAND_ADDR_AREA *(__IO uint8_t *)(BANK_NAND_ADDR | EXMC_ADDR_AREA)
#define NAND_DATA_AREA *(__IO uint8_t *)(BANK_NAND_ADDR | EXMC_DATA_AREA)
/* NAND Flash地址计算宏 */
#define ROW_ADDRESS (address.page + (address.block + (address.zone * NAND_ZONE_SIZE)) * NAND_BLOCK_SIZE)
/* 页位计数 */
#define PAGE_BIT 6
/* 私有变量 */
static uint8_t nand_initialized = 0;
/* 私有函数声明 */
static uint8_t exmc_nand_getstatus(void);
static uint8_t exmc_nand_writedata(uint8_t *pbuffer, nand_address_struct physicaladdress, uint16_t bytecount);
static uint8_t exmc_nand_readdata(uint8_t *pbuffer, nand_address_struct phyaddress, uint16_t bytecount);
static uint8_t exmc_nand_writepage(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount);
static uint8_t exmc_nand_readpage(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount);
static uint8_t exmc_nand_eraseblock(uint32_t blocknum);
static uint8_t exmc_nand_readstatus(void);
static void exmc_nand_gpio_config(void);
static void exmc_nand_timing_config(void);
static void exmc_nand_delay_ms(uint32_t ms);
/*!
\brief 配置NAND Flash的GPIO引脚
\param[in] none
\param[out] none
\retval none
\note 硬件配置:EXMC_D0-PD14, EXMC_D1-PD15, EXMC_D2-PD0, EXMC_D3-PD1,
EXMC_D4-PE7, EXMC_D5-PE8, EXMC_D6-PE9, EXMC_D7-PE10,
EXMC_NCE-PD7, EXMC_CLE-PD11, EXMC_ALE-PD12,
EXMC_NWE-PD5, EXMC_NOE-PD4, EXMC_NWAIT-PD6
*/
static void exmc_nand_gpio_config(void)
{
printf("配置NAND Flash GPIO引脚...\n");
/* 数据总线配置 */
printf(" 配置数据总线...\n");
/* D2(PD0), D3(PD1) - 数据线 */
gpio_af_set(GPIOD, NAND_EXMC_AF, NAND_EXMC_D2_PIN | NAND_EXMC_D3_PIN);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, NAND_EXMC_D2_PIN | NAND_EXMC_D3_PIN);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, NAND_EXMC_D2_PIN | NAND_EXMC_D3_PIN);
/* D0(PD14), D1(PD15) - 数据线 */
gpio_af_set(GPIOD, NAND_EXMC_AF, NAND_EXMC_D0_PIN | NAND_EXMC_D1_PIN);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, NAND_EXMC_D0_PIN | NAND_EXMC_D1_PIN);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, NAND_EXMC_D0_PIN | NAND_EXMC_D1_PIN);
/* D4(PE7), D5(PE8), D6(PE9), D7(PE10) - 数据线 */
gpio_af_set(GPIOE, NAND_EXMC_AF, NAND_EXMC_D4_PIN | NAND_EXMC_D5_PIN | NAND_EXMC_D6_PIN | NAND_EXMC_D7_PIN);
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, NAND_EXMC_D4_PIN | NAND_EXMC_D5_PIN | NAND_EXMC_D6_PIN | NAND_EXMC_D7_PIN);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, NAND_EXMC_D4_PIN | NAND_EXMC_D5_PIN | NAND_EXMC_D6_PIN | NAND_EXMC_D7_PIN);
/* 控制信号配置 */
printf(" 配置控制信号...\n");
/* CLE(PD11), ALE(PD12) - 命令/地址锁存使能 */
gpio_af_set(GPIOD, NAND_EXMC_AF, NAND_EXMC_CLE_PIN | NAND_EXMC_ALE_PIN);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, NAND_EXMC_CLE_PIN | NAND_EXMC_ALE_PIN);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, NAND_EXMC_CLE_PIN | NAND_EXMC_ALE_PIN);
/* NOE(PD4), NWE(PD5), NWAIT(PD6) - 读/写/等待信号 */
gpio_af_set(GPIOD, NAND_EXMC_AF, NAND_EXMC_NOE_PIN | NAND_EXMC_NWE_PIN | NAND_EXMC_NWAIT_PIN);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, NAND_EXMC_NOE_PIN | NAND_EXMC_NWE_PIN | NAND_EXMC_NWAIT_PIN);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, NAND_EXMC_NOE_PIN | NAND_EXMC_NWE_PIN | NAND_EXMC_NWAIT_PIN);
/* NCE(PD7) - 芯片使能信号 */
gpio_af_set(GPIOD, NAND_EXMC_AF, NAND_EXMC_NCE_PIN);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, NAND_EXMC_NCE_PIN);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, NAND_EXMC_NCE_PIN);
printf("NAND Flash GPIO配置完成\n");
}
/*!
\brief 配置NAND Flash时序参数
\param[in] none
\param[out] none
\retval none
*/
static void exmc_nand_timing_config(void)
{
printf("配置NAND Flash时序参数...\n");
/* 时序参数配置
SET: (tCS - tWP) = 15 - 12 = 3ns
HLD: tCH = 5ns
HIZ: tWH = 10ns
WAIT: tWP = 12ns
CTR: CLE to RE delay: 10ns
ATR: ALE to RE delay: 10ns
*/
printf(" 设置时序: SET=2, WAIT=12, HLD=5, HIZ=5\n");
}
/*!
\brief 毫秒级延时
\param[in] ms: 延时毫秒数
\param[out] none
\retval none
*/
static void exmc_nand_delay_ms(uint32_t ms)
{
for(volatile uint32_t i = 0; i < (SystemCoreClock / 4000) * ms; i++) {
__NOP();
}
}
/*!
\brief 初始化NAND Flash
\param[in] none
\param[out] none
\retval 0: 成功, -1: 失败
*/
int exmc_nandflash_init(void)
{
exmc_nand_parameter_struct nand_init_struct;
exmc_nand_timing_parameter_struct nand_timing_init_struct;
if(nand_initialized) {
return 0; /* 已初始化 */
}
printf("开始初始化NAND Flash...\n");
/* 使能时钟 */
printf(" 使能时钟...\n");
rcu_periph_clock_enable(RCU_EXMC);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_GPIOF);
rcu_periph_clock_enable(RCU_GPIOG);
/* 配置GPIO */
exmc_nand_gpio_config();
/* 配置时序 */
exmc_nand_timing_config();
/* EXMC NAND参数初始化 */
exmc_nand_struct_para_init(&nand_init_struct);
/* 时序参数配置 */
nand_timing_init_struct.setuptime = 2;
nand_timing_init_struct.waittime = 12; /* GD Flash时序 */
nand_timing_init_struct.holdtime = 5;
nand_timing_init_struct.databus_hiztime = 5;
/* NAND Flash参数配置 */
nand_init_struct.ecc_size = EXMC_ECC_SIZE_2048BYTES;
nand_init_struct.atr_latency = EXMC_ALE_RE_DELAY_5_CK_EXMC;
nand_init_struct.ctr_latency = EXMC_CLE_RE_DELAY_5_CK_EXMC;
nand_init_struct.ecc_logic = ENABLE;
nand_init_struct.databus_width = EXMC_NAND_DATABUS_WIDTH_8B;
nand_init_struct.wait_feature = ENABLE;
nand_init_struct.common_space_timing = &nand_timing_init_struct;
nand_init_struct.attribute_space_timing = &nand_timing_init_struct;
/* 初始化EXMC NAND */
exmc_nand_init(&nand_init_struct);
/* 使能EXMC NAND */
exmc_nand_enable();
nand_initialized = 1;
printf("NAND Flash初始化成功\n");
return 0;
}
/*!
\brief 去初始化NAND Flash
\param[in] none
\param[out] none
\retval none
*/
void exmc_nandflash_deinit(void)
{
/* 禁用EXMC NAND */
exmc_nand_disable();
/* 去初始化EXMC NAND */
exmc_nand_deinit();
/* 重置GPIO为输入模式 */
gpio_mode_set(GPIOD, GPIO_MODE_INPUT, GPIO_PUPD_NONE,
NAND_EXMC_D2_PIN | NAND_EXMC_D3_PIN | NAND_EXMC_NOE_PIN |
NAND_EXMC_NWE_PIN | NAND_EXMC_NWAIT_PIN | NAND_EXMC_NCE_PIN |
NAND_EXMC_CLE_PIN | NAND_EXMC_ALE_PIN | NAND_EXMC_D0_PIN | NAND_EXMC_D1_PIN);
gpio_mode_set(GPIOE, GPIO_MODE_INPUT, GPIO_PUPD_NONE,
NAND_EXMC_D4_PIN | NAND_EXMC_D5_PIN | NAND_EXMC_D6_PIN | NAND_EXMC_D7_PIN);
nand_initialized = 0;
printf("NAND Flash去初始化完成\n");
}
/*!
\brief 检查是否已初始化
\param[in] none
\param[out] none
\retval 1: 已初始化, 0: 未初始化
*/
uint8_t exmc_nandflash_is_initialized(void)
{
return nand_initialized;
}
/*!
\brief 读取NAND Flash ID
\param[in] nand_id: NAND Flash ID结构体指针
\param[out] none
\retval 0: 成功, -1: 失败
*/
int nand_read_id(nand_id_struct *nand_id)
{
uint32_t data;
if(!nand_initialized || !nand_id) {
return -1;
}
printf("读取NAND Flash ID...\n");
/* 发送读ID命令 */
NAND_CMD_AREA = NAND_CMD_READID;
__DSB();
/* 发送地址 */
NAND_ADDR_AREA = 0x00;
__DSB();
/* 读取ID */
data = *(__IO uint32_t *)(BANK_NAND_ADDR | EXMC_DATA_AREA);
nand_id->maker_id = ADDR_1ST_CYCLE(data);
nand_id->device_id = ADDR_2ND_CYCLE(data);
nand_id->third_id = ADDR_3RD_CYCLE(data);
nand_id->fourth_id = ADDR_4TH_CYCLE(data);
printf("NAND Flash ID: 制造商=0x%02X, 设备=0x%02X, 第三=0x%02X, 第四=0x%02X\n",
nand_id->maker_id, nand_id->device_id, nand_id->third_id, nand_id->fourth_id);
return 0;
}
/*!
\brief 写入NAND Flash页
\param[in] pbuffer: 数据缓冲区指针
\param[in] address: 写入地址
\param[in] bytecount: 写入字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
static uint8_t exmc_nand_writepage(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount)
{
uint16_t i;
uint32_t ecc_value;
uint8_t ecc_status = 0U;
if(!nand_initialized || !pbuffer) {
return -1;
}
/* ECC配置检查 */
if(RESET != (EXMC_NCTL & EXMC_NCTL_ECCEN)) {
exmc_nand_ecc_config(DISABLE);
exmc_nand_ecc_config(ENABLE);
ecc_status = 1U;
}
/* 发送页编程命令第一周期 */
NAND_CMD_AREA = NAND_CMD_WRITE_1ST;
__DSB();
/* 发送地址 */
NAND_ADDR_AREA = address.page_in_offset;
__DSB();
NAND_ADDR_AREA = address.page_in_offset >> 8;
__DSB();
NAND_ADDR_AREA = ADDR_1ST_CYCLE(ROW_ADDRESS);
__DSB();
NAND_ADDR_AREA = ADDR_2ND_CYCLE(ROW_ADDRESS);
__DSB();
/* 写入数据 */
for(i = 0; i < bytecount; i++) {
NAND_DATA_AREA = pbuffer[i];
__DSB();
}
/* 发送页编程命令第二周期 */
NAND_CMD_AREA = NAND_CMD_WRITE_2ND;
__DSB();
if(1U == ecc_status) {
while(RESET == (EXMC_NINTEN & EXMC_NINTEN_FFEPT));
ecc_value = exmc_ecc_get();
/* 避免编译警告 */
ecc_value = ecc_value;
}
/* 检查操作状态 */
if(NAND_READY == exmc_nand_getstatus()) {
return 0;
}
return -1;
}
/*!
\brief 读取NAND Flash页
\param[in] pbuffer: 数据缓冲区指针
\param[in] address: 读取地址
\param[in] bytecount: 读取字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
static uint8_t exmc_nand_readpage(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount)
{
uint16_t i;
uint32_t ecc_value;
uint8_t ecc_status = 0U;
if(!nand_initialized || !pbuffer) {
return -1;
}
/* ECC配置检查 */
if(RESET != (EXMC_NCTL & EXMC_NCTL_ECCEN)) {
exmc_nand_ecc_config(DISABLE);
exmc_nand_ecc_config(ENABLE);
ecc_status = 1U;
}
/* 发送读命令第一周期 */
NAND_CMD_AREA = NAND_CMD_READ1_1ST;
__DSB();
/* 发送地址 */
NAND_ADDR_AREA = address.page_in_offset;
__DSB();
NAND_ADDR_AREA = address.page_in_offset >> 8;
__DSB();
NAND_ADDR_AREA = ADDR_1ST_CYCLE(ROW_ADDRESS);
__DSB();
NAND_ADDR_AREA = ADDR_2ND_CYCLE(ROW_ADDRESS);
__DSB();
/* 发送读命令第二周期 */
NAND_CMD_AREA = NAND_CMD_READ1_2ND;
__DSB();
/* 读取数据 */
for(i = 0; i < bytecount; i++) {
pbuffer[i] = NAND_DATA_AREA;
}
if(1U == ecc_status) {
while(RESET == (EXMC_NINTEN & EXMC_NINTEN_FFEPT));
ecc_value = exmc_ecc_get();
/* 避免编译警告 */
ecc_value = ecc_value;
}
/* 检查操作状态 */
if(NAND_READY == exmc_nand_getstatus()) {
return 0;
}
return -1;
}
/*!
\brief 擦除NAND Flash块
\param[in] blocknum: 块号
\param[out] none
\retval 0: 成功, -1: 失败
*/
static uint8_t exmc_nand_eraseblock(uint32_t blocknum)
{
if(!nand_initialized) {
return -1;
}
printf("擦除块%d...\n", blocknum);
/* 发送擦除命令第一周期 */
NAND_CMD_AREA = NAND_CMD_ERASE_1ST;
__DSB();
/* 发送块地址 */
blocknum <<= PAGE_BIT;
NAND_ADDR_AREA = ADDR_1ST_CYCLE(blocknum);
__DSB();
NAND_ADDR_AREA = ADDR_2ND_CYCLE(blocknum);
__DSB();
/* 发送擦除命令第二周期 */
NAND_CMD_AREA = NAND_CMD_ERASE_2ND;
__DSB();
return (exmc_nand_getstatus());
}
/*!
\brief 写入数据到NAND Flash
\param[in] pbuffer: 数据缓冲区指针
\param[in] address: 写入地址
\param[in] bytecount: 写入字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
static uint8_t exmc_nand_writedata(uint8_t *pbuffer, nand_address_struct physicaladdress, uint16_t bytecount)
{
uint8_t *temp_pbuffer = pbuffer;
if(!nand_initialized || !pbuffer) {
return -1;
}
/* 写入前先擦除块 */
if(exmc_nand_eraseblock(physicaladdress.block) != NAND_READY) {
printf("擦除块%d失败\n", physicaladdress.block);
return -1;
}
/* 跨页写入处理 */
while(bytecount + physicaladdress.page_in_offset > NAND_PAGE_SIZE) {
if(exmc_nand_writepage(temp_pbuffer, physicaladdress, NAND_PAGE_SIZE - physicaladdress.page_in_offset) != 0) {
return -1;
}
bytecount -= NAND_PAGE_SIZE - physicaladdress.page_in_offset;
temp_pbuffer += NAND_PAGE_SIZE - physicaladdress.page_in_offset;
physicaladdress.page++;
physicaladdress.page_in_offset = 0;
}
/* 写入剩余数据 */
if(bytecount > 0) {
if(exmc_nand_writepage(temp_pbuffer, physicaladdress, bytecount) != 0) {
return -1;
}
}
return 0;
}
/*!
\brief 从NAND Flash读取数据
\param[in] pbuffer: 数据缓冲区指针
\param[in] phyaddress: 读取地址
\param[in] bytecount: 读取字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
static uint8_t exmc_nand_readdata(uint8_t *pbuffer, nand_address_struct phyaddress, uint16_t bytecount)
{
uint8_t *temp_pbuffer = pbuffer;
if(!nand_initialized || !pbuffer) {
return -1;
}
/* 跨页读取处理 */
while(bytecount + phyaddress.page_in_offset > NAND_PAGE_SIZE) {
if(exmc_nand_readpage(temp_pbuffer, phyaddress, NAND_PAGE_SIZE - phyaddress.page_in_offset) != 0) {
return -1;
}
phyaddress.page++;
temp_pbuffer += NAND_PAGE_SIZE - phyaddress.page_in_offset;
bytecount -= NAND_PAGE_SIZE - phyaddress.page_in_offset;
phyaddress.page_in_offset = 0;
}
/* 读取剩余数据 */
if(bytecount > 0) {
if(exmc_nand_readpage(temp_pbuffer, phyaddress, bytecount) != 0) {
return -1;
}
}
return 0;
}
/*!
\brief 读取NAND Flash状态
\param[in] none
\param[out] none
\retval 状态值
*/
static uint8_t exmc_nand_readstatus(void)
{
uint8_t data;
uint8_t status = NAND_BUSY;
/* 发送读状态命令 */
NAND_CMD_AREA = NAND_CMD_STATUS;
__DSB();
data = NAND_DATA_AREA;
if((data & NAND_ERROR) == NAND_ERROR) {
status = NAND_ERROR;
} else if((data & NAND_READY) == NAND_READY) {
status = NAND_READY;
} else {
status = NAND_BUSY;
}
return status;
}
/*!
\brief 获取NAND操作状态
\param[in] none
\param[out] none
\retval 操作状态
*/
static uint8_t exmc_nand_getstatus(void)
{
uint32_t timeout = 0x10000;
uint8_t status = NAND_READY;
status = exmc_nand_readstatus();
/* 等待操作完成或超时 */
while((status != NAND_READY) && (timeout != 0x00)) {
status = exmc_nand_readstatus();
timeout--;
}
if(timeout == 0x00) {
status = NAND_TIMEOUT_ERROR;
}
return status;
}
/*!
\brief 写入指定逻辑地址的数据
\param[in] memaddr: 逻辑地址
\param[in] pwritebuf: 写入数据缓冲区
\param[in] bytecount: 写入字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
int nand_write(uint32_t memaddr, uint8_t *pwritebuf, uint16_t bytecount)
{
uint32_t temp_blockremainsize;
nand_address_struct physicaladdress;
uint32_t temp;
if(!nand_initialized || !pwritebuf) {
return -1;
}
printf("写入数据到地址%d, 长度%d字节\n", memaddr, bytecount);
temp = memaddr % (NAND_BLOCK_SIZE * NAND_PAGE_SIZE);
/* 计算物理地址 */
physicaladdress.zone = memaddr / (NAND_BLOCK_SIZE * NAND_PAGE_SIZE * NAND_ZONE_SIZE);
physicaladdress.block = memaddr / (NAND_BLOCK_SIZE * NAND_PAGE_SIZE);
physicaladdress.page = temp / NAND_PAGE_SIZE;
physicaladdress.page_in_offset = temp % NAND_PAGE_SIZE;
temp_blockremainsize = (NAND_BLOCK_SIZE * NAND_PAGE_SIZE) - (NAND_PAGE_SIZE * physicaladdress.page +
physicaladdress.page_in_offset);
/* 跨块写入处理 */
while(bytecount > temp_blockremainsize) {
if(exmc_nand_writedata(pwritebuf, physicaladdress, temp_blockremainsize) != 0) {
printf("写入块%d失败\n", physicaladdress.block);
return -1;
}
physicaladdress.block++;
pwritebuf += temp_blockremainsize;
bytecount -= temp_blockremainsize;
physicaladdress.page = 0;
physicaladdress.page_in_offset = 0;
temp_blockremainsize = (NAND_BLOCK_SIZE * NAND_PAGE_SIZE);
}
/* 写入剩余数据 */
if(bytecount > 0) {
if(exmc_nand_writedata(pwritebuf, physicaladdress, bytecount) != 0) {
printf("写入剩余数据失败\n");
return -1;
}
}
printf("数据写入完成\n");
return 0;
}
/*!
\brief 从指定逻辑地址读取数据
\param[in] memaddr: 逻辑地址
\param[in] preadbuf: 读取数据缓冲区
\param[in] bytecount: 读取字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
int nand_read(uint32_t memaddr, uint8_t *preadbuf, uint16_t bytecount)
{
uint32_t temp_blockremainsize;
nand_address_struct physicaladdress;
uint32_t temp;
if(!nand_initialized || !preadbuf) {
return -1;
}
printf("从地址%d读取数据, 长度%d字节\n", memaddr, bytecount);
temp = memaddr % (NAND_BLOCK_SIZE * NAND_PAGE_SIZE);
/* 计算物理地址 */
physicaladdress.zone = memaddr / (NAND_BLOCK_SIZE * NAND_PAGE_SIZE * NAND_ZONE_SIZE);
physicaladdress.block = memaddr / (NAND_BLOCK_SIZE * NAND_PAGE_SIZE);
physicaladdress.page = temp / NAND_PAGE_SIZE;
physicaladdress.page_in_offset = temp % NAND_PAGE_SIZE;
temp_blockremainsize = (NAND_BLOCK_SIZE * NAND_PAGE_SIZE) - (NAND_PAGE_SIZE * physicaladdress.page + physicaladdress.page_in_offset);
/* 跨块读取处理 */
while(bytecount > temp_blockremainsize) {
if(exmc_nand_readdata(preadbuf, physicaladdress, temp_blockremainsize) != 0) {
printf("读取块%d失败\n", physicaladdress.block);
return -1;
}
physicaladdress.block++;
preadbuf += temp_blockremainsize;
bytecount -= temp_blockremainsize;
physicaladdress.page = 0;
physicaladdress.page_in_offset = 0;
temp_blockremainsize = (NAND_BLOCK_SIZE * NAND_PAGE_SIZE);
}
/* 读取剩余数据 */
if(bytecount > 0) {
if(exmc_nand_readdata(preadbuf, physicaladdress, bytecount) != 0) {
printf("读取剩余数据失败\n");
return -1;
}
}
printf("数据读取完成\n");
return 0;
}
/*!
\brief 格式化NAND Flash
\param[in] none
\param[out] none
\retval 0: 成功, -1: 失败
*/
int nand_format(void)
{
uint16_t i;
if(!nand_initialized) {
return -1;
}
printf("开始格式化NAND Flash (%d个块)...\n", NAND_BLOCK_COUNT);
for(i = 0; i < NAND_BLOCK_COUNT; i++) {
if(NAND_READY != exmc_nand_eraseblock(i)) {
printf("擦除块%d失败\n", i);
return -1;
}
/* 进度显示 */
if(i % 100 == 0) {
printf("格式化进度: %d/%d (%.1f%%)\n",
i + 1, NAND_BLOCK_COUNT,
(float)(i + 1) * 100.0f / NAND_BLOCK_COUNT);
}
}
printf("NAND Flash格式化完成\n");
return 0;
}
/*!
\brief 重置NAND Flash
\param[in] none
\param[out] none
\retval 0: 成功, -1: 失败
*/
int nand_reset(void)
{
if(!nand_initialized) {
return -1;
}
printf("重置NAND Flash...\n");
NAND_CMD_AREA = NAND_CMD_RESET;
__DSB();
/* 检查操作状态 */
if(NAND_READY == exmc_nand_getstatus()) {
printf("NAND Flash重置成功\n");
return 0;
}
printf("NAND Flash重置失败\n");
return -1;
}
/*!
\brief 写入备用区数据
\param[in] pbuffer: 数据缓冲区指针
\param[in] address: 写入地址
\param[in] bytecount: 写入字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
int exmc_nand_writespare(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount)
{
if(!nand_initialized || !pbuffer) {
return -1;
}
/* 检查备用区地址范围 */
if(address.page_in_offset <= NAND_PAGE_SIZE) {
printf("地址不在备用区范围\n");
return -1;
}
if(bytecount + address.page_in_offset >= NAND_PAGE_TOTAL_SIZE) {
printf("写入数据超出备用区范围\n");
return -1;
}
return exmc_nand_writepage(pbuffer, address, bytecount);
}
/*!
\brief 读取备用区数据
\param[in] pbuffer: 数据缓冲区指针
\param[in] address: 读取地址
\param[in] bytecount: 读取字节数
\param[out] none
\retval 0: 成功, -1: 失败
*/
int exmc_nand_readspare(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount)
{
if(!nand_initialized || !pbuffer) {
return -1;
}
/* 检查备用区地址范围 */
if(address.page_in_offset <= NAND_PAGE_SIZE) {
printf("地址不在备用区范围\n");
return -1;
}
if(bytecount + address.page_in_offset >= NAND_PAGE_TOTAL_SIZE) {
printf("读取数据超出备用区范围\n");
return -1;
}
return exmc_nand_readpage(pbuffer, address, bytecount);
}
/*!
\brief 填充缓冲区
\param[in] pbuffer: 数据缓冲区指针
\param[in] buffer_length: 缓冲区长度
\param[in] value: 填充值
\param[out] none
\retval none
*/
void fill_buffer_nand(uint8_t *pbuffer, uint16_t buffer_length, uint32_t value)
{
uint16_t index;
if(!pbuffer) {
return;
}
for(index = 0; index < buffer_length; index++) {
pbuffer[index] = value + index;
}
}
/*!
\brief NAND Flash自检
\param[in] none
\param[out] none
\retval 0: 成功, -1: 失败
*/
int nand_self_test(void)
{
printf("开始NAND Flash自检...\n");
/* 检查初始化状态 */
if(!nand_initialized) {
printf("NAND Flash未初始化\n");
return -1;
}
/* 读取ID测试 */
nand_id_struct nand_id;
if(nand_read_id(&nand_id) != 0) {
printf("❌ 读取NAND Flash ID失败\n");
return -1;
}
printf("✅ NAND Flash ID读取成功\n");
/* 重置测试 */
if(nand_reset() != 0) {
printf("❌ NAND Flash重置失败\n");
return -1;
}
printf("✅ NAND Flash重置成功\n");
/* 基本读写测试 */
printf("测试基本读写功能...\n");
/* 准备测试数据 */
uint8_t write_buffer[256];
uint8_t read_buffer[256];
for(int i = 0; i < 256; i++) {
write_buffer[i] = (uint8_t)(i & 0xFF);
}
/* 写入测试 */
if(nand_write(0, write_buffer, 256) != 0) {
printf("❌ 写入测试失败\n");
return -1;
}
/* 读取测试 */
if(nand_read(0, read_buffer, 256) != 0) {
printf("❌ 读取测试失败\n");
return -1;
}
/* 数据验证 */
int error_count = 0;
for(int i = 0; i < 256; i++) {
if(write_buffer[i] != read_buffer[i]) {
error_count++;
if(error_count <= 5) {
printf("数据错误: 索引%d, 写入0x%02X, 读取0x%02X\n",
i, write_buffer[i], read_buffer[i]);
}
}
}
if(error_count == 0) {
printf("✅ 基本读写测试通过\n");
} else {
printf("❌ 基本读写测试失败,错误数: %d\n", error_count);
return -1;
}
printf("NAND Flash自检完成\n");
return 0;
}
/*!
\brief NAND Flash信息显示
\param[in] none
\param[out] none
\retval none
*/
void nand_print_info(void)
{
printf("\n=== NAND Flash信息 ===\n");
printf("容量: %d MB (%d KB)\n",
(NAND_BLOCK_COUNT * NAND_BLOCK_SIZE * NAND_PAGE_SIZE) / (1024 * 1024),
(NAND_BLOCK_COUNT * NAND_BLOCK_SIZE * NAND_PAGE_SIZE) / 1024);
printf("块数量: %d\n", NAND_BLOCK_COUNT);
printf("每块页数: %d\n", NAND_BLOCK_SIZE);
printf("页大小: %d bytes\n", NAND_PAGE_SIZE);
printf("备用区大小: %d bytes\n", NAND_SPARE_AREA_SIZE);
printf("总页大小: %d bytes\n", NAND_PAGE_TOTAL_SIZE);
printf("数据总线宽度: 8 bits\n");
printf("ECC支持: 2048 bytes\n");
printf("硬件配置:\n");
printf(" 数据总线: D0-PD14, D1-PD15, D2-PD0, D3-PD1\n");
printf(" D4-PE7, D5-PE8, D6-PE9, D7-PE10\n");
printf(" 控制信号: NCE-PD7, CLE-PD11, ALE-PD12\n");
printf(" NWE-PD5, NOE-PD4, NWAIT-PD6\n");
printf("=========================\n\n");
}
#endif
+164
View File
@@ -0,0 +1,164 @@
/*!
\file exmc_nandflash_fixed.h
\brief header file of improved NAND Flash driver
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef EXMC_NANDFLASH_FIXED_H
#define EXMC_NANDFLASH_FIXED_H
#include "gd32h7xx.h"
/* EXMC NAND Flash区域定义 */
#define EXMC_CMD_AREA (uint32_t)(1<<16) /* A16 = CLE high command area */
#define EXMC_ADDR_AREA (uint32_t)(1<<17) /* A17 = ALE high address area */
#define EXMC_DATA_AREA ((uint32_t)0x00000000) /* data area */
/* NAND Flash命令定义 */
#define NAND_CMD_READ1_1ST ((uint8_t)0x00)
#define NAND_CMD_READ1_2ND ((uint8_t)0x30)
#define NAND_CMD_WRITE_1ST ((uint8_t)0x80)
#define NAND_CMD_WRITE_2ND ((uint8_t)0x10)
#define NAND_CMD_ERASE_1ST ((uint8_t)0x60)
#define NAND_CMD_ERASE_2ND ((uint8_t)0xD0)
#define NAND_CMD_READID ((uint8_t)0x90)
#define NAND_CMD_STATUS ((uint8_t)0x70)
#define NAND_CMD_LOCK_STATUS ((uint8_t)0x7A)
#define NAND_CMD_RESET ((uint8_t)0xFF)
/* NAND Flash状态定义 */
#define NAND_BUSY ((uint8_t)0x00)
#define NAND_ERROR ((uint8_t)0x01)
#define NAND_READY ((uint8_t)0x40)
#define NAND_TIMEOUT_ERROR ((uint8_t)0x80)
/* NAND Flash参数定义 */
#define NAND_ZONE_COUNT ((uint16_t)0x0001) /* 区域数量 */
#define NAND_ZONE_SIZE ((uint16_t)0x0400) /* 每区块数 (1024) */
#define NAND_BLOCK_SIZE ((uint16_t)0x0040) /* 每块页数 (64) */
#define NAND_PAGE_SIZE ((uint16_t)0x0800) /* 页大小 (2048字节) */
#define NAND_SPARE_AREA_SIZE ((uint16_t)0x0080) /* 备用区大小 (128字节) */
#define NAND_PAGE_TOTAL_SIZE (NAND_PAGE_SIZE + NAND_SPARE_AREA_SIZE) /* 总页大小 */
#define NAND_MAX_ADDRESS (((NAND_ZONE_COUNT*NAND_ZONE_SIZE)*NAND_BLOCK_SIZE)*NAND_PAGE_SIZE) /* 最大地址 */
#define NAND_BLOCK_COUNT 1024 /* 块总数 */
/* NAND Flash地址计算宏 */
#define ADDR_1ST_CYCLE(ADDR) (uint8_t)((ADDR)& 0xFF)
#define ADDR_2ND_CYCLE(ADDR) (uint8_t)(((ADDR)& 0xFF00) >> 8)
#define ADDR_3RD_CYCLE(ADDR) (uint8_t)(((ADDR)& 0xFF0000) >> 16)
#define ADDR_4TH_CYCLE(ADDR) (uint8_t)(((ADDR)& 0xFF000000) >> 24)
/* 返回值定义 */
#define NAND_OK 0
#define NAND_FAIL 1
/* EXMC NAND Flash基地址 */
#define BANK_NAND_ADDR ((uint32_t)0x80000000)
/* 硬件引脚定义 */
#define NAND_EXMC_D0_PIN GPIO_PIN_14 /* PD14 */
#define NAND_EXMC_D1_PIN GPIO_PIN_15 /* PD15 */
#define NAND_EXMC_D2_PIN GPIO_PIN_0 /* PD0 */
#define NAND_EXMC_D3_PIN GPIO_PIN_1 /* PD1 */
#define NAND_EXMC_D4_PIN GPIO_PIN_7 /* PE7 */
#define NAND_EXMC_D5_PIN GPIO_PIN_8 /* PE8 */
#define NAND_EXMC_D6_PIN GPIO_PIN_9 /* PE9 */
#define NAND_EXMC_D7_PIN GPIO_PIN_10 /* PE10 */
#define NAND_EXMC_NCE_PIN GPIO_PIN_7 /* PD7 */
#define NAND_EXMC_CLE_PIN GPIO_PIN_11 /* PD11 */
#define NAND_EXMC_ALE_PIN GPIO_PIN_12 /* PD12 */
#define NAND_EXMC_NWE_PIN GPIO_PIN_5 /* PD5 */
#define NAND_EXMC_NOE_PIN GPIO_PIN_4 /* PD4 */
#define NAND_EXMC_NWAIT_PIN GPIO_PIN_6 /* PD6 */
/* 硬件端口定义 */
#define NAND_EXMC_D0_PORT GPIOD
#define NAND_EXMC_D1_PORT GPIOD
#define NAND_EXMC_D2_PORT GPIOD
#define NAND_EXMC_D3_PORT GPIOD
#define NAND_EXMC_D4_PORT GPIOE
#define NAND_EXMC_D5_PORT GPIOE
#define NAND_EXMC_D6_PORT GPIOE
#define NAND_EXMC_D7_PORT GPIOE
#define NAND_EXMC_NCE_PORT GPIOD
#define NAND_EXMC_CLE_PORT GPIOD
#define NAND_EXMC_ALE_PORT GPIOD
#define NAND_EXMC_NWE_PORT GPIOD
#define NAND_EXMC_NOE_PORT GPIOD
#define NAND_EXMC_NWAIT_PORT GPIOD
/* 复用功能定义 */
#define NAND_EXMC_AF GPIO_AF_12
/* NAND Flash ID结构体 */
typedef struct {
uint8_t maker_id; /* 制造商ID */
uint8_t device_id; /* 设备ID */
uint8_t third_id; /* 第三字节ID */
uint8_t fourth_id; /* 第四字节ID */
} nand_id_struct;
/* NAND Flash地址结构体 */
typedef struct {
uint16_t zone; /* 区域号 */
uint16_t block; /* 块号 */
uint16_t page; /* 页号 */
uint16_t page_in_offset; /* 页内偏移 */
} nand_address_struct;
/* 新的API函数声明 */
/* 初始化和配置 */
int exmc_nandflash_init(void); /* 完整初始化 */
void exmc_nandflash_deinit(void); /* 完整去初始化 */
uint8_t exmc_nandflash_is_initialized(void); /* 检查初始化状态 */
/* 基本操作 */
int nand_read_id(nand_id_struct *nand_id); /* 读取NAND Flash ID */
int nand_reset(void); /* 重置NAND Flash */
int nand_format(void); /* 格式化NAND Flash */
/* 数据读写 */
int nand_write(uint32_t memaddr, uint8_t *pwritebuf, uint16_t bytecount); /* 写入数据 */
int nand_read(uint32_t memaddr, uint8_t *preadbuf, uint16_t bytecount); /* 读取数据 */
/* 备用区操作 */
int exmc_nand_writespare(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount); /* 写入备用区 */
int exmc_nand_readspare(uint8_t *pbuffer, nand_address_struct address, uint16_t bytecount); /* 读取备用区 */
/* 工具函数 */
void fill_buffer_nand(uint8_t *pbuffer, uint16_t buffer_length, uint32_t value); /* 填充缓冲区 */
int nand_self_test(void); /* 自检测试 */
void nand_print_info(void); /* 显示NAND Flash信息 */
/* 兼容原有接口已统一到新API中,无需重复声明 */
#endif /* EXMC_NANDFLASH_FIXED_H */
+526
View File
@@ -0,0 +1,526 @@
/*!
\file gd32h7xx_enet_eval.c
\brief ethernet hardware configuration
\version 2025-02-19, V2.1.0, demo for GD32H7xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32h7xx_enet.h"
#include "main.h"
/**
* PHY 复位 GPIO 阶段在调度器启动之前执行,禁止使用 vTaskDelay。
* 使用 DWT CYCCNT 忙等毫秒级延时(与 app/main.c 中 delay_ms 思路一致)。
*/
static void enet_phy_gpio_reset_delay_ms(uint32_t ms)
{
uint32_t start;
uint32_t ticks;
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
start = DWT->CYCCNT;
ticks = (SystemCoreClock / 1000U) * ms;
while ((DWT->CYCCNT - start) < ticks) {
__NOP();
}
}
static __IO uint32_t enet_init_status = 0U;
uint8_t enet_hw_is_ready(void)
{
return (enet_init_status != 0U) ? 1U : 0U;
}
static void enet_gpio_config(void);
static void enet_mac_dma_config(void);
static void enet_gpio_resetConfig(void);
/*!
\brief setup ethernet system(GPIOs, clocks, MAC, DMA, systick)
\param[in] none
\param[out] none
\retval none
*/
void enet_system_setup(void)
{
enet_gpio_resetConfig(); //PHY复位
/* configure the GPIO ports for ethernet pins */
enet_gpio_config();
/* configure the ethernet MAC/DMA */
enet_mac_dma_config();
if (0U == enet_init_status) {
/* 不自旋死锁:无网线/PHY 异常时仍应能进 FreeRTOS,运行灯可闪烁 */
return;
}
#ifdef USE_ENET0
enet_interrupt_enable(ENET0, ENET_DMA_INT_NIE);
enet_interrupt_enable(ENET0, ENET_DMA_INT_RIE);
#ifdef SELECT_DESCRIPTORS_ENHANCED_MODE
enet_desc_select_enhanced_mode(ENET0);
#endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */
#endif /* USE_ENET0 */
#ifdef USE_ENET1
enet_interrupt_enable(ENET1, ENET_DMA_INT_NIE);
enet_interrupt_enable(ENET1, ENET_DMA_INT_RIE);
#ifdef SELECT_DESCRIPTORS_ENHANCED_MODE
enet_desc_select_enhanced_mode(ENET1);
#endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */
#endif /* USE_ENET1 */
}
/*!
\brief configures the ethernet interface
\param[in] none
\param[out] none
\retval none
*/
static void enet_mac_dma_config(void)
{
ErrStatus reval_state = ERROR;
#ifdef USE_ENET0
/* enable ethernet clock */
rcu_periph_clock_enable(RCU_ENET0);
rcu_periph_clock_enable(RCU_ENET0TX);
rcu_periph_clock_enable(RCU_ENET0RX);
/* reset ethernet on AHB bus */
enet_deinit(ENET0);
reval_state = enet_software_reset(ENET0);
if (ERROR == reval_state) {
enet_init_status = 0U;
return;
}
#ifdef CHECKSUM_BY_HARDWARE
enet_init_status = enet_init(ENET0, ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);
#else
enet_init_status = enet_init(ENET0, ENET_AUTO_NEGOTIATION, ENET_NO_AUTOCHECKSUM, ENET_BROADCAST_FRAMES_PASS);
#endif /* CHECKSUM_BY_HARDWARE */
#endif /* USE_ENET0 */
#ifdef USE_ENET1
/* enable ethernet clock */
rcu_periph_clock_enable(RCU_ENET1);
rcu_periph_clock_enable(RCU_ENET1TX);
rcu_periph_clock_enable(RCU_ENET1RX);
/* reset ethernet on AHB bus */
enet_deinit(ENET1);
reval_state = enet_software_reset(ENET1);
if (ERROR == reval_state) {
enet_init_status = 0U;
return;
}
#ifdef CHECKSUM_BY_HARDWARE
enet_init_status = enet_init(ENET1, ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);
#else
enet_init_status = enet_init(ENET1, ENET_AUTO_NEGOTIATION, ENET_NO_AUTOCHECKSUM, ENET_BROADCAST_FRAMES_PASS);
#endif /* CHECKSUM_BY_HARDWARE */
#endif /* USE_ENET1 */
}
/*!
\brief configures the different GPIO ports
\param[in] none
\param[out] none
\retval none
*/
static void enet_gpio_config(void)
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOC);
// rcu_periph_clock_enable(RCU_GPIOD);
// rcu_periph_clock_enable(RCU_GPIOE);
// rcu_periph_clock_enable(RCU_GPIOG);
// rcu_periph_clock_enable(RCU_GPIOH);
// gpio_af_set(GPIOA, GPIO_AF_0, GPIO_PIN_8);
// gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
// gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_8);
/* enable SYSCFG clock */
rcu_periph_clock_enable(RCU_SYSCFG);
#ifdef MII_MODE
#ifdef PHY_CLOCK_MCO
/* output HXTAL clock (25MHz) on CKOUT0 pin(PA8) to clock the PHY */
rcu_ckout0_config(RCU_CKOUT0SRC_HXTAL, RCU_CKOUT0_DIV1);
#endif /* PHY_CLOCK_MCO */
#ifdef USE_ENET0
syscfg_enet_phy_interface_config(ENET0, SYSCFG_ENET_PHY_MII);
#endif /* USE_ENET0 */
#ifdef USE_ENET1
syscfg_enet_phy_interface_config(ENET1, SYSCFG_ENET_PHY_MII);
#endif /* USE_ENET1 */
#elif defined RMII_MODE
/* choose DIV12 to get 50MHz from 600MHz on CKOUT0 pin (PA8) to clock the PHY */
// rcu_ckout0_config(RCU_CKOUT0SRC_PLL0P, RCU_CKOUT0_DIV12);
#ifdef USE_ENET0
syscfg_enet_phy_interface_config(ENET0, SYSCFG_ENET_PHY_RMII);
#endif /* USE_ENET0 */
#ifdef USE_ENET1
syscfg_enet_phy_interface_config(ENET1, SYSCFG_ENET_PHY_RMII);
#endif /* USE_ENET1 */
#endif /* MII_MODE */
#ifdef USE_ENET0
#ifdef MII_MODE
/* PA1: ETH0_MII_RX_CLK */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_1);
/* PA2: ETH0_MDIO */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_2);
/* PA7: ETH0_MII_RX_DV */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_7);
gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_1);
gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_2);
gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_7);
/* PB8: ETH0_MII_TXD3 */
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_8);
/* PB10: ETH0_MII_RX_ER */
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_10);
gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_8);
gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_10);
/* PC1: ETH0_MDC */
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_1);
/* PC2: ETH0_MII_TXD2 */
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_2);
/* PC3: ETH0_MII_TX_CLK */
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_3);
/* PC4: ETH0_MII_RXD0 */
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_4);
/* PC5: ETH0_MII_RXD1 */
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_5);
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_1);
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_2);
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_3);
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_4);
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_5);
/* PH2: ETH0_MII_CRS */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_2);
/* PH3: ETH0_MII_COL */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_3);
/* PH6: ETH0_MII_RXD2 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_6);
/* PH7: ETH0_MII_RXD3 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_7);
gpio_af_set(GPIOH, GPIO_AF_11, GPIO_PIN_2);
gpio_af_set(GPIOH, GPIO_AF_11, GPIO_PIN_3);
gpio_af_set(GPIOH, GPIO_AF_11, GPIO_PIN_6);
gpio_af_set(GPIOH, GPIO_AF_11, GPIO_PIN_7);
/* PG11: ETH0_MII_TX_EN */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_11);
/* PG13: ETH0_MII_TXD0 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_13);
/* PG14: ETH0_MII_TXD1 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_14);
gpio_af_set(GPIOG, GPIO_AF_11, GPIO_PIN_11);
gpio_af_set(GPIOG, GPIO_AF_11, GPIO_PIN_13);
gpio_af_set(GPIOG, GPIO_AF_11, GPIO_PIN_14);
/* PD8: ETH0_INT */
gpio_mode_set(GPIOD, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_8);
#elif defined RMII_MODE
/* PC1: ETH0_MDC */
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_1);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_1);
/* PA2: ETH0_MDIO */
gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_2);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_2);
/* PB12: ETH0_RMII_TXD0 */
gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_12);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_12);
/* PB13: ETH0_RMII_TXD1 */
gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_13);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_13);
/* PB11: ETH0_RMII_TX_EN */
gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_11);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_11);
/* PC4: ETH0_RMII_RXD0 */
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_4);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_4);
/* PC5: ETH0_RMII_RXD1 */
gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_5);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_5);
/* PA7: ETH0_RMII_CRS_DV */
gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_7);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_7);
/* PA1: ETH0_RMII_REF_CLK */
gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_1);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_1);
#endif /* MII_MODE */
#endif /* USE_ENET0 */
#ifdef USE_ENET1
#ifdef MII_MODE
/* PH6: ETH1_MII_RXD2 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_6);
/* PH7: ETH1_MII_RXD3 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_7);
/* PH8: ETH1_MII_RXD0 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_8);
/* PH9: ETH1_MII_RXD1 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_9);
/* PH10: ETH1_MII_RX_ER */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_10);
/* PH11: ETH1_MII_RX_DV */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_11);
/* PH12: ETH1_MII_RX_CLK */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_12);
/* PH13: ETH1_MII_COL */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_13);
/* PH14: ETH1_MDIO */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_14);
/* PH15: ETH1_MII_CRS */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_15);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_15);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_6);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_7);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_8);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_9);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_10);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_11);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_12);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_13);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_14);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_15);
/* PG6: ETH1_MDC */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_6);
/* PG9: ETH1_MII_TX_CLK */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_9);
/* PG11: ETH1_MII_TX_EN */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_11);
/* PG12: ETH1_MII_TXD2 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_12);
/* PG13: ETH1_MII_TXD0 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_13);
/* PG14: ETH1_MII_TXD1 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_14);
/* PG15: ETH1_MII_TXD3 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_15);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_15);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_6);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_9);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_11);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_12);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_13);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_14);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_15);
/* PE1: ETH1_INT */
gpio_mode_set(GPIOE, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_1);
#elif defined RMII_MODE
/* PH8: ETH1_RMII_RXD0 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_8);
/* PH9: ETH1_RMII_RXD1 */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_9);
/* PH11: ETH1_RMII_CRS_DV */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_11);
/* PH12: ETH1_RMII_REF_CLK */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_12);
/* PH14: ETH1_MDIO */
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_14);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_8);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_9);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_11);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_12);
gpio_af_set(GPIOH, GPIO_AF_6, GPIO_PIN_14);
/* PG6: ETH1_MDC */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_6);
/* PG11: ETH1_RMII_TX_EN */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_11);
/* PG13: ETH1_RMII_TXD0 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_13);
/* PG14: ETH1_RMII_TXD1 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_14);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_6);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_11);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_13);
gpio_af_set(GPIOG, GPIO_AF_6, GPIO_PIN_14);
#endif /* MII_MODE */
#endif /* USE_ENET1 */
}
static void enet_gpio_resetConfig(void) //PHY复位引脚:PA0
{
rcu_periph_clock_enable(RCU_GPIOA);
gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_0);
gpio_bit_set(GPIOA, GPIO_PIN_0);
enet_phy_gpio_reset_delay_ms(10U);
gpio_bit_reset(GPIOA, GPIO_PIN_0); /* 复位 PHY */
enet_phy_gpio_reset_delay_ms(30U);
gpio_bit_set(GPIOA, GPIO_PIN_0);
/* 复位后勿立刻配置 MAC,至少 250ms(调度器未起,不可用 vTaskDelay */
enet_phy_gpio_reset_delay_ms(250U);
}
+47
View File
@@ -0,0 +1,47 @@
/*!
\file gd32h7xx_enet_eval.h
\brief the header file of gd32h7xx_enet_eval
\version 2025-02-19, V2.1.0, demo for GD32H7xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef GD32H7xx_ENET_EVAL_H
#define GD32H7xx_ENET_EVAL_H
#include <stdint.h>
#include "netif.h"
/* function declarations */
/* setup ethernet system(GPIOs, clocks, MAC, DMA, systick) */
void enet_system_setup(void);
/* 非 0 表示 MAC/PHY 初始化成功,可安全启用 lwIP 与 ENET 中断 */
uint8_t enet_hw_is_ready(void);
#endif /* GD32H7xx_ENET_EVAL_H */
+39
View File
@@ -0,0 +1,39 @@
#include "eth_link.h"
#include "lwip/netif.h"
#include "app_init/app_init.h"
#include "wdt_task/wdt_task.h"
#include "net_lwip/netconf.h"
/**
* @brief Ethernet link state monitor thread.
*
* @details
* - Aligns task architecture with reference project's ethernet_link_thread.
* - Polls link state of the given netif.
* - Feeds watchdog flag for EthLink task.
*
* @param argument Must be a pointer to struct netif.
*/
void ethernet_link_thread(void *argument)
{
struct netif *netif = (struct netif *)argument;
if (netif == NULL) {
for (;;) {
v_wdt_setFlag(TASK_ID_EthLink);
mSleep(MY_GET_SLEEP_TIME(TASK_ID_EthLink));
}
}
for (;;) {
(void)netif_is_link_up(netif);
v_wdt_setFlag(TASK_ID_EthLink);
mSleep(MY_GET_SLEEP_TIME(TASK_ID_EthLink));
}
}
int is_eth_link_up(void)
{
return (int)netif_is_link_up(NETCONF_ETH_NETIF);
}
+7
View File
@@ -0,0 +1,7 @@
#ifndef ETH_LINK_H
#define ETH_LINK_H
void ethernet_link_thread(void *argument);
int is_eth_link_up(void);
#endif
+220
View File
@@ -0,0 +1,220 @@
/*!
\file lwipopts.h
\brief LwIP options configuration
\version 2025-02-19, V2.1.0, demo for GD32H7xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef LWIPOPTS_H
#define LWIPOPTS_H
#define ETHARP_TRUST_IP_MAC 0
#define ARP_QUEUEING 0
#define SYS_LIGHTWEIGHT_PROT 1 /* SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection
for certain critical regions during buffer allocation,
deallocation and memory allocation and deallocation */
#define NO_SYS 0 /* NO_SYS==1: provides VERY minimal functionality.
Otherwise, use lwIP facilities */
/* memory options */
#define MEM_ALIGNMENT 4 /* should be set to the alignment of the CPU for which lwIP
is compiled. 4 byte alignment -> define MEM_ALIGNMENT
to 4, 2 byte alignment -> define MEM_ALIGNMENT to 2 */
#define MEM_SIZE (14*1024) /* the size of the heap memory, if the application will
send a lot of data that needs to be copied, this should
be set high */
/* Relocate the LwIP RAM heap pointer */
#define LWIP_RAM_HEAP_POINTER (0x30004000)
#define MEMP_NUM_PBUF 100 /* the number of memp struct pbufs. If the application
sends a lot of data out of ROM (or other static memory),
this should be set high */
#define MEMP_NUM_UDP_PCB 6 /* the number of UDP protocol control blocks, one
per active UDP "connection" */
#define MEMP_NUM_TCP_PCB 10 /* the number of simulatenously active TCP connections */
#define MEMP_NUM_TCP_PCB_LISTEN 5 /* the number of listening TCP connections */
#define MEMP_NUM_TCP_SEG 20 /* the number of simultaneously queued TCP segments */
#define MEMP_NUM_SYS_TIMEOUT 10 /* the number of simulateously active timeouts */
#define MEMP_NUM_NETBUF 8 /* the number of struct netbufs */
/* Pbuf options */
#define PBUF_POOL_SIZE 40 /* the number of buffers in the pbuf pool */
#define PBUF_POOL_BUFSIZE 1514 /* the size of each pbuf in the pbuf pool */
#define IP_REASS_MAX_PBUFS 20 /* total maximum amount of pbufs waiting to be reassembled */
/* TCP options */
#define LWIP_TCP 1
#define TCP_TTL 255
#define TCP_QUEUE_OOSEQ 0 /* controls if TCP should queue segments that arrive out of
order, Define to 0 if your device is low on memory. */
#define TCP_MSS (1500 - 40) /* TCP Maximum segment size,
TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */
#define TCP_SND_BUF (4*TCP_MSS) /* TCP sender buffer space (bytes) */
#define TCP_SND_QUEUELEN ((4* TCP_SND_BUF)/TCP_MSS) /* TCP sender buffer space (pbufs), this must be at least
as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work */
#define TCP_WND (4*TCP_MSS) /* TCP receive window */
/* ICMP options */
#define LWIP_ICMP 1
/* DHCP options */
#define LWIP_DHCP 0 /* define to 1 if you want DHCP configuration of interfaces,
DHCP is not implemented in lwIP 0.5.1, however, so
turning this on does currently not work. */
/* UDP options */
#define LWIP_UDP 1
#define UDP_TTL 255
/* DNS options */
#define LWIP_DNS 1
#define DNS_TABLE_SIZE 2
#define DNS_MAX_NAME_LENGTH 64
#define DNS_MAX_SERVERS 1
#define DNS_MAX_RETRIES 3
#define MEMP_NUM_DNS_API_MSG 2
#define MEMP_NUM_NETDB 1
#define LWIP_DNS_SECURE LWIP_DNS_SECURE_RAND_XID
/* statistics options */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
/* checksum options */
#define CHECKSUM_BY_HARDWARE /* computing and verifying the IP, UDP, TCP and ICMP
checksums by hardware */
/* sequential layer options */
#define LWIP_NETCONN 1 /* set to 1 to enable netconn API (require to use api_lib.c) */
#define MEMP_NUM_NETCONN 10 /* netconn pool, raise to avoid ENOBUFS during socket create */
/* socket options */
#define LWIP_SOCKET 1 /* set to 1 to enable socket API (require to use sockets.c) */
#define LWIP_SO_RCVTIMEO 1 /* set to 1 to enable receive timeout for sockets/netconns and
SO_RCVTIMEO processing */
/* Lwip debug options */
#define LWIP_DEBUG 0
#ifdef CHECKSUM_BY_HARDWARE
/* CHECKSUM_GEN_IP==0: generate checksums by hardware for outgoing IP packets.*/
#define CHECKSUM_GEN_IP 0
/* CHECKSUM_GEN_UDP==0: generate checksums by hardware for outgoing UDP packets.*/
#define CHECKSUM_GEN_UDP 0
/* CHECKSUM_GEN_TCP==0: generate checksums by hardware for outgoing TCP packets.*/
#define CHECKSUM_GEN_TCP 0
/* CHECKSUM_CHECK_IP==0: check checksums by hardware for incoming IP packets.*/
#define CHECKSUM_CHECK_IP 0
/* CHECKSUM_CHECK_UDP==0: check checksums by hardware for incoming UDP packets.*/
#define CHECKSUM_CHECK_UDP 0
/* CHECKSUM_CHECK_TCP==0: check checksums by hardware for incoming TCP packets.*/
#define CHECKSUM_CHECK_TCP 0
#define CHECKSUM_GEN_ICMP 0
#else
/* CHECKSUM_GEN_IP==1: generate checksums in software for outgoing IP packets.*/
#define CHECKSUM_GEN_IP 1
/* CHECKSUM_GEN_UDP==1: generate checksums in software for outgoing UDP packets.*/
#define CHECKSUM_GEN_UDP 1
/* CHECKSUM_GEN_TCP==1: generate checksums in software for outgoing TCP packets.*/
#define CHECKSUM_GEN_TCP 1
/* CHECKSUM_CHECK_IP==1: check checksums in software for incoming IP packets.*/
#define CHECKSUM_CHECK_IP 1
/* CHECKSUM_CHECK_UDP==1: check checksums in software for incoming UDP packets.*/
#define CHECKSUM_CHECK_UDP 1
/* CHECKSUM_CHECK_TCP==1: check checksums in software for incoming TCP packets.*/
#define CHECKSUM_CHECK_TCP 1
#define CHECKSUM_GEN_ICMP 1
#endif
/*
---------------------------------
---------- OS options ----------
---------------------------------
*/
#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "task.h"
#include "app_init/app_init.h"
#include <stdint.h>
/* 与 CCU601E_D 一致:关闭 lwIP FreeRTOS 端“多线程严格检查”,避免与第三方驱动并发风格冲突 */
#ifndef LWIP_FREERTOS_CHECK_CORE_LOCKING
#define LWIP_FREERTOS_CHECK_CORE_LOCKING 0
#endif
#define MEM_LIBC_MALLOC 1
#define mem_clib_free vPortFree
#define mem_clib_malloc pvPortMalloc
uint32_t get_current_seconds(void);
#define LWIP_RAND() ((u32_t)get_current_seconds())
/* TCPIP 线程参数直接引用 app_init 任务表,保证单一配置源。 */
#define TCPIP_THREAD_NAME MY_TCPIP_NAME
#define TCPIP_THREAD_STACKSIZE MY_TCPIP_SIZE
#define TCPIP_THREAD_PRIO MY_TCPIP_PRIO
#define TCPIP_MBOX_SIZE 8
#define DEFAULT_THREAD_STACKSIZE 1024
#define LWIP_COMPAT_MUTEX 1
#define DEFAULT_TCP_RECVMBOX_SIZE 6
#define DEFAULT_UDP_RECVMBOX_SIZE 6
#define DEFAULT_ACCEPTMBOX_SIZE 6
/* ethernetif.c 收包模式:0=仅中断唤醒(默认),1=中断+超时兜底轮询 */
#ifndef ENET_RX_FALLBACK_POLLING
#define ENET_RX_FALLBACK_POLLING 0
#endif
#endif /* LWIPOPTS_H */
+39
View File
@@ -0,0 +1,39 @@
/*!
\file net_init.c
\brief deferred Ethernet + lwIP bring-up (FreeRTOS task)
*/
#include "net_init.h"
#include "enet_PHY.h"
#include "netconf.h"
/* Match app_init.h: TASK_SIZE_SIZE_2048 == configMINIMAL_STACK_SIZE * 16 */
#ifndef NET_INIT_TASK_STACK_WORDS
#define NET_INIT_TASK_STACK_WORDS ((uint16_t)((configMINIMAL_STACK_SIZE * 16U) / sizeof(StackType_t)))
#endif
static void v_net_init_task(void *arg)
{
(void)arg;
/* 等待系统其余任务先进入稳定运行态,再启动网络 */
vTaskDelay(pdMS_TO_TICKS(NET_INIT_DELAY_MS));
enet_system_setup();
if (enet_hw_is_ready() != 0U) {
lwip_stack_init();
}
vTaskDelete(NULL);
}
BaseType_t net_init_task_create(void)
{
return xTaskCreate(v_net_init_task,
"NetInit",
NET_INIT_TASK_STACK_WORDS,
NULL,
(UBaseType_t)(tskIDLE_PRIORITY + 1U),
NULL);
}
+18
View File
@@ -0,0 +1,18 @@
/*!
\file net_init.h
\brief deferred Ethernet + lwIP bring-up (FreeRTOS task)
*/
#ifndef NET_INIT_H
#define NET_INIT_H
#include "FreeRTOS.h"
#include "task.h"
#ifndef NET_INIT_DELAY_MS
#define NET_INIT_DELAY_MS 1000U
#endif
BaseType_t net_init_task_create(void);
#endif /* NET_INIT_H */
+346
View File
@@ -0,0 +1,346 @@
/*!
\file netconf.c
\brief network connection configuration
\version 2025-02-19, V2.1.0, demo for GD32H7xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/dhcp.h"
#include "lwip/dns.h"
#include "ethernetif.h"
#include "main.h"
#include "netconf.h"
#include "publicdata/publicdata.h"
#include "lwip/tcpip.h"
#include <stdio.h>
#include "lwip/errno.h"
#include "queue.h"
#define DHCP_TRIES_MAX_TIMES 3
int errno;
typedef enum
{
DHCP_ADDR_NONE = 0,
DHCP_ADDR_BEGIN,
DHCP_ADDR_GOT,
DHCP_ADDR_FAIL
}dhcp_addr_status_enum;
#ifdef USE_DHCP
dhcp_addr_status_enum dhcp_state = DHCP_ADDR_NONE;
#endif /* USE_DHCP */
#ifdef USE_ENET0
struct netif g_mynetif0;
#endif /* USE_ENET0 */
#ifdef USE_ENET1
struct netif g_mynetif1;
#endif /* USE_ENET1 */
ip_addr_t ip_address = {0};
static uint8_t netconf_is_all_value(const U8_T *buf, U8_T value)
{
return (uint8_t)((buf[0] == value) && (buf[1] == value) && (buf[2] == value) && (buf[3] == value));
}
static uint8_t netconf_is_valid_host_ip(const U8_T *ip)
{
if (netconf_is_all_value(ip, 0x00U) || netconf_is_all_value(ip, 0xFFU)) {
return 0U;
}
if ((ip[0] == 0U) || (ip[0] == 127U) || (ip[0] >= 224U)) {
return 0U;
}
return 1U;
}
static uint8_t netconf_is_valid_netmask(const U8_T *mask)
{
uint32_t m;
uint8_t seen_zero = 0U;
if (netconf_is_all_value(mask, 0x00U) || netconf_is_all_value(mask, 0xFFU)) {
return 0U;
}
m = ((uint32_t)mask[0] << 24) | ((uint32_t)mask[1] << 16) | ((uint32_t)mask[2] << 8) | (uint32_t)mask[3];
for (uint32_t bit = 0U; bit < 32U; bit++) {
uint8_t cur = (uint8_t)((m >> (31U - bit)) & 0x1U);
if (cur == 0U) {
seen_zero = 1U;
} else if (seen_zero != 0U) {
return 0U;
}
}
return 1U;
}
static uint8_t netconf_is_same_subnet(const U8_T *ip, const U8_T *gw, const U8_T *mask)
{
uint32_t ip32 = ((uint32_t)ip[0] << 24) | ((uint32_t)ip[1] << 16) | ((uint32_t)ip[2] << 8) | (uint32_t)ip[3];
uint32_t gw32 = ((uint32_t)gw[0] << 24) | ((uint32_t)gw[1] << 16) | ((uint32_t)gw[2] << 8) | (uint32_t)gw[3];
uint32_t m32 = ((uint32_t)mask[0] << 24) | ((uint32_t)mask[1] << 16) | ((uint32_t)mask[2] << 8) | (uint32_t)mask[3];
return (uint8_t)(((ip32 & m32) == (gw32 & m32)) ? 1U : 0U);
}
static void netconf_get_static_ip_from_cfg(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
const U8_T *cfg_ip = g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_ip;
const U8_T *cfg_mask = g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_mask;
const U8_T *cfg_gw = g_t_share_data.t_sys_fix_cfg.s_net_cfg.u8_eth_gate;
uint8_t use_cfg = 1U;
if (netconf_is_valid_host_ip(cfg_ip) == 0U) {
use_cfg = 0U;
}
if (netconf_is_valid_host_ip(cfg_gw) == 0U) {
use_cfg = 0U;
}
if (netconf_is_valid_netmask(cfg_mask) == 0U) {
use_cfg = 0U;
}
if ((use_cfg != 0U) && (netconf_is_same_subnet(cfg_ip, cfg_gw, cfg_mask) == 0U)) {
use_cfg = 0U;
}
if (use_cfg != 0U) {
IP4_ADDR(ip, cfg_ip[0], cfg_ip[1], cfg_ip[2], cfg_ip[3]);
IP4_ADDR(mask, cfg_mask[0], cfg_mask[1], cfg_mask[2], cfg_mask[3]);
IP4_ADDR(gw, cfg_gw[0], cfg_gw[1], cfg_gw[2], cfg_gw[3]);
printf("[NETCONF] static ip from cfg: %u.%u.%u.%u, mask=%u.%u.%u.%u, gw=%u.%u.%u.%u\r\n",
cfg_ip[0], cfg_ip[1], cfg_ip[2], cfg_ip[3],
cfg_mask[0], cfg_mask[1], cfg_mask[2], cfg_mask[3],
cfg_gw[0], cfg_gw[1], cfg_gw[2], cfg_gw[3]);
} else {
IP4_ADDR(ip, BOARD_IP_ADDR0, BOARD_IP_ADDR1, BOARD_IP_ADDR2, BOARD_IP_ADDR3);
IP4_ADDR(mask, BOARD_NETMASK_ADDR0, BOARD_NETMASK_ADDR1, BOARD_NETMASK_ADDR2, BOARD_NETMASK_ADDR3);
IP4_ADDR(gw, BOARD_GW_ADDR0, BOARD_GW_ADDR1, BOARD_GW_ADDR2, BOARD_GW_ADDR3);
printf("[NETCONF] cfg ip invalid, fallback board ip: %u.%u.%u.%u\r\n",
BOARD_IP_ADDR0, BOARD_IP_ADDR1, BOARD_IP_ADDR2, BOARD_IP_ADDR3);
}
}
/*!
\brief after the netif is fully configured, it will be called to initialize the function of telnet, client and udp
\param[in] netif: the struct used for lwIP network interface
\param[out] none
\retval none
*/
void lwip_netif_status_callback(struct netif *netif)
{
if(((netif->flags & NETIF_FLAG_UP) != 0) && (0 != netif->ip_addr.addr)) {
}
}
/*!
\brief initializes the LwIP stack
\param[in] none
\param[out] none
\retval none
*/
void lwip_stack_init(void)
{
ip_addr_t gd_ipaddr;
ip_addr_t gd_netmask;
ip_addr_t gd_gw;
/* create tcp_ip stack thread */
tcpip_init(NULL, NULL);
/* IP address setting */
#ifdef USE_DHCP
gd_ipaddr.addr = 0;
gd_netmask.addr = 0;
gd_gw.addr = 0;
#else
netconf_get_static_ip_from_cfg(&gd_ipaddr, &gd_netmask, &gd_gw);
#endif /* USE_DHCP */
#ifdef USE_ENET0
/* add a new network interface */
netif_add(&g_mynetif0, &gd_ipaddr, &gd_netmask, &gd_gw, NULL, &ethernetif_init, &tcpip_input);
/* set a default network interface */
netif_set_default(&g_mynetif0);
/* set a callback when interface is up/down */
netif_set_status_callback(&g_mynetif0, lwip_netif_status_callback);
/* set the flag of netif as NETIF_FLAG_LINK_UP */
netif_set_link_up(&g_mynetif0);
/* bring an interface up and set the flag of netif as NETIF_FLAG_UP */
netif_set_up(&g_mynetif0);
#if LWIP_DNS
/* 静态 IP 时无 DHCP 下发 DNS,需手动设置 */
{
ip_addr_t dns_server;
IP_ADDR4(&dns_server, 114, 114, 114, 114);
dns_setserver(0, &dns_server);
}
#endif /* LWIP_DNS */
#endif /* USE_ENET0 */
#ifdef USE_ENET1
/* add a new network interface */
netif_add(&g_mynetif1, &gd_ipaddr, &gd_netmask, &gd_gw, NULL, &ethernetif_init, &tcpip_input);
/* set a default network interface */
netif_set_default(&g_mynetif1);
/* set a callback when interface is up/down */
netif_set_status_callback(&g_mynetif1, lwip_netif_status_callback);
/* set the flag of netif as NETIF_FLAG_LINK_UP */
netif_set_link_up(&g_mynetif1);
/* bring an interface up and set the flag of netif as NETIF_FLAG_UP */
netif_set_up(&g_mynetif1);
#if LWIP_DNS
{
ip_addr_t dns_server;
IP_ADDR4(&dns_server, 114, 114, 114, 114);
dns_setserver(0, &dns_server);
}
#endif /* LWIP_DNS */
#endif /* USE_ENET1 */
}
#ifdef USE_DHCP
/*!
\brief dhcp_task
\param[in] none
\param[out] none
\retval none
*/
void dhcp_task(void * pvParameters)
{
ip_addr_t gd_ipaddr;
ip_addr_t gd_netmask;
ip_addr_t gd_gw;
#ifdef USE_ENET0
struct dhcp *dhcp_client0;
#endif /* USE_ENET0 */
#ifdef USE_ENET1
struct dhcp *dhcp_client1;
#endif /* USE_ENET1 */
for(;;){
#ifdef USE_ENET0
switch(dhcp_state){
case DHCP_ADDR_NONE:
dhcp_start(&g_mynetif0);
/* IP address should be set to 0 every time we want to assign a new DHCP address*/
ip_address.addr = 0;
dhcp_state = DHCP_ADDR_BEGIN;
break;
case DHCP_ADDR_BEGIN:
/* got the IP address */
ip_address.addr = g_mynetif0.ip_addr.addr;
if(0 != ip_address.addr){
dhcp_state = DHCP_ADDR_GOT;
printf("\r\nDHCP -- eval board ip address: %d.%d.%d.%d \r\n", ip4_addr1_16(&ip_address), \
ip4_addr2_16(&ip_address), ip4_addr3_16(&ip_address), ip4_addr4_16(&ip_address));
}else{
/* DHCP timeout */
dhcp_client0 = netif_dhcp_data(&g_mynetif0);
if(dhcp_client0->tries > DHCP_TRIES_MAX_TIMES){
dhcp_state = DHCP_ADDR_FAIL;
/* stop DHCP */
dhcp_stop(&g_mynetif0);
/* use static address as IP address */
IP4_ADDR(&gd_ipaddr, BOARD_IP_ADDR0, BOARD_IP_ADDR1, BOARD_IP_ADDR2, BOARD_IP_ADDR3);
IP4_ADDR(&gd_netmask, BOARD_NETMASK_ADDR0, BOARD_NETMASK_ADDR1, BOARD_NETMASK_ADDR2, BOARD_NETMASK_ADDR3);
IP4_ADDR(&gd_gw, BOARD_GW_ADDR0, BOARD_GW_ADDR1, BOARD_GW_ADDR2, BOARD_GW_ADDR3);
netif_set_addr(&g_mynetif0, &gd_ipaddr, &gd_netmask, &gd_gw);
}
}
break;
default:
break;
}
#endif /* USE_ENET0 */
#ifdef USE_ENET1
switch(dhcp_state){
case DHCP_ADDR_NONE:
dhcp_start(&g_mynetif1);
/* IP address should be set to 0 every time we want to assign a new DHCP address*/
ip_address.addr = 0;
dhcp_state = DHCP_ADDR_BEGIN;
break;
case DHCP_ADDR_BEGIN:
/* got the IP address */
ip_address.addr = g_mynetif1.ip_addr.addr;
if(0 != ip_address.addr){
dhcp_state = DHCP_ADDR_GOT;
printf("\r\nDHCP -- eval board ip address: %d.%d.%d.%d \r\n", ip4_addr1_16(&ip_address), \
ip4_addr2_16(&ip_address), ip4_addr3_16(&ip_address), ip4_addr4_16(&ip_address));
}else{
/* DHCP timeout */
dhcp_client1 = netif_dhcp_data(&g_mynetif1);
if(dhcp_client1->tries > DHCP_TRIES_MAX_TIMES){
dhcp_state = DHCP_ADDR_FAIL;
/* stop DHCP */
dhcp_stop(&g_mynetif1);
/* use static address as IP address */
IP4_ADDR(&gd_ipaddr, BOARD_IP_ADDR0, BOARD_IP_ADDR1, BOARD_IP_ADDR2, BOARD_IP_ADDR3);
IP4_ADDR(&gd_netmask, BOARD_NETMASK_ADDR0, BOARD_NETMASK_ADDR1, BOARD_NETMASK_ADDR2, BOARD_NETMASK_ADDR3);
IP4_ADDR(&gd_gw, BOARD_GW_ADDR0, BOARD_GW_ADDR1, BOARD_GW_ADDR2, BOARD_GW_ADDR3);
netif_set_addr(&g_mynetif1, &gd_ipaddr, &gd_netmask, &gd_gw);
}
}
break;
default:
break;
}
#endif /* USE_ENET1 */
/* wait 250 ms */
vTaskDelay(250);
}
}
#endif /* USE_DHCP */
+68
View File
@@ -0,0 +1,68 @@
/*!
\file netconf.h
\brief the header file of netconf
\version 2025-02-19, V2.1.0, demo for GD32H7xx
*/
/*
Copyright (c) 2024, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef NETCONF_H
#define NETCONF_H
#include "main.h"
struct netif;
#if defined(USE_ENET0) && defined(USE_ENET1)
#error "USE_ENET0 and USE_ENET1 cannot both be defined (see main.h)"
#endif
#if !defined(USE_ENET0) && !defined(USE_ENET1)
#error "Define either USE_ENET0 or USE_ENET1 in main.h for Ethernet netif"
#endif
#ifdef USE_ENET0
extern struct netif g_mynetif0;
#define NETCONF_ETH_NETIF (&g_mynetif0)
#endif
#ifdef USE_ENET1
extern struct netif g_mynetif1;
#define NETCONF_ETH_NETIF (&g_mynetif1)
#endif
/* function declarations */
/* initializes the LwIP stack */
void lwip_stack_init(void);
/* dhcp_task */
void dhcp_task(void * pvParameters);
/* netif status callback function */
//void lwip_netif_status_callback(struct netif *netif);
#endif /* NETCONF_H */
+194
View File
@@ -0,0 +1,194 @@
# CCU621_M Pin Config Table
> 路径:`CCU621_M/BSP/pin_config_table.md`
> 说明:根据当前工程实际代码(`BSP/GPIO/gpio.h`、`BSP/sys_drv_init.h`)整理。
> 主索引:[README](../README.md)
> 若与硬件原理图/BOM有差异,以硬件文档为准。
## 1. DO输出(继电器/电子锁)
| 逻辑名 | 端口引脚 | 宏定义 | 用途 |
| --- | --- | --- | --- |
| DO1 | PG4 | `RELAY_K1_PORT/PIN` | 板载继电器 K1 |
| DO2 | PG5 | `RELAY_K2_PORT/PIN` | 板载继电器 K2 |
| DO3 | PG6 | `RELAY_K3_PORT/PIN` | 板载继电器 K3 |
| DO4 | PG7 | `RELAY_K4_PORT/PIN` | 板载继电器 K4 |
| DO5 | PG8 | `RELAY_K5_PORT/PIN` | 板载继电器 K5 |
| DO6 | PC8 | `RELAY_K6_PORT/PIN` | 板载继电器 K6 |
| DO7 | PC9 | `RELAY_K7_PORT/PIN` | 板载继电器 K7 |
| DO8 | PG9 | `RELAY_K8_PORT/PIN` | 板载继电器 K8 |
| DO9 | PG10 | `RELAY_K9_PORT/PIN` | 板载继电器 K9 |
| DO10 | PG11 | `RELAY_K10_PORT/PIN` | 板载继电器 K10 |
| ELOCK_A | PK1 | `ELOCK_A_PORT/PIN` | A枪电子锁控制 |
| ELOCK_B | PK2 | `ELOCK_B_PORT/PIN` | B枪电子锁控制 |
## 2. 绝缘检测相关DO
| 逻辑名 | 端口引脚 | 宏定义 | 用途 |
| --- | --- | --- | --- |
| INSA_CTL | PE4 | `INSA_GND_PORT/PIN` | A枪绝缘监测接地开关 |
| UNBALA_CTL | PE3 | `INSA_POS_PORT/PIN` | A枪绝缘监测正极回路开关 |
| INSB_CTL | PA5 | `INSB_GND_PORT/PIN` | B枪绝缘监测接地开关 |
| UNBALB_CTL | PE5 | `INSB_POS_PORT/PIN` | B枪绝缘监测正极回路开关 |
## 3. 其它控制DO
| 逻辑名 | 端口引脚 | 宏定义 | 用途 |
| --- | --- | --- | --- |
| H7_RUN | PG15 | `LED_RUN_PORT/PIN` | 运行指示灯 |
| CHK_CSA_CTL | PF8 | `CHK_CSA_PORT/PIN` | 连接确认模式选择 A |
| CHK_CSB_CTL | PF2 | `CHK_CSB_PORT/PIN` | 连接确认模式选择 B |
| WDO | PE2 | `WDO_PORT/PIN` | 看门狗翻转输出 |
| ESP8685_EN | PB7 | `ESP8685_EN_PORT/PIN` | ESP8685使能 |
| BLUE_EN | PE6 | `BLUE_EN_PORT/PIN` | 蓝牙/WiFi模块使能 |
| CIU_RST | PA10 | `CIU_RST_PORT/PIN` | 加密芯片复位 |
| 4G_POWER | PF3 | `PWR_4G_PORT/PIN` | 4G模块电源控制 |
| 4G_RST | PC13 | `RST_4G_PORT/PIN` | 4G模块复位控制 |
| TEMP_ADDR_A | PD10 | `TEMP_ADDR_A_PORT/PIN` | 温度采集地址A |
| TEMP_ADDR_B | PD13 | `TEMP_ADDR_B_PORT/PIN` | 温度采集地址B |
| TEMP_ADDR_C | PJ8 | `TEMP_ADDR_C_PORT/PIN` | 温度采集地址C |
## 4. DI输入(YX
| 逻辑名 | 端口引脚 | 宏定义 | 备注 |
| --- | --- | --- | --- |
| YX1 | PC0 | `YX1_PORT/PIN` | 系统DI |
| YX2 | PB0 | `YX2_PORT/PIN` | 系统DI |
| YX3 | PF14 | `YX3_PORT/PIN` | 系统DI |
| YX4 | PB1 | `YX4_PORT/PIN` | 系统DI |
| YX5 | PF15 | `YX5_PORT/PIN` | 系统DI |
| YX6 | PB2 | `YX6_PORT/PIN` | 系统DI |
| YX7 | PG0 | `YX7_PORT/PIN` | 系统DI |
| YX8 | PF11 | `YX8_PORT/PIN` | 系统DI |
| YX9 | PE11 | `YX9_PORT/PIN` | 系统DI |
| YX10 | PF12 | `YX10_PORT/PIN` | 系统DI |
| YX11 | PE15 | `YX11_PORT/PIN` | 系统DI |
| YX12 | PF13 | `YX12_PORT/PIN` | 系统DI |
## 5. 其它输入
| 逻辑名 | 端口引脚 | 宏定义 | 备注 |
| --- | --- | --- | --- |
| AUX_PADET | PG1 | `AUX_PADET_PORT/PIN` | A枪辅助电源检测 |
| AUX_PBDET | PB10 | `AUX_PBDT_PORT/PIN` | B枪辅助电源检测 |
| CHECKOUTA1 | PF10 | `CHECKOUTA1_PORT/PIN` | A枪连接确认输入 |
| CHECKOUTA2 | PF9 | `CHECKOUTA2_PORT/PIN` | A枪连接确认输入 |
| CHECKOUTB1 | PD3 | `CHECKOUTB1_PORT/PIN` | B枪连接确认输入 |
| CHECKOUTB2 | PA15 | `CHECKOUTB2_PORT/PIN` | B枪连接确认输入 |
## 6. 串口(USART/UART
| 外设 | TX | RX | 波特率 | 备注 |
| --- | --- | --- | --- | --- |
| USART0 | PF4 | PF5 | 115200 | 副板通信 `SubComm` |
| USART2 | PD8 | PD9 | 115200 | 通用串口 |
| UART3 | PC10 | PC11 | 2400 | 8E1 |
| UART4 | PC12 | PD2 | 2400 | 8E1 |
| USART5 | PC6 | PC7 | 115200 | 默认 `printf` 输出口 |
| UART6 | PB4 | PB3 | 115200 | 通用串口 |
| UART7 | PE1 | PE0 | 115200 | 通用串口 |
## 7. CAN
| 外设 | TX | RX | AF | 备注 |
| --- | --- | --- | --- | --- |
| CAN0 | PB9 | PB8 | AF9 | 250 kbps |
| CAN1 | PB6 | PB5 | AF9 | 250 kbps |
| CAN2 | PF7 | PF6 | AF2 | 125 kbps |
## 8. I2C / EEPROM
| 外设 | SCL | SDA | AF | 备注 |
| --- | --- | --- | --- | --- |
| I2C1 | PF1 | PF0 | AF4 | EEPROM / FM24CL16 |
## 9. SPI与模拟采集/存储
### 9.1 SPI1(预留安全芯片)
| 信号 | 引脚 | AF | 备注 |
| --- | --- | --- | --- |
| SPI1_NSS | PA8 | GPIO输出 | 软件片选 |
| SPI1_SCK | PA9 | AF5 | 时钟 |
| SPI1_MISO | PG2 | AF5 | 输入 |
| SPI1_MOSI | PG3 | AF5 | 输出 |
### 9.2 SPI3(绝缘/高压采样)
| 信号 | 引脚 | AF | 备注 |
| --- | --- | --- | --- |
| SPI3_CSA | PA4 | GPIO输出 | A枪片选 |
| SPI3_CSB | PA3 | GPIO输出 | B枪片选 |
| SPI3_SCK | PE12 | AF5 | 时钟 |
| SPI3_MISO | PE13 | AF5 | 输入 |
| SPI3_MOSI | PE14 | AF5 | 输出 |
### 9.3 SPI4(外部Flash
| 信号 | 引脚 | AF | 备注 |
| --- | --- | --- | --- |
| SPI4_CS | PJ9 | GPIO输出 | 外部Flash片选 |
| SPI4_SCK | PK0 | AF5 | 时钟 |
| SPI4_MOSI | PJ11 | AF5 | 输出 |
| SPI4_MISO | PJ10 | AF5 | 输入 |
### 9.4 SPI5(模拟量采集)
| 信号 | 引脚 | AF | 备注 |
| --- | --- | --- | --- |
| SPI5_CS | PG12 | GPIO输出 | 模拟采集芯片片选 |
| SPI5_SCK | PG13 | AF5 | 时钟 |
| SPI5_MISO | PA6 | AF8 | 输入 |
| SPI5_MOSI | PG14 | AF5 | 输出 |
## 10. NAND Flash / EXMC
| 信号 | 引脚 | AF | 备注 |
| --- | --- | --- | --- |
| EXMC_D0 | PD14 | AF12 | NAND数据线 |
| EXMC_D1 | PD15 | AF12 | NAND数据线 |
| EXMC_D2 | PD0 | AF12 | NAND数据线 |
| EXMC_D3 | PD1 | AF12 | NAND数据线 |
| EXMC_D4 | PE7 | AF12 | NAND数据线 |
| EXMC_D5 | PE8 | AF12 | NAND数据线 |
| EXMC_D6 | PE9 | AF12 | NAND数据线 |
| EXMC_D7 | PE10 | AF12 | NAND数据线 |
| EXMC_NCE | PD7 | AF12 | 片选 |
| EXMC_CLE | PD11 | AF12 | 命令锁存 |
| EXMC_ALE | PD12 | AF12 | 地址锁存 |
| EXMC_NWE | PD5 | AF12 | 写使能 |
| EXMC_NOE | PD4 | AF12 | 读使能 |
| EXMC_NWAIT | PD6 | AF12 | 等待输入 |
## 11. 以太网(ENET
> 当前工程代码中已配置 ENET 相关 GPIO;以下为代码里明确可见的 RMII/MII 相关引脚,实际使用模式以 `BSP/net_lwip/enet_PHY.c` 为准。
| 信号 | 引脚 | AF | 备注 |
| --- | --- | --- | --- |
| ENET_REF_CLK / RX_CLK | PA1 | AF11 | 以太网时钟相关 |
| ENET_MDIO | PA2 | AF11 | PHY管理 |
| ENET_CRS_DV / CRS | PA7 | AF11 | RMII/MII相关 |
| ENET_TXD3 / TX_EN相关 | PB8 | AF11 | 以太网数据 |
| ENET_RXD0 / RX_ER相关 | PB10 | AF11 | 以太网数据 |
| ENET_MDC / TXD0 / TXD1 / RXD0 / RXD1 | PC1 / PC2 / PC3 / PC4 / PC5 | AF11 | 以太网数据/管理 |
| ENET_TXD0/TXD1/TXD2/TXD3/COL/CRS/RXD2/RXD3/RX_ER/RX_DV | PH2 / PH3 / PH6 / PH7 / PH8 / PH9 / PH10 / PH11 / PH12 / PH13 / PH14 / PH15 | AF11/AF6 | 具体模式见源码 |
| ENET_TX_EN / RX_DV / RXD2 / RXD3 | PG11 / PG13 / PG14 / PG6 / PG9 / PG12 | AF11/AF6 | 具体模式见源码 |
## 12. IO电平约定(软件侧)
- `sys_drv_init.h` 中 DI 逻辑定义为:低电平导通、高电平断开(`DIx = DI_READ ? 0 : 1`)。
- `DOx` 宏当前约定:
- `DOx(0)`:断开
- `DOx(1)`:闭合
- `DO*_READ()` / `ELOCK_*_READ()` 返回GPIO引脚电平:`1=高电平``0=低电平`
## 13. 维护建议
- 变更任一引脚时,同步更新:
- `BSP/GPIO/gpio.h`
- `BSP/GPIO/gpio.c``v_outpin_config` / `v_inpin_config`
- `BSP/sys_drv_init.h`(宏映射)
- 外设驱动文件(如 `BSP/com/usart.c``BSP/can/can.c``BSP/eeprom/i2c1.c``BSP/adc/*.c``BSP/externalflash/*.c`
- 本文档
- 新增点位时建议增加“信号名/端口引脚/用途”三元信息,避免后续只看宏名难以定位硬件。
+147
View File
@@ -0,0 +1,147 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "ringfifo.h"
void fifo_register(_fifo_t *pfifo, uint8_t *pfifo_buf, uint32_t size,
lock_fun lock, lock_fun unlock)
{
pfifo->buf_size = size;
pfifo->buf = pfifo_buf;
pfifo->pwrite = pfifo->buf;
pfifo->pread = pfifo->buf;
pfifo->occupy_size = 0;
pfifo->lock = lock;
pfifo->unlock = unlock;
}
void fifo_release(_fifo_t *pfifo)
{
pfifo->buf_size = 0;
pfifo->occupy_size = 0;
pfifo->buf = NULL;
pfifo->pwrite = 0;
pfifo->pread = 0;
pfifo->lock = NULL;
pfifo->unlock = NULL;
}
uint32_t fifo_write(_fifo_t *pfifo, const uint8_t *pbuf, uint32_t size)
{
uint32_t w_size = 0;
uint32_t free_size = 0;
if ((size == 0U) || (pfifo == NULL) || (pbuf == NULL))
{
return 0;
}
free_size = fifo_get_free_size(pfifo);
if (free_size == 0U)
{
return 0;
}
if (free_size < size)
{
size = free_size;
}
w_size = size;
if (pfifo->lock != NULL)
{
pfifo->lock();
}
while (w_size-- > 0U)
{
*pfifo->pwrite++ = *pbuf++;
if (pfifo->pwrite >= (pfifo->buf + pfifo->buf_size))
{
pfifo->pwrite = pfifo->buf;
}
pfifo->occupy_size++;
}
if (pfifo->unlock != NULL)
{
pfifo->unlock();
}
return size;
}
uint32_t fifo_read(_fifo_t *pfifo, uint8_t *pbuf, uint32_t size)
{
uint32_t r_size = 0;
uint32_t occupy_size = 0;
if ((size == 0U) || (pfifo == NULL) || (pbuf == NULL))
{
return 0;
}
occupy_size = fifo_get_occupy_size(pfifo);
if (occupy_size == 0U)
{
return 0;
}
if (occupy_size < size)
{
size = occupy_size;
}
if (pfifo->lock != NULL)
{
pfifo->lock();
}
r_size = size;
while (r_size-- > 0U)
{
*pbuf++ = *pfifo->pread++;
if (pfifo->pread >= (pfifo->buf + pfifo->buf_size))
{
pfifo->pread = pfifo->buf;
}
pfifo->occupy_size--;
}
if (pfifo->unlock != NULL)
{
pfifo->unlock();
}
return size;
}
uint32_t fifo_get_total_size(_fifo_t *pfifo)
{
if (pfifo == NULL)
{
return 0;
}
return pfifo->buf_size;
}
uint32_t fifo_get_free_size(_fifo_t *pfifo)
{
uint32_t size;
if (pfifo == NULL)
{
return 0;
}
size = pfifo->buf_size - fifo_get_occupy_size(pfifo);
return size;
}
uint32_t fifo_get_occupy_size(_fifo_t *pfifo)
{
if (pfifo == NULL)
{
return 0;
}
return pfifo->occupy_size;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef RING_FIFO_H
#define RING_FIFO_H
#include <stdbool.h>
#include <stdint.h>
typedef void (*lock_fun)(void);
typedef struct
{
uint8_t *buf;
uint32_t buf_size;
uint32_t occupy_size;
uint8_t *pwrite;
uint8_t *pread;
void (*lock)(void);
void (*unlock)(void);
}_fifo_t;
void fifo_register(_fifo_t *pfifo, uint8_t *pfifo_buf, uint32_t size,
lock_fun lock, lock_fun unlock);
void fifo_release(_fifo_t *pfifo);
uint32_t fifo_write(_fifo_t *pfifo, const uint8_t *pbuf, uint32_t size);
uint32_t fifo_read(_fifo_t *pfifo, uint8_t *pbuf, uint32_t size);
uint32_t fifo_get_total_size(_fifo_t *pfifo);
uint32_t fifo_get_free_size(_fifo_t *pfifo);
uint32_t fifo_get_occupy_size(_fifo_t *pfifo);
#endif
+22
View File
@@ -0,0 +1,22 @@
#ifndef _RTC_H_
#define _RTC_H_
#include "gd32h7xx.h"
#include "publicdata/type.h"
/* 对外接口函数声明 */
void v_rtc_init(void);
/* app_rtc 兼容接口(对业务层使用 Comm_Time) */
void GetCurrentTime(Comm_Time *curTime);
void v_rtc_set_time(Comm_Time *curTime);
unsigned int xDate2Seconds(Comm_Time *time);
void xSeconds2Date(unsigned long seconds, Comm_Time *time);
U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type);
void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset);
void test_rtc(void);
#endif
+93
View File
@@ -0,0 +1,93 @@
# RTC 模块说明
## 1. 模块位置
- `BSP/rtc/rtc.c`
- `BSP/rtc/rtc.h`
## 2. 模块职责
- 完成 MCU 内部 RTC 外设初始化(默认 `LXTAL 32.768kHz`
- 提供统一时间读写接口(业务层使用 `Comm_Time`
- 提供时间算法工具:
- 本地时间(UTC+8)与 Unix 时间戳互转
- OCPP 时间戳解析
- 按时区偏移调整时间(支持跨天/月/年)
## 3. 硬件与掉电行为说明
### 3.1 `RTC_BKP0` 是什么
- `RTC_BKP0` 是 RTC 备份寄存器中的一个槽位(软件可读写)
- 本项目中它作为“RTC 是否已初始化”的标记位使用
- 典型逻辑:
- `RTC_BKP0 == BKP_VALUE`:认为已配置过,不覆盖当前时间
- `RTC_BKP0 != BKP_VALUE`:按默认时间初始化并写入标记
### 3.2 掉电后时间会不会继续走
- `RTC_BKP0` 只是存储标记,不会自动计时
- 时间能否在掉电期间继续增加,取决于 RTC 域是否持续供电(VBAT)以及 RTC 时钟是否持续工作
- 若掉电导致备份域失电,RTC 计时会停止;下次上电会按“首次初始化”逻辑处理
## 4. 默认时间策略
- 当前默认时间由 `rtc.c` 宏定义控制:
- `RTC_DEFAULT_YEAR_BCD`
- `RTC_DEFAULT_MONTH_BCD`
- `RTC_DEFAULT_DAY_BCD`
- `RTC_DEFAULT_HOUR_BCD`
- `RTC_DEFAULT_MINUTE_BCD`
- `RTC_DEFAULT_SECOND_BCD`
- `RTC_DEFAULT_WEEKDAY`
- 默认值仅在“未初始化/备份标记无效”时生效,不会每次上电都覆盖已有时间
## 5. 对外接口
- `void v_rtc_init(void);`
- RTC 初始化入口(由 `BSP/sys_drv_init.c` 调用)
- `void GetCurrentTime(Comm_Time *curTime);`
- 读取当前时间到 `Comm_Time`
- `void v_rtc_set_time(Comm_Time *curTime);`
- 写 RTC 时间(内含时间合法性检查)
- `unsigned int xDate2Seconds(Comm_Time *time);`
- 本地时间(UTC+8)转 Unix 秒(UTC
- `void xSeconds2Date(unsigned long seconds, Comm_Time *time);`
- Unix 秒(UTC)转本地时间(UTC+8
- `U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type);`
- 解析 OCPP 时间戳(支持 `Z``+08:00``-05:30` 等)
- `time_type=0`:输出 UTC
- `time_type=1`:输出北京时间(UTC+8
- `void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset);`
-`Comm_Time` 按时区偏移修正
- `void test_rtc(void);`
- RTC 自测接口(打印当前时间)
## 6. 启动与调用链
1. `main.c` -> `v_sys_hardware_init()`
2. `BSP/sys_drv_init.c` -> `v_rtc_init()`
3. 业务层通过 `GetCurrentTime/v_rtc_set_time` 访问时间
4. `app/app_init/app_test.c` 在周期测试中可触发 RTC 读写测试
## 7. 常见问题
- **Q: 修改默认时间后,为什么上电看到的不是默认值?**
A: 因为备份标记仍有效,模块判断为“已初始化”,不会覆盖当前时间。
- **Q: 想强制重新走默认时间怎么办?**
A: 清空备份标记(例如清 `RTC_BKP0`)或清备份域后重启。
## 8. 相关文档
- [返回主说明 README](../../README.md)
- [externalflash 模块说明](../externalflash/externalflash模块说明.md)
+330
View File
@@ -0,0 +1,330 @@
/*!
\file spi1_fixed.c
\brief SPI1 configuration file (Corrected Version)
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "spi1_fixed.h"
#include <stdio.h>
#include "gd32h7xx.h"
/* 私有变量 */
static uint8_t spi1_initialized = 0;
/*!
\brief configure SPI1 GPIO peripheral
\param[in] none
\param[out] none
\retval none
\note SPI1引脚配置:
- SPI1_NSS -> PA8 (软件控制)
- SPI1_SCK -> PA9 (AF5)
- SPI1_MISO -> PG2 (AF5)
- SPI1_MOSI -> PG3 (AF5)
*/
void spi1_gpio_config(void)
{
/* 使能GPIO时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOG);
/* 使能SPI1时钟 */
rcu_periph_clock_enable(RCU_SPI1);
/* 配置SPI1时钟源 */
rcu_spi_clock_config(IDX_SPI1, RCU_SPISRC_PLL0Q);
/* 配置NSS引脚 (PA8) - 软件控制,GPIO输出模式 */
gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_8);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_8);
/* 默认拉高,不选中从设备 */
gpio_bit_set(GPIOA, GPIO_PIN_8);
/* 配置SPI1引脚复用功能 */
gpio_af_set(GPIOA, GPIO_AF_5, GPIO_PIN_9); // SCK
gpio_af_set(GPIOG, GPIO_AF_5, GPIO_PIN_2 | GPIO_PIN_3); // MISO, MOSI
/* 配置SCK引脚 (PA9) - 复用功能输出 */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_9);
/* 配置MISO引脚 (PG2) - 复用功能输入 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2);
/* 配置MOSI引脚 (PG3) - 复用功能输出 */
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_3);
}
/*!
\brief configure SPI1 peripheral
\param[in] none
\param[out] none
\retval none
\note SPI1基本配置,使用8位数据,MSB先行,主模式
*/
void spi1_config(void)
{
spi_parameter_struct spi_init_struct;
/* 去初始化SPI1 */
spi_i2s_deinit(SPI1);
/* 初始化SPI参数结构体 */
spi_struct_para_init(&spi_init_struct);
/* SPI1参数配置 */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全双工模式
spi_init_struct.device_mode = SPI_MASTER; // 主模式
spi_init_struct.data_size = SPI_DATASIZE_8BIT; // 8位数据帧 (修正: frame_size -> data_size)
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; // 时钟极性低,第一边沿采样
spi_init_struct.nss = SPI_NSS_SOFT; // 软件NSS控制
spi_init_struct.prescale = SPI_PSC_16; // 16分频
spi_init_struct.endian = SPI_ENDIAN_MSB; // MSB先行
/* 初始化SPI1 */
spi_init(SPI1, &spi_init_struct);
/* 使能字节访问 */
spi_byte_access_enable(SPI1);
/* 使能NSS输出 (虽然使用软件控制,但保持兼容性) */
spi_nss_output_enable(SPI1);
}
/*!
\brief SPI1完整初始化
\param[in] none
\param[out] none
\retval 0: 成功, -1: 失败
\note 完整的SPI1初始化流程
*/
int spi1_init(void)
{
if(spi1_initialized) {
return 0; // 已初始化
}
/* 配置GPIO */
spi1_gpio_config();
/* 配置SPI外设 */
spi1_config();
/* 使能SPI1 */
spi_enable(SPI1);
spi1_initialized = 1;
return 0;
}
/*!
\brief SPI1去初始化
\param[in] none
\param[out] none
\retval none
\note 禁用SPI1并复位相关配置
*/
void spi1_deinit(void)
{
/* 禁用SPI1 */
spi_disable(SPI1);
/* 去初始化SPI1 */
spi_i2s_deinit(SPI1);
/* NSS引脚拉高 */
gpio_bit_set(GPIOA, GPIO_PIN_8);
spi1_initialized = 0;
}
/*!
\brief SPI1发送数据
\param[in] data: 要发送的数据
\param[out] none
\retval none
\note 阻塞式发送
*/
void spi1_send_data(uint8_t data)
{
/* 等待发送缓冲区为空 */
while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_TP));
/* 发送数据 (修正API名称) */
spi_i2s_data_transmit(SPI1, data);
}
/*!
\brief SPI1接收数据
\param[in] none
\param[out] none
\retval 接收到的数据
\note 阻塞式接收
*/
uint8_t spi1_receive_data(void)
{
/* 等待接收缓冲区非空 */
while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_RP));
/* 接收数据 (修正API名称) */
return (uint8_t)spi_i2s_data_receive(SPI1);
}
/*!
\brief SPI1发送接收数据
\param[in] tx_data: 要发送的数据
\param[out] none
\retval 接收到的数据
\note 全双工通信,同时发送和接收
*/
uint8_t spi1_transmit_receive(uint8_t tx_data)
{
/* 等待发送缓冲区为空 */
while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_TP));
/* 发送数据 (修正API名称) */
spi_i2s_data_transmit(SPI1, tx_data);
/* 等待接收缓冲区非空 */
while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_RP));
/* 接收数据 (修正API名称) */
return (uint8_t)spi_i2s_data_receive(SPI1);
}
/*!
\brief SPI1发送数据块
\param[in] data: 数据缓冲区指针
\param[in] size: 数据大小
\param[out] none
\retval 0: 成功, -1: 参数错误
*/
int spi1_send_buffer(uint8_t *data, uint16_t size)
{
if(!data || size == 0) {
return -1;
}
for(uint16_t i = 0; i < size; i++) {
spi1_send_data(data[i]);
}
return 0;
}
/*!
\brief SPI1接收数据块
\param[in] data: 数据缓冲区指针
\param[in] size: 数据大小
\param[out] none
\retval 0: 成功, -1: 参数错误
*/
int spi1_receive_buffer(uint8_t *data, uint16_t size)
{
if(!data || size == 0) {
return -1;
}
for(uint16_t i = 0; i < size; i++) {
data[i] = spi1_receive_data();
}
return 0;
}
/*!
\brief SPI1片选控制
\param[in] enable: 1-选中从设备, 0-取消选中
\param[out] none
\retval none
\note 软件控制NSS信号
*/
void spi1_nss_control(uint8_t enable)
{
if(enable) {
/* 拉低NSS,选中从设备 */
gpio_bit_reset(GPIOA, GPIO_PIN_8);
} else {
/* 拉高NSS,取消选中从设备 */
gpio_bit_set(GPIOA, GPIO_PIN_8);
}
}
/*!
\brief 检查SPI1是否已初始化
\param[in] none
\param[out] none
\retval 1: 已初始化, 0: 未初始化
*/
uint8_t spi1_is_initialized(void)
{
return spi1_initialized;
}
/*!
\brief SPI1中断处理函数
\param[in] none
\param[out] none
\retval none
\note 需要在SPI1_IRQHandler中调用此函数
*/
void spi1_irq_handler(void)
{
/* 检查接收缓冲区非空中断标志 */
if(spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_FLAG_RP)) {
/* 读取接收数据,清除中断标志 */
uint8_t data = (uint8_t)spi_i2s_data_receive(SPI1);
/* 这里可以添加数据处理逻辑,例如存储到接收缓冲区 */
(void)data; /* 避免未使用变量警告 */
}
/* 检查发送缓冲区空中断标志 */
if(spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_FLAG_TP)) {
/* 这里可以添加发送数据处理逻辑 */
}
/* 检查错误中断标志 */
if(spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_FLAG_CRCERR)) {
/* CRC错误处理 */
spi_i2s_flag_clear(SPI1, SPI_FLAG_CRCERR);
}
if(spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_FLAG_RXORERR)) {
/* 溢出错误处理 */
spi_i2s_flag_clear(SPI1, SPI_FLAG_RXORERR);
}
if(spi_i2s_interrupt_flag_get(SPI1, SPI_I2S_INT_FLAG_TXURERR)) {
/* 欠载错误处理 */
spi_i2s_flag_clear(SPI1, SPI_FLAG_TXURERR);
}
}
+83
View File
@@ -0,0 +1,83 @@
/*!
\file spi1_fixed.h
\brief header file of SPI1 (Corrected Version)
\version 2026-03-09, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#ifndef SPI1_FIXED_H
#define SPI1_FIXED_H
#include <stdint.h>
/* SPI1引脚定义 */
#define SPI1_NSS_PIN GPIO_PIN_8 // PA8
#define SPI1_SCK_PIN GPIO_PIN_9 // PA9
#define SPI1_MISO_PIN GPIO_PIN_2 // PG2
#define SPI1_MOSI_PIN GPIO_PIN_3 // PG3
#define SPI1_NSS_PORT GPIOA
#define SPI1_SCK_PORT GPIOA
#define SPI1_MISO_PORT GPIOG
#define SPI1_MOSI_PORT GPIOG
/* SPI1复用功能编号 */
#define SPI1_AF GPIO_AF_5
/* 函数声明 */
/* GPIO配置 */
void spi1_gpio_config(void);
/* SPI外设配置 */
void spi1_config(void);
/* 完整初始化和去初始化 */
int spi1_init(void);
void spi1_deinit(void);
/* 状态检查 */
uint8_t spi1_is_initialized(void);
/* 数据传输函数 */
void spi1_send_data(uint8_t data);
uint8_t spi1_receive_data(void);
uint8_t spi1_transmit_receive(uint8_t tx_data);
/* 缓冲区操作 */
int spi1_send_buffer(uint8_t *data, uint16_t size);
int spi1_receive_buffer(uint8_t *data, uint16_t size);
/* 片选控制 */
void spi1_nss_control(uint8_t enable);
/* 中断处理 */
void spi1_irq_handler(void);
#endif /* SPI1_FIXED_H */
+185
View File
@@ -0,0 +1,185 @@
#include "sys_drv_init.h"
#include "gpio.h"
#include "usart.h"
#include "app_rtc/app_rtc.h"
#include "gd32h7xx.h"
#include "can.h"
#include "adc_temp.h"
#include "nandflash_exmc_MT29F4G08.h"
#include "exflash_spi4_gd25qxx.h"
#include "flash_external_data.h"
#include "adc_spi3_insuVolt.h"
#include "adc_spi5_batVoltCurr.h"
#include "fm24cl16.h"
#include "tim/app_tim.h"
#include "main.h"
//#include <cachel1_armv7.h>
/**
* @brief 使能 Cortex-M7 指令/数据缓存
* @note 先失效 D-Cache,再开启 D-Cache,避免历史脏数据影响
*/
static void cache_enable(void)
{
SCB_EnableICache();
SCB_DisableDCache();
SCB_InvalidateDCache();
SCB_EnableDCache();
}
/**
* @brief 配置 NVIC 优先级分组
* @note FreeRTOS 建议使用 PRE4_SUB0(4 位抢占优先级 + 0 位子优先级)
*/
static void nvic_configuration(void)
{
/* 统一中断优先级配置入口(参考 CCU601E_D 的 NVIC_Priority_Config 思路) */
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
/* ENET IRQ 内会调用 xSemaphoreGiveFromISR,优先级必须 >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY(5) */
#ifdef USE_ENET0
nvic_irq_enable(ENET0_IRQn, 6U, 0U);
#endif
#ifdef USE_ENET1
nvic_irq_enable(ENET1_IRQn, 6U, 0U);
#endif
/* USART/UART 中断优先级统一配置 */
nvic_irq_enable(USART0_IRQn, 3U, 1U);
nvic_irq_enable(USART2_IRQn, 3U, 2U);
nvic_irq_enable(UART3_IRQn, 3U, 0U);
nvic_irq_enable(UART4_IRQn, 2U, 0U);
nvic_irq_enable(USART5_IRQn, 1U, 0U);
nvic_irq_enable(UART6_IRQn, 0U, 0U);
nvic_irq_enable(UART7_IRQn, 3U, 7U);
/* ADC DMA 中断优先级统一配置 */
nvic_irq_enable(DMA1_Channel0_IRQn, 6U, 0U);
/* CAN 中断优先级统一配置 */
nvic_irq_enable(CAN0_Message_IRQn, 0U, 0U);
nvic_irq_enable(CAN1_Message_IRQn, 0U, 0U);
nvic_irq_enable(CAN2_Message_IRQn, 0U, 0U);
/* CAN error/busoff/warning IRQs (some CAN configs may route events here) */
nvic_irq_enable(CAN0_Busoff_IRQn, 0U, 0U);
nvic_irq_enable(CAN0_Error_IRQn, 0U, 0U);
nvic_irq_enable(CAN0_TEC_IRQn, 0U, 0U);
nvic_irq_enable(CAN0_REC_IRQn, 0U, 0U);
nvic_irq_enable(CAN1_Busoff_IRQn, 0U, 0U);
nvic_irq_enable(CAN1_Error_IRQn, 0U, 0U);
nvic_irq_enable(CAN1_TEC_IRQn, 0U, 0U);
nvic_irq_enable(CAN1_REC_IRQn, 0U, 0U);
nvic_irq_enable(CAN2_Busoff_IRQn, 0U, 0U);
nvic_irq_enable(CAN2_Error_IRQn, 0U, 0U);
nvic_irq_enable(CAN2_TEC_IRQn, 0U, 0U);
nvic_irq_enable(CAN2_REC_IRQn, 0U, 0U);
nvic_irq_enable(TIMER7_UP_IRQn, 5U, 0U);
}
/**
* @brief 配置 MPUMemory Protection Unit)区域属性
*
* 详细说明:
* 1) 该函数主要用于给“被 DMA 与 CPU 同时访问”的内存区域设置合适的属性,
* 避免 D-Cache 引入的数据一致性问题(CPU 看到旧数据、DMA 写入不可见等)。
*
* 2) 当前配置了两块 16KB 区域(均位于 SRAM3):
* - 区域00x30000000 ~ 0x30003FFF
* 典型用于 DMA 描述符、ETH Rx/Tx 缓冲等需要强一致性的内存。
* - 区域10x30004000 ~ 0x30007FFF
* 典型用于 lwIP heappbuf/memp 等运行期内存)。
*
* 3) 两个区域都设置为 Non-Cacheable(不可缓存):
* - 目的:让 CPU 每次都从实际内存读取,保证与 DMA 读写一致。
* - 代价:这部分区域访问速度会比 Cacheable 区域慢,但稳定性更高。
*
* 4) 该函数应在外设初始化前执行(当前在 v_sys_hardware_init() 早期调用),
* 这样后续网络/DMA模块在运行时就使用了正确的内存属性。
*
* 5) 结论:此函数是“网络与 DMA 稳定运行”的基础配置,尤其在 Cortex-M7
* 启用 D-Cache 场景下必不可少。
*/
void mpu_config(void)
{
mpu_region_init_struct mpu_init_struct;
mpu_region_struct_para_init(&mpu_init_struct);
/* disable MPU */
ARM_MPU_SetRegion(0U, 0U);
/* 区域0:DMA描述符/收发缓冲区(非缓存,避免 DMA 与 Cache 不一致) */
mpu_init_struct.region_base_address = 0x30000000;
mpu_init_struct.region_size = MPU_REGION_SIZE_16KB;
mpu_init_struct.access_permission = MPU_AP_FULL_ACCESS;
mpu_init_struct.access_bufferable = MPU_ACCESS_BUFFERABLE;
mpu_init_struct.access_cacheable = MPU_ACCESS_NON_CACHEABLE;
mpu_init_struct.access_shareable = MPU_ACCESS_NON_SHAREABLE;
mpu_init_struct.region_number = MPU_REGION_NUMBER0;
mpu_init_struct.subregion_disable = MPU_SUBREGION_ENABLE;
mpu_init_struct.instruction_exec = MPU_INSTRUCTION_EXEC_PERMIT;
mpu_init_struct.tex_type = MPU_TEX_TYPE0;
mpu_region_config(&mpu_init_struct);
mpu_region_enable();
/* 区域1lwIP heap(非缓存 + shareable,便于总线主设备协同访问) */
mpu_init_struct.region_base_address = 0x30004000;
mpu_init_struct.region_size = MPU_REGION_SIZE_16KB;
mpu_init_struct.access_permission = MPU_AP_FULL_ACCESS;
mpu_init_struct.access_bufferable = MPU_ACCESS_NON_BUFFERABLE;
mpu_init_struct.access_cacheable = MPU_ACCESS_NON_CACHEABLE;
mpu_init_struct.access_shareable = MPU_ACCESS_SHAREABLE;
mpu_init_struct.region_number = MPU_REGION_NUMBER1;
mpu_init_struct.subregion_disable = MPU_SUBREGION_ENABLE;
mpu_init_struct.instruction_exec = MPU_INSTRUCTION_EXEC_PERMIT;
mpu_init_struct.tex_type = MPU_TEX_TYPE1;
mpu_region_config(&mpu_init_struct);
mpu_region_enable();
/* enable MPU */
ARM_MPU_Enable(MPU_MODE_PRIV_DEFAULT);
}
/**
* @brief 系统硬件初始化总入口
* @note 按“基础系统 -> 通信/IO -> 业务外设”顺序初始化
*/
void v_sys_hardware_init(void)
{
/* 基础系统配置 */
nvic_configuration();
mpu_config();
cache_enable();
/* 基础外设 */
/* 先初始化关键DO输出,避免上电过程继电器抖动/误动作 */
v_outpin_config();
v_rtc_init();
v_inpin_config();
/* 通信外设 */
v_usart_init_all();
/* EEPROM(FM24CL16) I2C1 初始化 */
eeprom_driver_init();
/* SPI Flash 驱动初始化(统一封装接口) */
(void)v_flash_dataflash_init();
/* CAN init moved into hardware stage to avoid first-frame loss after power-on. */
v_can_interface_init();
/* 1ms hardware timer interrupt for periodic software counters. */
v_app_tim_init();
/* 以太网与 lwIP 须在 FreeRTOS 调度器启动后初始化(见 app_os_init),
* 否则 enet_system_setup() 失败会死在 while(1),运行灯也不会亮。 */
/* 采样与电源相关外设 */
adc_spi3_insuVolt_init();
adc_spi5_batVoltCurr_init();
v_temp_config();
// v_4g_config();
/* 可选外设(按需启用)
exmc_nandflash_init();
lwip_system_init();
*/
}
+88
View File
@@ -0,0 +1,88 @@
#ifndef _SYS_DRV_INIT_H_
#define _SYS_DRV_INIT_H_
#include "gd32h7xx.h"
#include "gpio.h"
#include "gd32h7xx_gpio.h"
//函数执行 调用时需要加;
#define RUN_LED_TOGGLE() gpio_bit_toggle(LED_RUN_PORT, LED_RUN_PIN) //运行灯翻转
#define RUN_LED_ON() RELAY_OFF(LED_RUN_PORT, LED_RUN_PIN) //运行灯亮
#define RUN_LED_OFF() RELAY_ON(LED_RUN_PORT, LED_RUN_PIN) //运行灯灭
//DI口 低电平是导通,高电平断开
#define DI1 (DI_READ(YX1_PORT,YX1_PIN)?0:1)
#define DI2 (DI_READ(YX2_PORT,YX2_PIN)?0:1)
#define DI3 (DI_READ(YX3_PORT,YX3_PIN)?0:1)
#define DI4 (DI_READ(YX4_PORT,YX4_PIN)?0:1)
#define DI5 (DI_READ(YX5_PORT,YX5_PIN)?0:1)
#define DI6 (DI_READ(YX6_PORT,YX6_PIN)?0:1)
#define DI7 (DI_READ(YX7_PORT,YX7_PIN)?0:1)
#define DI8 (DI_READ(YX8_PORT,YX8_PIN)?0:1)
#define DI9 (DI_READ(YX9_PORT,YX9_PIN)?0:1)
#define DI10 (DI_READ(YX10_PORT,YX10_PIN)?0:1)
#define DI11 (DI_READ(YX11_PORT,YX11_PIN)?0:1)
#define DI12 (DI_READ(YX12_PORT,YX12_PIN)?0:1)
#define AUX_A (DI_READ(AUX_PADET_PORT,AUX_PADET_PIN)?0:1)
#define AUX_B (DI_READ(AUX_PBDT_PORT,AUX_PBDT_PIN)?0:1)
//DO口 低电平是导通,高电平时断开-- 输出口控制,0断开,1闭合
/* K1K10 与 PK1/PK2 引脚 mode 及上电后相对复位默认的输出取反:BSP/GPIO/gpio.c -> v_outpin_config() */
#define DO1(x) (x!=0 ? RELAY_OFF(RELAY_K1_PORT, RELAY_K1_PIN) : RELAY_ON(RELAY_K1_PORT, RELAY_K1_PIN))
#define DO2(x) (x!=0 ? RELAY_OFF(RELAY_K2_PORT, RELAY_K2_PIN) : RELAY_ON(RELAY_K2_PORT, RELAY_K2_PIN))
#define DO3(x) (x!=0 ? RELAY_OFF(RELAY_K3_PORT, RELAY_K3_PIN) : RELAY_ON(RELAY_K3_PORT, RELAY_K3_PIN))
#define DO4(x) (x!=0 ? RELAY_OFF(RELAY_K4_PORT, RELAY_K4_PIN) : RELAY_ON(RELAY_K4_PORT, RELAY_K4_PIN))
#define DO5(x) (x!=0 ? RELAY_OFF(RELAY_K5_PORT, RELAY_K5_PIN) : RELAY_ON(RELAY_K5_PORT, RELAY_K5_PIN))
#define DO6(x) (x!=0 ? RELAY_OFF(RELAY_K6_PORT, RELAY_K6_PIN) : RELAY_ON(RELAY_K6_PORT, RELAY_K6_PIN))
#define DO7(x) (x!=0 ? RELAY_OFF(RELAY_K7_PORT, RELAY_K7_PIN) : RELAY_ON(RELAY_K7_PORT, RELAY_K7_PIN))
#define DO8(x) (x!=0 ? RELAY_OFF(RELAY_K8_PORT, RELAY_K8_PIN) : RELAY_ON(RELAY_K8_PORT, RELAY_K8_PIN))
#define DO9(x) (x!=0 ? RELAY_OFF(RELAY_K9_PORT, RELAY_K9_PIN) : RELAY_ON(RELAY_K9_PORT, RELAY_K9_PIN))
#define DO10(x) (x!=0 ? RELAY_OFF(RELAY_K10_PORT,RELAY_K10_PIN): RELAY_ON(RELAY_K10_PORT, RELAY_K10_PIN))
//电子锁控制(PK1/PK2
#define ELOCK_A(x) (x==0 ? RELAY_OFF(ELOCK_A_PORT, ELOCK_A_PIN) : RELAY_ON(ELOCK_A_PORT, ELOCK_A_PIN))
#define ELOCK_B(x) (x==0 ? RELAY_OFF(ELOCK_B_PORT, ELOCK_B_PIN) : RELAY_ON(ELOCK_B_PORT, ELOCK_B_PIN))
/* DO/电子锁 输出引脚电平回读:1=高电平,0=低电平(与 gpio_output_bit_get 一致) */
#define DO_PIN_LEVEL_READ(port, pin) ((gpio_output_bit_get((port), (pin)) == SET) ? 1U : 0U)
#define DO1_READ() DO_PIN_LEVEL_READ(RELAY_K1_PORT, RELAY_K1_PIN)
#define DO2_READ() DO_PIN_LEVEL_READ(RELAY_K2_PORT, RELAY_K2_PIN)
#define DO3_READ() DO_PIN_LEVEL_READ(RELAY_K3_PORT, RELAY_K3_PIN)
#define DO4_READ() DO_PIN_LEVEL_READ(RELAY_K4_PORT, RELAY_K4_PIN)
#define DO5_READ() DO_PIN_LEVEL_READ(RELAY_K5_PORT, RELAY_K5_PIN)
#define DO6_READ() DO_PIN_LEVEL_READ(RELAY_K6_PORT, RELAY_K6_PIN)
#define DO7_READ() DO_PIN_LEVEL_READ(RELAY_K7_PORT, RELAY_K7_PIN)
#define DO8_READ() DO_PIN_LEVEL_READ(RELAY_K8_PORT, RELAY_K8_PIN)
#define DO9_READ() DO_PIN_LEVEL_READ(RELAY_K9_PORT, RELAY_K9_PIN)
#define DO10_READ() DO_PIN_LEVEL_READ(RELAY_K10_PORT, RELAY_K10_PIN)
#define ELOCK_A_READ() DO_PIN_LEVEL_READ(ELOCK_A_PORT, ELOCK_A_PIN)
#define ELOCK_B_READ() DO_PIN_LEVEL_READ(ELOCK_B_PORT, ELOCK_B_PIN)
//绝缘检测继电器控制
#define GUN1_INS_CTL(x) (x==0 ? RELAY_OFF(INSA_GND_PORT, INSA_GND_PIN) : RELAY_ON(INSA_GND_PORT, INSA_GND_PIN))
#define GUN1_UBAL_CTL(x) (x==0 ? RELAY_OFF(INSA_POS_PORT, INSA_POS_PIN) : RELAY_ON(INSA_POS_PORT, INSA_POS_PIN))
#define GUN2_INS_CTL(x) (x==0 ? RELAY_OFF(INSB_GND_PORT, INSB_GND_PIN) : RELAY_ON(INSB_GND_PORT, INSB_GND_PIN))
#define GUN2_UBAL_CTL(x) (x==0 ? RELAY_OFF(INSB_POS_PORT, INSB_POS_PIN) : RELAY_ON(INSB_POS_PORT, INSB_POS_PIN))
#define GUN1_INS_KM_READ DO_PIN_LEVEL_READ(INSA_POS_PORT, INSA_POS_PIN)
#define GUN2_INS_KM_READ DO_PIN_LEVEL_READ(INSB_POS_PORT, INSB_POS_PIN)
//连接确认控制
#define GUN1_UBAL_READ_H (DI_READ(CHECKOUTA1_PORT, CHECKOUTA1_PIN))
#define GUN1_UBAL_READ_L (DI_READ(CHECKOUTA2_PORT, CHECKOUTA2_PIN))
#define GUN2_UBAL_READ_H (DI_READ(CHECKOUTB1_PORT, CHECKOUTB1_PIN))
#define GUN2_UBAL_READ_L (DI_READ(CHECKOUTB2_PORT, CHECKOUTB2_PIN))
// 硬件看门狗喂狗
#define WATCKDOG_TOGGLE() gpio_bit_toggle(WDO_PORT, WDO_PIN)
//4G模块控制
#define PWR_4G(x) (((x)==0) ? RELAY_OFF(PWR_4G_PORT, PWR_4G_PIN) : RELAY_ON(PWR_4G_PORT, PWR_4G_PIN))
#define RST_4G(x) (((x)==0) ? RELAY_OFF(RST_4G_PORT, RST_4G_PIN) : RELAY_ON(RST_4G_PORT, RST_4G_PIN))
/* FC41D 复位 PE6x=0 正常运行(低)x=1 拉复位(高) */
#define FC41D_RST(x) (((x)==0) ? RELAY_OFF(FC41D_RST_PORT, FC41D_RST_PIN) : RELAY_ON(FC41D_RST_PORT, FC41D_RST_PIN))
//温度采集及板卡供电检测
#define TEMP_MULT_CTRA(x) (x==0 ? RELAY_OFF(TEMP_ADDR_A_PORT,TEMP_ADDR_A_PIN) : RELAY_ON(TEMP_ADDR_A_PORT, TEMP_ADDR_A_PIN))
#define TEMP_MULT_CTRB(x) (x==0 ? RELAY_OFF(TEMP_ADDR_B_PORT,TEMP_ADDR_B_PIN) : RELAY_ON(TEMP_ADDR_B_PORT, TEMP_ADDR_B_PIN))
#define TEMP_MULT_CTRC(x) (x==0 ? RELAY_OFF(TEMP_ADDR_C_PORT,TEMP_ADDR_C_PIN) : RELAY_ON(TEMP_ADDR_C_PORT, TEMP_ADDR_C_PIN))
//===============================================
void v_sys_hardware_init(void);
#endif
+57
View File
@@ -0,0 +1,57 @@
#include "tim/app_tim.h"
#include "bms/bms_interface.h"
#include "gd32h7xx.h"
#include "sys_drv_init.h"
void v_app_tim_init(void)
{
timer_parameter_struct timer_initpara;
/* TIMER7: generate update interrupt periodically. */
rcu_periph_clock_enable(RCU_TIMER7);
timer_deinit(TIMER7);
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler = 23999U;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 9U;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0U;
timer_init(TIMER7, &timer_initpara);
timer_interrupt_flag_clear(TIMER7, TIMER_INT_FLAG_UP);
timer_interrupt_enable(TIMER7, TIMER_INT_UP);
timer_enable(TIMER7);
}
void v_app_tim_irq_handler(void)
{
if (RESET != timer_interrupt_flag_get(TIMER7, TIMER_INT_FLAG_UP))
{
timer_interrupt_flag_clear(TIMER7, TIMER_INT_FLAG_UP);
v_app_tim_tick_1ms();
}
}
void v_app_tim_tick_1ms(void)
{
static U16_T cnt = 0U;
cnt++;
/* Run LED heartbeat from timer context (independent from IdleHook). */
if ((cnt % 500U) == 0U)
{
RUN_LED_TOGGLE();
WATCKDOG_TOGGLE();
}
if ((cnt % 10U) == 0U)
{
v_bms_add_timer(); /* 10ms */
}
v_delay_set_tymer_val(); /* bms timeout counter */
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef APP_TIM_H
#define APP_TIM_H
#ifdef __cplusplus
extern "C" {
#endif
void v_app_tim_init(void);
void v_app_tim_irq_handler(void);
void v_app_tim_tick_1ms(void);
#ifdef __cplusplus
}
#endif
#endif /* APP_TIM_H */
+111
View File
@@ -0,0 +1,111 @@
#include "tls/bs_tls_mbed.h"
#if (BS_TLS_MBEDTLS_EN)
/*
* 简单的 mbedtls TLS client 封装,供 plat_comm 使用。
* 证书校验策略、CA 证书加载等可根据项目需要在此扩展。
*/
/* 可根据项目实际把根证书放到只读 Flash,这里先留空,由用户自行填充 */
static const char *s_default_root_ca_pem = NULL;
int bs_tls_client_connect(BS_TLS_CTX *ctx, int fd, const char *host)
{
int ret;
const char *pers = "ccu-bs-tls";
if (ctx == NULL || host == NULL) {
return -1;
}
memset(ctx, 0, sizeof(BS_TLS_CTX));
mbedtls_net_init(&ctx->net_ctx);
mbedtls_ssl_init(&ctx->ssl);
mbedtls_ssl_config_init(&ctx->conf);
mbedtls_ctr_drbg_init(&ctx->ctr_drbg);
mbedtls_entropy_init(&ctx->entropy);
if ((ret = mbedtls_ctr_drbg_seed(&ctx->ctr_drbg,
mbedtls_entropy_func,
&ctx->entropy,
(const unsigned char *)pers,
strlen(pers))) != 0) {
return ret;
}
if ((ret = mbedtls_ssl_config_defaults(&ctx->conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
return ret;
}
/* 证书校验策略:先使用 VERIFY_OPTIONAL,后续可根据需要收紧为 VERIFY_REQUIRED */
mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
/* TODO: 如需严格校验证书,可在此加载根证书:
* mbedtls_x509_crt_init(...);
* mbedtls_x509_crt_parse(..., s_default_root_ca_pem, ...);
* mbedtls_ssl_conf_ca_chain(&ctx->conf, &cacert, NULL);
*/
if ((ret = mbedtls_ssl_setup(&ctx->ssl, &ctx->conf)) != 0) {
return ret;
}
if ((ret = mbedtls_ssl_set_hostname(&ctx->ssl, host)) != 0) {
return ret;
}
/* 将已有的 TCP socket fd 绑定到 mbedtls 的 net_sockets 封装上 */
ctx->net_ctx.fd = fd;
mbedtls_ssl_set_bio(&ctx->ssl, &ctx->net_ctx, mbedtls_net_send, mbedtls_net_recv, NULL);
while ((ret = mbedtls_ssl_handshake(&ctx->ssl)) != 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
return ret;
}
}
ctx->inited = 1U;
return 0;
}
int bs_tls_client_send(BS_TLS_CTX *ctx, const unsigned char *buf, size_t len)
{
if (ctx == NULL || ctx->inited == 0U) {
return -1;
}
return mbedtls_ssl_write(&ctx->ssl, buf, len);
}
int bs_tls_client_recv(BS_TLS_CTX *ctx, unsigned char *buf, size_t len)
{
if (ctx == NULL || ctx->inited == 0U) {
return -1;
}
return mbedtls_ssl_read(&ctx->ssl, buf, len);
}
void bs_tls_client_close(BS_TLS_CTX *ctx)
{
if (ctx == NULL || ctx->inited == 0U) {
return;
}
(void)mbedtls_ssl_close_notify(&ctx->ssl);
mbedtls_ssl_free(&ctx->ssl);
mbedtls_ssl_config_free(&ctx->conf);
mbedtls_ctr_drbg_free(&ctx->ctr_drbg);
mbedtls_entropy_free(&ctx->entropy);
mbedtls_net_free(&ctx->net_ctx);
ctx->inited = 0U;
}
#endif /* BS_TLS_MBEDTLS_EN */
+43
View File
@@ -0,0 +1,43 @@
#ifndef BS_TLS_MBED_H
#define BS_TLS_MBED_H
#include "publicdata/public_define.h"
#if (BS_TLS_MBEDTLS_EN)
/* 仅在启用 TLS 时才编译 mbedtls 相关代码,避免未下载库时报错 */
#include "mbedtls/platform.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include "mbedtls/error.h"
typedef struct
{
mbedtls_net_context net_ctx; /* 绑定的底层 TCP socket fd */
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_entropy_context entropy;
U8_T inited; /* 0=未初始化 1=已初始化 */
} BS_TLS_CTX;
/*
* 初始化 TLS 上下文(不包含握手),fd 由上层创建 TCP 后传入
* host 用于 SNI/证书校验
*/
int bs_tls_client_connect(BS_TLS_CTX *ctx, int fd, const char *host);
/* TLS 封装的发送/接收接口,返回值语义与 mbedtls_ssl_write/read 一致 */
int bs_tls_client_send(BS_TLS_CTX *ctx, const unsigned char *buf, size_t len);
int bs_tls_client_recv(BS_TLS_CTX *ctx, unsigned char *buf, size_t len);
/* 关闭 TLS 会话并释放资源,但不主动关闭底层 fd(交由上层统一处理) */
void bs_tls_client_close(BS_TLS_CTX *ctx);
#endif /* BS_TLS_MBEDTLS_EN */
#endif /* BS_TLS_MBED_H */