由于在使用zigbee网络时往往有对电机、灯具等执行器进行控制的需要,而出于成本和简化系统的需求,我们又往往希望在CC2530上实现相关的控制及ZigBee的组网工作,使用CC2530芯片实现PWM输出则是一项重要的控制能力。
首先,需要选定输出PWM的IO口,查阅CC2530的数据手册关于GPIO与外设的映射关系如下:
为了方便程序编写,选择TIMER1作为PWM的定时器,选择其备用位置1作为PWM比较输出的位置。注意到TIMER1与USART0存在默认位置的冲突,故这里仅选用TIMER1的2、3、4通道,1、2通道留给USART0串口使用,方便程序调试。
对于这样的复用功能,由于串口的优先级往往更高,因此还需要对寄存器进行进一步的配置。查阅数据手册中配置USART0与TIMER1的优先级顺序的寄存器,如下图:
可以看到,相关寄存器位于P2DIR中,这是由于P2的IO口数量较少(不足8个),因此与其它功能共用一个8bit寄存器地址。(需要注意的是,我们需要配置的是位于P0的IO口,因此不要错误使用P2SEL对其它备用位置的IO进行优先级配置)
而定时器的启动及相关配置较为简单,在此不再赘述,可参考数据手册与代码增进理解。
仿照TI官方的HAL库写法,添加两个文件,分别是hal_pwm.h和hal_pwm.c。
//hal_pwm.h
#ifndef HAL_PWM_H
#define HAL_PWM_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "hal_board.h"
void PWM_Init(uint16 freq); // PWM初始化并设置频率
void PWM_Set(uint8 duty1, uint8 duty2, uint8 duty3); // 传入3个通道的比较值,0~255有效
#ifdef __cplusplus
}
#endif
#endif
//hal_pwm.c
#include "hal_pwm.h"
#define PWM_1 P0_4
#define PWM_2 P0_5
#define PWM_3 P0_6
void PWM_Init(uint16 freq)
{
freq = 250000 / freq;
PWM_1 = 1;
PWM_2 = 1;
PWM_3 = 1;
CLKCONCMD &= ~0x40; //设置系统时钟源为32MHZ晶振
while(CLKCONSTA & 0x40); //等待晶振稳定为32M
CLKCONCMD &= ~0x07; //设置系统主时钟频率为32MHZ
CLKCONCMD |= 0x38; //设置定时器所分频率为250KHZ
PERCFG &= 0x00; //选择定时器1映射的IO口位置,此处我们选择备用位置(Alt)1,它映射的IO口包括P0_4/5/6
P2DIR |= 0xC0; //当PERCFG分配给一些外设到相同引脚的时候,此时设定谁的优先级高,此处设定第1优先级为定时器1通道2-3
P0DIR |= 0x70; //设置P0_4/5/6为输出
P0SEL |= 0x70; //此时将P0_4/5/6配置为外设功能,即定时器1的输出比较
T1CC0H = HI_UINT16(freq); //设定整个信号的周期时间(CC0即使不用也要设置)
T1CC0L = LO_UINT16(freq) - 1;
T1CC2H = 0x00;
T1CC2L = 0x00;
T1CCTL2 = 0x24;
T1CC3H = 0x00;
T1CC3L = 0x00;
T1CCTL3 = 0x24;
T1CC4H = 0x00;
T1CC4L = 0x00;
T1CCTL4 = 0x24;
T1CTL = 0x02; //设定定时器频率250KHz,1分频,设定运行模式为"模"模式,即从0x0000到T1CC0反复计数
}
void PWM_Set(uint8 duty1, uint8 duty2, uint8 duty3) //0~255
{
uint16 duty_set;
uint16 duty_comp = BUILD_UINT16(T1CC0L, T1CC0H);
float duty_precent;
duty_precent = duty1/255.0; //取得PWM实际占空比
duty_set = (uint16)(duty_precent * duty_comp); //计算比较值
T1CC2H = HI_UINT16(duty_set); //高位赋值
T1CC2L = LO_UINT16(duty_set); //低位赋值
duty_precent = duty2/255.0;
duty_set = (uint16)(duty_precent * duty_comp);
T1CC3H = HI_UINT16(duty_set);
T1CC3L = LO_UINT16(duty_set);
duty_precent = duty3/255.0;
duty_set = (uint16)(duty_precent * duty_comp);
T1CC4H = HI_UINT16(duty_set);
T1CC4L = LO_UINT16(duty_set);
}
#endif
代码中首先配置了系统时钟,若使用TI的Zstack协议栈则这些项目都是默认进行的配置(TI内部的OSAL系统使用TIMER4的1ms中断驱动操作系统工作)。
其次便是将定时器配置为1分频(不分频)模式,能够产生250KHz的最高频率,留给我们更多的配置空间。
通过对PERCFG寄存器的配置选择了TIMER0的位置选择,P2DIR则决定了这些IO上的优先等级。
P0DIR与P0SEL的功能与它们的名称一致,配置了IO映射到复用功能上,并设定方向为输出。
T1CC0则需要引起注意,由于我们选择的是输出的PWM频率与占空比皆可变化的“模”模式,因此T1CC0是PWM频率比较的基准,即使我们不使用TIMER1和通道0,也必须设置该寄存器。设置该寄存器即决定了整个PWM周期的频率,需要注意的是从0计算至T1CC0,因此设置值时需要注意减去1,对于频率敏感的场合尤其需要注意。
而T1CC2/T1CC3/T1CC4则默认设置为0,等待后续PWM配置函数进行计算和配置。
T1CCTL2/T1CCTL3/T1CCTL4则用于配置各个通道的输出模式与极性。在这里设置的值将使各个通道均工作在比较输出模式,并在计数值由0增加至T1CCx期间输出高电平,此后由T1CCx至T1CC0的过程中输出低电平,即输出正占空比,与一般的使用方式相符合。
在PWM_Set函数中,首先结合T1CC0的值,计算了PWM的实际占空比,并由此设置了3个输出通道的比较值。
在代码中,使用了TI公司提供的对数据结构进行转换的宏定义,摘录如下。
#define HI_UINT16(a) (((a) >> 8) & 0xFF)
#define LO_UINT16(a) ((a) & 0xFF)
#define BUILD_UINT16(loByte, hiByte) \
((uint16)(((loByte) & 0x00FF) + (((hiByte) & 0x00FF) << 8)))
希望能够对大家学习CC2530及ZigBee有所启发~