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_OCInitStructure.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))
-
-
/
****
****
****
****
****
****
****
** 速度环的PID参数 **
****
****
****
****
****
****
****
****
****
****
****
****
****
*/
-
#define PID_SPEED_REFERENCE_RPM (s16)1500 //电机的设定转速
-
#define PID_SPEED_REFERENCE (u16)(PID_SPEED_REFERENCE_RPM/6) //电机转速和速度环的设定值一般都不相等,电机不同,它们的关系也不同
-
#define PID_SPEED_KP_DEFAULT (s16)300
-
#define PID_SPEED_KI_DEFAULT (s16)100
-
#define PID_SPEED_KD_DEFAULT (s16)0000
-
#define NOMINAL_CURRENT (s16)5289 //motor nominal current (0-pk),3倍的额定电流
-
#define IQMAX NOMINAL_CURRENT //速度环输出最大值
-
-
/****************************** 速度环PID参数的放大倍数 *******************************************************/
-
#define SP_KPDIV ((u16)(16))
-
#define SP_KIDIV ((u16)(256))
-
#define SP_KDDIV ((u16)(16))
-
-
volatile s16 hTorque_Reference; //q轴设定值
-
volatile s16 hFlux
_Reference; //d轴设定值
-
volatile s16 hSpeed_Reference; //速度环设定值
-
void PID
_Init (PID_Struct
_t *PID_Torque, PID
_Struct_t
*PID_Flux, PID_Struct_t *PID
_Speed)
-
{
-
hTorque_Reference = PID
_TORQUE_REFERENCE; //q轴设定值初始化
-
/
****
****
****
****
****
****
****
****
****
****
*** 下面是控制扭矩的PID参数,即q轴大小 **************************************************************/
-
PID_Torque->hKp_Gain = PID_TORQUE_KP_DEFAULT; //Kp参数,放大了hKp_Divisor倍。调节结果除以hKp_Divisor才是真实结果
-
PID_Torque->hKp
_Divisor = TF_KPDIV; //Kp参数分数因子
-
PID_Torque->hKi_Gain = PID_TORQUE_KI_DEFAULT; //Ki参数
-
PID_Torque->hKi
_Divisor = TF_KIDIV; //Ki参数分数因子
-
PID_Torque->hKd_Gain = PID_TORQUE_KD_DEFAULT; //Kd参数
-
PID_Torque->hKd
_Divisor = TF_KDDIV; //Kd参数分数因子
-
PID_Torque->wPreviousError = 0; //上次计算的误差值,用于D调节
-
PID_Torque->hLower
_Limit_Output=S16
_MIN; //PID输出下限幅
-
PID_Torque->hUpper
_Limit_Output= S16
_MAX; //PID输出上限幅
-
PID_Torque->wLower
_Limit_Integral = S16
_MIN * TF_KIDIV; //I调节的下限福
-
PID_Torque->wUpper_Limit_Integral = S16_MAX * TF_KIDIV; //I调节的上限幅
-
PID_Torque->wIntegral = 0; //I调节的结果,因为是积分,所以要一直累积
-
/******************************************* 上面是控制扭矩的PID参数,即q轴大小 **************************************************************/
-
-
hFlux_Reference = PID_FLUX_REFERENCE; //对于SM-PMSM电机,Id = 0
-
/******************************************* 下面是控制转子磁通的PID参数,即d轴大小 **************************************************************/
-
PID_Flux->hKp_Gain = PID_FLUX_KP_DEFAULT;
-
PID_Flux->hKp
_Divisor = TF_KPDIV;
-
PID_Flux->hKi_Gain = PID_FLUX_KI_DEFAULT;
-
PID_Flux->hKi
_Divisor = TF_KIDIV;
-
PID_Flux->hKd_Gain = PID_FLUX_KD_DEFAULT;
-
PID_Flux->hKd
_Divisor = TF_KDDIV;
-
PID_Flux->wPreviousError = 0;
-
PID_Flux->hLower
_Limit_Output=S16
_MIN;
-
PID_Flux->hUpper
_Limit_Output= S16
_MAX;
-
PID_Flux->wLower
_Limit_Integral = S16
_MIN * TF_KIDIV;
-
PID_Flux->wUpper_Limit_Integral = S16_MAX * TF_KIDIV;
-
PID_Flux->wIntegral = 0;
-
/******************************************* 上面是控制转子磁通的PID参数,即d轴大小 **************************************************************/
-
-
hSpeed_Reference = PID_SPEED_REFERENCE;
-
/******************************************* 下面是速度环的PID参数 **************************************************************/
-
PID_Speed->hKp_Gain = PID_SPEED_KP_DEFAULT;
-
PID_Speed->hKp
_Divisor = SP_KPDIV;
-
PID_Speed->hKi_Gain = PID_SPEED_KI_DEFAULT;
-
PID_Speed->hKi
_Divisor = SP_KIDIV;
-
PID_Speed->hKd_Gain = PID_SPEED_KD_DEFAULT;
-
PID_Speed->hKd
_Divisor = SP_KDDIV;
-
PID_Speed->wPreviousError = 0;
-
PID_Speed->hLower
_Limit_Output= -IQMAX;
-
PID_Speed->hUpper_Limit_Output= IQMAX;
-
PID_Speed->wLower
_Limit_Integral = -IQMAX
* SP_KIDIV;
-
PID_Speed->wUpper_Limit_Integral = IQMAX * SP
_KIDIV;
-
PID_Speed->wIntegral = 0;
-
/**
****
****
****
****
****
****
****
****
****
***** 上面是速度环的PID参数 **
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****/
-
}
-
-
//#define DIFFERENTIAL_TERM
_ENABLED //不使用PID的D调节
-
typedef signed long long s64;
-
s16 PID_Regulator(s16 hReference, s16 hPresentFeedback, PID
_Struct_t
*PID_Struct)
-
{
-
s32 wError, wProportional_Term,wIntegral_Term, houtput_32;
-
s64 dwAux;
-
#ifdef DIFFERENTIAL_TERM_ENABLED //如果使能了D调节
-
s32 wDifferential_Term;
-
#endif
-
-
wError= (s32)(hReference - hPresentFeedback); //设定值-反馈值,取得需要误差量delta_e
-
wProportional_Term = PID_Struct->hKp_Gain * wError; //PID的P调节,即比例放大调节:wP = Kp
* delta_e
-
-
if (PID_Struct->hKi_Gain == 0) //下面进行PID的I调节,即误差的累积调节
-
{
-
PID_Struct->wIntegral = 0; //如果I参数=0,I调节就=0
-
}
-
else
-
{
-
wIntegral_Term = PID_Struct->hKi_Gain * wError; //wI = Ki
* delta_e ,本次积分项
-
dwAux = PID_Struct->wIntegral + (s64)(wIntegral_Term); //积分累积的调节量 = 以前的积分累积量 + 本次的积分项
-
-
if (dwAux > PID_Struct->wUpper_Limit_Integral) //对PID的I调节做限幅
-
{
-
PID_Struct->wIntegral = PID_Struct->wUpper_Limit_Integral; //上限
-
}
-
else if (dwAux < PID_Struct->wLower_Limit_Integral) //下限
-
{
-
PID_Struct->wIntegral = PID_Struct->wLower_Limit_Integral;
-
}
-
else
-
{
-
PID_Struct->wIntegral = (s32)(dwAux); //不超限, 更新积分累积项为dwAux
-
}
-
}
-
#ifdef DIFFERENTIAL_TERM_ENABLED //如果使能了D调节
-
{
-
s32 wtemp;
-
-
wtemp = wError - PID_Struct->wPreviousError; //取得上次和这次的误差之差
-
wDifferential_Term = PID_Struct->hKd_Gain * wtemp; //D调节结果,wD = Kd
* delta_d
-
PID_Struct->wPreviousError = wError; //更新上次误差,用于下次运算
-
-
}
-
houtput_32 = (wProportional_Term/PID_Struct->hKp_Divisor+ //输出总的调节量 = 比例调节量/分数因子 +
-
PID_Struct->wIntegral/PID_Struct->hKi_Divisor + // + 积分调节量/分数因子
-
wDifferential_Term/PID_Struct->hKd_Divisor); // + 微分调节量/分数因子
-
-
#else
-
//把P调节和I调节结果除以分数因子再相加,得到PI控制的结果
-
houtput_32 = (wProportional_Term/PID_Struct->hKp_Divisor + PID_Struct->wIntegral/PID_Struct->hKi_Divisor);
-
#endif
-
if (houtput_32 >= PID_Struct->hUpper_Limit_Output) //PI控制结果限幅
-
{
-
return(PID_Struct->hUpper_Limit_Output);
-
}
-
else if (houtput_32 < PID_Struct->hLower_Limit_Output) //下限
-
{
-
return(PID_Struct->hLower_Limit_Output);
-
}
-
else
-
{
-
return((s16)(houtput_32)); //不超限。输出结果 houtput_32
-
}
-
}
PID控制没什么好说的,网上资料很多。此处用的PID也很普通,很容易看懂。
使用了宏定义,选择是否使用PID的D调节。一般PI控制就已经足够。
6、使用RevPark_Circle_Limitation函数对PID的输出进行归一化
看下面的FOC的处理过程:
可以发现在PID计算后,要进行CircleLimitation处理,为什么呢?因为我们需要控制旋转磁场大小的恒定,如果d、q轴给得太大了,那么输出的旋转磁场就会过调制,圆形磁场就会凸起来一块。PID控制器只能对单独的d、q轴大小进行限制,可是它控制不了d、q轴合成矢量的大小。比如:最大值要求限制在1,d=0.8,q=0.9,他们各自的大小都没有超过1,可是它们的合成矢量大小却超过了1。示意图如下,就是要用比例关系,把外圆上的d、q值等比例缩小到内圆上:
-
/****
根据载波频率来选择调制系数,频率越大,调制系数越小(实质是控制的最大占空比)
****/
-
//#define
MAX_MODULATION_100_PER_CENT
//
up
to
11.4
kHz
PWM
frequency
-
//#define
MAX_MODULATION_99_PER_CENT
//
up
to
11.8
kHz
-
//#define
MAX_MODULATION_98_PER_CENT
//
up
to
12.2
kHz
-
//#define
MAX_MODULATION_97_PER_CENT
//
up
to
12.9
kHz
-
//#define
MAX_MODULATION_96_PER_CENT
//
up
to
14.4
kHz
-
#define MAX_MODULATION_95_PER_CENT // up to 14.8 kHz
-
//#define
MAX_MODULATION_94_PER_CENT
//
up
to
15.2
kHz
-
//#define
MAX_MODULATION_93_PER_CENT
//
up
to
16.7
kHz
-
//#define
MAX_MODULATION_92_PER_CENT
//
up
to
17.1
kHz
-
//#define
MAX_MODULATION_89_PER_CENT
//
up
to
17.5
kHz
-
-
/****
以下是根据选择的调制系数,计算d、q轴合成矢量模的最大值。用宏定义,优化计算速度
****/
-
#ifdef MAX_MODULATION_77_PER_CENT
-
#define MAX_MODULE 25230 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*77%
-
#endif
-
-
#ifdef MAX_MODULATION_79_PER_CENT
-
#define MAX_MODULE 25885 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*79%
-
#endif
-
-
#ifdef MAX_MODULATION_81_PER_CENT
-
#define MAX_MODULE 26541 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*81%
-
#endif
-
-
#ifdef MAX_MODULATION_83_PER_CENT
-
#define MAX_MODULE 27196 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*83%
-
#endif
-
-
#ifdef MAX_MODULATION_85_PER_CENT
-
#define MAX_MODULE 27851 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*85%
-
#endif
-
-
#ifdef MAX_MODULATION_87_PER_CENT
-
#define MAX_MODULE 28507 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*87%
-
#endif
-
-
#ifdef MAX_MODULATION_89_PER_CENT
-
#define MAX_MODULE 29162 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*89%
-
#endif
-
-
#ifdef MAX_MODULATION_91_PER_CENT
-
#define MAX_MODULE 29817 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*91%
-
#endif
-
-
#ifdef MAX_MODULATION_92_PER_CENT
-
#define MAX_MODULE 30145 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*92%
-
#endif
-
-
#ifdef MAX_MODULATION_93_PER_CENT
-
#define MAX_MODULE 30473 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*93%
-
#endif
-
-
#ifdef MAX_MODULATION_94_PER_CENT
-
#define MAX_MODULE 30800 //root(Vd^2+Vq^2) <= MAX_MODULE = 32767*94%
-
#endif
-
-
#ifdef MAX_MODULATION_95_PER_CENT
-
#define MAX_MODULE 31128 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*95%
-
#endif
-
-
#ifdef MAX_MODULATION_96_PER_CENT
-
#define MAX_MODULE 31456 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*96%
-
#endif
-
-
#ifdef MAX_MODULATION_97_PER_CENT
-
#define MAX_MODULE 31783 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*97%
-
#endif
-
-
#ifdef MAX_MODULATION_98_PER_CENT
-
#define MAX_MODULE 32111 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*98%
-
#endif
-
-
#ifdef MAX_MODULATION_99_PER_CENT
-
#define MAX_MODULE 32439 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*99%
-
#endif
-
-
#ifdef MAX_MODULATION_100_PER_CENT
-
#define MAX_MODULE 32767 // root(Vd^2+Vq^2) <= MAX_MODULE = 32767*100%
-
#endif
-
-
#ifdef MAX_MODULATION_77_PER_CENT
-
#define START_INDEX 37
-
static
const
u16
circle_limit_table[91]=
-
{
-
32468,
32176,
31893,
31347,
31084,
30578,
30334,
29863,
29636,
29414,
28984,
28776,
28373,
-
28178,
27987,
27616,
27436,
27086,
26916,
26585,
26425,
26267,
25959,
25809,
25518,
25375,
-
25098,
24962,
24829,
24569,
24442,
24194,
24072,
23953,
23719,
23604,
23381,
23271,
23056,
-
22951,
22848,
22644,
22545,
22349,
22254,
22066,
21974,
21883,
21704,
21616,
21444,
21359,
-
21275,
21111,
21030,
20872,
20793,
20640,
20564,
20490,
20343,
20270,
20128,
20058,
19920,
-
19852,
19785,
19653,
19587,
19459,
19396,
19271,
19209,
19148,
19028,
18969,
18852,
18795,
-
18738,
18625,
18570,
18460,
18406,
18299,
18246,
18194,
18091,
18040,
17940,
17890,
17792
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_79_PER_CENT
-
#define START_INDEX 39
-
static
const
u16
circle_limit_table[89]=
-
{
-
32489,
32217,
31952,
31442,
31195,
30719,
30489,
30045,
29830,
29413,
29211,
28819,
28629,
-
28442,
28080,
27904,
27562,
27396,
27072,
26914,
26607,
26457,
26165,
26022,
25882,
25608,
-
25475,
25214,
25086,
24836,
24715,
24476,
24359,
24130,
24019,
23908,
23692,
23586,
23378,
-
23276,
23077,
22979,
22787,
22692,
22507,
22416,
22326,
22150,
22063,
21893,
21809,
21645,
-
21564,
21405,
21327,
21173,
21097,
21022,
20875,
20802,
20659,
20589,
20450,
20382,
20247,
-
20181,
20051,
19986,
19923,
19797,
19735,
19613,
19553,
19434,
19375,
19260,
19202,
19090,
-
19034,
18979,
18871,
18817,
18711,
18659,
18555,
18504,
18403,
18354,
18255
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_81_PER_CENT
-
#define START_INDEX 41
-
static
const
u16
circle_limit_table[87]=
-
{
-
32508,
32255,
32008,
31530,
31299,
30852,
30636,
30216,
30012,
29617,
29426,
29053,
28872,
-
28520,
28349,
28015,
27853,
27536,
27382,
27081,
26934,
26647,
26507,
26234,
26101,
25840,
-
25712,
25462,
25340,
25101,
24984,
24755,
24643,
24422,
24315,
24103,
24000,
23796,
23696,
-
23500,
23404,
23216,
23123,
22941,
22851,
22763,
22589,
22504,
22336,
22253,
22091,
22011,
-
21854,
21776,
21624,
21549,
21401,
21329,
21186,
21115,
20976,
20908,
20773,
20706,
20575,
-
20511,
20383,
20320,
20196,
20135,
20015,
19955,
19838,
19780,
19666,
19609,
19498,
19443,
-
19334,
19280,
19175,
19122,
19019,
18968,
18867,
18817,
18719
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_83_PER_CENT
-
#define START_INDEX 44
-
static
const
u16
circle_limit_table[84]=
-
{
-
32291,
32060,
31613,
31397,
30977,
30573,
30377,
29996,
29811,
29451,
29276,
28934,
28768,
-
28444,
28286,
27978,
27827,
27533,
27390,
27110,
26973,
26705,
26574,
26318,
26069,
25948,
-
25709,
25592,
25363,
25251,
25031,
24923,
24711,
24607,
24404,
24304,
24107,
24011,
23821,
-
23728,
23545,
23456,
23279,
23192,
23021,
22854,
22772,
22610,
22530,
22374,
22297,
22145,
-
22070,
21922,
21850,
21707,
21636,
21497,
21429,
21294,
21227,
21096,
21032,
20904,
20778,
-
20717,
20595,
20534,
20416,
20357,
20241,
20184,
20071,
20015,
19905,
19851,
19743,
19690,
-
19585,
19533,
19431,
19380,
19280,
19182
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_85_PER_CENT
-
#define START_INDEX 46
-
static
const
u16
circle_limit_table[82]=
-
{
-
32324,
32109,
31691,
31489,
31094,
30715,
30530,
30170,
29995,
29654,
29488,
29163,
29005,
-
28696,
28397,
28250,
27965,
27825,
27552,
27418,
27157,
26903,
26779,
26535,
26416,
26182,
-
26067,
25842,
25623,
25515,
25304,
25201,
24997,
24897,
24701,
24605,
24415,
24230,
24139,
-
23960,
23872,
23699,
23614,
23446,
23282,
23201,
23042,
22964,
22810,
22734,
22584,
22437,
-
22365,
22223,
22152,
22014,
21945,
21811,
21678,
21613,
21484,
21421,
21296,
21234,
21112,
-
21051,
20932,
20815,
20757,
20643,
20587,
20476,
20421,
20312,
20205,
20152,
20048,
19996
-
,
19894,
19844,
19744,
19645
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_87_PER_CENT
-
#define START_INDEX 48
-
static
const
u16
circle_limit_table[81]=
-
{
-
32559,
32154,
31764,
31575,
31205,
31025,
30674,
30335,
30170,
29847,
-
29689,
29381,
29083,
28937,
28652,
28375,
28239,
27974,
27844,
27589,
-
27342,
27220,
26983,
26866,
26637,
26414,
26305,
26090,
25984,
25777,
-
25575,
25476,
25280,
25184,
24996,
24811,
24720,
24542,
24367,
24281,
-
24112,
24028,
23864,
23703,
23624,
23468,
23391,
23240,
23091,
23018,
-
22874,
22803,
22662,
22524,
22456,
22322,
22191,
22126,
21997,
21934,
-
21809,
21686,
21625,
21505,
21446,
21329,
21214,
21157,
21045,
20990,
-
20880,
20772,
20719,
20613,
20561,
20458,
20356,
20306,
20207,
20158,
-
20109
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_89_PER_CENT
-
#define START_INDEX 50
-
static
const
u16
circle_limit_table[78]=
-
{
-
32574,
32197,
32014,
31656,
31309,
31141,
30811,
30491,
30335,
30030,
-
29734,
29589,
29306,
29031,
28896,
28632,
28375,
28249,
28002,
27881,
-
27644,
27412,
27299,
27076,
26858,
26751,
26541,
26336,
26235,
26037,
-
25844,
25748,
25561,
25378,
25288,
25110,
24936,
24851,
24682,
24517,
-
24435,
24275,
24118,
24041,
23888,
23738,
23664,
23518,
23447,
23305,
-
23166,
23097,
22962,
22828,
22763,
22633,
22505,
22442,
22318,
22196,
-
22135,
22016,
21898,
21840,
21726,
21613,
21557,
21447,
21338,
21284,
-
21178,
21074,
21022,
20919,
20819,
20769,
20670,
20573
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_91_PER_CENT
-
#define START_INDEX 52
-
static
const
u16
circle_limit_table[76]=
-
{
-
32588,
32411,
32066,
31732,
31569,
31250,
30940,
30789,
30492,
30205,
-
29925,
29788,
29519,
29258,
29130,
28879,
28634,
28395,
28278,
28048,
-
27823,
27713,
27497,
27285,
27181,
26977,
26777,
26581,
26485,
26296,
-
26110,
26019,
25840,
25664,
25492,
25407,
25239,
25076,
24995,
24835,
-
24679,
24602,
24450,
24301,
24155,
24082,
23940,
23800,
23731,
23594,
-
23460,
23328,
23263,
23135,
23008,
22946,
22822,
22701,
22641,
22522,
-
22406,
22291,
22234,
22122,
22011,
21956,
21848,
21741,
21636,
21584,
-
21482,
21380,
21330,
21231,
21133,
21037
-
}
;
-
#endif
-
-
-
#ifdef MAX_MODULATION_92_PER_CENT
-
#define START_INDEX 54
-
const
u16
circle_limit_table[74]=
-
{
-
32424,
32091,
31929,
31611,
31302,
31002,
30855,
30568,
30289,
30017,
-
29884,
29622,
29368,
29243,
28998,
28759,
28526,
28412,
28187,
27968,
-
27753,
27648,
27441,
27238,
27040,
26942,
26750,
26563,
26470,
26288,
-
26110,
25935,
25849,
25679,
25513,
25350,
25269,
25111,
24955,
24803,
-
24727,
24579,
24433,
24361,
24219,
24079,
23942,
23874,
23740,
23609,
-
23479,
23415,
23289,
23165,
23042,
22982,
22863,
22745,
22629,
22572,
-
22459,
22347,
22292,
22183,
22075,
21970,
21917,
21813,
21711,
21610,
-
21561,
21462,
21365,
21268
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_93_PER_CENT
-
#define START_INDEX 55
-
const
u16
circle_limit_table[73]=
-
{
-
32437,
32275,
31959,
31651,
31353,
31207,
30920,
30642,
30371,
30107,
-
29977,
29723,
29476,
29234,
29116,
28883,
28655,
28433,
28324,
28110,
-
27900,
27695,
27594,
27395,
27201,
27011,
26917,
26733,
26552,
26375,
-
26202,
26116,
25948,
25783,
25621,
25541,
25383,
25228,
25076,
25001,
-
24854,
24708,
24565,
24495,
24356,
24219,
24084,
24018,
23887,
23758,
-
23631,
23506,
23444,
23322,
23202,
23083,
23025,
22909,
22795,
22683,
-
22627,
22517,
22409,
22302,
22250,
22145,
22042,
21941,
21890,
21791,
-
21693,
21596,
21500
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_94_PER_CENT
-
#define START_INDEX 56
-
const
u16
circle_limit_table[72]=
-
{
-
32607,
32293,
31988,
31691,
31546,
31261,
30984,
30714,
30451,
30322,
-
30069,
29822,
29581,
29346,
29231,
29004,
28782,
28565,
28353,
28249,
-
28044,
27843,
27647,
27455,
27360,
27174,
26991,
26812,
26724,
26550,
-
26380,
26213,
26049,
25968,
25808,
25652,
25498,
25347,
25272,
25125,
-
24981,
24839,
24699,
24630,
24494,
24360,
24228,
24098,
24034,
23908,
-
23783,
23660,
23600,
23480,
23361,
23245,
23131,
23074,
22962,
22851,
-
22742,
22635,
22582,
22477,
22373,
22271,
22170,
22120,
22021,
21924,
-
21827,
21732
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_95_PER_CENT
-
#define START_INDEX 57
-
const
u16
circle_limit_table[71]=
-
{
-
32613,
32310,
32016,
31872,
31589,
31314,
31046,
30784,
30529,
30404,
-
30158,
29919,
29684,
29456,
29343,
29122,
28906,
28695,
28488,
28285,
-
28186,
27990,
27798,
27610,
27425,
27245,
27155,
26980,
26808,
26639,
-
26473,
26392,
26230,
26072,
25917,
25764,
25614,
25540,
25394,
25250,
-
25109,
24970,
24901,
24766,
24633,
24501,
24372,
24245,
24182,
24058,
-
23936,
23816,
23697,
23580,
23522,
23408,
23295,
23184,
23075,
23021,
-
22913,
22808,
22703,
22600,
22499,
22449,
22349,
22251,
22154,
22059,
-
21964
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_96_PER_CENT
-
#define START_INDEX 58
-
const
u16
circle_limit_table[70]=
-
{
-
32619,
32472,
32184,
31904,
31631,
31365,
31106,
30853,
30728,
30484,
-
30246,
30013,
29785,
29563,
29345,
29238,
29028,
28822,
28620,
28423,
-
28229,
28134,
27946,
27762,
27582,
27405,
27231,
27061,
26977,
26811,
-
26649,
26489,
26332,
26178,
26027,
25952,
25804,
25659,
25517,
25376,
-
25238,
25103,
25035,
24903,
24772,
24644,
24518,
24393,
24270,
24210,
-
24090,
23972,
23855,
23741,
23627,
23516,
23461,
23352,
23244,
23138,
-
23033,
22930,
22828,
22777,
22677,
22579,
22481,
22385,
22290,
22196
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_97_PER_CENT
-
#define START_INDEX 60
-
const
u16
circle_limit_table[68]=
-
{
-
32483,
32206,
31936,
31672,
31415,
31289,
31041,
30799,
30563,
30331,
-
30105,
29884,
29668,
29456,
29352,
29147,
28947,
28750,
28557,
28369,
-
28183,
28002,
27824,
27736,
27563,
27393,
27226,
27062,
26901,
26743,
-
26588,
26435,
26360,
26211,
26065,
25921,
25780,
25641,
25504,
25369,
-
25236,
25171,
25041,
24913,
24788,
24664,
24542,
24422,
24303,
24186,
-
24129,
24015,
23902,
23791,
23681,
23573,
23467,
23362,
23258,
23206,
-
23105,
23004,
22905,
22808,
22711,
22616,
22521,
22429
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_98_PER_CENT
-
#define START_INDEX 61
-
const
u16
circle_limit_table[67]=
-
{
-
32494,
32360,
32096,
31839,
31587,
31342,
31102,
30868,
30639,
30415,
-
30196,
29981,
29771,
29565,
29464,
29265,
29069,
28878,
28690,
28506,
-
28325,
28148,
27974,
27803,
27635,
27470,
27309,
27229,
27071,
26916,
-
26764,
26614,
26467,
26322,
26180,
26039,
25901,
25766,
25632,
25500,
-
25435,
25307,
25180,
25055,
24932,
24811,
24692,
24574,
24458,
24343,
-
24230,
24119,
24009,
23901,
23848,
23741,
23637,
23533,
23431,
23331,
-
23231,
23133,
23036,
22941,
22846,
22753,
22661
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_99_PER_CENT
-
#define START_INDEX 62
-
-
const
u16
circle_limit_table[66]=
-
{
-
32635,
32375,
32121,
31873,
31631,
31394,
31162,
30935,
30714,
30497,
-
30284,
30076,
29872,
29672,
29574,
29380,
29190,
29003,
28820,
28641,
-
28464,
28291,
28122,
27955,
27791,
27630,
27471,
27316,
27163,
27012,
-
26864,
26718,
26575,
26434,
26295,
26159,
26024,
25892,
25761,
25633,
-
25569,
25444,
25320,
25198,
25078,
24959,
24842,
24727,
24613,
24501,
-
24391,
24281,
24174,
24067,
23963,
23859,
23757,
23656,
23556,
23458,
-
23361,
23265,
23170,
23077,
22984,
22893
-
}
;
-
#endif
-
-
#ifdef MAX_MODULATION_100_PER_CENT
-
#define START_INDEX 63
-
const
u16
circle_limit_table[65]=
-
{
-
32767,
32390,
32146,
31907,
31673,
31444,
31220,
31001,
30787,
30577,
30371,
-
30169,
29971,
29777,
29587,
29400,
29217,
29037,
28861,
28687,
28517,
-
28350,
28185,
28024,
27865,
27709,
27555,
27404,
27256,
27110,
26966,
-
26824,
26685,
26548,
26413,
26280,
26149,
26019,
25892,
25767,
25643,
-
25521,
25401,
25283,
25166,
25051,
24937,
24825,
24715,
24606,
24498,
-
24392,
24287,
24183,
24081,
23980,
23880,
23782,
23684,
23588,
23493,
-
23400,
23307,
23215,
23125
-
}
;
-
#endif
-
-
Volt_Components
Stat_Volt_q_d;
-
/**********************************************************************************************************
-
把经过PID调整过的d、q值的合成矢量模值限制在最大值,如果超过了,那么等比例缩小d、q值
-
**********************************************************************************************************/
-
void
RevPark_Circle_Limitation(void)
-
{
-
s32
temp;
-
//计算Vd^2+Vq^2
-
temp
=
Stat_Volt_q_d.qV_Component1
*
Stat_Volt_q_d.qV_Component1
+
Stat_Volt_q_d.qV_Component2
*
Stat_Volt_q_d.qV_Component2;
-
-
if
(
temp
>
(u32)((
MAX_MODULE
*
MAX_MODULE)
)
)
//如果(Vd^2+Vq^2)大于MAX_MODULE^2,就要进行比例缩小
-
{
-
u16
index;
//假设Vq=x*32767,
Vd=y*32767
-
-
temp
/=
(u32)(512*32768);
//(Vq^2+Vd^2)/(512*32768)
=
(x^2+y^2)*32767^2/(512*32768)=64(x^2+y^2)
-
temp
-=
START_INDEX
;
//程序中,载波频率=15K,因此调制系数选择95%,MAX_MODULE=32767*95%=31128,START_INDEX=57
-
index
=
circle_limit_table[
(u8)temp]
;
//当Vq、Vd接近于最大值32767,即x、y接近于1,temp=64(x^2+y^2)-START_INDEX=128-57有最大值71
-
//当Vd^2+Vq^2的值只比MAX_MODULE^2大一点,temp=(Vq^2+Vd^2)/(512*32768)-START_INDEX=MAX_MODULE^2/(512*32768)-57有最小值0
-
//因此调制系数为95%的circle_limit_table表中只有72个数,并且根据Vd^2+Vq^2的大小来选择缩小的系数
-
temp
=
(s16)Stat_Volt_q_d.qV_Component1
*
(u16)(index);
//使用缩小系数来缩小Vq
-
Stat_Volt_q_d.qV_Component1
=
(s16)(temp/32768);
-
-
temp
=
(s16)Stat_Volt_q_d.qV_Component2
*
(u16)(index);
//使用缩小系数来缩小Vd
-
Stat_Volt_q_d.qV_Component2
=
(s16)(temp/32768);
-
}
-
}
上面的过程也没有什么好说的,具体可以看注释。为了优化处理速度,能用查表的地方都用查表了。
7、SVPWM的实现
根据FOC的处理过程,相电流采样后经过clark变换、park变换得到d、q轴值,然后和参考值做PID运算,再经过归一化,反park变换得到 Vα、Vβ,这就是FOC的处理结果,然后输出给SVPWM去执行,下面我们来看看SVPWM处理函数:
-
#define SQRT_3 1.732051 //根号3
-
#define T (PWM_PERIOD * 4) //TIM1 ARR值的4倍
-
#define T_SQRT3 (u16)(T * SQRT_3)
-
#define SECTOR_1 (u32)1
-
#define SECTOR_2 (u32)2
-
#define SECTOR_3 (u32)3
-
#define SECTOR_4 (u32)4
-
#define SECTOR_5 (u32)5
-
#define SECTOR_6 (u32)6
-
-
void
CALC_SVPWM(Volt_Components Stat_Volt_Input)
-
{
-
u8 bSector;
-
s32 wX, wY, wZ, wUAlpha, wUBeta;
-
u16 hTimePhA=
0, hTimePhB=
0, hTimePhC=
0;
-
-
wUAlpha = Stat_Volt_Input.qV_Component1 * T_SQRT3;
-
wUBeta = -(Stat_Volt_Input.qV_Component2 * T);
-
-
wX = wUBeta;
-
wY = (wUBeta + wUAlpha)/
2;
-
wZ = (wUBeta - wUAlpha)/
2;
-
-
//下面是查找定子电流的扇区号
-
if (wY<
0)
-
{
-
if (wZ<
0)
-
{
-
bSector = SECTOR_5;
-
}
-
else
// wZ >= 0
-
if (wX<=
0)
-
{
-
bSector = SECTOR_4;
-
}
-
else
// wX > 0
-
{
-
bSector = SECTOR_3;
-
}
-
}
-
else
// wY > 0
-
{
-
if (wZ>=
0)
-
{
-
bSector = SECTOR_2;
-
}
-
else
// wZ < 0
-
if (wX<=
0)
-
{
-
bSector = SECTOR_6;
-
}
-
else
// wX > 0
-
{
-
bSector = SECTOR_1;
-
}
-
}
-
-
switch(bSector)
//根据所在扇区号,计算三相占空比
-
{
-
case SECTOR_1:
-
case SECTOR_4:
-
hTimePhA = (T/
8) + ((((T + wX) - wZ)/
2)/
131072);
-
hTimePhB = hTimePhA + wZ/
131072;
-
hTimePhC = hTimePhB - wX/
131072;
-
break;
-
case SECTOR_2:
-
case SECTOR_5:
-
hTimePhA = (T/
8) + ((((T + wY) - wZ)/
2)/
131072);
-
hTimePhB = hTimePhA + wZ/
131072;
-
hTimePhC = hTimePhA - wY/
131072;
-
break;
-
-
case SECTOR_3:
-
case SECTOR_6:
-
hTimePhA = (T/
8) + ((((T - wX) + wY)/
2)/
131072);
-
hTimePhC = hTimePhA - wY/
131072;
-
hTimePhB = hTimePhC + wX/
131072;
-
break;
-
default:
-
break;
-
}
-
-
TIM1->CCR1 = hTimePhA;
-
TIM1->CCR2 = hTimePhB;
-
TIM1->CCR3 = hTimePhC;
-
}
1、上面的处理的处理函数,先用输入的Vα、Vβ计算wUAlpha、wUBeta,再根据wUAlpha、wUBeta计算wX、wY、wZ,再由wX、wY、wZ判断扇区号和占空比,计算过程完全同手册说的一样:
计算wUAlpha、wUBeta:
计算wX、wY、wZ:
,
判断扇区号:
计算占空比:
2、扇区判断和占空比计算的原理与这篇文章说的一样,这里就不细说了。代码中有疑虑就是那个T/8和131072是什么意思。
-
hTimePhA = (T/
8) + ((((T + wX) - wZ)/
2)/
131072)
;
-
hTimePhB = hTimePhA + wZ/
131072
;
-
hTimePhC = hTimePhB - wX/
131072
;
因为T等于4倍的ARR值,所以T/8就等于ARR/2,相当于把3个通道的比较值平移了半个周期。因为当3个通道的波形相同时,实际上并没有输出(只有3个低侧开关管或高侧开关管打开),真正有输出的是3个通道波形不一样的地方(有高侧管子打开也有低侧管子打开),所以让3个通道同时移动相同ARR/2对输出并没有什么影响,同时方便了计算,避免了出现通道比较值为负的情况。
至于131072,网上有人是这么说的:
(2^15)*4 = 32768*4 = 131072
#define T (PWM_PERIOD * 4),这里有一个4倍的放大。
然后电流采用了Q15表示(左对齐),2^15 = 32768。
所以最后计算需要除以131072。
这篇帖子也有相关讨论:链接
因为用的st的库,svpwm的实现函数也没怎么去研究过。感觉这个函数有点绕,让人晕,其实知道了原理后,读者可以自己实现。
8、FOC总的处理函数:
-
Curr_Components Stat_Curr_a_b;
-
Curr_Components Stat_Curr_alfa_beta;
-
Curr_Components Stat_Curr_q_d;
-
Curr_Components Stat_Curr_q_d_ref_ref;
//电流环的给定值,用于电流环Id,Iq和前馈电流控制的给定值
-
Volt_Components Stat_Volt_q_d;
-
Volt_Components Stat_Volt_alfa_beta;
-
-
void FOC_Model(void) //电流环处理函数
-
{
-
Stat_Curr_a_b = GET_PHASE_CURRENTS();
//读取2相的电流值
-
Stat_Curr_alfa_beta = Clarke(Stat_Curr_a_b);
//Ia,Ib通过Clark变换得到Ialpha和Ibeta
-
Stat_Curr_q_d = Park( Stat_Curr_alfa_beta,ENC_Get_Electrical_Angle() );
//输入电角度、Ialpha和Ibeta,经过Park变换得到Iq、Id
-
-
//q轴的pid运算,得到Vq
-
Stat_Volt_q_d.qV_Component1 = PID_Regulator(Stat_Curr_q_d_ref_ref.qI_Component1,Stat_Curr_q_d.qI_Component1, &PID_Torque_InitStructure);
-
//d轴的pid运算,得到Vd
-
Stat_Volt_q_d.qV_Component2 = PID_Regulator(Stat_Curr_q_d_ref_ref.qI_Component2,Stat_Curr_q_d.qI_Component2, &PID_Flux_InitStructure);
-
-
RevPark_Circle_Limitation();
//归一化
-
Stat_Volt_alfa_beta = Rev_Park(Stat_Volt_q_d);
//反Park变换
-
CALC_SVPWM(Stat_Volt_alfa_beta);
//svpwm实现函数,实际的电流输出控制
-
}
把这个函数放在ADC注入组转换完成中断中调用即可。
现在我们梳理一下整个过程:配置TIM1、ADC,并设置ADC的注入组转换由TIM1触发。这样在每个载波周期,都会触发一次ADC注入组采样,相电流采样完成后调用FOC_Model进行FOC运算处理,然后把最后的计算结果更新到TIM1的输出。现在还有一个问题:现在自己手中有上面的代码、相关的硬件和一个电机,怎么让电机开始转起来呢?那就要看这篇文章了。
9、弱磁控制
待续。。
PS:工程要的人多,我放在这儿了https://www.cirmall.com/circuit/16544,卖3块,平台收30%,得2.1,当赏瓶可乐吧。
工程中的代码在本文中已经基本给出来了,为了方便大家直接看,才给出工程的,少去了你自己去整理代码。至于为什么卖3块,我已经说了是“赏”,你觉得看了文章有帮助,“赏”给我的。
那些因为我卖钱,就骂我的白嫖党也是牛逼,你觉得我花时间写这些东西给你们看,是天经地义的?爱看看,不看滚。
转载:https://blog.csdn.net/qlexcel/article/details/95227991