资讯详情

FOC和SVPWM的C语言代码实现 

SVPWM这里解释了原理:https://blog.csdn.net/qlexcel/article/details/74787619#comments

现在开始分析C语言的代码(代码建议复制到notepad 为方便读者实验,每个代码都是独立的子模块,复制到工程中即可编译操作:

一、配置高级定时器TIM1产生6路互补PWM,带刹车保护

详细配置代码如下,复制以下程序段main.c可直接输出到中间PWM波形(保证BKIN下拉),方便读者验证:

static void TIM1_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9| GPIO_Pin_10 | GPIO_Pin_11; //CH1--A8 CH2--A9 CH3--A10 CH4-A11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 CH1N-B13 CH2N-B14 CH3N-B15 BKIN-B12 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13| GPIO_Pin_14 | GPIO_Pin_15; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //BKIN-B12 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_PinLockConfig(GPIOA, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 ); //住高侧IO口的配置寄存器,避免以后误修改 } #define CKTIM ((u32)72000000uL) //主频 #define PWM_PRSC ((u8)0) //TIM1分频系数 #define PWM_FREQ ((u16) 15000) //PWM频率(Hz) #define PWM_PERIOD ((u16) (CKTIM / (u32)(2 * PWM_FREQ *(PWM_PRSC 1)))) #define REP_RATE (1) //该参数可调节电流环的刷新频率和周期:(REP_RATE 1)/(2*PWM_FREQ) 秒 ///因为电流环的采样取决于TIM1来触发的 #define DEADTIME_NS ((u16)1000) ///死区时间(ns),范围:0-3500 #define DEADTIME (u16)((unsigned long long)CKTIM/2 * (unsigned long long)DEADTIME_NS/1000000000uL) static void TIM1_Mode_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_BDTRInitTypeDef TIM1_BDTRInitStructure; NVIC_InitTypeDef NVIC_InitStructure; TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD; //计数周期 TIM_TimeBaseStructure.TIM_Prescaler = PWM_PRSC; //分频系数 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2; //设置外部时钟TIM1ETR的滤波时间 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; //中央对齐模式1,从0计数到 TIM_Period 然后开始减到0,循环 TIM_TimeBaseStructure.TIM_RepetitionCounter = REP_RATE; //重复计数意味着在产生溢出中断之前,重复溢出多少次(产生更新事件)ADC采样) TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能CHx的PWM输出 TIM_OCInitStructur.TIM_OutputNState = TIM_OutputNState_Enable;//互补输出使能,使能CHxN的PWM输出     TIM_OCInitStructure.TIM_Pulse = 800;                           //设置跳变值,当计数器计数到这个值时,电平发生跳变     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;      //CHx有效电平的极性为高电平(高侧)     TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;    //CHxN有效电平的极性为高电平(低侧)     TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;   //在空闲时CHx输出低(高侧), 调用TIM_CtrlPWMOutputs(TIM1, DISABLE)后就是空闲状态。     TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;   //在空闲时CHxN输出高(低侧),打开低侧管子可以用来锁电机                                                                        //TIM_OCIdleState 和 TIM_OCNIdleState不能同时为高     TIM_OC1Init(TIM1, &TIM_OCInitStructure);                       //配置CH1       TIM_OCInitStructure.TIM_Pulse = 800;                              TIM_OC2Init(TIM1, &TIM_OCInitStructure);                       //配置CH2       TIM_OCInitStructure.TIM_Pulse = 800;                              TIM_OC3Init(TIM1, &TIM_OCInitStructure);                       //配置CH3       //设置刹车特性,死区时间,锁电平,OSSI,OSSR状态和AOE(自动输出使能)     TIM1_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable; //MOE=1且定时器不工作时,CHx和CHxN的输出状态     TIM1_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable; //MOE=0且定时器不工作时,CHx和CHxN的输出状态(详情看用户手册,一般都是ENABLE,不用深究)     TIM1_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;      //BDTR寄存器写保护等级,防止软件错误误写。     TIM1_BDTRInitStructure.TIM_DeadTime = DEADTIME;              //设置死区时间     TIM1_BDTRInitStructure.TIM_Break = TIM_Break_Enable;         //使能TIM1刹车输入(BKIN),要把BKIN引脚拉低才有PWM输出     TIM1_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;          //刹车输入(BKIN)输入高电平有效     TIM1_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable;   //刹车有效标志只能被软件清除,不能被自动清除     TIM_BDTRConfig(TIM1, &TIM1_BDTRInitStructure);       NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);              //4个抢先级、4个子优先级         /* Enable the TIM1 BRK Interrupt */     NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_IRQn;     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                   NVIC_Init(&NVIC_InitStructure);          TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);      //使用TIM1的更新事件作为触发输出,这个输出可以触发ADC进行采样,电流环的采样     TIM_ClearITPendingBit(TIM1, TIM_IT_Break);                //清除刹车中断,BKIN输入导致的中断     TIM_ITConfig(TIM1, TIM_IT_Break, ENABLE);                 //使能刹车中断     TIM_CtrlPWMOutputs(TIM1, ENABLE);                         //PWM输出使能         TIM_Cmd(TIM1, ENABLE);                                    //使能TIM1 } void TIM1_PWM_Init(void) {     TIM1_GPIO_Config();     TIM1_Mode_Config(); }   /******************************************************************************* * Function Name  : TIM1_BRK_IRQHandler * Description    : This function handles TIM1 Break interrupt request. *******************************************************************************/ void TIM1_BRK_IRQHandler(void) {     //关闭IGBT,并报错     TIM_ClearITPendingBit(TIM1, TIM_IT_Break); } 1、配置TIM1的CH1--A8、CH2--A9、CH3--A10、CH4-A11、CH1N-B13、CH2N-B14、CH3N-B15、BKIN-B12

BKIN作为报警信号或者刹车信号的输入,当检测此信号时,TIM1的PWM会硬件上停止输出,实时性好,起到保护硬件电路的作用。

2、观察SVPWM的PWM波形是对称的:

正好配置TIM1为中央对齐模式1,在上面代码的配置中,载波周期为15KHz,TIM_Period(ARR)=2400,CH1的TIM_Pulse(CCR)=800。采用的PWM1模式,即CNT小于CCR时,输出有效电平,大于CCR小于ARR时,输出无效电平,又配置CHx的有效电平为高电平,CHxN的有效电平为高电平,则可以得到下面的PWM波形:

如果CHxN的有效电平是低电平,则输出的CHx和CHxN的波形是相同的。(可能CHx和CHxN有效电平的叫法相反)

3、配置CHx和CHxN空闲时的电平,调用TIM_CtrlPWMOutputs(TIM1, DISABLE)后,就进入空闲状态了,高侧没什么用,让空闲时低侧的管子导通,可以使相线连在一起,起到锁定的作用。

4、改变 REP_RATE 的值,可以更改TRGO信号输出的频率。因为相电流采样由TIM1的TRGO信号触发,故更改REP_RATE可以调整电流环的计算频率(每次相电流采样后,会进行一次FOC运算)。采样频率关系:(2*PWM_FREQ)/(REP_RATE + 1),如:当PWM_FREQ=15KHz,REP_RATE=0,则采样频率为30KHz。

5、TIM_OSSRState和TIM_OSSIState直接Enable就可以了,详情可以去看用户手册。

6、使用TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);函数设置TRGO信号的产生源,TIM_TRGOSource_Update参数代表TIM1产生一次更新事件,就输出一次TRGO信号。TRGO信号用来触发相电流的采样。

当然也可以使用TIM1的CH4来触发相电流采样,参数为TIM_TRGOSource_OC4Ref  ,再打开CH4,并配置CH4的比较值,比如配置比较值为PWM_PERIOD-5。这样当CNT计数到PWM_PERIOD-5时就会触发相电流的ADC采样,这种方法比较灵活,可以合理设置CH4的比较值,来让相电流采样点避开开关噪声。

二、配置双ADC模式和规则组、注入组,其中注入组由TIM1的TRGO触发

//A相电流采样 #define PHASE_A_ADC_CHANNEL               ADC_Channel_11 #define PHASE_A_GPIO_PORT                 GPIOC #define PHASE_A_GPIO_PIN                  GPIO_Pin_1 //B相电流采样 #define PHASE_B_ADC_CHANNEL               ADC_Channel_10 #define PHASE_B_GPIO_PORT                 GPIOC #define PHASE_B_GPIO_PIN                  GPIO_Pin_0 //读取散热器温度(过热保护) #define TEMP_FDBK_CHANNEL                 ADC_Channel_13 #define TEMP_FDBK_CHANNEL_GPIO_PORT       GPIOC #define TEMP_FDBK_CHANNEL_GPIO_PIN        GPIO_Pin_3 //读取总线电压值(过压、欠压保护) #define BUS_VOLT_FDBK_CHANNEL             ADC_Channel_14 #define BUS_VOLT_FDBK_CHANNEL_GPIO_PORT   GPIOC #define BUS_VOLT_FDBK_CHANNEL_GPIO_PIN    GPIO_Pin_4 //读取电位器值,可以用来调速等 #define POT1_VOLT_FDBK_CHANNEL             ADC_Channel_12 #define POT1_VOLT_FDBK_CHANNEL_GPIO_PORT   GPIOC #define POT1_VOLT_FDBK_CHANNEL_GPIO_PIN    GPIO_Pin_2 //读取母线电流值(过流保护) #define BUS_SHUNT_CURR_CHANNEL             ADC_Channel_15 #define BUS_SHUNT_CURR_CHANNEL_GPIO_PORT   GPIOC #define BUS_SHUNT_CURR_CHANNEL_GPIO_PIN    GPIO_Pin_5 //读取刹车电阻电流(刹车电阻过流保护) #define BRK_SHUNT_CURR_CHANNEL             ADC_Channel_7 #define BRK_SHUNT_CURR_CHANNEL_GPIO_PORT   GPIOA #define BRK_SHUNT_CURR_CHANNEL_GPIO_PIN    GPIO_Pin_7 //备用通道 #define AIN0_VOLT_FDBK_CHANNEL             ADC_Channel_8 #define AIN0_VOLT_FDBK_CHANNEL_GPIO_PORT   GPIOB #define AIN0_VOLT_FDBK_CHANNEL_GPIO_PIN    GPIO_Pin_0 //备用导通 #define AIN1_VOLT_FDBK_CHANNEL             ADC_Channel_9 #define AIN1_VOLT_FDBK_CHANNEL_GPIO_PORT   GPIOB #define AIN1_VOLT_FDBK_CHANNEL_GPIO_PIN    GPIO_Pin_1   #define   ADC1_DR_Address    ((uint32_t)0x4001244C)       //ADC数据寄存器地址 #define   BufferLenght       36     volatile u32  ADC_DualConvertedValueTab[BufferLenght]; volatile u16  ADC1_RegularConvertedValueTab[BufferLenght]; volatile u16  ADC2_RegularConvertedValueTab[BufferLenght]; static u16 hPhaseAOffset; static u16 hPhaseBOffset;   void ADC_DMA_Init(void) {     u8 bIndex;          GPIO_InitTypeDef GPIO_InitStructure;     ADC_InitTypeDef ADC_InitStructure;     DMA_InitTypeDef   DMA_InitStructure;     NVIC_InitTypeDef NVIC_InitStructure;          RCC_ADCCLKConfig(RCC_PCLK2_Div6);                        //ADCCLK = PCLK2/6=72M/6=12MHz,ADC最大频率不能超过14MHz     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);       //DMA1     RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);     RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA |                            RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOE, ENABLE);           GPIO_StructInit(&GPIO_InitStructure);                         //Fills each GPIO_InitStruct member with its default value         GPIO_InitStructure.GPIO_Pin = PHASE_A_GPIO_PIN;          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(PHASE_A_GPIO_PORT, &GPIO_InitStructure);       GPIO_InitStructure.GPIO_Pin = PHASE_B_GPIO_PIN;           GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(PHASE_B_GPIO_PORT, &GPIO_InitStructure);       GPIO_InitStructure.GPIO_Pin = TEMP_FDBK_CHANNEL_GPIO_PIN;               GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(TEMP_FDBK_CHANNEL_GPIO_PORT, &GPIO_InitStructure);       GPIO_InitStructure.GPIO_Pin = BUS_VOLT_FDBK_CHANNEL_GPIO_PIN;           GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(BUS_VOLT_FDBK_CHANNEL_GPIO_PORT, &GPIO_InitStructure);       GPIO_InitStructure.GPIO_Pin = POT1_VOLT_FDBK_CHANNEL_GPIO_PIN;          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(POT1_VOLT_FDBK_CHANNEL_GPIO_PORT, &GPIO_InitStructure);       GPIO_InitStructure.GPIO_Pin = BUS_SHUNT_CURR_CHANNEL_GPIO_PIN;          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(BUS_SHUNT_CURR_CHANNEL_GPIO_PORT, &GPIO_InitStructure);       GPIO_StructInit(&GPIO_InitStructure);     GPIO_InitStructure.GPIO_Pin = BRK_SHUNT_CURR_CHANNEL_GPIO_PIN;          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(BRK_SHUNT_CURR_CHANNEL_GPIO_PORT, &GPIO_InitStructure);       GPIO_StructInit(&GPIO_InitStructure);     GPIO_InitStructure.GPIO_Pin = AIN0_VOLT_FDBK_CHANNEL_GPIO_PIN | AIN1_VOLT_FDBK_CHANNEL_GPIO_PIN;       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;     GPIO_Init(AIN0_VOLT_FDBK_CHANNEL_GPIO_PORT, &GPIO_InitStructure);       //设置DMA1,用于自动存储ADC1和ADC2规则通道的转换值     DMA_DeInit(DMA1_Channel1);     DMA_StructInit(&DMA_InitStructure);     DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;                  //ADC数据寄存器地址(源地址)     DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_DualConvertedValueTab;  //转换值存储地址(目标地址)     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                           //从外设到内存     DMA_InitStructure.DMA_BufferSize = BufferLenght;                             //传输大小     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;             //外设地址不增     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                      //内存地址自增     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;      //外设数据单位(每次传输32位)     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;              //内存数据单位(每次传输32位)     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                              //循环模式     DMA_InitStructure.DMA_Priority = DMA_Priority_High;                          //本DMA通道优先级(用了多个通道时,本参数才有效果)     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                 //没有使用内存到内存的传输     DMA_Init(DMA1_Channel1, &DMA_InitStructure);          DMA_ClearITPendingBit(DMA1_IT_TC1);                //清除通道1传输完成中断               DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);    //打开通道1传输完成中断         DMA_Cmd(DMA1_Channel1, ENABLE);                    //使能DMA1   /****使用双ADC模式,ADC1为主,ADC2为从。当ADC转换配置成由外部事件触发时,用户必须设置成仅触发主ADC,从ADC设置成软件触发,这样可以防止意外的触发从转换。 但是,主和从ADC的外部触发必须同时被激活,要调用 ADC_ExternalTrigConvCmd(ADC2, ENABLE);//ADC2外部触发使能****/     ADC_DeInit(ADC1);     ADC_DeInit(ADC2);     ADC_StructInit(&ADC_InitStructure);     ADC_InitStructure.ADC_Mode = ADC_Mode_RegInjecSimult;  //ADC1工作在混合同步规则及注入模式     ADC_InitStructure.ADC_ScanConvMode = ENABLE;           //轮流采集各个通道的值     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;     //连续转换模式,触发后就会一直转换     ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换触发信号选择,使用一个软件信号触发ADC1     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;  //数据左对齐,ADC是12位,要存到DR寄存器的高16位或低16位,就有左右对齐问题,决定了高4位无效或低4位无效     ADC_InitStructure.ADC_NbrOfChannel = 3;                   //要进行ADC转换的通道数:BUS_SHUNT(母线电压)+BREAK_SHUNT(刹车电阻电流)+Chip Temp(MCU温度)     ADC_Init(ADC1, &ADC_InitStructure);       ADC_DMACmd(ADC1, ENABLE);                              //使能ADC1的DMA       ADC_StructInit(&ADC_InitStructure);     ADC_InitStructure.ADC_Mode = ADC_Mode_RegInjecSimult;  //ADC2工作在混合同步规则及注入模式     ADC_InitStructure.ADC_ScanConvMode = ENABLE;                ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;     //连续转换模式,触发后就会一直转换     ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  //转换触发信号选择,由软件给信号触发,双ADC模式的从ADC必须设置为软件触发     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;     ADC_InitStructure.ADC_NbrOfChannel = 3;                //要进行ADC转换的通道数:POT1(电位器)+AIN0(备用)+AIN1(备用)     ADC_Init(ADC2, &ADC_InitStructure);     ADC_ExternalTrigConvCmd(ADC2, ENABLE);                 //ADC2外部触发使能,双ADC模式的从ADC必须要用这条语句          ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5);           //MCU温度     ADC_RegularChannelConfig(ADC1, BRK_SHUNT_CURR_CHANNEL, 2, ADC_SampleTime_239Cycles5);   //刹车电阻电流     ADC_RegularChannelConfig(ADC1, BUS_SHUNT_CURR_CHANNEL, 3, ADC_SampleTime_239Cycles5);   //母线电流     ADC_RegularChannelConfig(ADC2, POT1_VOLT_FDBK_CHANNEL, 1, ADC_SampleTime_239Cycles5);   //电位器     ADC_RegularChannelConfig(ADC2, AIN0_VOLT_FDBK_CHANNEL, 2, ADC_SampleTime_239Cycles5);   //备用     ADC_RegularChannelConfig(ADC2, AIN1_VOLT_FDBK_CHANNEL, 3, ADC_SampleTime_239Cycles5);    //备用           ADC_Cmd(ADC1, ENABLE);                               //ADC1使能     ADC_TempSensorVrefintCmd(ENABLE);                  //开启MCU内存温度传感器及Vref通道          ADC_ResetCalibration(ADC1);                        //复位校准寄存器     while(ADC_GetResetCalibrationStatus(ADC1));       //等待校准寄存器复位完成     ADC_StartCalibration(ADC1);                        //ADC1开始校准     while(ADC_GetCalibrationStatus(ADC1));            //等待校准完成       ADC_Cmd(ADC2, ENABLE);                             //ADC2使能     ADC_ResetCalibration(ADC2);                        //复位校准寄存器     while(ADC_GetResetCalibrationStatus(ADC2));       //等待校准寄存器复位完成     ADC_StartCalibration(ADC2);                        //ADC2开始校准       while(ADC_GetCalibrationStatus(ADC2));               //等待校准完成   /**** 获取A、B相零电流值,下面是临时配置 ****/     ADC_InjectedSequencerLengthConfig(ADC1,2);         //设置ADC1注入组通道数量     ADC_ITConfig(ADC1, ADC_IT_JEOC, DISABLE);          //关闭注入组转换完成中断     hPhaseAOffset=0;                                   //A相零电流值     hPhaseBOffset=0;                                   //B相零电流值     ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_None);        //ADC1注入组转换的触发信号选择,注入组转换由软件触发     ADC_ExternalTrigInjectedConvCmd(ADC1,ENABLE);                                    //使能外部信号触发注入组转换的功能     ADC_InjectedChannelConfig(ADC1, PHASE_A_ADC_CHANNEL,1,ADC_SampleTime_7Cycles5);  //配置ADC1的注入组通道,设置它们的转化顺序和采样时间     ADC_InjectedChannelConfig(ADC1, PHASE_B_ADC_CHANNEL,2,ADC_SampleTime_7Cycles5);  //A相电流和B相电流       ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);                    //清除注入组转换完成中断标志     ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);         //给一个软件触发信号,开始注入组转换       for(bIndex=16; bIndex !=0; bIndex--)     {         while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_JEOC)) { }  //等待注入组转换完成             //求Q1.15格式的零电流值,16个(零电流值/8)的累加,把最高符号位溢出。         hPhaseAOffset += (ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1)>>3);  //注入组左对齐,数据要右移3位才是真实数据         hPhaseBOffset += (ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_2)>>3);         ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);                                                //清除注入组转换完成中断标志          ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);                                     //给一个软件触发信号,开始注入组转换     } /**** 获取A、B相零电流值的临时配置使用结束,下面恢复ADC1的正常配置 ****/       ADC_InjectedChannelConfig(ADC1, PHASE_A_ADC_CHANNEL,  1, ADC_SampleTime_7Cycles5);     //A相电流     ADC_InjectedChannelConfig(ADC1, BUS_VOLT_FDBK_CHANNEL,2, ADC_SampleTime_7Cycles5);     //母线电压值     ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO);           //ADC1注入组转换的触发信号选择,注入组转换由TIM1的TRGO触发     ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLE);                                               //这里才能打开注入组转换完成中断           ADC_InjectedSequencerLengthConfig(ADC2,2);        //设置ADC2注入组通道数量     ADC_InjectedChannelConfig(ADC2, PHASE_B_ADC_CHANNEL, 1,ADC_SampleTime_7Cycles5);       //B相电流                         ADC_InjectedChannelConfig(ADC2, TEMP_FDBK_CHANNEL,   2,ADC_SampleTime_7Cycles5);       //散热器温度                           ADC_ExternalTrigInjectedConvCmd(ADC2,ENABLE);      //使能外部信号触发注入组转换的功能          NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      //4个抢先级、4个子优先级       NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;     NVIC_Init(&NVIC_InitStructure);       NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;     NVIC_Init(&NVIC_InitStructure);   //    ADC_ExternalTrigConvCmd(ADC1, ENABLE);   //ADC1外部触发使能,如果ADC的触发信号是外部就要调用 //    ADC_ExternalTrigConvCmd(ADC2, ENABLE);   //ADC2外部触发使能     ADC_SoftwareStartConvCmd(ADC1, ENABLE);    //给主ADC一个软件触发信号,之后ADC就会一直转换下去 }   u16 h_ADCBusvolt; u16 h_ADCTemp; u16 h_ADCPhase_A; u16 h_ADCPhase_B; void ADC1_2_IRQHandler(void)        //AD中断有三种情况:AD_EOC、AD_JEOC、AD_AWD {         if((ADC1->SR & ADC_FLAG_JEOC) == ADC_FLAG_JEOC)      {         ADC1->SR = ~(u32)ADC_FLAG_JEOC;           //清除注入组转换完成中断标志                             //获取散热器温度和母线电压值,做出相应的处理         h_ADCTemp=ADC_GetInjectedConversionValue(ADC2,ADC_InjectedChannel_2);     //散热器温度         h_ADCBusvolt=ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_2);  //母线电压                  //获取两相电流值,进行FOC运算         h_ADCPhase_A=ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1);     //散热器温度         h_ADCPhase_B=ADC_GetInjectedConversionValue(ADC2,ADC_InjectedChannel_1);  //母线电压             } }   void DMA1_Channel1_IRQHandler(void) {     u8 i,j=0;       if(DMA_GetITStatus(DMA1_IT_TC1))     {         DMA_ClearITPendingBit(DMA1_IT_GL1);    //清除DMA通道1传输完成中断         for(i=0;i<BufferLenght;i++)         {             ADC1_RegularConvertedValueTab[j++] = (uint16_t)(ADC_DualConvertedValueTab[i]>>4);   //ADC1规则组左对齐,要右移4位         }         //ADC1_RegularConvertedValueTab[0]是MCU温度,ADC1_RegularConvertedValueTab[1]是刹车电阻电流,ADC1_RegularConvertedValueTab[2]是母线电流                 j = 0;           for(i=0;i<BufferLenght;i++)         {             ADC2_RegularConvertedValueTab[j++] = (uint16_t)(ADC_DualConvertedValueTab[i] >> 20);//ADC2规则组左对齐,要右移20位         }         //ADC2_RegularConvertedValueTab[0]是电位器,ADC2_RegularConvertedValueTab[1]是备用,ADC2_RegularConvertedValueTab[2]是备用         } } 1、stm32的ADC转换速度为1MHz,精度为12位。采样时间可设置(1.5到239.5个周期),最小采样时间107ns。使用双ADC模式,同时触发ADC1、ADC2采集电机的两相电流,可保证采集到的两相电流值时间误差最小。配置ADC1为主,ADC2为从,用ADC1触发ADC2。

2、在众多的ADC采样通道中,A相、B相、母线电压值、散热器温度是对实时性要求比较高的,于是把他们配置成注入组通道,其余的配置成规则组。(注入组与规则组的关系和main中的while循环与中断类似,当注入组被触发时会打断规则组的ADC转换,优先转换注入组的通道,当注入组转换完成,规则组才继续转换)

3、在双ADC模式下,DR寄存器的高16位存储了ADC2的转换数据,低16位存储了ADC1的转换数据:

4、左右对齐的问题,ADC的转换精度只有12位,要保存的数据宽度为16位,因此存在靠左还是靠右的问题。

如上,注入组合规则组的左右对齐并不一样。推荐使用右对齐,直接取低12位即可。(上面的例程使用的左对齐,要做修改)

5、为了获取A、B相零电流时的值用于后面电机运行电流的矫正,先把注入组设置为软件触发,把零电流值采集完成后,再把配置修改成用TIM1的TRGO信号触发。因为是双ADC模式,只需要配置ADC1的触发信号就可以了。

6、求Q1.15格式的零电流值,16个(零电流值/8)的累加,把最高符号位溢出:

我们都知道MCU处理定点数会很快,处理浮点数比较慢,但是相电流采样值一般都比较小,会有小数,因此我们要使用Q格式来让浮点数据转化为定点数,提高处理速度。

        因此我们上面使用的Q1.15格式(也称Q15),就是用15位来表示小数部分,最高位是符号位。浮点数转化为Q15,要将数据乘以2的15次方。Q15数据转化为浮点数,将数据除以2的15次方。

        更多可以看这两篇文章:文章1 文章2 ,不想深究的,只需要导致浮点数和Q格式数的转换方法即可。

        因此得到A、B相零电流的过程如下:

        已知 :寄存器中的值/4096*Vref 是实际采样电阻上的电压值。hPhaseAOffset、hPhaseBOffset是16个零电流值的和。hPhaseAOffset、hPhaseBOffset是U16类型。

        于是A相零电流的Q15格式值为:(hPhaseAOffset/16)/4096*Vref*2^15=(((hPhaseAOffset>>4)>>12)*Vref)<<15,当Vref=2V,A相零电流的Q15格式值就刚好等于hPhaseAOffset。为方便计算,那我们就统一Vref=2了。(在程序中,Vref取多少都没有关系,只要统一就行)

 

三、编码器的配置

#define U32_MAX          ((u32)4294967295uL) #define    POLE_PAIR_NUM      (u8)2        //电机的极对数 #define ENCODER_PPR      (u16)(1000)  //编码器线数,即转一圈,编码器输出的脉冲数 #define ALIGNMENT_ANGLE  (u16)90      //上电时,电机转子的初始电角度,范围:0-359 #define COUNTER_RESET    (u16)((((s32)(ALIGNMENT_ANGLE)*4*ENCODER_PPR/360)-1)/POLE_PAIR_NUM)  //根据初始电角度计算TIM2_CNT值 #define ICx_FILTER       (u8) 8       // 8<-> 670nsec   void ENC_Init(void) {     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;     TIM_ICInitTypeDef TIM_ICInitStructure;     GPIO_InitTypeDef GPIO_InitStructure;     NVIC_InitTypeDef NVIC_InitStructure;       RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);       GPIO_StructInit(&GPIO_InitStructure);     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //TIM2_CH1--PA0  TIM2_CH2--PA1     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                //设定浮空模式     GPIO_Init(GPIOA, &GPIO_InitStructure);       /* Enable the TIM2 Update Interrupt */     NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;     NVIC_Init(&NVIC_InitStructure);       TIM_DeInit(TIM2);     TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);     TIM_TimeBaseStructure.TIM_Prescaler = 0x0;                   //No prescaling ,可以改变编码器计数对于脉冲输入个数的倍数                                                                  //即当TIM_Prescaler=0,输入一个脉冲,CNT增加4.当TIM_Prescaler=1,输入一个脉冲,CNT只增加2        TIM_TimeBaseStructure.TIM_Period = (4*ENCODER_PPR)-1;        //ARR值,当CNT增加到ARR会溢出,产生更新中断     TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);       TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);  //4倍计数,输入A、B信号不反相       TIM_ICStructInit(&TIM_ICInitStructure);     TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;     TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;    //滤波值是1-15,看情况设定,是用来滤除编码器信号干扰     TIM_ICInit(TIM2, &TIM_ICInitStructure);           //配置通道1的滤波值       TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;     TIM_ICInit(TIM2, &TIM_ICInitStructure);           //配置通道2的滤波值       TIM_ClearFlag(TIM2, TIM_FLAG_Update);     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);     TIM2->CNT = COUNTER_RESET;                        //设定电机转子初始电角度,一般电机校零后,掉电时要把电角度保存下来,再次上电时直接使用,就不用再校零了      TIM_Cmd(TIM2, ENABLE); }   void TIM2_IRQHandler(void) {     TIM_ClearFlag(TIM2, TIM_FLAG_Update);     //TIM2->CNT向上溢出或向下溢出,都会触发此中断,即3999加一到0,或0减一到3999 }   /******************************************************************************* * Description    : 返回电机转子的电角度 * Return         : Rotor electrical angle: 0 -> 0 degrees,  *                                          S16_MAX-> 180 degrees,  *                                          S16_MIN-> -180 degrees *******************************************************************************/ s16 ENC_Get_Electrical_Angle(void) {   s32 temp;      temp = (s32)(TIM_GetCounter(TIM2)) * (s32)(U32_MAX / (4*ENCODER_PPR));            temp *= POLE_PAIR_NUM;     return((s16)(temp/65536));   //s16 result } 1、定时器的编码器模式只用CH1、CH2,因此把编码器的A、B信号接在TIM2_CH1、TIM2_CH2即可。

2、编码器的模式选择TIM_EncoderMode_TI12,即TI1和TI2都要计数,用两张图就能说明白编码器模式的原理了:

        TI1和TI2对应了编码器的A、B信号,第一列是指编码器的3种模式,即只计数TI1、只计数TI2和都要计数。第二列是相对信号的电平,比如我们讨论TI1的边沿,它的相对信号就是TI2。

        我们就可以来看了,首先看第二行,只计数TI1信号的模式。当它的相对信号(即TI2)是高电平时,如果TI1来一个上升沿,那么CNT就要向下计数(Down),如果来一个下降沿,那么CNT就要向上计数(Up)。示意图如下:

在时刻1,当TI2为低电平时,TI1来一个上升沿,CNT向上计数1.

在时刻3,当TI2为高电平时,TI1来一个下降沿,CNT又向上计数1.

        同样的道理你也可以理解只计数TI2信号的模式,然后把这两个模式加起来你也可以理解同时计数TI1和TI2的模式。

3、TIM_ICPolarity_Rising 表示极性不反相。TIM_ICPolarity_falling:表示极性反向。库函数中的配置代码是这样的:

  /* Set the TI1 and the TI2 Polarities */   tmpccer &= (uint16_t)(((uint16_t)~((uint16_t)TIM_CCER_CC1P)) & ((uint16_t)~((uint16_t)TIM_CCER_CC2P)));   tmpccer |= (uint16_t)(TIM_IC1Polarity | (uint16_t)(TIM_IC2Polarity << (uint16_t)4));   /* Write to TIMx CCER */   TIMx->CCER = tmpccer; 配置选项有3种:

#define  TIM_ICPolarity_Rising             ((uint16_t)0x0000) #define  TIM_ICPolarity_Falling            ((uint16_t)0x0002) #define  TIM_ICPolarity_BothEdge           ((uint16_t)0x000A) 寄存器:

实际配置的是CC1P、CC1NP、CC2P、CC2NP位,改位描述如下:

4、ENC_Get_Electrical_Angle函数的问题

1)、首先是S16_MAX对应180度、S16_MIN对应-180的问题。这里其实就是利用了u32和s32数据类型表示的范围不同,而巧妙的产生了负数。我们知道u32的范围是:0到4294967295。而s32的范围是:-2147483648到2147483647。

        (s32)(U32_MAX / (4*ENCODER_PPR))就是把4294967295分成4000份,如果CNT的值在0-2000,那么得到的结果最大也就是4294967295的一半,即2147483647,这时还没有超过s32的范围。当CNT=2001,得到2148555741,超过了s32范围,那么会怎么样呢?会从s32范围的最小值开始往上增加,就像一个环一样,最大值和最小值之间只差1。于是2148555741超出了2147483647:2148555741-2147483647=1072094,绕了一圈后得到:-2147483648+1072094-1=-2146411555。

        有没有发现数据类型的这种特性和电机的电角度也是类似的?电机的电角度从0度增加到180度,然后再增加就变成-179度,又从-179度增加到0度,完成一圈。和数据类型的:从0增加到2147483647,再增加就变成-2147483648,又从-2147483648增加到0,完成一圈。

最后再把s32的数整除65536,就可以得到s16的数据类型了。(不能移位,会把符号位也移动了)

2)、极对数和电角度的关系

在函数还有一句:temp *= POLE_PAIR_NUM,即电角度要被极对数放大,这是为什么呢?

首先看极对数是什么:极对数是每相励磁绕组含有的磁极个数。

如果极对数是1,即每相只有一对磁极(一对磁极=2极,两对磁极=4极)

那么这3个相的磁极互差120度分布,相电流呈正弦规律变化一次,合成电压矢量旋转一圈,旋转磁场也会旋转一圈:

如果极对数是2,即每相有2对磁极:

那么这3个相的磁极互差60度分布,相电流呈正弦规律变化一次,合成电压矢量旋转半圈,旋转磁场也旋转半圈:

(此处差一个gif。。。有没有谁知道上面那种gif怎么画的。。。)

因此SVPWM输入的电角度和电机转子的机械角度之间就有极对数的倍数关系了:

如果极对数是1,那么SVPWM输出的磁场旋转一圈,电机转子也旋转一圈,电角度和电机转子角度是一一对应的。

如果极对数是2,那么SVPWM输出的磁场旋转一圈,电机转子只旋转半圈,电角度是电机转子角度的2倍。

编码器的角度反映的是电机转子的机械角度。

后话:既然1对极电机就能转了,干嘛还要2对、4对呢?虽然极对数越多,转速越慢,但是扭矩可以越大。参考:https://toutiao.1688.com/article/1067976.htm

 

四、FOC相关变换的代码实现

前面中的代码中我们用TIM1的TRGO信号触发ADC注入组的转换。然后ADC的注入组转换完成后会产生中断,然后在中断函数对相电流进行采样,再经过clark变换把Ia, Ib, Ic变换成Iα, Iβ,再经过park变换,把Iα, Iβ变换成Iq, Id。

获取相电流采样值、clark变换、park变换、反park变换的函数如下:

typedef struct  {     s16 qI_Component1;     s16 qI_Component2; } Curr_Components;  //电流值结构体   typedef struct  {   s16 qV_Component1;   s16 qV_Component2; } Volt_Components;   typedef struct     //电压值结构体 {   s16 hCos;   s16 hSin; } Trig_Components;  //存放角度sin和cos函数值的结构体   #define S16_MAX    ((s16)32767) #define S16_MIN    ((s16)-32768)   /********************************************************************************************************** 在注入组采样完成中断中调用,获取相电流的采样值。返回(电流采样值-零电流值),Q15格式 **********************************************************************************************************/  Curr_Components GET_PHASE_CURRENTS(void)                {     Curr_Components Local_Stator_Currents;     s32 wAux;       wAux = ((ADC1->JDR1)<<1)-(s32)(hPhaseAOffset);     //把电流采样值转换为Q1.15格式,再减去零电流值     if (wAux < S16_MIN)         Local_Stator_Currents.qI_Component1= S16_MIN;  //下限幅     else  if (wAux > S16_MAX)         Local_Stator_Currents.qI_Component1= S16_MAX;  //上限幅     else         Local_Stator_Currents.qI_Component1= wAux;       wAux = ((ADC2->JDR1)<<1)-(s32)(hPhaseBOffset);     //B相电流     if (wAux < S16_MIN)         Local_Stator_Currents.qI_Component2= S16_MIN;     else  if (wAux > S16_MAX)         Local_Stator_Currents.qI_Component2= S16_MAX;     else         Local_Stator_Currents.qI_Component2= wAux;       return(Local_Stator_Currents); }   #define divSQRT_3    (s16)0x49E6      //1/sqrt(3)的Q15格式,1/sqrt(3)*2^15=18918=0x49E6    /********************************************************************************************************** Clarke变换,输入Ia,Ib,得到Ialpha和Ibeta **********************************************************************************************************/  Curr_Components Clarke(Curr_Components Curr_Input)          {   Curr_Components Curr_Output;    s32 qIa_divSQRT3_tmp;   s32 qIb_divSQRT3_tmp;    //定义32位有符号数,用来暂存Q30格式     s16 qIa_divSQRT3;   s16 qIb_divSQRT3 ;     Curr_Output.qI_Component1 = Curr_Input.qI_Component1;     //Ialpha = Ia     qIa_divSQRT3_tmp = divSQRT_3 * Curr_Input.qI_Component1;  //计算Ia/√3   qIa_divSQRT3_tmp /=32768;                                 //两个Q15数相乘,会变成Q30,因此要右移15位,变回Q15           qIb_divSQRT3_tmp = divSQRT_3 * Curr_Input.qI_Component2;  //计算Ib/√3   qIb_divSQRT3_tmp /=32768;      qIa_divSQRT3=((s16)(qIa_divSQRT3_tmp));                    //s32赋值给s16                qIb_divSQRT3=((s16)(qIb_divSQRT3_tmp));                         Curr_Output.qI_Component2=(-(qIa_divSQRT3)-(qIb_divSQRT3)-(qIb_divSQRT3));  //Ibeta = -(2*Ib+Ia)/sqrt(3)    return(Curr_Output);  }   #define SIN_MASK  0x0300 #define U0_90     0x0200 #define U90_180   0x0300 #define U180_270  0x0000 #define U270_360  0x0100 const s16 hSin_Cos_Table[256] = {\ 0x0000,0x00C9,0x0192,0x025B,0x0324,0x03ED,0x04B6,0x057F,\ 0x0648,0x0711,0x07D9,0x08A2,0x096A,0x0A33,0x0AFB,0x0BC4,\ 0x0C8C,0x0D54,0x0E1C,0x0EE3,0x0FAB,0x1072,0x113A,0x1201,\ 0x12C8,0x138F,0x1455,0x151C,0x15E2,0x16A8,0x176E,0x1833,\ 0x18F9,0x19BE,0x1A82,0x1B47,0x1C0B,0x1CCF,0x1D93,0x1E57,\ 0x1F1A,0x1FDD,0x209F,0x2161,0x2223,0x22E5,0x23A6,0x2467,\ 0x2528,0x25E8,0x26A8,0x2767,0x2826,0x28E5,0x29A3,0x2A61,\ 0x2B1F,0x2BDC,0x2C99,0x2D55,0x2E11,0x2ECC,0x2F87,0x3041,\ 0x30FB,0x31B5,0x326E,0x3326,0x33DF,0x3496,0x354D,0x3604,\ 0x36BA,0x376F,0x3824,0x38D9,0x398C,0x3A40,0x3AF2,0x3BA5,\ 0x3C56,0x3D07,0x3DB8,0x3E68,0x3F17,0x3FC5,0x4073,0x4121,\ 0x41CE,0x427A,0x4325,0x43D0,0x447A,0x4524,0x45CD,0x4675,\ 0x471C,0x47C3,0x4869,0x490F,0x49B4,0x4A58,0x4AFB,0x4B9D,\ 0x4C3F,0x4CE0,0x4D81,0x4E20,0x4EBF,0x4F5D,0x4FFB,0x5097,\ 0x5133,0x51CE,0x5268,0x5302,0x539B,0x5432,0x54C9,0x5560,\ 0x55F5,0x568A,0x571D,0x57B0,0x5842,0x58D3,0x5964,0x59F3,\ 0x5A82,0x5B0F,0x5B9C,0x5C28,0x5CB3,0x5D3E,0x5DC7,0x5E4F,\ 0x5ED7,0x5F5D,0x5FE3,0x6068,0x60EB,0x616E,0x61F0,0x6271,\ 0x62F1,0x6370,0x63EE,0x646C,0x64E8,0x6563,0x65DD,0x6656,\ 0x66CF,0x6746,0x67BC,0x6832,0x68A6,0x6919,0x698B,0x69FD,\ 0x6A6D,0x6ADC,0x6B4A,0x6BB7,0x6C23,0x6C8E,0x6CF8,0x6D61,\ 0x6DC9,0x6E30,0x6E96,0x6EFB,0x6F5E,0x6FC1,0x7022,0x7083,\ 0x70E2,0x7140,0x719D,0x71F9,0x7254,0x72AE,0x7307,0x735E,\ 0x73B5,0x740A,0x745F,0x74B2,0x7504,0x7555,0x75A5,0x75F3,\ 0x7641,0x768D,0x76D8,0x7722,0x776B,0x77B3,0x77FA,0x783F,\ 0x7884,0x78C7,0x7909,0x794A,0x7989,0x79C8,0x7A05,0x7A41,\ 0x7A7C,0x7AB6,0x7AEE,0x7B26,0x7B5C,0x7B91,0x7BC5,0x7BF8,\ 0x7C29,0x7C59,0x7C88,0x7CB6,0x7CE3,0x7D0E,0x7D39,0x7D62,\ 0x7D89,0x7DB0,0x7DD5,0x7DFA,0x7E1D,0x7E3E,0x7E5F,0x7E7E,\ 0x7E9C,0x7EB9,0x7ED5,0x7EEF,0x7F09,0x7F21,0x7F37,0x7F4D,\ 0x7F61,0x7F74,0x7F86,0x7F97,0x7FA6,0x7FB4,0x7FC1,0x7FCD,\ 0x7FD8,0x7FE1,0x7FE9,0x7FF0,0x7FF5,0x7FF9,0x7FFD,0x7FFE};     /******************************************************************************* * Function Name  : Trig_Functions  * Description    : 本函数返回输入角度的cos和sin函数值 * Input          : angle in s16 format * Output         : Cosine and Sine in s16 format *******************************************************************************/ Trig_Components Trig_Functions(s16 hAngle)  //hAngle=0,转子电角度=0度。hAngle=S16_MAX,转子电角度=180度。hAngle=S16_MIN,转子电角度=-180度 {   u16 hindex;   Trig_Components Local_Components;      /* 10 bit index computation  */     hindex = (u16)(hAngle + 32768);     hindex /= 64;            switch (hindex & SIN_MASK)    {   case U0_90:     Local_Components.hSin = hSin_Cos_Table[(u8)(hindex)];     Local_Components.hCos = hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];     break;      case U90_180:        Local_Components.hSin = hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];      Local_Components.hCos = -hSin_Cos_Table[(u8)(hindex)];     break;      case U180_270:      Local_Components.hSin = -hSin_Cos_Table[(u8)(hindex)];      Local_Components.hCos = -hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];     break;      case U270_360:      Local_Components.hSin =  -hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];      Local_Components.hCos =  hSin_Cos_Table[(u8)(hindex)];      break;   default:     break;   }   return (Local_Components); }   Trig_Components Vector_Components; /********************************************************************************************************** Park变换,输入电角度、Ialpha和Ibeta,经过Park变换得到Iq、Id **********************************************************************************************************/  Curr_Components Park(Curr_Components Curr_Input, s16 Theta)        {    Curr_Components Curr_Output;   s32 qId_tmp_1, qId_tmp_2;   s32 qIq_tmp_1, qIq_tmp_2;        s16 qId_1, qId_2;     s16 qIq_1, qIq_2;        Vector_Components = Trig_Functions(Theta);                      //计算电角度的cos和sin      qIq_tmp_1 = Curr_Input.qI_Component1 * Vector_Components.hCos;  //计算Ialpha*cosθ       qIq_tmp_1 /= 32768;   qIq_tmp_2 = Curr_Input.qI_Component2 *Vector_Components.hSin;   //计算Ibeta*sinθ   qIq_tmp_2 /= 32768;     qIq_1 = ((s16)(qIq_tmp_1));   qIq_2 = ((s16)(qIq_tmp_2));   Curr_Output.qI_Component1 = ((qIq_1)-(qIq_2));    //Iq=Ialpha*cosθ- Ibeta*sinθ      qId_tmp_1 = Curr_Input.qI_Component1 * Vector_Components.hSin;  //计算Ialpha*sinθ   qId_tmp_1 /= 32768;   qId_tmp_2 = Curr_Input.qI_Component2 * Vector_Components.hCos;  //计算Ibeta*cosθ   qId_tmp_2 /= 32768;      qId_1 = (s16)(qId_tmp_1);            qId_2 = (s16)(qId_tmp_2);                       Curr_Output.qI_Component2 = ((qId_1)+(qId_2));   //Id=Ialpha*sinθ+ Ibeta*cosθ      return (Curr_Output); }   /********************************************************************************************************** 反park变换,输入Uq、Ud得到Ualpha、Ubeta **********************************************************************************************************/  Volt_Components Rev_Park(Volt_Components Volt_Input) {        s32 qValpha_tmp1,qValpha_tmp2,qVbeta_tmp1,qVbeta_tmp2;   s16 qValpha_1,qValpha_2,qVbeta_1,qVbeta_2;   Volt_Components Volt_Output;       qValpha_tmp1 = Volt_Input.qV_Component1 * Vector_Components.hCos;  //Uq*cosθ   qValpha_tmp1 /= 32768;    qValpha_tmp2 = Volt_Input.qV_Component2 * Vector_Components.hSin;  //Ud*sinθ   qValpha_tmp2 /= 32768;            qValpha_1 = (s16)(qValpha_tmp1);           qValpha_2 = (s16)(qValpha_tmp2);               Volt_Output.qV_Component1 = ((qValpha_1)+(qValpha_2));             //Ualpha=Uq*cosθ+ Ud*sinθ      qVbeta_tmp1 = Volt_Input.qV_Component1 * Vector_Components.hSin;   //Uq*sinθ   qVbeta_tmp1 /= 32768;     qVbeta_tmp2 = Volt_Input.qV_Component2 * Vector_Components.hCos;   //Ud*cosθ   qVbeta_tmp2 /= 32768;     qVbeta_1 = (s16)(qVbeta_tmp1);                   qVbeta_2 = (s16)(qVbeta_tmp2);                     Volt_Output.qV_Component2 = -(qVbeta_1)+(qVbeta_2);                //Ubeta=Ud*cosθ- Uq*sinθ     return(Volt_Output); } 1、读取ADC注入组的转换值:ADC1->JDR1和ADC2->JDR1。数据的对齐还是满足下表的关系:

我们是注入组左对齐,因此右移3位才是真实的转换值。电流采样值的Q15格式为:((ADC1->JDR1<<1)>>4)/4096*Vref*2^15=((((ADC1->JDR1<<1)>>4)>>12)*Vref)<<15=ADC1->JDR1<<1.

2、用ADC采集电机的两相电流,即可通过Ia+Ib+Ic=0得到第三相的电流。电机的三相电流是在时间上互差120度,成正弦规律变化的波形。电流波形如下:

表示为矢量:

我们通过clark变换,把Ia、Ib、Ic变换为Iα、Iβ:

变换公式:(可以当成矢量分解来理解)

公式中有一个系数k,一般取2/3。有兴趣可以去这儿了解:https://blog.csdn.net/daidi1989/article/details/89926324。带入2/3:

再代入Ia+Ib+Ic=0:

注:ST的FOC库,采用的Iβ轴(即虚轴)滞后Iα90度的表示方法。与其他地方说的Iβ提前Iα90度是一样的效果。只是表示方法不同而已,选择了这种表示方法,后面的park变换也和提前90度的不同。

3、Trig_Functions函数,使用查表的方法计算目标角度的三角函数。速度快,精度也还可以,读者可以自己测试一下精度。

4、使用park变换将电流 Iα、Iβ 和转子的电角度θ转化为电流 Iq、Id。

公式为:

5、反park变换就是上面park变换的逆过程,公式就不推了。

 

五、PID控制

typedef struct {     s16 hKp_Gain;               //比例系数     u16 hKp_Divisor;           //比例系数因子     s16 hKi_Gain;               //积分系数     u16 hKi_Divisor;             //积分系数因子     s16 hLower_Limit_Output;   //总输出下限     s16 hUpper_Limit_Output;   //总输出上限     s32 wLower_Limit_Integral; //积分项下限     s32 wUpper_Limit_Integral; //积分项上限     s32 wIntegral;               //积分累积和     s16 hKd_Gain;               //微分系数     u16 hKd_Divisor;           //微分系数因子     s32 wPreviousError;           //上次误差 } PID_Struct_t; /****************************** 扭矩的PID参数,即q轴 *******************************************************/ #define PID_TORQUE_REFERENCE   (s16)3000  //q轴的设定值,PID的目的就是要让测量的q轴值与设定值误差为0     #define PID_TORQUE_KP_DEFAULT  (s16)1578  //Kp默认值 #define PID_TORQUE_KI_DEFAULT  (s16)676   //Ki默认值 #define PID_TORQUE_KD_DEFAULT  (s16)100   //Kd默认值 /****************************** 转子磁通的PID参数,即d轴 *******************************************************/ #define PID_FLUX_REFERENCE   (s16)0       //d轴的设定值 #define PID_FLUX_KP_DEFAULT  (s16)1578 #define PID_FLUX_KI_DEFAULT  (s16)676 #define PID_FLUX_KD_DEFAULT  (s16)100 /****************************** q轴和d轴PID参数的放大倍数 *******************************************************/ #define TF_KPDIV ((u16)(1024))    //因为Kp、Ki、Kd值很小,而我们需要整数计算,所以需要放大。得出计算结果之后,再缩小。 #define TF_KIDIV ((u16)(16384)) #define TF_KDDIV ((u16)(8192))   /*****************

标签: 传感器bc5wx11电位器3a电流138电位器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台