ADC
是将模拟信号转化为数字量的芯片,模拟信号指电压信号,不管是什么传感器,如温度,电流,光敏,都是转换为电压值的测量。
STM32G474-ADC
实际上这里是H7的,只不过端口数量不一样,其他该有的都有
①VREF+电压
②ADC双时钟域架构:主时钟是正常工作时的,频率较高,辅助时钟可以用于低功耗状态下提供时钟,他们的分频系数不一样,但出自同一个系统时钟,所以是同步的。这很重要。
③输入通道:被测量的电压输入,差分的,但我们这里用的是单端的,另外一个接地就行。
④转换序列
⑤触发源:可选,注入和规则
⑥转换时间:软件配置对应寄存器,我们调hal库吧
⑦参考电压
⑧ADC核心:逐次逼近
⑨数据寄存器
⑩中断:中断线左侧的白色框是触发中断的事件
⑪通道预选控制信号:通道预选控制信号的主要作用是告诉ADC当前需要处理哪个通道的模拟信号。在ADC系统中,可能有多个模拟信号源,每个信号源都连接到一个不同的ADC通道上。通过改变通道预选控制信号,可以选择不同的通道进行模数转换。可以软件设置。
我把G474的也发出来,结构差不多
分类
并联比较型和逐次逼近型,前者转化速度快,成本高,后者相反。
在stm32芯片里用的是后者,注意要检测的电压一定不能大于adc的参考电压。
转换序列
规则通道和注入通道基本区别
规则通道多达16个通道,而注入通道只有4个通道。
每个注入通道均有一个用于储存转换结果的注入数据寄存器,而所有规则通道均共用一个规则数据寄存器。
注入通道可以中断规则通道的转换,然后再恢复规则通道的转换。
监测电机工作时的电压、电流等信息
ADC配置
void adc_init(void)
{
g_adc_handle.Instance = ADC_ADCX; /*ADC1*/
g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 4分频,ADCCLK = SYSCLK/4 = 170/4 = 42.5Mhz 用于调整ADC模块时钟频率*/
g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式,设置ADC的分辨率为12位。这意味着ADC将输入信号转换为12位的数字值 */
g_adc_handle.Init.GainCompensation = 0; /* 不需要增益补偿。在某些ADC中,增益补偿用于校正ADC的增益误差。*/
g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐,这意味着ADC转换结果的最低有效位(LSB)位于最低位 */
g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 非扫描模式,在扫描模式下,ADC会连续转换多个通道。禁用此模式意味着ADC只会转换一个指定的通道。 */
g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* 单通道转换结束标志,设置转换结束(EOC)标志的触发条件为单通道转换结束。 */
g_adc_handle.Init.LowPowerAutoWait = DISABLE; /* 禁用低功耗延迟模式,这有助于减少ADC的功耗,但可能会增加转换的延迟。*/
g_adc_handle.Init.ContinuousConvMode = ENABLE; /* 使能连续转换,在此模式下,ADC会连续进行转换,而无需在每次转换后重新触发。*/
g_adc_handle.Init.NbrOfConversion = 1; /* 1个转换在规则序列中 也就是只转换规则序列1,由于禁用了扫描模式,这实际上意味着ADC只会转换一个通道。*/
g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式,这通常与扫描模式一起使用,用于在不同的通道组之间进行不连续的采样。 */
g_adc_handle.Init.NbrOfDiscConversion = 0; /* 不连续采样通道数为0 ,由于禁用了不连续采样模式,此设置实际上没有作用。*/
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发,这意味着你需要通过软件命令来启动ADC的转换。 */
g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发 */
g_adc_handle.Init.DMAContinuousRequests = DISABLE; /* 关闭DMA请求,这意味着ADC不会通过DMA连续地传输数据到内存。 */
g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 数据溢出时覆盖ADC数据,如果ADC的转换结果没有及时读取,新的转换结果会覆盖旧的结果。*/
g_adc_handle.Init.OversamplingMode = DISABLE; /* 禁用过采样功能 ,过采样可以提高ADC的分辨率和信噪比。*/
HAL_ADC_Init(&g_adc_handle); /* 初始化,句柄 */
HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED); /* 以单端模式运行ADC校准 */
}
GPIO以及中断配置代码
/**
* @brief ADC底层驱动,引脚配置,时钟使能
此函数会被HAL_ADC_Init()调用
* @param hadc:ADC句柄
* @retval 无
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC_ADCX)
{
GPIO_InitTypeDef gpio_init_struct;
ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADCx时钟 */
ADC_ADCX_CHY_GPIO_CLK_ENABLE(); /* 开启GPIO时钟 */
/* AD采集引脚模式设置,模拟输入 */
gpio_init_struct.Pin = ADC_ADCX_CHY_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
gpio_init_struct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);
}
}
ADC工作参数设置
/**
* @brief 设置ADC通道采样时间
* @param adcx : adc句柄指针,ADC_HandleTypeDef
* @param ch : 通道号, ADC_CHANNEL_0~ADC_CHANNEL_17
* @param stime: 采样时间 0~7, 对应关系为:
* @arg ADC_SAMPLETIME_2CYCLES_5, 2.5个ADC时钟周期 ADC_SAMPLETIME_6CYCLES_5, 6.5个ADC时钟周期
* @arg ADC_SAMPLETIME_12CYCLES_5, 12.5个ADC时钟周期 ADC_SAMPLETIME_24CYCLES_5, 24.5个ADC时钟周期
* @arg ADC_SAMPLETIME_47CYCLES_5, 47.5个ADC时钟周期 ADC_SAMPLETIME_92CYCLES_5, 92.5个ADC时钟周期
* @arg ADC_SAMPLETIME_247CYCLES_5 , 247.5个ADC时钟周期 ADC_SAMPLETIME_640CYCLES_5,640.5个ADC时钟周期
* @param rank: 多通道采集时需要设置的采集编号,
假设你定义channel1的rank=1,channel2的rank=2,
那么对应你在DMA缓存空间的变量数组AdcDMA[0] 就是通道1的转换结果,AdcDMA[1]就是通道2的转换结果。
单通道设置为 ADC_REGULAR_RANK_1
* @arg 编号1~16:ADC_REGULAR_RANK_1~ADC_REGULAR_RANK_16
* @retval 无
*/
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime)
{
/* 配置对应ADC通道 */
ADC_ChannelConfTypeDef adc_channel;
adc_channel.Channel = ch; /* 设置ADCX对通道ch */
adc_channel.Rank = rank; /* 设置采样序列 */
adc_channel.SamplingTime = stime; /* 设置采样时间 */
adc_channel.SingleDiff = ADC_SINGLE_ENDED; /* 单端输入模式 */
HAL_ADC_ConfigChannel( adc_handle, &adc_channel );
}
电压检测
直接使用 ADC 采集 VBUS 的电压,再根据公式:POWER= 25 * VBUS,即可算出电源电压。
代码讲解
/* init_adc_val存储电流测量对应的参考电压ADC值,这里进行滤波 */
init_adc_val = g_adc_val[2]; /* 取出第一次得到的值 */
for(t=0;t<1000;t++)
{
init_adc_val += g_adc_val[2]; /* 现在的值和上一次存储的值相加 */
init_adc_val /= 2; /* 取平均值 */
delay_ms(1);
}
这里是main里抓取参考电压的方法,测1000次,取平均值,不管是电压,电流,还是温度,都是检测电压值,所以都会用到去电压平均值的方法。
电压计算
if(t % 20 == 0)
{
/* 电压计算公式:
* V_POWER = V_BUS * 25
* ADC 值转换为电压值:电压=ADC 值*3.3/4096
* 整合公式可以得出电压 V_POWER= ADC 值 *(3.3f * 25 / 4096)
*/
#define ADC2VBUS (float)(3.3f * 25 / 4096)
printf("Valtage:%.1fV \r\n", g_adc_val[0]*ADC2VBUS); /* 打印电压值 */
}
电流检测
由于 STM32 内部 ADC 并不能直接对电流进行采集,所以我们需要先把电流的信号转换为电压的信号
我们在 H 桥的回路中串接一个 20mR 的采样电阻。
这样就可以得到一个采样电压 I-V = 0.02R * 实际电流 I,但是这个采样电压太小了,直接用 ADC 进行采集的话偏差较大,我们需要对它进行差分放大。差分放大倍数= 12 /(1+1)=6 倍,参考电压为 1.27V,由此可得输出电压Iout(即 CURT点的电压)=6 * 0.02R * 实际电流 I + 1.27V。
我们只需要采集 Iout 的电压,再根据公式:实际电流 I=(输出电压 Iout-1.27)/0.12A,即可算出实际电流。
代码分析
/* 电流计算公式:
* I=(最终输出电压-初始参考电压)/(6*0.02)A
* ADC 值转换为电压值:电压=ADC 值*3.3/4096,这里电压单位为 V,我们换算成
mV,4096/1000=4.096,后面就直接算出为 mA
* 整合公式可以得出电流 I= (当前 ADC 值-初始参考 ADC 值)* (3.3 / 4.096 / 0.12)
*/
#define ADC2CURT (float)(3.3f / 4.096f / 0.12f)
printf("Current:%.1fmA \r\n\r\n", abs(g_adc_val[2]-init_adc_val)*ADC2CURT); /* 打印电流值 */