AM32开源代码之代码分析 - ADC
1. 源由
Sensorless control 主要采用了 Back-EMF 方式采样,通过 Sinusoidal 方式对电机进行驱动。
除此之外,还有几个非常重要的参数在飞行日志分析和性能分析的时候非常重要:
- 电压值
- 电流值
- 温度
上述内容最重要的一点就是ADC的数据采样。接下来一起研读下ADC方面的采样代码。
2. 框架设计
uint16_t ADC_raw_temp;
uint16_t ADC_raw_volts;
uint16_t ADC_raw_current;
uint16_t ADC_raw_input;
2.1 初始化
main
├──> initCorePeripherals
│ └──> MX_COMP2_Init
└──> enableCorePeripherals
└──> <USE_ADC>
├──> ADC_Init
├──> enableADC_DMA
└──> activateADC
2.2 ADC采样
2.2.1 温度
main
└──> <LOOP> <adc_counter > 200>
├──> ADC_DMA_Callback()
├──> LL_ADC_REG_StartConversion(ADC1)
├──> converted_degrees = __LL_ADC_CALC_TEMPERATURE(3300, ADC_raw_temp, LL_ADC_RESOLUTION_12B)
└──> degrees_celsius = converted_degrees
注:无滤波处理。
2.2.2 电压
main
└──> <LOOP> <adc_counter > 200>
├──> ADC_DMA_Callback()
├──> LL_ADC_REG_StartConversion(ADC1)
└──> battery_voltage = ((7 * battery_voltage) + ((ADC_raw_volts * 3300 / 4095 * VOLTAGE_DIVIDER) / 100)) >> 3
低通滤波器的表达式通常表示为一种递归方程,它将当前输入信号与前一个输出信号结合,以平滑高频成分。对于离散信号,低通滤波器的一般形式可以表示为:
y [ n ] = α ⋅ x [ n ] + ( 1 − α ) ⋅ y [ n − 1 ] y[n] = \alpha \cdot x[n] + (1 - \alpha) \cdot y[n-1] y[n]=α⋅x[n]+(1−α)⋅y[n−1]
其中:
-
y [ n ] y[n] y[n] 是当前时刻 n n n 的滤波后输出信号。
-
x [ n ] x[n] x[n] 是当前时刻 n n n 的输入信号。
-
y [ n − 1 ] y[n-1] y[n−1] 是前一时刻 n − 1 n-1 n−1 的滤波后输出信号。
-
α \alpha α 是滤波系数,取值范围为 0 ≤ α ≤ 1 0 \leq \alpha \leq 1 0≤α≤1,用于控制新输入信号和先前输出信号的权重比例。
-
当 α \alpha α 较大时,滤波器对新输入信号的响应更快,输出信号的变化也更快。
-
当 α \alpha α 较小时,滤波器对先前输出信号的权重更大,输出信号的变化更缓慢,这样可以更有效地平滑噪声。
battery_voltage = 7 ⋅ battery_voltage + new_voltage_reading 8 \text{battery\_voltage} = \frac{7 \cdot \text{battery\_voltage} + \text{new\_voltage\_reading}}{8} battery_voltage=87⋅battery_voltage+new_voltage_reading
battery_voltage
对应 y [ n ] y[n] y[n] 和 y [ n − 1 ] y[n-1] y[n−1]。new_voltage_reading
对应 x [ n ] x[n] x[n]。- 权重 α \alpha α 大约为 1 8 \frac{1}{8} 81,即 0.125。
2.2.3 电流
main
└──> <LOOP> <adc_counter > 200>
├──> ADC_DMA_Callback()
├──> LL_ADC_REG_StartConversion(ADC1)
├──> smoothed_raw_current = getSmoothedCurrent()
└──> actual_current = ((smoothed_raw_current * 3300 / 41) - (CURRENT_OFFSET * 100)) / (MILLIVOLT_PER_AMP)
uint16_t getSmoothedCurrent()
{
total = total - readings[readIndex];
readings[readIndex] = ADC_raw_current;
total = total + readings[readIndex];
readIndex = readIndex + 1;
if (readIndex >= numReadings) {
readIndex = 0;
}
smoothedcurrent = total / numReadings;
return smoothedcurrent;
}
函数 getSmoothedCurrent()
实现了一种简单的移动平均滤波器,这也是一种低通滤波器。
readings[]
:存储最近的电流读数数组。readIndex
:当前要更新的数组索引。numReadings
:数组的大小,即移动平均滤波器的窗口大小。total
:最近numReadings
个电流读数的总和。smoothedcurrent
:平滑后的电流值。
这个滤波器使用了窗口大小为 numReadings
的简单移动平均法,因此它可以表示为以下形式的低通滤波器:
y [ n ] = 1 N ∑ k = 0 N − 1 x [ n − k ] y[n] = \frac{1}{N} \sum_{k=0}^{N-1} x[n-k] y[n]=N1∑k=0N−1x[n−k]
其中:
-
y
[
n
]
y[n]
y[n] 是第
n
n
n 个采样点的滤波后输出(即
smoothedcurrent
)。 -
x
[
n
−
k
]
x[n-k]
x[n−k] 是第
n
−
k
n-k
n−k 个采样点的原始输入(即
readings[k]
)。 -
N
N
N 是窗口大小(即
numReadings
)。
-
更新总和
total
:total
首先减去旧的读数readings[readIndex]
,然后加上新的读数ADC_raw_current
,以保持最近的numReadings
个读数的总和。
-
更新数组
readings[]
:- 更新当前索引处的读数为新的 ADC 读数,然后更新索引。
-
计算平滑后的电流值
smoothedcurrent
:- 平滑后的电流值通过将
total
除以numReadings
得到。
- 平滑后的电流值通过将
上述函数用移动平均滤波器表示,实际上是一个具有固定窗口大小
N
N
N 的低通滤波器,通过平均最近的 N
个读数来平滑信号。这种方法简单且有效地减少了信号中的高频噪声。
对应的低通滤波表达式为:
smoothedcurrent [ n ] = 1 N ∑ k = 0 N − 1 readings [ n − k ] \text{smoothedcurrent}[n] = \frac{1}{N} \sum_{k=0}^{N-1} \text{readings}[n-k] smoothedcurrent[n]=N1∑k=0N−1readings[n−k]
N
即为numReadings
- readings [ n − k ] \text{readings}[n-k] readings[n−k] 表示最近的 ADC 读数。
2.2.4 Gimbal input
main
└──> <LOOP> <adc_counter > 200>
├──> ADC_DMA_Callback()
├──> LL_ADC_REG_StartConversion(ADC1)
├──> smoothed_raw_current = getSmoothedCurrent()
└──> ADC_smoothed_input = (((10 * ADC_smoothed_input) + ADC_raw_input) / 11)
这是一种指数加权移动平均(EWMA)滤波器。
ADC_smoothed_input
:滤波后的ADC输入信号,这是输出信号,也是前一次滤波结果。ADC_raw_input
:当前时刻的原始ADC输入信号。10 * ADC_smoothed_input
:前一次滤波结果的权重为10。ADC_raw_input
:当前ADC输入信号的权重为1。/ 11
:这个操作将加权的总和除以11,完成了滤波。
表达式可以用递归公式表示为:
y [ n ] = 10 11 ⋅ y [ n − 1 ] + 1 11 ⋅ x [ n ] y[n] = \frac{10}{11} \cdot y[n-1] + \frac{1}{11} \cdot x[n] y[n]=1110⋅y[n−1]+111⋅x[n]
其中:
-
y [ n ] y[n] y[n] 是当前时刻 n n n 的滤波后输出(即
ADC_smoothed_input
)。 -
y [ n − 1 ] y[n-1] y[n−1] 是前一时刻 n − 1 n-1 n−1 的滤波后输出(即之前的
ADC_smoothed_input
)。 -
x [ n ] x[n] x[n] 是当前时刻 n n n 的输入信号(即
ADC_raw_input
)。 -
10 11 \frac{10}{11} 1110 是滤波器对前一次输出信号的权重系数。
-
1 11 \frac{1}{11} 111 是滤波器对当前输入信号的权重系数。
-
低通滤波:这一表达式给前一次输出信号较大的权重((\frac{10}{11})),给当前输入信号较小的权重((\frac{1}{11})),因此它平滑了输入信号中的高频噪声,使得输出信号变化更缓慢和稳定。
-
滤波器特性:通过调整这两个权重系数(即
10
和1
的比例),可以控制滤波器的平滑程度。较大的前一项系数意味着滤波器响应较慢,更倾向于保持过去的信号趋势,而较大的当前项系数则使滤波器更敏感于新输入信号。
这个表达式实现的低通滤波器是一种递归滤波器,也就是指数加权移动平均(EWMA)滤波器,它在当前输入和前一输出之间进行加权平均,用以平滑信号,减少噪声影响。
对应的低通滤波公式为:
y [ n ] = α ⋅ y [ n − 1 ] + ( 1 − α ) ⋅ x [ n ] y[n] = \alpha \cdot y[n-1] + (1 - \alpha) \cdot x[n] y[n]=α⋅y[n−1]+(1−α)⋅x[n]
其中:
- (\alpha = \frac{10}{11}) 对应
10/11
。 -
(
1
−
α
)
=
1
11
(1 - \alpha) = \frac{1}{11}
(1−α)=111 对应
1/11
。
2.3 相关函数
2.3.1 ADC_DMA_Callback
直接从DMA内存中获取ADC采样值。
void ADC_DMA_Callback()
{ // read dma buffer and set extern variables
#ifdef USE_ADC_INPUT
ADC_raw_temp = ADCDataDMA[3];
ADC_raw_volts = ADCDataDMA[1] / 2;
ADC_raw_current = ADCDataDMA[2];
ADC_raw_input = ADCDataDMA[0];
#else
ADC_raw_temp = ADCDataDMA[2];
ADC_raw_volts = ADCDataDMA[1];
ADC_raw_current = ADCDataDMA[0];
#endif
}
2.3.2 __LL_ADC_CALC_TEMPERATURE
ADC之转换温度修正函数(芯片内部函数)。
实际情况比较复杂,需要比较多的技术手段来比较准确的给出实际ESC设备的温度极值。
- 通过红外热成像可以找到高温点位
- 采用热分析手段(传导、辐射)合理安排NTC外置温度传感器位置
- 合理的PCB Layout布局,通过数据采集&计算,分析,科学拟合温度曲线
/**
* @brief Helper macro to calculate the temperature (unit: degree Celsius)
* from ADC conversion data of internal temperature sensor.
* @note Computation is using temperature sensor calibration values
* stored in system memory for each device during production.
* @note Calculation formula:
* Temperature = ((TS_ADC_DATA - TS_CAL1)
* * (TS_CAL2_TEMP - TS_CAL1_TEMP))
* / (TS_CAL2 - TS_CAL1) + TS_CAL1_TEMP
* with TS_ADC_DATA = temperature sensor raw data measured by ADC
* Avg_Slope = (TS_CAL2 - TS_CAL1)
* / (TS_CAL2_TEMP - TS_CAL1_TEMP)
* TS_CAL1 = equivalent TS_ADC_DATA at temperature
* TEMP_DEGC_CAL1 (calibrated in factory)
* TS_CAL2 = equivalent TS_ADC_DATA at temperature
* TEMP_DEGC_CAL2 (calibrated in factory)
* Caution: Calculation relevancy under reserve that calibration
* parameters are correct (address and data).
* To calculate temperature using temperature sensor
* datasheet typical values (generic values less, therefore
* less accurate than calibrated values),
* use helper macro @ref __LL_ADC_CALC_TEMPERATURE_TYP_PARAMS().
* @note As calculation input, the analog reference voltage (Vref+) must be
* defined as it impacts the ADC LSB equivalent voltage.
* @note Analog reference voltage (Vref+) must be either known from
* user board environment or can be calculated using ADC measurement
* and ADC helper macro @ref __LL_ADC_CALC_VREFANALOG_VOLTAGE().
* @note On this STM32 serie, calibration data of temperature sensor
* corresponds to a resolution of 12 bits,
* this is the recommended ADC resolution to convert voltage of
* temperature sensor.
* Otherwise, this macro performs the processing to scale
* ADC conversion data to 12 bits.
* @param __VREFANALOG_VOLTAGE__ Analog reference voltage (unit: mV)
* @param __TEMPSENSOR_ADC_DATA__ ADC conversion data of internal
* temperature sensor (unit: digital value).
* @param __ADC_RESOLUTION__ ADC resolution at which internal temperature
* sensor voltage has been measured.
* This parameter can be one of the following values:
* @arg @ref LL_ADC_RESOLUTION_12B
* @arg @ref LL_ADC_RESOLUTION_10B
* @arg @ref LL_ADC_RESOLUTION_8B
* @arg @ref LL_ADC_RESOLUTION_6B
* @retval Temperature (unit: degree Celsius)
*/
#define __LL_ADC_CALC_TEMPERATURE(__VREFANALOG_VOLTAGE__,\
__TEMPSENSOR_ADC_DATA__,\
__ADC_RESOLUTION__) \
(((( ((int32_t)((__LL_ADC_CONVERT_DATA_RESOLUTION((__TEMPSENSOR_ADC_DATA__), \
(__ADC_RESOLUTION__), \
LL_ADC_RESOLUTION_12B) \
* (__VREFANALOG_VOLTAGE__)) \
/ TEMPSENSOR_CAL_VREFANALOG) \
- (int32_t) *TEMPSENSOR_CAL1_ADDR) \
) * (int32_t)(TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP) \
) / (int32_t)((int32_t)*TEMPSENSOR_CAL2_ADDR - (int32_t)*TEMPSENSOR_CAL1_ADDR) \
) + TEMPSENSOR_CAL1_TEMP \
)
2.3.3 MX_COMP2_Init
MX_COMP2_Init()
|
├── 1. 初始化阶段
│ ├── 1.1. 定义初始化结构体
│ │ ├── LL_COMP_InitTypeDef COMP_InitStruct = { 0 };
│ │ └── LL_GPIO_InitTypeDef GPIO_InitStruct = { 0 };
│ └── 1.2. 启用 GPIO 时钟
│ ├── LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
│ └── LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
|
├── 2. GPIO 配置
│ ├── 2.1. 配置 PA2 为 COMP2_INM
│ │ ├── GPIO_InitStruct.Pin = LL_GPIO_PIN_2;
│ │ ├── GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
│ │ └── GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
│ │ └── LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
│ ├── 2.2. 配置 PA3 为 COMP2_INP
│ │ ├── GPIO_InitStruct.Pin = LL_GPIO_PIN_3;
│ │ ├── GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
│ │ └── GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
│ │ └── LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
│ ├── 2.3. 在非 `N_VARIANT` 模式下配置 PB3
│ │ ├── GPIO_InitStruct.Pin = LL_GPIO_PIN_3;
│ │ ├── GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
│ │ └── GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
│ │ └── LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
│ └── 2.4. 配置 PB7 为模拟模式
│ ├── GPIO_InitStruct.Pin = LL_GPIO_PIN_7;
│ ├── GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
│ └── GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
│ └── LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
├── 3. 启用 SYSCFG 时钟
│ └── LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
|
├── 4. 配置 COMP2
│ ├── 4.1. 输入配置
│ │ ├── COMP_InitStruct.InputPlus = LL_COMP_INPUT_PLUS_IO3;
│ │ └── COMP_InitStruct.InputMinus = LL_COMP_INPUT_MINUS_IO3;
│ ├── 4.2. 输入滞后设置
│ │ └── COMP_InitStruct.InputHysteresis = LL_COMP_HYSTERESIS_NONE;
│ ├── 4.3. 输出极性设置
│ │ └── COMP_InitStruct.OutputPolarity = LL_COMP_OUTPUTPOL_NONINVERTED;
│ ├── 4.4. 输出消隐源设置
│ │ └── COMP_InitStruct.OutputBlankingSource = LL_COMP_BLANKINGSRC_TIM1_OC5;
│ ├── 4.5. 初始化 COMP2
│ │ └── LL_COMP_Init(COMP2, &COMP_InitStruct);
│ ├── 4.6. 设置功耗模式
│ │ └── LL_COMP_SetPowerMode(COMP2, LL_COMP_POWERMODE_HIGHSPEED);
│ ├── 4.7. 设置通用窗口模式
│ │ └── LL_COMP_SetCommonWindowMode(__LL_COMP_COMMON_INSTANCE(COMP2), LL_COMP_WINDOWMODE_DISABLE);
│ └── 4.8. 设置通用窗口输出
│ └── LL_COMP_SetCommonWindowOutput(__LL_COMP_COMMON_INSTANCE(COMP2), LL_COMP_WINDOWOUTPUT_EACH_COMP);
|
├── 5. 等待电压标度器稳定
│ ├── 5.1. 计算等待循环次数
│ │ └── wait_loop_index = (LL_COMP_DELAY_VOLTAGE_SCALER_STAB_US * (SystemCoreClock / (1000000 * 2)));
│ └── 5.2. 执行等待循环
│ └── while (wait_loop_index != 0) { wait_loop_index--; }
|
├── 6. 禁用 EXTI 事件和中断
│ ├── LL_EXTI_DisableEvent_0_31(LL_EXTI_LINE_18);
│ └── LL_EXTI_DisableIT_0_31(LL_EXTI_LINE_18);
|
└── 7. 配置和启用中断
├── 7.1. 设置中断优先级
│ └── NVIC_SetPriority(ADC1_COMP_IRQn, 0);
└── 7.2. 启用中断
└── NVIC_EnableIRQ(ADC1_COMP_IRQn);
2.3.4 ADC_Init
ADC_Init()
│
├── 初始化结构体
│ ├── LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0}
│ └── LL_ADC_InitTypeDef ADC_InitStruct = {0}
│
├── 启用时钟
│ ├── LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC) // 启用 ADC 时钟
│ └── LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA) // 启用 GPIOA 时钟
│
├── GPIO 配置
│ ├── VOLTAGE_ADC_PIN 配置
│ │ ├── GPIO_InitStruct.Pin = VOLTAGE_ADC_PIN
│ │ ├── GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG
│ │ └── GPIO_InitStruct.Pull = LL_GPIO_PULL_NO
│ └── CURRENT_ADC_PIN 配置
│ ├── GPIO_InitStruct.Pin = CURRENT_ADC_PIN
│ ├── GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG
│ └── GPIO_InitStruct.Pull = LL_GPIO_PULL_NO
│
├── DMA 配置
│ ├── LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_2, LL_DMAMUX_REQ_ADC1)
│ ├── LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_2, LL_DMA_DIRECTION_PERIPH_TO_MEMORY)
│ ├── LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PRIORITY_HIGH)
│ ├── LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MODE_CIRCULAR)
│ ├── LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PERIPH_NOINCREMENT)
│ ├── LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MEMORY_INCREMENT)
│ ├── LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PDATAALIGN_WORD)
│ └── LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MDATAALIGN_HALFWORD)
│
└── ADC 配置
├── 内部通道配置
│ └── LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_CHANNEL_TEMPSENSOR)
│
├── 全局配置
│ ├── ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE
│ ├── ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_3RANKS
│ ├── ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE
│ ├── ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE
│ ├── ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_LIMITED
│ └── ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED
│
├── 采样和触发设置
│ ├── LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE)
│ ├── LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_LOW)
│ ├── LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE)
│ ├── LL_ADC_SetClock(ADC1, LL_ADC_CLOCK_ASYNC_DIV4)
│ ├── LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_19CYCLES_5)
│ ├── LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_2, LL_ADC_SAMPLINGTIME_160CYCLES_5)
│ ├── LL_ADC_DisableIT_EOC(ADC1)
│ └── LL_ADC_DisableIT_EOS(ADC1)
│
├── 分辨率和对齐
│ ├── ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B
│ ├── ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT
│ └── ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE
│
└── 配置通道
├── LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, CURRENT_ADC_CHANNEL)
├── LL_ADC_SetChannelSamplingTime(ADC1, CURRENT_ADC_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1)
├── LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, VOLTAGE_ADC_CHANNEL)
├── LL_ADC_SetChannelSamplingTime(ADC1, VOLTAGE_ADC_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1)
├── LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_3, LL_ADC_CHANNEL_TEMPSENSOR)
└── LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_TEMPSENSOR, LL_ADC_SAMPLINGTIME_COMMON_2)
2.3.5 enableADC_DMA
enableADC_DMA()
│
├── 配置 NVIC 中断
│ ├── NVIC_SetPriority(DMA1_Channel2_3_IRQn, 3) // 设置 DMA 通道 2 和 3 的中断优先级
│ └── NVIC_EnableIRQ(DMA1_Channel2_3_IRQn) // 启用 DMA 通道 2 和 3 的中断请求
│
├── 配置 DMA 地址
│ └── LL_DMA_ConfigAddresses(
│ │
│ ├── DMA1, LL_DMA_CHANNEL_2 // 选择 DMA1 的通道 2
│ ├── LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA) // 源地址(ADC 数据寄存器)
│ └── (uint32_t)&ADCDataDMA, LL_DMA_DIRECTION_PERIPH_TO_MEMORY) // 目标地址和方向(外设到内存)
│
├── 设置 DMA 传输长度
│ ├── #ifdef USE_ADC_INPUT
│ │ └── LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 4) // 如果定义了 USE_ADC_INPUT,则传输 4 个数据
│ └── #else
│ └── LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 3) // 如果未定义 USE_ADC_INPUT,则传输 3 个数据
│
├── 启用 DMA 中断
│ ├── LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_2) // 启用 DMA 传输完成中断
│ └── LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_2) // 启用 DMA 传输错误中断
│
└── 启用 DMA 通道
└── LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2) // 启用 DMA1 的通道 2
2.3.6 activateADC
activateADC()
│
├── 初始化变量
│ ├── __IO uint32_t wait_loop_index = 0U
│ └── __IO uint32_t backup_setting_adc_dma_transfer = 0U
│
├── 检查 ADC 是否已启用
│ └── if (LL_ADC_IsEnabled(ADC1) == 0)
│
│ ├── 启用 ADC 内部电压调节器
│ │ ├── LL_ADC_EnableInternalRegulator(ADC1) // 启用内部电压调节器
│ │ └── 等待电压稳定
│ │ ├── wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10)
│ │ └── while (wait_loop_index != 0) { wait_loop_index--; } // 等待稳定
│ │
│ ├── 备份和设置 DMA 传输请求
│ │ ├── backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(ADC1) // 备份当前 DMA 传输设置
│ │ └── LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE) // 禁用 DMA 传输请求
│ │
│ ├── 启动 ADC 自校准
│ │ ├── LL_ADC_StartCalibration(ADC1) // 启动校准
│ │ └── 等待校准完成
│ │ └── while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0) {} // 等待校准完成
│ │
│ ├── 恢复 DMA 传输请求设置
│ │ └── LL_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer) // 恢复备份的 DMA 传输设置
│ │
│ ├── 延迟以便完成校准和启用 ADC
│ │ ├── wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1) // 设置延迟
│ │ └── while (wait_loop_index != 0) { wait_loop_index--; } // 等待延迟
│ │
│ └── 启用 ADC 并检查准备就绪状态
│ ├── LL_ADC_Enable(ADC1) // 启用 ADC
│ └── while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0) {} // 等待 ADC 就绪
│
└── 启用温度传感器
└── ADC->CCR |= ADC_CCR_TSEN // 启用 ADC 温度传感器
3. 总结
上述代码主要是将AM32代码中的四个ADC数据进行采样、滤波处理,最后通过以下电传报文回传给系统。
makeTelemPackage(degrees_celsius, battery_voltage, actual_current, (uint16_t)consumed_current, e_rpm)
4. 参考资料
【1】BLDC ESC 无刷直流电子调速器简介
【2】BLDC ESC 无刷直流电子调速器工作原理
【3】BLDC ESC 无刷直流电子调速器工作过程
【4】BLDC ESC 无刷直流电子调速器驱动方式
【5】AM32开源代码之工程结构