- 修正HBI访问模型:将ESC核心CSR(0x0000-0x0FFF)改为通过ECAT_CSR_CMD/DATA 间接端口访问,解决此前直接寻址导致核心寄存器恒读0/写丢弃的问题 - 新增csrRead/csrWrite/pramRead/pramWrite接口,正确实现LAN9252访问端口操作 - 添加PDI就绪检测:等待DL_STATUS bit0置位确认EEPROM加载完成和EtherCAT核心使能 - 实现IRQ引脚输出使能:配置IRQ_CFG和INT_EN寄存器,使EtherCAT事件能到达PB1, 解决PDI_Isr未触发导致SAFEOP->OP无限挂起的问题 - 为ESC_EXMC硬件诊断添加CONFIG_ESC_EXMC_DIAG门控,默认关闭避免影响正常运行 - 修正system_gd32e50x.c中HXTAL故障诊断的寄存器偏移和位设置错
271 lines
9.9 KiB
C++
271 lines
9.9 KiB
C++
/**
|
|
* @file adc.cpp
|
|
* @brief ADC 采样模块实现。
|
|
*/
|
|
|
|
#include "main.h"
|
|
#include "device/adc.h"
|
|
#include "sys/systick.h"
|
|
#include "sys/config.h"
|
|
#include <math.h>
|
|
|
|
namespace {
|
|
/* ---- 校验常量与辅助函数 ---- */
|
|
constexpr float kMinValidVdda = 2.7f;
|
|
constexpr float kMaxValidVdda = 3.6f;
|
|
constexpr float kMinValidTemperature = -40.0f;
|
|
constexpr float kMaxValidTemperature = 125.0f;
|
|
|
|
static bool isValidAdcRaw(uint16_t raw)
|
|
{
|
|
return (raw > 0U) && (raw < static_cast<uint16_t>(CONFIG_ADC_MAX_VALUE));
|
|
}
|
|
|
|
static bool isFiniteFloat(float value)
|
|
{
|
|
return isfinite(value) != 0;
|
|
}
|
|
|
|
/* ---- DMA 配置 ---- */
|
|
constexpr uint32_t kDmaPeriph = DMA0;
|
|
constexpr dma_channel_enum kDmaChannel = DMA_CH0;
|
|
constexpr uint32_t kChannelCount = 3U;
|
|
constexpr uint32_t kVinGpio = GPIOA;
|
|
constexpr uint32_t kVinPin = GPIO_PIN_6;
|
|
volatile uint16_t s_dmaBuffer[kChannelCount] = {0U, 0U, 0U};
|
|
} // namespace
|
|
|
|
/**
|
|
* @brief 初始化 ADC DMA 循环搬运。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval 无返回值。
|
|
* @note 配置 DMA0 Channel0,将 ADC 规则组结果连续搬运到 `s_dmaBuffer`。
|
|
*/
|
|
void Adc::initDma()
|
|
{
|
|
dma_parameter_struct dma_initpara{};
|
|
|
|
rcu_periph_clock_enable(RCU_DMA0);
|
|
dma_deinit(kDmaPeriph, kDmaChannel);
|
|
dma_struct_para_init(&dma_initpara);
|
|
|
|
dma_initpara.periph_addr = (uint32_t)(&ADC_RDATA(CONFIG_ADC_INSTANCE));
|
|
dma_initpara.memory_addr = (uint32_t)s_dmaBuffer;
|
|
dma_initpara.direction = DMA_PERIPHERAL_TO_MEMORY;
|
|
dma_initpara.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
|
|
dma_initpara.memory_width = DMA_MEMORY_WIDTH_16BIT;
|
|
dma_initpara.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
|
|
dma_initpara.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
|
|
dma_initpara.number = kChannelCount;
|
|
dma_initpara.priority = DMA_PRIORITY_HIGH;
|
|
dma_init(kDmaPeriph, kDmaChannel, &dma_initpara);
|
|
dma_circulation_enable(kDmaPeriph, kDmaChannel);
|
|
dma_channel_enable(kDmaPeriph, kDmaChannel);
|
|
}
|
|
|
|
/**
|
|
* @brief 带超时等待 ADC_CTL1 指定位清零。
|
|
* @param[in] bit ADC_CTL1 位(RSTCLB/CLB)。
|
|
* @param[in] timeout_ms 超时毫秒数。
|
|
* @retval bool true=位已清零;false=超时(硬件未响应)。
|
|
* @note 替代库函数 adc_calibration_enable 内部的无超时死等——
|
|
* ADC 硬件异常时库函数会永久挂住 init,且无任何诊断。
|
|
*/
|
|
static bool waitAdcCtl1BitClear(uint32_t bit, uint32_t timeout_ms)
|
|
{
|
|
const uint32_t start = SysTickTimer::getMs();
|
|
while ((ADC_CTL1(CONFIG_ADC_INSTANCE) & bit) != 0U) {
|
|
if ((SysTickTimer::getMs() - start) >= timeout_ms) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief 带超时执行 ADC 校准(RSTCLB → CLB 两段各等清零)。
|
|
* @retval uint8_t Adc::InitFault 故障码(0=校准完成;1=RSTCLB 超时;2=CLB 超时)。
|
|
*/
|
|
static uint8_t runCalibrationWithTimeout()
|
|
{
|
|
constexpr uint32_t kCalTimeoutMs = 10U; /* 校准实际仅数十 µs,10ms 裕量极大 */
|
|
ADC_CTL1(CONFIG_ADC_INSTANCE) |= ADC_CTL1_RSTCLB;
|
|
if (!waitAdcCtl1BitClear(ADC_CTL1_RSTCLB, kCalTimeoutMs)) {
|
|
return Adc::kFaultCalRstTimeout;
|
|
}
|
|
ADC_CTL1(CONFIG_ADC_INSTANCE) |= ADC_CTL1_CLB;
|
|
if (!waitAdcCtl1BitClear(ADC_CTL1_CLB, kCalTimeoutMs)) {
|
|
return Adc::kFaultCalTimeout;
|
|
}
|
|
return Adc::kFaultOk;
|
|
}
|
|
|
|
/**
|
|
* @brief 初始化 ADC、DMA 与规则通道扫描配置,并验证采样链有效。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval uint8_t Adc::InitFault 故障码(0=校准完成且 VREFINT→VDDA 换算在合理区间)。
|
|
* @note 按 `VIN_ADC / TEMP / VREFINT` 三通道顺序启用连续扫描与 DMA 采样。
|
|
* 参考 GD32E508 文档要求,内部温度与 VREFINT 通道使用长采样时间,以满足 ts_temp 要求。
|
|
*/
|
|
uint8_t Adc::init()
|
|
{
|
|
rcu_periph_clock_enable(RCU_ADC0);
|
|
rcu_periph_clock_enable(RCU_GPIOA);
|
|
rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
|
|
|
|
/* 原理图:PA6 -> VIN_ADC,为外部电源分压检测输入 */
|
|
gpio_init(kVinGpio, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, kVinPin);
|
|
|
|
initDma();
|
|
adc_deinit(CONFIG_ADC_INSTANCE);
|
|
adc_mode_config(ADC_MODE_FREE);
|
|
adc_special_function_config(CONFIG_ADC_INSTANCE, ADC_SCAN_MODE, ENABLE);
|
|
adc_special_function_config(CONFIG_ADC_INSTANCE, ADC_CONTINUOUS_MODE, ENABLE);
|
|
adc_data_alignment_config(CONFIG_ADC_INSTANCE, ADC_DATAALIGN_RIGHT);
|
|
adc_channel_length_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, kChannelCount);
|
|
|
|
/* 内部温度传感器和 VREFINT 使用长采样时间,确保满足文档要求。 */
|
|
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 0U, CONFIG_ADC_VIN_CHANNEL, ADC_SAMPLETIME_239POINT5);
|
|
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 1U, CONFIG_ADC_TEMP_CHANNEL, ADC_SAMPLETIME_239POINT5);
|
|
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 2U, CONFIG_ADC_VREFINT_CHANNEL, ADC_SAMPLETIME_239POINT5);
|
|
|
|
adc_external_trigger_source_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
|
|
adc_external_trigger_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, ENABLE);
|
|
adc_dma_mode_enable(CONFIG_ADC_INSTANCE);
|
|
|
|
adc_tempsensor_vrefint_enable();
|
|
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
|
|
|
|
adc_enable(CONFIG_ADC_INSTANCE);
|
|
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
|
|
const uint8_t calFault = runCalibrationWithTimeout();
|
|
if (calFault != kFaultOk) {
|
|
return calFault;
|
|
}
|
|
adc_software_trigger_enable(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL);
|
|
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
|
|
|
|
/* 验证采样链:DMA 循环搬运在跑且 VREFINT 换算 VDDA 落在合理区间。
|
|
* calculateVdda 对非法 raw/超范围返回 0.0f。 */
|
|
const float vdda = calculateVdda(readVrefRaw());
|
|
return ((vdda >= kMinValidVdda) && (vdda <= kMaxValidVdda)) ? kFaultOk : kFaultVddaInvalid;
|
|
}
|
|
|
|
/**
|
|
* @brief 按逻辑通道号读取一个原始 ADC 数值。
|
|
* @param[in] channel 逻辑 ADC 通道号。
|
|
* @param[out] 无。
|
|
* @retval uint16_t 返回对应通道的原始采样值;若通道不受支持则返回 0。
|
|
*/
|
|
uint16_t Adc::readRaw(uint8_t channel)
|
|
{
|
|
if(channel == CONFIG_ADC_VIN_CHANNEL) return s_dmaBuffer[0];
|
|
if(channel == CONFIG_ADC_TEMP_CHANNEL) return s_dmaBuffer[1];
|
|
if(channel == CONFIG_ADC_VREFINT_CHANNEL) return s_dmaBuffer[2];
|
|
return 0U;
|
|
}
|
|
|
|
/**
|
|
* @brief 读取 VIN_ADC 原始采样值。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval uint16_t 返回 VIN_ADC 通道原始值。
|
|
*/
|
|
uint16_t Adc::readVinRaw() { return readRaw(CONFIG_ADC_VIN_CHANNEL); }
|
|
|
|
/**
|
|
* @brief 读取内部参考电压原始采样值。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval uint16_t 返回 VREFINT 通道原始值。
|
|
*/
|
|
uint16_t Adc::readVrefRaw() { return readRaw(CONFIG_ADC_VREFINT_CHANNEL); }
|
|
|
|
/**
|
|
* @brief 读取内部温度通道原始采样值。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval uint16_t 返回内部温度通道原始值。
|
|
*/
|
|
uint16_t Adc::readTempRaw() { return readRaw(CONFIG_ADC_TEMP_CHANNEL); }
|
|
|
|
/**
|
|
* @brief 将 VIN_ADC 原始值换算为 ADC 引脚电压。
|
|
* @param[in] vinRaw VIN_ADC 原始采样值。
|
|
* @param[in] vdda 当前 ADC 供电电压。
|
|
* @param[out] 无。
|
|
* @retval float 返回 ADC 引脚电压,单位 V。
|
|
*/
|
|
float Adc::calculateVinAdcVoltage(uint16_t vinRaw, float vdda)
|
|
{
|
|
return (static_cast<float>(vinRaw) * vdda) / CONFIG_ADC_MAX_VALUE;
|
|
}
|
|
|
|
/**
|
|
* @brief 根据内部参考电压原始值换算当前 VDDA。
|
|
* @param[in] vrefRaw 内部参考电压通道原始值。
|
|
* @param[out] 无。
|
|
* @retval float 返回换算得到的 VDDA,单位 V;若原始值为 0,则返回默认值。
|
|
*/
|
|
float Adc::calculateVdda(uint16_t vrefRaw)
|
|
{
|
|
if(!isValidAdcRaw(vrefRaw)) return 0.0f;
|
|
|
|
const float vdda = (CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE * CONFIG_ADC_MAX_VALUE) /
|
|
static_cast<float>(vrefRaw);
|
|
if(!isFiniteFloat(vdda) || vdda < kMinValidVdda || vdda > kMaxValidVdda) {
|
|
return 0.0f;
|
|
}
|
|
return vdda;
|
|
}
|
|
|
|
/**
|
|
* @brief 根据温度通道原始值换算芯片温度。
|
|
* @param[in] tempRaw 温度通道原始值。
|
|
* @param[in] vdda 当前 ADC 供电电压。
|
|
* @param[out] 无。
|
|
* @retval float 返回换算得到的芯片温度,单位 ℃。
|
|
* @note 按 GD32E508 文档推荐公式处理:
|
|
* Temperature = (V25 - Vtemperature) / Avg_Slope + 25
|
|
*/
|
|
float Adc::calculateTemperature(uint16_t tempRaw, float vdda)
|
|
{
|
|
if(!isValidAdcRaw(tempRaw)) return 0.0f;
|
|
if(!isFiniteFloat(vdda) || vdda < kMinValidVdda || vdda > kMaxValidVdda) return 0.0f;
|
|
|
|
const float sensorVoltage = (static_cast<float>(tempRaw) * vdda) / CONFIG_ADC_MAX_VALUE;
|
|
const float temperature = ((CONFIG_ADC_TEMP_V25 - sensorVoltage) / CONFIG_ADC_TEMP_AVG_SLOPE) + 25.0f;
|
|
if(!isFiniteFloat(temperature) ||
|
|
temperature < kMinValidTemperature ||
|
|
temperature > kMaxValidTemperature) {
|
|
return 0.0f;
|
|
}
|
|
return temperature;
|
|
}
|
|
|
|
/**
|
|
* @brief 收集一组完整 ADC 采样结果。
|
|
* @param[in] sample 指向输出采样结构体的指针。
|
|
* @param[out] sample 输出原始值与工程值结果;允许为空。
|
|
* @retval 无返回值。
|
|
* @note 若 `sample == nullptr`,函数直接返回。
|
|
*/
|
|
void Adc::collectSample(Sample *sample)
|
|
{
|
|
if(sample == nullptr) return;
|
|
|
|
sample->vin_raw = readVinRaw();
|
|
sample->temp_raw = readTempRaw();
|
|
sample->vref_raw = readVrefRaw();
|
|
sample->vdda = calculateVdda(sample->vref_raw);
|
|
sample->vin_adc_v = calculateVinAdcVoltage(sample->vin_raw, sample->vdda);
|
|
sample->temperature = calculateTemperature(sample->temp_raw, sample->vdda);
|
|
sample->valid = (isValidAdcRaw(sample->temp_raw) &&
|
|
isValidAdcRaw(sample->vref_raw) &&
|
|
(sample->vdda >= kMinValidVdda) && (sample->vdda <= kMaxValidVdda) &&
|
|
(sample->temperature >= kMinValidTemperature) &&
|
|
(sample->temperature <= kMaxValidTemperature)) ? 1U : 0U;
|
|
}
|