电力电子
4
 
STM32输出三路交错PWM!
金坷居士 2018-12-15 22:43:23

多相交错技术是低压大电流电源里面必备的技术之一,不光可以突破单个电感电流容量的限制,而且还能分散热点,更重要的是极大降低了有效值电流还提高了效率。多相交错电源里面每一路pwm都是需要相差一定角度的,比如三路交错就是需要三个相位之间相差120度的信号。本帖忽略匀流问题(实际中必须处理下否则相之间负载不平衡会让负载大的相烧掉)。如果启用了互补输出还可以做到三相交错的互补PWM。

STM32单片机是一种很常见性价比很高的32位单片机,最低端的STM32F0系列也有ADC,众多定时器等外设,于是本帖使用STM32F030K6为例,讲解高速多路交错PWM的产生方式,同样的原理也可以拓展到STM32F1/F3/F4系列和其他系列的单片机里面。在单片机里面有这么一种东西叫做时钟树,描述的是单片机内部每个模块的时钟频率,来源还有可用的分频/倍频选项,STM32F030K6的时钟树如图:

pic

这个图是在STM32 CubeMX里面自动给出的,这个软件很不错,可以用图形界面来生成时钟树以及其他外设的初始化代码,再也不用死坑datasheet啦 哈哈哈(雾

STM32所有定时器的时钟都是接到APB1 Timer Clocks这里的,也就是说所有的定时器都是在同一时刻对Counter进行累加的。如果配置三个定时器为PWM输出模式,频率相同,但是开始计数的时间人为的叉开,就可以达到多相PWM的目的。我们可以配置三个定时器为同频率的PWM输出,然后再用一个定时器对这三个定时器在特定时间进行使能即可。GPIO的占用如下图:

pic

 TIM1, TIM16和TIM17分别输出三路100KHz的PWM以及他们的互补。

 这是硬件初始化的代码,在main的主循环之前被调用,分别初始化了HAL库,系统时钟,还有用到的GPIO和定时器:

void hw_init(void) {
    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    SystemClock_Config();
    GPIO_Init();

    Timer1_Init();
    Timer16_Init();
    Timer17_Init();
    Timer14_Init();
    Timer_Sync_1_16_17();
}

GPIO_Init();是把GPIO口变成PWM输出的,参见服见里面的platform.c这里就不贴出了。

TIM1, TIM16, TIM17的初始化代码很相似,这里就只贴TIM1的代码了:

static void Timer1_Init(void) {
    __HAL_RCC_TIM1_CLK_ENABLE();//启用TIM1外射的时钟,如果没开时钟根本不工作也不能被配置的哦
    TIM1->CR1 = 0;
    TIM1->CR2 = 0;
    TIM1->DIER = 0;

    TIM1->ARR = TIM_ARR_SW;	// Auto-reload value : 100kHz overflow
    TIM1->PSC = 0;
    TIM1->CCR2 = 0;

    TIM1->CCMR1 = 0x6800;	// 110: PWM mode 1, up counting.
    TIM1->CCER = 0x0050; 	// CH2 output enable with complement, active high

    TIM1->BDTR = TIM_BDTR_DTG_Msk & TIM_DEADTIME;	// Set dead-time
}

这部分代码是很典型的把定时器配置成向上计数的PWM模式1并加互补死区的操作,就不详细解释了,如果有不懂可以参见RM0091(股沟得到)

重点来了!TIM14被用作了同步TIM1,TIM16和TIM17,但是这个同步并没有用到任何硬件上的同步(硬件也没有提供啊QAQ)

static void Timer14_Init(void) {
    __HAL_RCC_TIM14_CLK_ENABLE();

    TIM14->CR1 = 0;
    TIM14->DIER = TIM_DIER_UIE;

    TIM14->ARR = TIM_ARR_PHSYNC;	// Auto-reload value : 300kHz overflow
    TIM14->PSC = 0;

    NVIC_EnableIRQ(TIM14_IRQn);
}

首先还是开启时钟,这个千万别忘,很常见的错误,然后TIM14被配置成了自动重装模式,以300KHz的频率触发中断。300KHz的一个周期正好是我们想要的相位差的时间。Timer_Sync_1_16_17()首先确保TIM1,TIM14,TIM16和TIM17的计数器都是0,然后启动TIM14并等待TIM14被停止。

static void Timer_Sync_1_16_17(void) {
    extern volatile uint32_t* tim_cr1;

    // Clear counter value
    TIM14->CNT = 0;
    TIM1->CNT = 0;
    TIM16->CNT = 0;
    TIM17->CNT = 0;

    // Enable the synchronizer timer
    TIM14->CR1 |= 1;

    // Wait until the synchronization is done
    while (tim_cr1 > 0);

while那会让程序卡住,但是还是会进定时器中断的。TIM14中断的程序如下:

static uint32_t* tim_cr1 = &(TIM1->CR1);
void TIM14_IRQHandler(void) {
    // This can be commented out, since update is the only source of interrupt
    if(TIM14->SR & TIM_SR_UIF) {
        TIM14->SR &= ~TIM_SR_UIF; // clear UIF flag

        if (tim_cr1 > 0) {
            *tim_cr1 |= TIM_CR1_CEN;

            // Set the next timer to be enabled
            if (tim_cr1 == &(TIM1->CR1)) {
                tim_cr1 = &(TIM16->CR1);
            } else if (tim_cr1 == &(TIM16->CR1)) {
                tim_cr1 = &(TIM17->CR1);
            } else if (tim_cr1 == &(TIM17->CR1)) {
                // All timers have been synchronized to the correct phase order
                tim_cr1 = 0;	// No more CR1 to be set

                // Disable the timer
                TIM14->CR1 &=~ TIM_CR1_CEN;
            }
        } else {
            // Execute control routine here
        }
    } // if(TIM14->SR & TIM_SR_UIF)
}

在单片机刚上电之后,tim_cr1会被设置为TIM1的CR1寄存器的地址,这样做是告诉单片机我们下次进中断需要开启TIM1并在开启TIM1之后为下次进中断开启TIM16做准备,就是把tim_cr1会被设置为TIM16的CR1寄存器的地址。这段代码确保了从进入中断到相应的CR1寄存器最低位被置1(定时器计数使能位)的指令条数完全相等,于是TIM1,TIM16,TIM17会依次在正确的时间被打开,完成三个定时器的同步产生正确的相位差。当TIM17被开启之后,tim_cr1会被置0(标识为无效状态,这样当TIM14作为其他用途用的时候会跳过同步TIM1-17的代码),然后TIM14的也会被禁用。退出中断程序后,while (tim_cr1 > 0); 这里也能够继续执行了。TIM14在同步TIM1,TIM16,TIM17完成之后可以用作其他用途比如执行反馈环的计算等。之后的TIM14中断处理程序写在"} // if(TIM14->SR & TIM_SR_UIF)"这行之后。static void Timer_Sync_1_16_17(void) 的后半段重新配置了TIM14:

    // Wait until the synchronization is done
    while (tim_cr1 > 0);

    // Reconfigure timer14 for executing control routine
    TIM14->CR1 = 0;
    TIM14->DIER = TIM_DIER_UIE;

    TIM14->ARR = TIM_ARR_CON;		// Auto-reload value : 50kHz overflow
    TIM14->PSC = 0;
    TIM14->CNT = 0;

    // Enable the timer again
    TIM14->CR1 |= TIM_CR1_CEN;
}

下面三个宏分别是启用PWM输出,禁用PWM输出还有设置占空比:

__STATIC_INLINE void TIM_PWM_ENABLE() {
    TIM1->BDTR |= TIM_BDTR_MOE;
    TIM16->BDTR |= TIM_BDTR_MOE;
    TIM17->BDTR |= TIM_BDTR_MOE;
}

__STATIC_INLINE void TIM_PWM_DISABLE() {
    TIM1->BDTR &=~ TIM_BDTR_MOE;
    TIM16->BDTR &=~ TIM_BDTR_MOE;
    TIM17->BDTR &=~ TIM_BDTR_MOE;
}

__STATIC_INLINE void TIM_PWM_SETVAL(uint32_t val) {
    TIM1->CCR2 = val;
    TIM16->CCR1 = val;
    TIM17->CCR1 = val;
}

这个是实验效果:

pic

 

pic

 

有的同学可能想问,为啥这么多直接操作寄存器的?全部换成HAL不好吗?我感觉在这种需要对硬件行为进行精确把控的情况下使用HAL是不明智的选择,HAL的实现可能会在函数调用的时候产生短暂的无效输出状态,有可能会导致MOS管的误导通,这对于电源是灾难性的,会booooom


代码在附件里面,HAL库需要手动从Cube里考进来,当然如果把HAL的时钟GPIO初始化换成其他库或者寄存器操作也可以不要HAL。于是大家好好玩233333

3ph.zip136k1次下载

 

+10  科创币    钢珠子母弹   2018-12-16   优秀设计
+5  科创币    WangGC   2018-12-16   资瓷资瓷,期待什么时候做个三相电源
2018-12-16 02:48:43
1楼
0

六花出品 必属精品,软件上实现多相位好像很方便,请问泥之前做的硬件多相交错buck是怎么实现让多个yc3843相位差实现同步的呢?还是说把单个ic的控制信号分为三相?

2018-12-18 20:06:30
金坷居士(作者)
2楼
0

窝的方法是用几片74数字电路产生一个多相交错的时钟,每个UC3843都同步到这些时钟上面,出来的PWM也是交错的了。这堆74可以用一小片CPLD代替,画出逻辑图烧进去就能用了。附件里是10相交错时钟的发生器,需要10倍PWM频率的时钟驱动哦

10Phase.ms14246k5次下载

 


想参与大家的讨论?现在就 登录 或者 注册

万流景仰
专栏收藏夹发私信
学术分 11科创币 16.43总主题 171 帖总回复 1711 楼拥有证书:会员 学者 机友 笔友
注册于 2011-09-23 14:21最后登录 2019-04-25 01:17

个人简介

怪哉!灵异的三极管电流流向! 这素一个在仿真的RCC电路,示波器上绿色的是集电极电流红色的是发射极电流。窝萌都知道发射姬电流素集电极电流和基极电流之和,所以讲道理发射极电流一定比集电极略大。可仿真结果刷了三观,Q1集电极电流一部分流经基极,然后流经Q2的C->E。

Github  https://github.com/kccd/nkc.git

科创研究院 (c)2001-2019

蜀ICP备11004945号-2 川公网安备51010802000058号