单片机学习小组
直播中

h1654155272.9717

9年用户 1261经验值
擅长:电源/新能源
私信 关注

为什么要打开AFIO时钟

为什么要打开AFIO时钟?

中心对齐模式123的区别是什么,为什么要选择模式1?

回帖(2)

李恩佳

2022-1-25 11:10:46
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.
举报

李泽坚

2022-1-25 11:10:48
Q2:ADC的外部触发

  如上图所示,注意这里的外部触发就是除了软件手动开启之外的触发,就叫做外部触发,这个外部可以理解为相对于ADC之外的外设。同时注意红框的部分,要求是外部触发信号只有上升沿才可以启动转换。这里由于是使用定时器的PWM作为触发信号,并且使用的是TIM1,并且根据FOC的原理可以知道,需要在定时器中央对齐模式的上升溢出或者下降溢出的时候触发ADC的采集。这里根据ADC的配置可以知道这里使用的是TIM1_TRGO时间作为ADC的触发信号。
再去看TIM1的TRGO信号来源,如下图所示。根据定时器的配置可以知道,是选择OC4REF信号作为TRGO的触发输出,这里的OCxREF就是PWM信号输出之前的信号,也就是输出比较之后的信号。因为实际的输出信号和这个REF信号之间还有死区时间配置、输出控制等等,所以实际输出信号和这个REF信号稍微还有所区别,但是作为ADC触发的信号肯定是使用REF信号,也就是输出比较后最先得到的信号。
  根据TIM的配置可以知道,选择了OC4_REF信号作为TRGO的输出。再根据前面TIM1的配置可以知道,配置了TIM1的技术模式为PWM模式1,这个模式的解释如下:
  那么这个有效电平是怎么配置的呢?就是程序中的这句话:
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;   // 输出比较的极性,也就是有效电平为高电平  
那么在PWM1模式和有效电平为高电平的配置下,得到的控制电机的三个PWM信号的输出通道波形如下(注意下图也解释了定时器的三种中心对齐模式的不同之处,其实就在于输出比较标志位的置位位置不同,也就是图中的向右上角的箭头在不同的位置):
  根据FOC的原理可以知道,需要在下桥打开的时候进行电流采样,也就是对应上图的低电平的中心进行电流采样。采样的触发是通过OC4通道来的,这个触发信号需要是上升沿,而且需要是靠近TIM计数溢出的时刻,也就是CNT接近ARR的数值,也就对应了下面这句话:

这里注意ARR的值是取PWM_PERIOD_CYCLES/2,因为根据公式可以知道,2*ARR=TIM_CLK/f=PWM_PERIOD_CYCLES,f是PWM的频率。这里取OC4的CCR值为ARR-10,也就是在控制电机的PWM信号低电平中间靠前一点进行触发ADC的采样。
那么这里为什么要更改OC4的模式为PWM2呢?就是和ADC要求触发信号为上升沿有关。当OC4在PWM2模式的时候,有效电平仍然为高电平,那么OC4REF的波形应该就和上图的相反,也就是中间是高,两边是低,而且由于CCR的值只比ARR小10,所以高电平时间非常短,也就是上升沿非常接近ARR溢出的时刻,也就是控制电机的PWM信号的低电平的中间时刻。如下图所示:
3.定点数

补充:这里不用去看底层计算机机器码的定点数是如何运算的,只用知道Q15格式是表示小数的运算,移位是进行数据的缩放即可, 其他的不用管。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分