1.定时器的配置
void bsp_pwm_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 时基结构体
TIM_OCInitTypeDef TIM1_OCInitStructure; // 输出比较结构体
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // Q1:这里为什么要打开AFIO时钟?
/* 配置定时器1使用的引脚 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Lock all the pin config */
GPIO_PinLockConfig(GPIOA, GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10); // 锁定所有的PWM引脚配置,防止后面被更改出错
TIM_DeInit(TIM1);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 计数器时钟不分频,仍旧是72M
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; // Q2:中心对齐模式1,为什么是1?
TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD_CYCLES / 2; // 自动重装值,也就是ARR的值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //输入捕获或者死区时间用到,主要是给输入滤波器和死区时间提供时钟,这里就是设置这个时钟的分频系数
TIM_TimeBaseStructure.TIM_RepetitionCounter = 1; // Q3:重复计数器为1,为什么?
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_OC4Ref); // Q4:这一步很重要,用于选择TRGO触发信号的信号源,这里选择为OC4REF
/* OC1 OC2 OC3 config*/
TIM_OCStructInit(&TIM1_OCInitStructure);
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // Q5:PWM模式1
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 主输出使能
TIM1_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; // 关闭互补输出通道
TIM1_OCInitStructure.TIM_Pulse = 0; // 这里设置占空比,就是CCR的值
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出比较的记性,也就是有效电平为高电平
TIM1_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; // pwm不输出的时候,也就是空闲时刻的电平为低电平
TIM_OC1Init(TIM1, &TIM1_OCInitStructure);
TIM_OC2Init(TIM1, &TIM1_OCInitStructure);
TIM_OC3Init(TIM1, &TIM1_OCInitStructure);
/* 第4通道,作为采样的触发通道 */
TIM_OCStructInit(&TIM1_OCInitStructure);
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // Q6:这里为什么又改为PWM模式2呢?
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM1_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM1_OCInitStructure.TIM_Pulse = 0;
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM1_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM1_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM1_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC4Init(TIM1, &TIM1_OCInitStructure);
TIM_Cmd(TIM1, ENABLE);
TIM_GenerateEvent(TIM1, TIM_EventSource_Update); // Q7:为什么要手动软件触发一个定时器更新事件?
TIM1->CCR1 = 0;
TIM1->CCR2 = 0;
TIM1->CCR3 = 0;
TIM_CtrlPWMOutputs(TIM1, ENABLE); // Q:使能定时器输出,一般的定时器用不到,高级定时器,比如TIM1还有TIM8才用的到
Q1:为什么要打开AFIO时钟?
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // Q1:这里为什么要打开AFIO时钟?
这里应该是由于有引脚的外设复用,也就是这个引脚上有多个外设功能,并且有两个及以上的外设功能同时用到,此时就必须把其中的一些外设功能复用到其他引脚上,所以这里就需要打开外设服用AFIO时钟。实际上不考虑功耗问题的话,这里不用管为什么,直接打开这个时钟即可。
Q2:中心对齐模式123的区别是什么,为什么要选择模式1?
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; // Q2:中心对齐模式1?
中心对齐模式又分为中心对齐模式 1/2/3 三种,具体由寄存器 CR1 位 CMS[1:0]配置。具体的区别就是输出比较中断标志位 CCxIF在何时置 1:中心模式 1在 CNT递减计数的时候置 1,中心对齐模式 2在 CNT递增计数时置 1,中心模式 3在 CNT递增和递减计数时都置 1。
这里的输出比较中断标志位,就是在CNT=CCR的时候,CCxIF标志位置1.
Q3:重复计数器为1,为什么?
这里是卖家的程序为了兼容有电流环和无电流环的程序而不同设置的,因为有电流环的程序是在ADC采样的中断中运行FOC,无电流采样的程序是在定时器计数溢出中断中运行FOC(实际上这里没有电流环,就是执行SVPWM)。对于无电流环的程序,由于每个PWM周期内定时器计数会溢出两次,而仅仅需要执行一次FOC,所以这里重复计数器设置为1,也就是重复两次才产生一次中断(问题:怎么能保证此时是定时器向上计数溢出中断呢?)
所以在有电流环的这里,重复计数器的值是多少不影响程序功能,因为没有开启定时器的中断。
问题:怎么能保证此时是定时器向上计数溢出中断呢?
解答:实际上,由于此时没有电流采样,所以只要保证每个PWM周期都执行一次SVPWM生成即可,所以这里没有必要非在定时器上溢出中断中执行。所以这里设置重复计数器值为1,那么每个PWM周期内,上溢出一次加下溢出一次就是两次,就产生一次中断,所以实际是在TIM计数的下溢出中断中执行的,如下图所示:
Q4:选择TRGO触发信号的信号源,这里选择为OC4REF
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_OC4Ref); // Q4:这一步很重要,用于选择TRGO触发信号的信号源,这里选择为OC4REF
STM32定时器的TRGO信号
OCxREF是在OC和OCN(也就是最终输出的信号)之前的信号,也就是第一级经过输出比较得到的PWM波形。
Q5:PWM模式1
Q6:为什么OC4通道要改为PWM模式2呢?
见后面ADC地方的解释。
Q7:为什么要手动软件触发一个定时器更新事件?
TIM_GenerateEvent(TIM1, TIM_EventSource_Update);
猜测:为了触发定时器更新事件后,可以直接更新ARR和CCR计数器的值,因为使能了影子寄存器,所以产生更新事件之后设置的寄存器的值才能真正有效?
2.ADC的配置
void bsp_adc_current_init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF |RCC_APB2Periph_ADC3, ENABLE ); //使能ADC3通道时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
/*
* IA: PA4(ADC12_IN4)
* IB: PA1(ADC12_IN1)
* IC: PB1(ADC12_IN9)
*/
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_Cmd(ADC1, DISABLE);
ADC_Cmd(ADC2, DISABLE);
ADC_DeInit(ADC1);
ADC_DeInit(ADC2);
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_InjecSimult; // Q1:ADC工作模式,同步注入模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式,每次转换所有通道都进行转换
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 不停转换的功能,关闭
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 不用外部触发,软件开启转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1; // ADC1的转换通道个数为1
ADC_Init(ADC1, &ADC_InitStructure);
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_InjecSimult;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC2, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
ADC_ResetCalibration(ADC1); // 初始化 ADC 校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待校准寄存器初始化完成
ADC_StartCalibration(ADC1); // ADC 开始校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
ADC_ResetCalibration(ADC2);
while(ADC_GetResetCalibrationStatus(ADC2));
ADC_StartCalibration(ADC2);
while(ADC_GetCalibrationStatus(ADC2));
delay_ms(100);
bsp_injected_adc_offset_config(); // 没有启动ADC的时候进行几个数值的采样,消除硬件偏差
bsp_injected_adc_conv_config();
NVIC_InitStruct.NVIC_IRQChannel = ADC1_2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct );
ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLE);
}
/**
* @brief 不启动电机的时候测量ADC采样值,这个值作为0点时候值,消除硬件带来的偏差
*/
void bsp_injected_adc_offset_config(void)
{
uint32_t i;
uint32_t offset[3];
offset[0] = 0;
offset[1] = 0;
offset[2] = 0;
ADC_ITConfig(ADC1, ADC_IT_JEOC, DISABLE);
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_None);
ADC_InjectedSequencerLengthConfig(ADC1,3);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_13Cycles5);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_13Cycles5);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_9, 3, ADC_SampleTime_13Cycles5);
ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);
for(i=0; i<64; i++)
{
ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_JEOC) == RESET);
ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);
offset[0] += ADC1->JDR1;
offset[1] += ADC1->JDR2;
offset[2] += ADC1->JDR3;
delay_ms(1);
}
offset[0] /= 64;
offset[1] /= 64;
offset[2] /= 64;
ADC1->JOFR1 = offset[0];
ADC2->JOFR1 = offset[1];
}
/**
* @brief ADC配置为inject模式
*/
void bsp_injected_adc_conv_config(void)
{
// Pharse A
ADC_InjectedSequencerLengthConfig(ADC1, 1);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_7Cycles5); // 这里设置7.5个ADC周期,最短是1.5个, 但是时间越短结果就越不准确,所以到底设置多长的时间是根据实际的情况来看的,比如PWM频率、FOC执行时间。
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO); // 选择
ADC_ExternalTrigInjectedConvCmd(ADC1,ENABLE);
// Pharse B
ADC_InjectedSequencerLengthConfig(ADC2, 1);
ADC_InjectedChannelConfig(ADC2, ADC_Channel_1, 1, ADC_SampleTime_7Cycles5);
ADC_ExternalTrigInjectedConvCmd(ADC2,ENABLE);
}
Q1:ADC工作模式,同步注入模式
独立模式的 ADC 采集需要在一个通道采集并且转换完成后才会进行下一个通道的采集。而双重 ADC 的机制就是使用两个 ADC 同时采样一个或者多个通道。双重 ADC 模式较独立模式一个最大的优势就是提高了采样率,弥补了单个 ADC 采样不够快的缺点
同步注入模式 ADC1 和 ADC2 同时转换一个注入通道组,其中 ADC1 为主,ADC2 为从。 转换的数据存储在每个 ADC接口的 ADC_JDRx寄存器中。
注意理解上图,上图中的通道0-3只是一个代指,解释如下:
也就是说,实际真实存在的物理通道就是ADC的通道1-16,可以选择这些通道中的某些通道作为规则组或者注入组,比如选择通道1,2,3,4作为注入组。那么上面的同步注入模式意思是由于使用一个ADC的时候,需要把所有的通道放到一个ADC中进行转换,那么就比较浪费时间,比如上面的4个通道就要挨个转换完成。但是使用同步模式的话可以把要转换的4个通道分给两个ADC来转换,比如ADC1转换通道1,2,ADC2转换通道3,4,这样的话时间就节省了一倍。并且这里要求同一个通道不能被两个ADC同时转化,可能这样会起冲突。另外这样同步转换还有一个好处,就是同步性,因为两个ADC的触发是同步的,也就是说每次两个ADC采集的数据都是同时的,这对于FOC的电流采集来说是有好处的,这样才能保证得到的电流是同时的相电流。(不要被上面的图误解,不是说把所有的要采集的通道都分给ADC1和2,让他们错峰采集所有通道,而主要是理解“同步”这个概念,也就是两个ADC可以采集同时的两个模拟量,保证数据的同步性。)
另外可以从上图看到不论是规则组还是注入组,都需要设置转换顺序和转换的总通道数,这在程序中分别对应如下语句:
ADC_InjectedChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_7Cycles5); // 设置转换顺序
ADC_InjectedSequencerLengthConfig(ADC1, 1); //设置转换的总通道数,也就是序列长度
另外注意,同步模式下分为主从ADC,一般主为ADC1,从为ADC2,设置触发源的时候只设置主ADC的即可,也就是设置ADC1的触发源即可,实际在有触发信号的时候会同步触发ADC2.
1.定时器的配置
void bsp_pwm_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 时基结构体
TIM_OCInitTypeDef TIM1_OCInitStructure; // 输出比较结构体
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // Q1:这里为什么要打开AFIO时钟?
/* 配置定时器1使用的引脚 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Lock all the pin config */
GPIO_PinLockConfig(GPIOA, GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10); // 锁定所有的PWM引脚配置,防止后面被更改出错
TIM_DeInit(TIM1);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 计数器时钟不分频,仍旧是72M
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; // Q2:中心对齐模式1,为什么是1?
TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD_CYCLES / 2; // 自动重装值,也就是ARR的值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //输入捕获或者死区时间用到,主要是给输入滤波器和死区时间提供时钟,这里就是设置这个时钟的分频系数
TIM_TimeBaseStructure.TIM_RepetitionCounter = 1; // Q3:重复计数器为1,为什么?
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_OC4Ref); // Q4:这一步很重要,用于选择TRGO触发信号的信号源,这里选择为OC4REF
/* OC1 OC2 OC3 config*/
TIM_OCStructInit(&TIM1_OCInitStructure);
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // Q5:PWM模式1
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 主输出使能
TIM1_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; // 关闭互补输出通道
TIM1_OCInitStructure.TIM_Pulse = 0; // 这里设置占空比,就是CCR的值
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出比较的记性,也就是有效电平为高电平
TIM1_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; // pwm不输出的时候,也就是空闲时刻的电平为低电平
TIM_OC1Init(TIM1, &TIM1_OCInitStructure);
TIM_OC2Init(TIM1, &TIM1_OCInitStructure);
TIM_OC3Init(TIM1, &TIM1_OCInitStructure);
/* 第4通道,作为采样的触发通道 */
TIM_OCStructInit(&TIM1_OCInitStructure);
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // Q6:这里为什么又改为PWM模式2呢?
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM1_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM1_OCInitStructure.TIM_Pulse = 0;
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM1_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM1_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM1_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC4Init(TIM1, &TIM1_OCInitStructure);
TIM_Cmd(TIM1, ENABLE);
TIM_GenerateEvent(TIM1, TIM_EventSource_Update); // Q7:为什么要手动软件触发一个定时器更新事件?
TIM1->CCR1 = 0;
TIM1->CCR2 = 0;
TIM1->CCR3 = 0;
TIM_CtrlPWMOutputs(TIM1, ENABLE); // Q:使能定时器输出,一般的定时器用不到,高级定时器,比如TIM1还有TIM8才用的到
Q1:为什么要打开AFIO时钟?
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // Q1:这里为什么要打开AFIO时钟?
这里应该是由于有引脚的外设复用,也就是这个引脚上有多个外设功能,并且有两个及以上的外设功能同时用到,此时就必须把其中的一些外设功能复用到其他引脚上,所以这里就需要打开外设服用AFIO时钟。实际上不考虑功耗问题的话,这里不用管为什么,直接打开这个时钟即可。
Q2:中心对齐模式123的区别是什么,为什么要选择模式1?
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; // Q2:中心对齐模式1?
中心对齐模式又分为中心对齐模式 1/2/3 三种,具体由寄存器 CR1 位 CMS[1:0]配置。具体的区别就是输出比较中断标志位 CCxIF在何时置 1:中心模式 1在 CNT递减计数的时候置 1,中心对齐模式 2在 CNT递增计数时置 1,中心模式 3在 CNT递增和递减计数时都置 1。
这里的输出比较中断标志位,就是在CNT=CCR的时候,CCxIF标志位置1.
Q3:重复计数器为1,为什么?
这里是卖家的程序为了兼容有电流环和无电流环的程序而不同设置的,因为有电流环的程序是在ADC采样的中断中运行FOC,无电流采样的程序是在定时器计数溢出中断中运行FOC(实际上这里没有电流环,就是执行SVPWM)。对于无电流环的程序,由于每个PWM周期内定时器计数会溢出两次,而仅仅需要执行一次FOC,所以这里重复计数器设置为1,也就是重复两次才产生一次中断(问题:怎么能保证此时是定时器向上计数溢出中断呢?)
所以在有电流环的这里,重复计数器的值是多少不影响程序功能,因为没有开启定时器的中断。
问题:怎么能保证此时是定时器向上计数溢出中断呢?
解答:实际上,由于此时没有电流采样,所以只要保证每个PWM周期都执行一次SVPWM生成即可,所以这里没有必要非在定时器上溢出中断中执行。所以这里设置重复计数器值为1,那么每个PWM周期内,上溢出一次加下溢出一次就是两次,就产生一次中断,所以实际是在TIM计数的下溢出中断中执行的,如下图所示:
Q4:选择TRGO触发信号的信号源,这里选择为OC4REF
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_OC4Ref); // Q4:这一步很重要,用于选择TRGO触发信号的信号源,这里选择为OC4REF
STM32定时器的TRGO信号
OCxREF是在OC和OCN(也就是最终输出的信号)之前的信号,也就是第一级经过输出比较得到的PWM波形。
Q5:PWM模式1
Q6:为什么OC4通道要改为PWM模式2呢?
见后面ADC地方的解释。
Q7:为什么要手动软件触发一个定时器更新事件?
TIM_GenerateEvent(TIM1, TIM_EventSource_Update);
猜测:为了触发定时器更新事件后,可以直接更新ARR和CCR计数器的值,因为使能了影子寄存器,所以产生更新事件之后设置的寄存器的值才能真正有效?
2.ADC的配置
void bsp_adc_current_init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF |RCC_APB2Periph_ADC3, ENABLE ); //使能ADC3通道时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
/*
* IA: PA4(ADC12_IN4)
* IB: PA1(ADC12_IN1)
* IC: PB1(ADC12_IN9)
*/
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_Cmd(ADC1, DISABLE);
ADC_Cmd(ADC2, DISABLE);
ADC_DeInit(ADC1);
ADC_DeInit(ADC2);
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_InjecSimult; // Q1:ADC工作模式,同步注入模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式,每次转换所有通道都进行转换
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 不停转换的功能,关闭
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 不用外部触发,软件开启转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1; // ADC1的转换通道个数为1
ADC_Init(ADC1, &ADC_InitStructure);
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_InjecSimult;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC2, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
ADC_ResetCalibration(ADC1); // 初始化 ADC 校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待校准寄存器初始化完成
ADC_StartCalibration(ADC1); // ADC 开始校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
ADC_ResetCalibration(ADC2);
while(ADC_GetResetCalibrationStatus(ADC2));
ADC_StartCalibration(ADC2);
while(ADC_GetCalibrationStatus(ADC2));
delay_ms(100);
bsp_injected_adc_offset_config(); // 没有启动ADC的时候进行几个数值的采样,消除硬件偏差
bsp_injected_adc_conv_config();
NVIC_InitStruct.NVIC_IRQChannel = ADC1_2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct );
ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLE);
}
/**
* @brief 不启动电机的时候测量ADC采样值,这个值作为0点时候值,消除硬件带来的偏差
*/
void bsp_injected_adc_offset_config(void)
{
uint32_t i;
uint32_t offset[3];
offset[0] = 0;
offset[1] = 0;
offset[2] = 0;
ADC_ITConfig(ADC1, ADC_IT_JEOC, DISABLE);
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_None);
ADC_InjectedSequencerLengthConfig(ADC1,3);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_13Cycles5);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_13Cycles5);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_9, 3, ADC_SampleTime_13Cycles5);
ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);
for(i=0; i<64; i++)
{
ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_JEOC) == RESET);
ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);
offset[0] += ADC1->JDR1;
offset[1] += ADC1->JDR2;
offset[2] += ADC1->JDR3;
delay_ms(1);
}
offset[0] /= 64;
offset[1] /= 64;
offset[2] /= 64;
ADC1->JOFR1 = offset[0];
ADC2->JOFR1 = offset[1];
}
/**
* @brief ADC配置为inject模式
*/
void bsp_injected_adc_conv_config(void)
{
// Pharse A
ADC_InjectedSequencerLengthConfig(ADC1, 1);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_7Cycles5); // 这里设置7.5个ADC周期,最短是1.5个, 但是时间越短结果就越不准确,所以到底设置多长的时间是根据实际的情况来看的,比如PWM频率、FOC执行时间。
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO); // 选择
ADC_ExternalTrigInjectedConvCmd(ADC1,ENABLE);
// Pharse B
ADC_InjectedSequencerLengthConfig(ADC2, 1);
ADC_InjectedChannelConfig(ADC2, ADC_Channel_1, 1, ADC_SampleTime_7Cycles5);
ADC_ExternalTrigInjectedConvCmd(ADC2,ENABLE);
}
Q1:ADC工作模式,同步注入模式
独立模式的 ADC 采集需要在一个通道采集并且转换完成后才会进行下一个通道的采集。而双重 ADC 的机制就是使用两个 ADC 同时采样一个或者多个通道。双重 ADC 模式较独立模式一个最大的优势就是提高了采样率,弥补了单个 ADC 采样不够快的缺点
同步注入模式 ADC1 和 ADC2 同时转换一个注入通道组,其中 ADC1 为主,ADC2 为从。 转换的数据存储在每个 ADC接口的 ADC_JDRx寄存器中。
注意理解上图,上图中的通道0-3只是一个代指,解释如下:
也就是说,实际真实存在的物理通道就是ADC的通道1-16,可以选择这些通道中的某些通道作为规则组或者注入组,比如选择通道1,2,3,4作为注入组。那么上面的同步注入模式意思是由于使用一个ADC的时候,需要把所有的通道放到一个ADC中进行转换,那么就比较浪费时间,比如上面的4个通道就要挨个转换完成。但是使用同步模式的话可以把要转换的4个通道分给两个ADC来转换,比如ADC1转换通道1,2,ADC2转换通道3,4,这样的话时间就节省了一倍。并且这里要求同一个通道不能被两个ADC同时转化,可能这样会起冲突。另外这样同步转换还有一个好处,就是同步性,因为两个ADC的触发是同步的,也就是说每次两个ADC采集的数据都是同时的,这对于FOC的电流采集来说是有好处的,这样才能保证得到的电流是同时的相电流。(不要被上面的图误解,不是说把所有的要采集的通道都分给ADC1和2,让他们错峰采集所有通道,而主要是理解“同步”这个概念,也就是两个ADC可以采集同时的两个模拟量,保证数据的同步性。)
另外可以从上图看到不论是规则组还是注入组,都需要设置转换顺序和转换的总通道数,这在程序中分别对应如下语句:
ADC_InjectedChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_7Cycles5); // 设置转换顺序
ADC_InjectedSequencerLengthConfig(ADC1, 1); //设置转换的总通道数,也就是序列长度
另外注意,同步模式下分为主从ADC,一般主为ADC1,从为ADC2,设置触发源的时候只设置主ADC的即可,也就是设置ADC1的触发源即可,实际在有触发信号的时候会同步触发ADC2.
举报