feat(ADC): 重构ADC采样系统以支持同步电流采集

- 将ADC缓冲区从6通道减少到3通道,专门用于MCU温度、DRV温度和VBUS监测
- 实现了注入组ADC同步采样,通过TIMER1 TRGO触发三相电流同时采样
- 添加了PWM中心对齐模式以提供更好的采样窗口
- 在FOC控制器中增加了ADC错误检查和三相公共模偏置消除
- 优化了电流采样更新机制,使用中断驱动的数据搬运
- 调整了ADC通道配置,规则组用于后台温度/VBUS采样,注入组用于PWM同步电流采样
This commit is contained in:
cbb
2026-05-16 13:09:57 +08:00
parent 12b2577d6b
commit c6e00e1a49
4 changed files with 116 additions and 78 deletions
+14 -3
View File
@@ -17,7 +17,7 @@ extern angle_sensor KTH7823;
extern CAN_transmiter CAN0_transmiter;
extern EncoderVelocityKalman Vel_kalman; // 速度滤波
extern CurrentKalman Current_kalman;
extern u16 adc_value[6]; // ADC DMA缓冲区,用于快速电压检测
extern u16 adc_value[3]; // regular DMA缓冲区:MCU温度 / DRV温度 / VBUS
// 初始变量及函数定义
#define _constrain(amt, low, high) \
@@ -52,6 +52,17 @@ void calibrate_zero_position(void) {
// 通过Ia,Ib,Ic计算Iq,Id(目前仅输出Iq)
inline float FOC_Controller::cal_Iq_Id(float current_a, float current_b,
float current_c) {
if (current_a == ERROR_ADC_VAL || current_b == ERROR_ADC_VAL ||
current_c == ERROR_ADC_VAL) {
return Current_kalman.getCurrent();
}
// 去除三相公共模偏置,使 Ia + Ib + Ic 更接近 0
const float current_avg = (current_a + current_b + current_c) / 3.0f;
current_a -= current_avg;
current_b -= current_avg;
current_c -= current_avg;
// 计算电气角度
float angle_el = fmod(shaft_angle * PP - KTH7823.calibration_angle, 360.0f);
@@ -351,8 +362,8 @@ void FOC_Controller::update_power(float voltage) {
* - VBUS_DEBOUNCE_COUNT: 防抖计数 (默认5次)
*/
void FOC_Controller::fast_voltage_check(void) {
// 直接从DMA缓冲区读取最新ADC值(通道5是Vbus)
float current_vbus = adc_value[5] * (3.3f / 4096.0f) * 11.0f;
// 直接从DMA缓冲区读取最新ADC值(regular rank2 是 VBUS)
float current_vbus = adc_value[2] * (3.3f / 4096.0f) * 11.0f;
// 更新基准电压(峰值检测)
// 电压上升时,基准电压跟随;电压下跌时,基准电压保持
+81 -53
View File
@@ -3,6 +3,7 @@
#include "FOC.h"
#include "Filter.h"
#include "config.h"
#include "initial.h"
#include "math.h"
#include "systick.h"
#include <cstdint>
@@ -17,7 +18,14 @@ constexpr float CURRENT_FACTOR =
extern CAN_transmiter CAN0_transmiter;
extern FOC_Controller FOC;
sampler sampler; // 采样器初始化
u16 adc_value[6] = {0}; // 显式初始化数组元素为0
u16 adc_value[3] = {0}; // regular DMA: MCU温度 / DRV温度 / VBUS
static volatile u16 adc_current_value[3] = {0}; // injected 同步采样: U / V / W
static volatile u8 adc_current_frame_ready = 0;
static inline float current_adc_to_ma(u16 raw_adc) {
return (raw_adc * ADC_SCALE + CURRENT_OFFSET) * CURRENT_FACTOR;
}
/*!
\brief configure the DMA peripheral
\param[in] none
@@ -41,7 +49,7 @@ void dma_config(void) {
dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_data_parameter.direction = DMA_PERIPHERAL_TO_MEMORY;
dma_data_parameter.number = 6;
dma_data_parameter.number = 3;
dma_data_parameter.priority = DMA_PRIORITY_HIGH;
dma_init(DMA0, DMA_CH0, &dma_data_parameter);
@@ -67,38 +75,48 @@ void adc_config(void) {
adc_deinit(ADC0);
/* ADC模式配置 */
adc_mode_config(ADC_MODE_FREE);
/* 使能ADC连续转换功能 */
/* regular 组保留连续扫描,用于温度/VBUS 后台采样 */
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
/* 使能ADC扫描模式 */
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
/* injected 组用于 PWM 同步电流采样,关闭自动串联,独立由 TIMER1 触发 */
adc_special_function_config(ADC0, ADC_INSERTED_CHANNEL_AUTO, DISABLE);
/* ADC数据右对齐配置 */
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
/* 使能ADC温度传感器和内部参考电压 */
adc_tempsensor_vrefint_enable();
/* 配置ADC通道长度 */
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 6);
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 3);
/* 配置ADC规则通道 */
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_16,
ADC_SAMPLETIME_41POINT5);
adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_9, ADC_SAMPLETIME_28POINT5);
adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_3, ADC_SAMPLETIME_28POINT5);
adc_regular_channel_config(ADC0, 3, ADC_CHANNEL_4, ADC_SAMPLETIME_28POINT5);
adc_regular_channel_config(ADC0, 4, ADC_CHANNEL_5, ADC_SAMPLETIME_28POINT5);
adc_regular_channel_config(ADC0, 5, ADC_CHANNEL_6, ADC_SAMPLETIME_28POINT5);
adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_6, ADC_SAMPLETIME_28POINT5);
/* ADC触发配置 */
/* regular 组继续软件触发连续转换:本组只服务低速量 */
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL,
ADC0_1_EXTTRIG_REGULAR_NONE);
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
/* injected 组改为 TIMER1 CH3 导出的 TRGO 触发,实现三相同一采样时刻锁存 */
adc_channel_length_config(ADC0, ADC_INSERTED_CHANNEL, 4);
adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_3, ADC_SAMPLETIME_55POINT5);
adc_inserted_channel_config(ADC0, 1, ADC_CHANNEL_3, ADC_SAMPLETIME_55POINT5);
adc_inserted_channel_config(ADC0, 2, ADC_CHANNEL_4, ADC_SAMPLETIME_55POINT5);
adc_inserted_channel_config(ADC0, 3, ADC_CHANNEL_5, ADC_SAMPLETIME_55POINT5);
adc_external_trigger_source_config(ADC0, ADC_INSERTED_CHANNEL,
ADC0_1_EXTTRIG_INSERTED_T1_TRGO);
adc_external_trigger_config(ADC0, ADC_INSERTED_CHANNEL, ENABLE);
/* 使能ADC接口 */
adc_enable(ADC0);
/* ADC校准 */
adc_calibration_enable(ADC0);
/* 使能ADC DMA功能 */
adc_dma_mode_enable(ADC0);
/* 使能ADC软件触发 */
/* injected 完成中断负责搬运完整三相样本 */
adc_interrupt_enable(ADC0, ADC_INT_EOIC);
/* 先启动 regular 组连续后台采样 */
adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
}
/**
@@ -107,20 +125,22 @@ void adc_config(void) {
u8 sampler::init() {
dma_config(); // DMA采样ADC初始化
adc_config(); // ADC初始化
current_sample_head = 0;
adc_current_frame_ready = 0;
delay_1ms(1);
const float current_init = current_adc_to_ma(
static_cast<u16>((-CURRENT_OFFSET / ADC_SCALE) + 0.5f));
// 初始采样
for (int i = 0; i < ADC_FILTER_SIZE + 1; i++) {
for (int i = 0; i < ADC_FILTER_SIZE; i++) {
mcu_temp_samples[i] =
(1.43f - adc_value[0] * ADC_SCALE) * TEMP_FACTOR + 35.0f;
currU_samples[i] =
(adc_value[2] * ADC_SCALE + CURRENT_OFFSET) * CURRENT_FACTOR;
currV_samples[i] =
(adc_value[3] * ADC_SCALE + CURRENT_OFFSET) * CURRENT_FACTOR;
currW_samples[i] =
(adc_value[4] * ADC_SCALE + CURRENT_OFFSET) * CURRENT_FACTOR;
currU_samples[i] = current_init;
currV_samples[i] = current_init;
currW_samples[i] = current_init;
vbus_samples[i] = adc_value[5] * ADC_SCALE * VCC_FACTOR;
vbus_samples[i] = adc_value[2] * ADC_SCALE * VCC_FACTOR;
float R_now = (float)adc_value[1] / (4096 - (float)adc_value[1]) * 4700;
drv_temp_samples[i] =
@@ -135,7 +155,7 @@ u8 sampler::init() {
* 温度值,单位为摄氏度,范围应在-40到145之间,如果读取失败则返回0.0f
*/
float sampler::get_mcu_temp() {
tempora_sample = medium_filter(mcu_temp_samples, ADC_FILTER_SIZE + 1);
tempora_sample = medium_filter(mcu_temp_samples, ADC_FILTER_SIZE);
// 检查温度读数是否在有效范围内
if (tempora_sample < MIN_TEMP or tempora_sample > MAX_TEMP) {
return ERROR_ADC_VAL;
@@ -151,7 +171,7 @@ float sampler::get_mcu_temp() {
* 如果计算结果超出有效范围(-40到255摄氏度),则重新采样计算。
*/
float sampler::get_drv_temp() {
tempora_sample = medium_filter(drv_temp_samples, ADC_FILTER_SIZE + 1);
tempora_sample = medium_filter(drv_temp_samples, ADC_FILTER_SIZE);
// 检查温度读数是否在有效范围内
if (tempora_sample < MIN_TEMP || tempora_sample > MAX_TEMP) {
return ERROR_ADC_VAL;
@@ -167,24 +187,18 @@ float sampler::get_drv_temp() {
* 通过ADC采样计算各相电流值
*/
float sampler::get_current(char type) {
u8 idx = current_sample_index;
u8 idx = (current_sample_head == 0U) ? (ADC_FILTER_SIZE - 1U)
: (current_sample_head - 1U);
switch (type) {
case 'U': {
// return currU_samples[current_sample_index];
// tempora_sample = medium_filter(currU_samples, ADC_FILTER_SIZE + 1);
tempora_sample = currU_samples[idx];
break;
}
case 'V': {
// return currV_samples[current_sample_index];
// tempora_sample = medium_filter(currV_samples, ADC_FILTER_SIZE + 1);
tempora_sample = currV_samples[idx];
break;
}
case 'W': {
// return currW_samples[current_sample_index];
// tempora_sample = medium_filter(currW_samples, ADC_FILTER_SIZE + 1);
tempora_sample = currW_samples[idx];
break;
}
@@ -207,10 +221,10 @@ float sampler::get_current(char type) {
* 如果计算结果超出有效范围(-40到255伏特),则重新采样计算。
*/
float sampler::get_vbus_voltage() {
tempora_sample = medium_filter(vbus_samples, ADC_FILTER_SIZE + 1);
tempora_sample = medium_filter(vbus_samples, ADC_FILTER_SIZE);
// 检查电压读数是否在有效范围内
if (vbus_samples[ADC_FILTER_SIZE / 2] > MAX_VCC or
vbus_samples[ADC_FILTER_SIZE / 2] < MIN_VCC) {
const u8 mid = ADC_FILTER_SIZE / 2;
if (vbus_samples[mid] > MAX_VCC or vbus_samples[mid] < MIN_VCC) {
return ERROR_ADC_VAL;
}
return tempora_sample;
@@ -224,8 +238,8 @@ float sampler::get_vbus_voltage() {
*/
void sampler::update_mcu_temp() {
memmove(mcu_temp_samples, mcu_temp_samples + 1,
ADC_FILTER_SIZE * sizeof(float));
mcu_temp_samples[ADC_FILTER_SIZE] =
(ADC_FILTER_SIZE - 1) * sizeof(float));
mcu_temp_samples[ADC_FILTER_SIZE - 1] =
(1.43f - adc_value[0] * ADC_SCALE) * TEMP_FACTOR + 35.0f;
}
@@ -234,9 +248,9 @@ void sampler::update_mcu_temp() {
*/
void sampler::update_drv_temp() {
memmove(drv_temp_samples, drv_temp_samples + 1,
ADC_FILTER_SIZE * sizeof(float));
(ADC_FILTER_SIZE - 1) * sizeof(float));
float R_now = (float)adc_value[1] / (4096 - (float)adc_value[1]) * 4700;
drv_temp_samples[ADC_FILTER_SIZE] =
drv_temp_samples[ADC_FILTER_SIZE - 1] =
1.0f / (0.00335f + 0.00025f * log(R_now / 10000.0f)) - 273.15f;
}
@@ -244,27 +258,41 @@ void sampler::update_drv_temp() {
* @brief 更新相电流
*/
void sampler::update_current() {
u8 idx = current_sample_index;
extern FOC_Controller FOC;
// 优化计算
const float scale = ADC_SCALE * CURRENT_FACTOR;
const float offset = CURRENT_OFFSET * CURRENT_FACTOR;
currU_samples[idx] = adc_value[2] * scale + offset;
currV_samples[idx] = adc_value[3] * scale + offset;
currW_samples[idx] = adc_value[4] * scale + offset;
// 更新索引
current_sample_index++;
if (current_sample_index >= ADC_FILTER_SIZE - 1) {
current_sample_index = 0;
if (adc_current_frame_ready == 0U) {
return;
}
const u8 idx = current_sample_head;
currU_samples[idx] = current_adc_to_ma(adc_current_value[0]);
currV_samples[idx] = current_adc_to_ma(adc_current_value[1]);
currW_samples[idx] = current_adc_to_ma(adc_current_value[2]);
current_sample_head++;
if (current_sample_head >= ADC_FILTER_SIZE) {
current_sample_head = 0;
}
adc_current_frame_ready = 0U;
}
/**
* @brief 更新供电电压
*/
void sampler::update_vbus_voltage() {
memmove(vbus_samples, vbus_samples + 1, ADC_FILTER_SIZE * sizeof(float));
vbus_samples[ADC_FILTER_SIZE] = adc_value[5] * ADC_SCALE * VCC_FACTOR;
}
memmove(vbus_samples, vbus_samples + 1, (ADC_FILTER_SIZE - 1) * sizeof(float));
vbus_samples[ADC_FILTER_SIZE - 1] = adc_value[2] * ADC_SCALE * VCC_FACTOR;
}
extern "C" void ADC0_1_IRQHandler(void) {
if (adc_interrupt_flag_get(ADC0, ADC_INT_FLAG_EOIC) != RESET) {
/* 丢弃 rank0 dummy,真实样本使用 rank1/2/3,对应 U/V/W 同一 PWM 时刻 */
(void)adc_inserted_data_read(ADC0, ADC_INSERTED_CHANNEL_0);
adc_current_value[0] = adc_inserted_data_read(ADC0, ADC_INSERTED_CHANNEL_1);
adc_current_value[1] = adc_inserted_data_read(ADC0, ADC_INSERTED_CHANNEL_2);
adc_current_value[2] = adc_inserted_data_read(ADC0, ADC_INSERTED_CHANNEL_3);
adc_current_frame_ready = 1U;
adc_interrupt_flag_clear(ADC0, ADC_INT_FLAG_EOIC);
extern TaskScheduler scheduler;
scheduler.taskFlags |= (1u << TASK_CURRENT_UPDATE);
}
}
+12 -15
View File
@@ -10,7 +10,7 @@
#include "initial.h"
volatile u32 total_us100_counter = 0; // 总计数器,每 100 微秒增加 1
extern u16 adc_value[7];
extern u16 adc_value[3];
extern FOC_Controller FOC;
extern TaskScheduler scheduler;
extern CAN_transmiter CAN0_transmiter;
@@ -59,6 +59,7 @@ void nvic_config(void) {
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
/* 使能 TIMER2 中断,设置抢占优先级为 1,子优先级为 1 */
nvic_irq_enable(TIMER2_IRQn, 0, 1);
nvic_irq_enable(ADC0_1_IRQn, 0, 0);
nvic_irq_enable(TIMER1_IRQn, 0, 2);
}
@@ -216,7 +217,8 @@ void timer1_pwm_config(void) {
// 配置 TIMER1 基本参数
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler = TIMER1_PRESCALER; // 预分频器
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timer_initpara.alignedmode = TIMER_COUNTER_CENTER_BOTH; // 中心对齐,给采样留对称平台窗
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = TIMER1_PERIOD; // 计数周期
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_init(TIMER1, &timer_initpara);
@@ -231,9 +233,9 @@ void timer1_pwm_config(void) {
timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocinitpara);
timer_channel_output_config(TIMER1, TIMER_CH_2, &timer_ocinitpara);
// 配置第四个通道 (CH3) 用于在 PWM 周期 x% 位置触发中断
// 配置第四个通道 (CH3) 为固定采样点,只用于导出 ADC 触发
timer_ocinitpara.outputstate =
TIMER_CCX_DISABLE; // 不输出 PWM 波形,仅用于触发中断
TIMER_CCX_DISABLE; // 不输出 PWM 波形,仅用于比较事件
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_channel_output_config(TIMER1, TIMER_CH_3, &timer_ocinitpara);
@@ -244,13 +246,16 @@ void timer1_pwm_config(void) {
timer_channel_output_mode_config(TIMER1, TIMER_CH_3,
TIMER_OC_MODE_PWM0); // CH3 也设为 PWM 模式
/* 将 CH3 预装载比较点作为 TRGO 导出,供 ADC injected 组同步采样 */
timer_master_output_trigger_source_select(TIMER1, TIMER_TRI_OUT_SRC_O3CPRE);
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_0,
TIMER1_DEFAULT_PULSE);
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1,
TIMER1_DEFAULT_PULSE);
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_2,
TIMER1_DEFAULT_PULSE);
// 设置 CH3 比较值为 PWM 周期 x% 位置
// 设置 CH3 为固定 ADC 采样点,避免与三相 PWM duty 耦合
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_3,
TIMER1_CH3_COMPARE_VALUE);
@@ -258,26 +263,18 @@ void timer1_pwm_config(void) {
timer_primary_output_config(TIMER1, ENABLE);
timer_auto_reload_shadow_enable(TIMER1);
// 使能 CH3 比较中断
timer_interrupt_enable(TIMER1, TIMER_INT_CH3);
// 启动 TIMER1
timer_enable(TIMER1);
}
// 添加 TIMER1-PWM 中断处理函数
// CH3 现在仅作为 TRGO 采样点保留;采样完成由 ADC injected 中断接管
/**
* @brief TIMER1 中断处理函数
* @return void
*/
void TIMER1_IRQHandler(void) {
// 检查 CH3 比较中断标志并处理
if (timer_interrupt_flag_get(TIMER1, TIMER_INT_FLAG_CH3) != RESET) {
// 清除 CH3 比较中断标志
timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_CH3);
scheduler.taskFlags |= (1 << TASK_CURRENT_UPDATE);
// 例如触发 ADC 采样或其他操作
}
}
@@ -313,4 +310,4 @@ void Set_3Pwm(float duty_cycle0, float duty_cycle1, float duty_cycle2) {
TIMER_CH0CV(TIMER1) = (uint32_t)(duty_cycle0 * TIMER1_PERIOD);
TIMER_CH1CV(TIMER1) = (uint32_t)(duty_cycle1 * TIMER1_PERIOD);
TIMER_CH2CV(TIMER1) = (uint32_t)(duty_cycle2 * TIMER1_PERIOD);
}
}
+9 -7
View File
@@ -9,13 +9,13 @@ extern "C" {
class sampler {
private:
float vbus_samples[ADC_FILTER_SIZE + 1];
float currU_samples[ADC_FILTER_SIZE + 1];
float currV_samples[ADC_FILTER_SIZE + 1];
float currW_samples[ADC_FILTER_SIZE + 1];
float mcu_temp_samples[ADC_FILTER_SIZE + 1];
float drv_temp_samples[ADC_FILTER_SIZE + 1];
u8 current_sample_index;
float vbus_samples[ADC_FILTER_SIZE];
float currU_samples[ADC_FILTER_SIZE];
float currV_samples[ADC_FILTER_SIZE];
float currW_samples[ADC_FILTER_SIZE];
float mcu_temp_samples[ADC_FILTER_SIZE];
float drv_temp_samples[ADC_FILTER_SIZE];
u8 current_sample_head;
float tempora_sample;
@@ -32,6 +32,8 @@ class sampler {
float get_current(char);
};
void ADC0_1_IRQHandler(void);
#ifdef __cplusplus
}
#endif