/*! \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 #include "gd32h7xx.h" #include "FreeRTOS.h" #include "task.h" #include "spi_if.h" #include "sgm51652hx.h" #include #include "sys_drv_init.h" // ======== 配置 ========= #define NUM_CHS 4 //通道数:芯片1(CH0、CH1)+芯片2(CH0、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; // 芯片1(A枪) static sgm51652hx_dev_t sgm51652hx_chip2; // 芯片2(B枪) /* 私有函数声明 */ 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/NSS)lyn260401 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; // 模式3,sgm51652只能工作在Model spi_init_struct.nss = SPI_NSS_SOFT; // 软件NSS控制:soft-自己用GPIO控制CS1/CS2 spi_init_struct.prescale = SPI_PSC_32; // 分频系数,sgm51652最高时钟10Mhz~10Mhz,SPI3在APB2(300Mhz) 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使用不同的片选引脚,需要单独处理*/ /* 初始化芯片1(A枪) */ spi_init_dev(&spi3_dev1, SPI3, GPIOA, GPIO_PIN_4, 0, 0); sgm51652hx_init(&sgm51652hx_chip1, &spi3_dev1); /* 初始化芯片2(B枪) */ 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_REF,ADC读到的电压值减去 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 融合进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); } }