1 STM32CubeMx配置
1.1 整体配置
1.2 补充图
1.3 通道排序以及设置采样周期
我的理解是每个ADC模块,同一时刻只能转换一个通道的模拟电压为数字电压。那么如果我用来ADC模块16个通道其中的3个,或者更多。 那么我们不能同一时刻把3个通道的模拟电压都转换了。 而是一个一个的转换,这就涉及到一个排序和采样周期。 电机的三相电流没有说非要固定的顺序什么的,我们就根据通道由小到大来排序,即Channel 0,Channel 2, Channel 3. (那有人就会想为什么没有这个Channel1,这是因为我们硬件使用的IO口,那只允许我们选择 ADC1 - ADC3中的某一个固定的通道, 比如IO口PA0,它只能选择ADC1/2/3_IN0(通道0) 而能够选择Channel1的IO口PA1,它没有连到我们的电路里面去,所以我们就不能选择这个通道), 关于采样周期,我的理解是,采样周期越长,采的电压越精确,我认为选一个合适的值就行了,别太短(精度低),也别太长(延迟高),15个采样周期是根据前辈的经验选择的。
1.4 DMA的相关配置
在之前的串口和SPI的学习中,我们已经接触到了DMA, 用DMA来处理数据能够帮主芯片减负,下面看一下DMA的设置,Mode要选Circular,DataWidth要选Word。
1.5 Mode为什么要选Circular呢?
网上讲的内容,反正串口的DMA用Circular不一定好
不过ADC转换开Circular模式的话,可能的效果就是不停的把转换后数字电压数据搬运到我指定的数组里面,这个还好,而且数据量不大,就三个word。 我们不是往外面发送。
而且STM32的中文手册上确实专门说了ADC扫描模式下用,那不是巧了,我们正好就是ADC + 扫描模式。
DataWidth选Word是因为,我们的ADC模块可以支持12位,如果你选择half word,那就只有8位,所以你必须选择Word才能把数据完整的送过来, 另外,上面的配置我们也看到了, 是Right alignment的,也就是说,这16位的高4位是0,我们不用管的。读出来可直接使用。
1.6 代码
uint32_t ADC_RESULT1[3]={0}; // 定义一个存储转换后数字电压的数组
HAL_ADC_Start_DMA(&hadc1,ADC_RESULT1,3); // 在需要使用的时候,调用DMA Start转换。
配置完之后, 初始化代码STM32CubeMx会帮你自动生成,然后呢,你需要写的代码就不多了。
2 原理图 + 芯片datasheet的阅读
2.1 原理图
电机的三相电压输出 Out1 Out2 Out3,都分别接了 0.2Ω的电阻, 我们测量电阻两边的电压(MOTOR_OUTx- 和 MOTOR_OUTx+),再除以电阻,就能得到电流了。 而这个电压,我们通过一个芯片MAX40056FAUA/V+来转化,芯片的输出脚,最终就直接连到了的STM32主芯片的IO脚,通过ADC的通道进行模拟电压到数字电压的转换。
2.2 芯片datasheet
直接挑重点不罗嗦,都是在芯片手册上看的。
2.2.1 探测电压范围计算
这个公式,用于计算芯片能够探测电压范围, 注意,有负电压哟, 我们的三相电流也是有正有负的。公式里面需要知道的参数, VREF, VDD,GAIN, 原理图我们看到了,VDD接的是3V3。 VREF接的是GND,也就是说使用内部的参考电压1.5V。(另外,当 VREF > 1.5V的时候,比如典型的2.5V,芯片就会使用2.5V作为参考电压,这时候VDD接5V,这时候测量范围还是对称的。 -50mV to +50mV )。
从下表中我们也能看到。
另外,关于这个GAIN,下面有个表,我们的型号对应的是50.
但,根据知情人士透露,但是原理图上的型号没有买到, 可能买到了 最小面的三种。 也就是GAIN为10,测量电压范围在 -150mv 到 +180mv。 这样也好,这样我们测量范围就更宽了。 再看看输出公式,我们可以利用这个公式来反算我们的相电流。
2.2.2 相电流计算
Isense = (VOUT - VREF)/GAIN/RSENSE
前面我们也谈到了, VREF = 1.5V, GAIN = 10V/V, RSENCE=0.2Ω
也就是说, Isense = (Vout - 1.5)/ 10 / 0.2 = (Vout - 1.5)/ 2 = (Vout - 1.5)* 0 .5
这样呢,也能和代码对的上了。
2.2.3 代码
#define AD_TRANSFORM_VAL 0.0008056640625f //3.3/4096
void Get_Adc_Val(void)
{
HAL_ADC_Start_DMA(&hadc1,Adc_Val_Pitch,3);
HAL_ADC_Start_DMA(&hadc2,Adc_Val_Yaw,3);
Curr_Pitch[0] = (((float)Adc_Val_Pitch[0]*AD_TRANSFORM_VAL-Ref_Volt_Pitch[0])*0.5);
Curr_Pitch[1] = (((float)Adc_Val_Pitch[1]*AD_TRANSFORM_VAL-Ref_Volt_Pitch[1])*0.5);
Curr_Pitch[2] = (((float)Adc_Val_Pitch[2]*AD_TRANSFORM_VAL-Ref_Volt_Pitch[2])*0.5);
Curr_Yaw[0] = (((float)Adc_Val_Yaw[0]*AD_TRANSFORM_VAL-Ref_Volt_Yaw[0])*0.5);
Curr_Yaw[1] = (((float)Adc_Val_Yaw[1]*AD_TRANSFORM_VAL-Ref_Volt_Yaw[1])*0.5);
Curr_Yaw[2] = (((float)Adc_Val_Yaw[2]*AD_TRANSFORM_VAL-Ref_Volt_Yaw[2])*0.5);
}
这部分内容写的有点匆忙,最近可能有些许的浮躁
后记:
一件非常重要的事情,需要记住,就是当你使用函数HAL_ADC_Start_DMA的时候,传进去用于存储转换后的电压值的指针必须定义为全局指针,而不是在某个函数体内部义的局部变量,否则就会出现一些BUG,谨记! 这个问题好像之前遇到过类似的。
// √ Right place ▲▲▲
uint32_t ADCBufA[3] = {0, 0, 0};
uint32_t ADCBufE[3] = {0, 0, 0};
void MAX40056FAUAV_init(void){
int i = 0;
float VrefA[3] = {0, 0, 0};
float VrefE[3] = {0, 0, 0};
// × Wrong place ▲▲▲
// uint32_t ADCBufA[3] = {0, 0, 0};
// uint32_t ADCBufE[3] = {0, 0, 0};
for( i = 0; i < 1000; i++)
{
HAL_ADC_Start_DMA(&hadc2,ADCBufA,3);
HAL_ADC_Start_DMA(&hadc1,ADCBufE,3);
...