快速导航
- 写在前面
- 库
- 配置时钟
- 配置GPIO
- 配置定时器
- 配置串口
- 配置硬件PWM
- 特殊功能
- 同步功能
- 总结
写在前面
不出意外这是我第一次也是最后一次使用STC的芯片,写这篇博的目的纯粹记录下前段时间调试的痛苦经历,所有目前打算选或是已经开始调试这款芯片的朋友,建议你们看完本文之后深思熟虑一下。用M0或者M3不香么?
这篇博客主要是用到硬件PWM,实现的是6路PWM同步输出的功能。
使用过程遇到问题,可以感受一下STC原厂的态度,向原厂指出他们的错误,原厂不仅不核查,还会嘲讽你菜鸟(贴个截图给大家感受一下 这是STC原厂庄伟)
库
首先不得不说STC弄的库,可谓是四不像,想学arm内核那样用结构体来初始化外设功能,但是官方demo只有最简单的应用,高级功能的库函数是缺失的,自己想要用高级功能,那就自己摸索把,库里面和demo是找不到相应的函数来调用的和参考的。
比如我这里用到STC的硬件PWM,库函数里面就只有两个可以调用的函数:
u8 PWM_Configuration(u8 PWM, PWMx_InitDefine *PWMx);
void UpdatePwm(u8 PWM, PWMx_Duty *PWMx);
一个是初始化函数,另一个是更新占空比的函数。说真的,我没见过这么简陋的库。
贴个新唐的MS51对比一下:
void PWM0_ClockSource(unsigned char u8PWMCLKSource, unsigned char u8PWM0CLKDIV);
void PWM0_ConfigOutputChannel(unsigned char u8PWM0ChannelNum,
unsigned char u8PWM0OPMode,
unsigned char u8PWM0PwmType,
unsigned long u32PWM0Frequency,
unsigned int u16PWM0DutyCycle);
void PWM0_DeadZoneEnable(unsigned char u8PWM0Pair, unsigned int u16PWM0DZValue);
void PWM0_DeadZone_ALL_Disable(void);
void PWM0_RUN(void);
void PWM0_STOP(void);
void PWM1_ClockSource(unsigned char u8PWMCLKSource, unsigned char u8PWM0CLKDIV);
void PWM1_ConfigOutputChannel(unsigned char u8PWM1ChannelNum,
unsigned char u8PWM1OPMode,
unsigned char u8PWM1PwmType,
unsigned long u32PWM1Frequency,
unsigned int u16PWM1DutyCycle);
void PWM1_DeadZoneEnable(unsigned char u8PWM0Pair, unsigned int u16PWM0DZValue);
void PWM1_DeadZone_ALL_Disable(void);
void PWM1_RUN(void);
void PWM1_STOP(void);
STC你真就是连个暂停和开启的子函数都懒得写么,我真的迷惑。
回到正题,这个库你可以接受,那么开始第一步
配置时钟
如果使用了外部时钟,那么这一步你需要关注一下
void main(void)
{
P_SW2 = 0x80;
XOSCCR = 0xc0; //启动外部晶振
while (!(XOSCCR & 1)); //等待时钟稳定
CLKDIV = 0x00; //时钟不分频
CLKSEL = 0x01; //选择外部晶振
P_SW2 = 0x00;
}
这几句是初始化了外部晶振。
配置GPIO
调库: GPIO_config();
void GPIO_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //结构定义 EN脚
GPIO_InitStructure.Pin = GPIO_Pin_1 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5; //135 PWM 1N 2N 3N P14 SD为1的时候就不出波
GPIO_InitStructure.Mode = GPIO_OUT_PP;
GPIO_Inilize(GPIO_P1,&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_Pin_0; //P10 EN输入 相当于波形的总开关
GPIO_InitStructure.Mode = GPIO_PullUp;
GPIO_Inilize(GPIO_P1,&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_Pin_3 | GPIO_Pin_4; //P33 P34 PWM 7_2 8_2
GPIO_InitStructure.Mode = GPIO_OUT_PP;
GPIO_Inilize(GPIO_P3,&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_Pin_6 | GPIO_Pin_7; //UART 配置为双向口 P36 P37 与MS51 UART通信脚
GPIO_InitStructure.Mode = GPIO_PullUp;
GPIO_Inilize(GPIO_P3,&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_Pin_4; //P54 PWM 6_2
GPIO_InitStructure.Mode = GPIO_OUT_PP;
GPIO_Inilize(GPIO_P5,&GPIO_InitStructure);
}
结构体里面元素的初值,在GPIO.h文件中都有参考值。
我这里就是初始化了PWM1/2/3/6/7/8这六路的GPIO为输出,然后与主控的串口GPIO,还有输入信号EN和对74HC244芯片的控制脚SD
配置定时器
调库:Timer_config();
void Timer_config(void)
{
TIM_InitTypeDef TIM_InitStructure; //结构定义
TIM_InitStructure.TIM_Mode = TIM_16BitAutoReload; //指定工作模式,
TIM_InitStructure.TIM_Priority = Priority_0; //指定中断优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
TIM_InitStructure.TIM_Interrupt = ENABLE; //中断是否允许, ENABLE或DISABLE
TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_1T; //指定时钟源, TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext
TIM_InitStructure.TIM_ClkOut = DISABLE; //是否输出高速脉冲, ENABLE或DISABLE
TIM_InitStructure.TIM_Value = 65536UL - (MAIN_Fosc / 1000); //初值,1ms
TIM_InitStructure.TIM_Run = ENABLE; //是否初始化后启动定时器, ENABLE或DISABLE
Timer_Inilize(Timer0,&TIM_InitStructure); //初始化Timer0 Timer0,Timer1,Timer2,Timer3,Timer4
}
这个初值 MAIN_Focs就是你的实际晶振,比如32Mhz的 这个就定义为32000000L,如果是用的内部RC震荡,那么库函数里面config.h文件要注意改一下:
#ifndef __CONFIG_H
#define __CONFIG_H
/*********************************************************/
#define MAIN_Fosc 32000000L //定义主时钟
// #define MAIN_Fosc 22118400L //定义主时钟
//#define MAIN_Fosc 12000000L //定义主时钟
//#define MAIN_Fosc 11059200L //定义主时钟
//#define MAIN_Fosc 5529600L //定义主时钟
// #define MAIN_Fosc 24000000L //定义主时钟
#define STC8Hxx //STC8H系列
//#define STC8Gxx //STC8G系列
/*********************************************************/
#include "STC8xxxx.H"
#endif
定时时间就比较好算了:STC是1T的指令周期,那么定时器自增/自减一次的时间就是:1/32000000 s 即 1000000/32000000 = 1/32us 那么计时1ms就需要 32000次。如果是定时器是上数模式的话,初值就是 65536-32000. STC给的公式就比较简单了,MAIN_Fosc是32000000,如果定时1ms,那么这里就写 65536-(MAIN_Fosc/1000);如果需要定时10ms,那么就改为12T模式,初值填 65536UL - (MAIN_Fosc / (100*12))。参考STC官方Demo。其实自己手动配寄存器也就是几句话的事。
还需要提一下的就是,库函数依旧是没有暂停/开启定时器和使能/失能定时器中断的函数可以调用,用户需要自己根据Timer_Inilize函数去看开启定时器是调用了STC8xxxx.H文件里面的哪个函数,或者简单粗暴一点就是直接看手册,直接去改变控制寄存器相应的位来实现自己的需求。
使用下来一个心得就是,一定要会到STC8xxxx.H文件里面找相应的宏或是函数,结合注释和手册,来调用芯片的某些高级功能
配置串口
调库:void UART_config(void)
void UART_config(void)
{
COMx_InitDefine COMx_InitStructure;
COMx_InitStructure.UART_Mode = UART_8bit_BRTx;
COMx_InitStructure.UART_BRT_Use = BRT_Timer1;
COMx_InitStructure.UART_BaudRate = 115200ul;
COMx_InitStructure.UART_RxEnable = ENABLE;
COMx_InitStructure.BaudRateDouble = DISABLE;
COMx_InitStructure.UART_Interrupt = ENABLE;
COMx_InitStructure.UART_Priority = Priority_0;
COMx_InitStructure.UART_P_SW = UART1_SW_P36_P37;
UART_Configuration(UART1, &COMx_InitStructure);
}
没啥好说的,没用到串口的高级功能
配置硬件PWM
调库:PWM_config();
void PWM_config(void)
{
PWMx_InitDefine PWMx_InitStructure;
PWMx_InitStructure.PWM1_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM2_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM3_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM4_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM5_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM6_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM7_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM8_Mode = CCMRn_PWM_MODE1;//CCMRn_MATCH_VALID;
PWMx_InitStructure.PWM1_SetPriority = Priority_0;
PWMx_InitStructure.PWM2_SetPriority = Priority_0;
PWMx_InitStructure.PWM3_SetPriority = Priority_0;
PWMx_InitStructure.PWM4_SetPriority = Priority_0;
PWMx_InitStructure.PWM5_SetPriority = Priority_0;
PWMx_InitStructure.PWM_Period = 333;
PWMx_InitStructure.PWM1_Duty = PWMA_Duty.PWM1_Duty;
PWMx_InitStructure.PWM2_Duty = PWMA_Duty.PWM2_Duty;
PWMx_InitStructure.PWM3_Duty = PWMA_Duty.PWM3_Duty;
PWMx_InitStructure.PWM4_Duty = PWMA_Duty.PWM4_Duty;
PWMx_InitStructure.PWM5_Duty = PWMA_Duty.PWM5_Duty;
PWMx_InitStructure.PWM6_Duty = PWMA_Duty.PWM6_Duty;
PWMx_InitStructure.PWM7_Duty = PWMA_Duty.PWM7_Duty;
PWMx_InitStructure.PWM8_Duty = PWMA_Duty.PWM8_Duty;
PWMx_InitStructure.PWM_DeadTime = 0;
PWMx_InitStructure.PWM_EnoSelect = ENO1N | ENO2N | ENO3N;
PWMx_InitStructure.PWM_PS_SW = PWM1_SW_P10_P11| PWM2_SW_P12_P13 | PWM3_SW_P14_P15;
PWMx_InitStructure.PWM_CC1NEnable = ENABLE;
PWMx_InitStructure.PWM_CC2NEnable = ENABLE;
PWMx_InitStructure.PWM_CC3NEnable = ENABLE;
PWMx_InitStructure.PWM_MainOutEnable= ENABLE; //主输出使能
PWMx_InitStructure.PWM_CEN_Enable = DISABLE; //主时钟失能
PWM_Configuration(PWMA, &PWMx_InitStructure); //初始化PWMA
PWMx_InitStructure.PWM_CC6Enable = ENABLE;
PWMx_InitStructure.PWM_CC7Enable = ENABLE;
PWMx_InitStructure.PWM_CC8Enable = ENABLE;
PWMx_InitStructure.PWM_EnoSelect = ENO6P | ENO7P | ENO8P;
PWMx_InitStructure.PWM_PS_SW = PWM6_SW_P54 | PWM7_SW_P33 | PWM8_SW_P34;
PWM_Configuration(PWMB, &PWMx_InitStructure); //初始化PWMB
EAXSFR(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
PWMA_ENO = 0; //禁止所有输出
PWMB_ENO = 0;
// PWMB_MainModeSel(MMSn_UPDATE); //配置PWMB为主模式 输出更新信号UEV
// PWMA_SMCR_Source(SMCR_TSn_ITR2); //触发源选择为PWMB产生的更新信号
// PWMA_SMCR_SMS(SMCR_SMSA_RESET); //配置PWMA为复位模式
// PWMA_CC1IE_Enable(); //允许PWMA更新中断
// PWMA_SR1 = 0; //清楚所有中断标志
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
}
说实话这个初始化函数真的一言难尽,你们写这个库函数的时候,没有发现这个函数没有配置PWM输出的有效电平和计数器的计数方向么?(需要设置PWM有效电平和计数方向的 请到STC8xxxx.H文件里面找到相应的库函数)
我把这两句自己加在了u8 PWM_Configuration(u8 PWM, PWMx_InitDefine *PWMx);里面:
还有PWMB的:
再比如这句:PWMx_InitStructure.PWM_MainOutEnable= ENABLE; //主输出使能,观察发现这句配置的是PWMA_BKR寄存器或是PWMB_BKR寄存器,首先是寄存器名字错了,库里面叫做PWMA_BRK和PWMB_BRK,其次这个是PWMA/PWMB刹车寄存器,不懂为什么禁止/使能通道和互补通道输出要配置这一位,只能理解为总开关。
开了总开关之后,还需要配置PWMA_CCER1这个寄存器。
CC1P控制的是OC1的输入捕获/比较输出的有效电平,CC1E控制的是输入捕获/比较输出 的使能/失能。
比如我这里用到的是比较输出,那么我需要配置有效电平,然后把使能打开。
这是第二个开关。
这是第三个开关。。。。。
说真的,这第三个开关,我不细看STC8xxxx.H文件我是真的不知道,库函数和demo例程都一点没提这个寄存器。第一步我把PWM开起来之后,有波形输出了,然后我发现我无论如何都关不掉这个波形,再后来才查到用PWMx_ENO寄存器来控制输出。
难道库函数多写一个暂停/开启函数,真的要花那么大精力么??
经过上述的配置,这时候 mian函数变成了:
void main(void)
{
//u8 i;
P_SW2 = 0x80;
XOSCCR = 0xc0; //启动外部晶振
while (!(XOSCCR & 1)); //等待时钟稳定
CLKDIV = 0x00; //时钟不分频
CLKSEL = 0x01; //选择外部晶振
P_SW2 = 0x00;
pwm_proid = 333;
PWMA_Duty.PWM1_Duty = 0;
PWMA_Duty.PWM2_Duty = 0;
PWMA_Duty.PWM3_Duty = 0;
PWMA_Duty.PWM6_Duty = 0;
PWMA_Duty.PWM7_Duty = 0;
PWMA_Duty.PWM8_Duty = 0;
GPIO_config();
Timer_config();
UART_config();
PWM_config();
EA = 1;
ALL_OFF; //所有PWM的GPIO全部输出无效电平 不过这句貌似没用,得要先把GPIO的PWM使能给关了才可以改变
while (1)
{
}
}
这时候中断就已经开起来了,硬件PWM也是开启的,串口也是正常的收发的。
特殊功能
这个项目是一个工控的机器,主控那边把当前用户设定好的频率和占空比的数据通过串口发送过来STC芯片,经过校验核对无误之后,再把相应的波形输出出去,并给74HC244使能信号,让PWM信号发送出去。有6路PWM同步的需求,还有其中两路PWM错相的需求。
同步功能
这里原计划是使用PWMA比较/捕获中断或者是通过PWMB使能PWMA,但是说实在的这两个功能我是真的玩不明白,感觉芯片总是出现一些不该出现的状态,最后没办法任务紧急这里放弃研究了,有心人愿意钻研的可以好好研究一下这两个功能。STC把自家硬件PWM吹的天花乱坠,但是实际上呢,连个DEMO都不做,我真不知道用户要怎么去玩转这些功能。
简单的贴一下PWMB启动PWMA把:(在PWM_config();的末尾 被我注释掉了)
// PWMB_MainModeSel(MMSn_UPDATE); //配置PWMB为主模式 输出更新信号UEV
// PWMA_SMCR_Source(SMCR_TSn_ITR2); //触发源选择为PWMB产生的更新信号
// PWMA_SMCR_SMS(SMCR_SMSA_TRIG); //配置PWMA为触发模式
STC手册是这么写的:
实测下来,开PWMB之后,PWMA确实也能跟着起来,但是没有达到两个波形同步的效果。于是乎放弃这个方法。
改用PWMA产生比较/捕获中断,然后再在中断里面把PWMB开启,那么我通过在中断里面改变PWMB计数器的初值,也能精准控制两路PWM的错相间隔,通过补偿就能实现两路PWM同步。(理论可行,实际还是不行)
在PWM_config();的末尾:
// PWMA_CC1IE_Enable(); //允许PWMA比较/捕获中断
// PWMA_SR1 = 0; //清楚所有中断标志
这样就把PWM比较/捕获中断打开了。
这里还有其他中断可以使能:
不过我已经试过了,无论是更新中断还是比较/捕获中断都无法实现我想要的效果。
另外这里出现了开篇的那个坑:Demo程序清中断标志和失能中断允许,是起不到效果的,原因就是没有切换到操作SFR寄存器。
这个才是正确的中断服务函数:
//========================================================================
// 函数: void PWMA_ISR(void) interrupt PWMA_VECTOR
// 描述: PWMA中断处理程序. 捕获数据通过 TIM1-> CCRnH / TIM1-> CCRnL 读取
// 参数: None
// 返回: none.
// 版本: V1.0, 2021-6-1
//========================================================================
void PWMA_ISR(void) interrupt PWMA_VECTOR
{
EAXSFR();
P32 = ~P32;
PWMA_SR1 = 0; //清除中断标志
// pwm_cnt++;
// if(pwm_cnt >=2)
// {
// pwm_cnt=0;
// //PWMA_CC1IE_Disable(); //禁止比较/捕获中断
// }
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
}
虽然中断入口地址,肯定是可以在手册上找到,这个函数呢,用户自己摸索之后也可以写出来,但是你作为原厂,为什么关于这些高级功能的内容连个demo都不做??真就是菜鸟不配用你家的芯片么?(反正我用过这一次以后再也不会选了)
再就是,所谓的论坛资源很丰富。上面这个函数就是我在论坛置顶帖子下载的《高级PWM相关程序》,里面原厂的工程梁工还在帖子下回帖加精了。。。。我想问一句,梁工,路人随手一弄的东西,你自己不测测的??没有EAXSFR();这句代码,这个中断无法清除中断标志,任凭你怎么改PWM的占空比和频率,进中断的时间始终一致,你们这么大的公司没一个人发现么??
按照我修改以后的代码,总算是可以随着PWM计数器计数值与设定占空比值匹配然后产生中断进中断服务函数,但是依旧是无法实现我的需求,那几天改的头发昏,写博的时候已经不知道当时是什么原因的。按道理来说,这个是一定可行的。
贴几张匹配中断的截图:
6通道就是P32的值,P32是根据PWM发生匹配的时候进终端,然后翻转一次。现在这么看起来,好像还是很同步的,然后放大看:
可以看出,中断滞后了424ns,当然这是正常的,毕竟有那么多条指令需要执行,到那时不太明白当时为什么使用这种方法无法使两路PWM完全同步。
后来这段就是我最后采用的办法了,通过这个办法也是成功实现了两路波形基本同步:(篇幅较长,中间扯得比较远)
首先是我串口接受到数据会把一个标志位置一:f_duty_change=1;
在主函数中
if(f_duty_change==1)
{
Timer0_Stop();
Timer0_InterruptDisable();
SD=1; //停止74HC244传输数据
EAXSFR(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
PWMA_ENO = 0;
PWMB_ENO = 0;
PWMA_CR1 &= ~0x01; //0:禁止计数器
PWMB_CR1 &= ~0x01; //0:禁止计数器
ALL_OFF;
if(maikuan_row >= 11)
{
PWMA_Prescaler(1);
PWMB_Prescaler(1);
}
else
{
PWMA_Prescaler(0);
PWMB_Prescaler(0);
}
if(hlvol == 2) //F模式
{
pwm_proid = maikuan_ayy_F[maikuan_row][maijian-3]; //maikuan:总共14组 对应14行 maijian: 3-15 对应0-12列
PWMA_Duty.PWM1_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM2_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM3_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM6_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM7_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM8_Duty = duty_ayy[maikuan_row];
}
else //H/L模式
{
pwm_proid = maikuan_ayy_HL[maikuan_row][maijian-3]; //maikuan:总共14组 对应14行 maijian: 3-15 对应0-12列
if(maikuan >= 16)
{
if(maijian == 3) {j = (pwm_proid/100)*33; k = (pwm_proid/100)*29; } //不同maijian模式下 占空比也不同 maijian越大 占空比越低
else if(maijian == 4) {j = (pwm_proid/100)*22; k = (pwm_proid/500)*97; }
else if(maijian == 5) {j = (pwm_proid/100)*17; k = (pwm_proid/500)*73; }
else if(maijian == 6) {j = (pwm_proid/100)*13; k = (pwm_proid/1000)*117;}
else if(maijian == 7) {j = (pwm_proid/100)*11; k = (pwm_proid/500)*49; }
else if(maijian == 8) {j = (pwm_proid/500)*49; k = (pwm_proid/250)*21; }
else if(maijian == 9) {j = (pwm_proid/500)*43; k = (pwm_proid/500)*37; }
else if(maijian == 10) {j = (pwm_proid/1000)*77;k = (pwm_proid/200)*13; }
else if(maijian == 11) {j = (pwm_proid/1000)*69;k = (pwm_proid/1000)*59; }
else if(maijian == 12) {j = (pwm_proid/1000)*63;k = (pwm_proid/500)*27; }
else if(maijian == 13) {j = (pwm_proid/1000)*57;k = (pwm_proid/1000)*49; }
else if(maijian == 14) {j = (pwm_proid/1000)*53;k = (pwm_proid/200)*9; }
else if(maijian == 15) {j = (pwm_proid/2000)*99;k = (pwm_proid/500)*21; }
PWMA_Duty.PWM3_Duty = (unsigned int)j;
PWMA_Duty.PWM6_Duty = (unsigned int)k;
}
else
{
PWMA_Duty.PWM3_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM6_Duty = duty_ayy[maikuan_row];
}
PWMA_Duty.PWM1_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM2_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM7_Duty = duty_ayy[maikuan_row];
PWMA_Duty.PWM8_Duty = duty_ayy[maikuan_row];
}
pwm_proid_hig = (pwm_proid&0xff00)>>8;
pwm_proid_low = (pwm_proid&0x00ff);
PWMA_ARR = pwm_proid;
PWMB_ARR = pwm_proid;
PWMA_CCR1 = PWMA_Duty.PWM1_Duty;
PWMA_CCR2 = PWMA_Duty.PWM2_Duty;
PWMA_CCR3 = PWMA_Duty.PWM3_Duty;
PWMB_CCR6 = PWMA_Duty.PWM6_Duty;
PWMB_CCR7 = PWMA_Duty.PWM7_Duty;
PWMB_CCR8 = PWMA_Duty.PWM8_Duty;
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
f_duty_change=0;
f_status_change=1;
f_exactly_once=0;
f_cnt_stop_out=1;
t_cnt_stop_out=0;
Timer0_InterruptEnable();
Timer0_Run();
}
这部分内容读者可能看不明白,因为是我项目内的一些特殊设置,贴出来是记录下类似波形的处理方法。
因为我是收到主控那边传过来的 占空比档位 总周期档位,但是对应到我实际出PWM的芯片上,是无法对应成相应的数值的,是需要自己去调整PWM的自动重装载寄存器(PWMA_ARR、PWMB_ARR)的值来调整总周期的,然后调整PWMA_CCR1、PWMA_CCR2等寄存器来调整占空比的。
这里就需要建表之后调表来快速匹配到相应的初值,建表过程比较痛苦,后来才发现一个比较快速的办法。
首先我这里抓取到了波形的实际数据:
首先我这里已经测量到了14个档位的有效电平时间和无效电平时间,每个档位有13种不同的频率,也就是14*13种组合。
首先我随便给一个总周期值,先把14档不同的占空比值对应的寄存器值得出来:
u16 duty_ayy[14] = { 69, 134, 260, 320, 390, 575, 1025, 1260, 1900, 2550, 3830, 2550, 3190, 3975};
然后通过excel的处理,得到一张占空比表。比如 第一档的占空比 时间是 2.16us 对应的寄存器初值是69,总周期是2.16+8.33=10.49us,占空比约等于20.6%,倒推出来的周期值就是69/0.206=334
第二档的第一种频率占空比为 4.15/(4.15+14.30) =0.2249 ,那么倒推出来第二档第一种频率的周期值应该为134/0.2249=595.82=595。以此类推。
我建的周期表为:
u16 maikuan_ayy_HL[14][13]=
{
{ 333 , 395 , 500 , 570 , 650 , 715 , 790 , 870 , 950 , 1040 , 1110 , 1195 , 1260 },//0
{ 600 , 735 , 875 , 1020 , 1160 , 1290 , 1430 , 1580 , 1740 , 1870 , 1995 , 2140 , 2280 },//1
{ 1100, 1380 , 1640 , 1910 , 2185 , 2440 , 2700 , 2985 , 3240 , 3530 , 3790 , 4050 , 4330 },//2
{ 1360, 1680 , 2030 , 2360 , 2695 , 3020 , 3335 , 3700 , 4010 , 4330 , 4690 , 5000 , 5320 },//3
{ 1620, 2000 , 2420 , 2800 , 3190 , 3600 , 4000 , 4400 , 4800 , 5200 , 5600 , 5980 , 6350 },//4
{ 2380, 2970 , 3570 , 4130 , 4750 , 5320 , 5900 , 6500 , 7045 , 7620 , 8270 , 8840 , 9420 },//5
{ 2072, 3082 , 4092 , 5102 , 6100 , 7290 , 8136 , 9170 , 10141, 11175, 12177, 13180, 14182},//6
{ 2542, 3787 , 5032 , 6276 , 7517 , 8694 , 10025, 11264, 12503, 13649, 14981, 16251, 17490},//7
{ 3822, 5707 , 7592 , 9476 , 11311, 13193, 15138, 17020, 18870, 20721, 22666, 24548, 26430},//8
{ 5123, 7657 , 10192, 12727, 15227, 17763, 20330, 22833, 25400, 27904, 30471, 32975, 35510},//9
{ 7682, 11497 , 15312, 19126, 22940, 26762, 30584, 34406, 38228, 42050, 45872, 49694, 53516},//10
{ 5111, 7653 , 10196, 12738, 15297, 17846, 20396, 22945, 25494, 28044, 30593, 33142, 35692},//11
{ 6391, 9574 , 12753, 15938, 19154, 22347, 25540, 28733, 31926, 35119, 38152, 41504, 44538},//12
{ 7961, 11928 , 15896, 19863, 23885, 27867, 31690, 35831, 39654, 43795, 47618, 51759, 55582} //13
};
当然这里实际值跟我理论计算并不一样,是因为我前面没想到这种倒推的办法,这是一点点通过调整填入自动重装载寄存器的初值来调的。
实际用好excel的话,这个表只需要半小时就建出来了。
在1ms一次的定时中断函数中:
void timer0_int (void) interrupt TIMER0_VECTOR //1ms
{
if(EN == 1)
{
if(f_duty_stop == 0)
{
f_duty_stop = 1;
f_rece_done = 0; //EN脚信号被拉高说明进入调节模式 当EN重新拉低时 等待UART接受完一次数据之后 再把pwm停止输出标志位清零(为了下一次直接输出设置的目标波形)
EAXSFR(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
SD = 1;
PWMA_ENO = 0;
PWMB_ENO = 0;
PWMA_CR1 &= ~0x01; //0:禁止计数器
PWMB_CR1 &= ~0x01; //0:禁止计数器
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
ALL_OFF;
f_current_change = 0;
f_status_change = 0;
}
}
else
{
if(f_duty_stop == 1 && f_rece_done == 1)
{
f_duty_stop=0;
f_duty_change=1;
}
}
if(f_duty_change == 0 && f_duty_stop == 0)
{
if(f_cnt_stop_out==1)
{
t_cnt_stop_out++;
if(t_cnt_stop_out>3)
{
t_cnt_stop_out=0;
f_cnt_stop_out=0;
}
}
else if(f_current_change == 1)
{
f_status_change=0;
EAXSFR(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
t_current_change++;
if(t_current_change < 78)
{
if(f_exactly_once == 0)
{
f_exactly_once=1;
PWMA_ENO |= 0x02; //开启第一路
PWMA_CNTRH = pwm_proid_hig;
PWMA_CNTRL = pwm_proid_low;
PWMA_CR1 |= 0x01; //1:使能计数器
SD = 0;
if(current == 1) {f_current_change=0;t_current_change=0;f_exactly_once=0;}
}
}
else if(t_current_change >= 78 && t_current_change < 82)
{
if(f_exactly_once == 1)
{
f_exactly_once=0;
SD = 1;
PWMA_ENO = 0;
PWMB_ENO = 0;
PWMA_CR1 &= ~0x01; //0:禁止计数器
PWMB_CR1 &= ~0x01; //0:禁止计数器
ALL_OFF;
}
}
else if(t_current_change >= 82 && t_current_change<160)
{
if(f_exactly_once == 0)
{
f_exactly_once=1;
PWMA_ENO |= 0x0A; //第一二路同时开启
PWMA_CNTRH = pwm_proid_hig;
PWMA_CNTRL = pwm_proid_low;
PWMA_CR1 |= 0x01; //1:使能计数器
SD = 0;
if(current==2) {f_current_change=0;t_current_change=0;f_exactly_once=0;}
}
}
else if(t_current_change >= 160 && t_current_change < 164)
{
if(f_exactly_once == 1)
{
f_exactly_once=0;
SD = 1;
PWMA_ENO = 0;
PWMB_ENO = 0;
PWMA_CR1 &= ~0x01; //0:禁止计数器
PWMB_CR1 &= ~0x01; //0:禁止计数器
ALL_OFF;
}
}
else if(t_current_change >= 164 && t_current_change<242)
{
if(f_exactly_once == 0)
{
f_exactly_once=1;
PWMA_ENO |= 0x2A; //开启 1 2 3 路
if(current == 3)
{
f_current_change=0;t_current_change=0;f_exactly_once=0;
}
else if(current >= 4)
{
PWMB_ENO |= 0x04; //如果还有第四路 那么就同时开启三四路
if(current == 4)
{
f_current_change=0;t_current_change=0;f_exactly_once=0;
}
}
PWMB_CNTRH = pwm_proid_hig;
PWMB_CNTRL = pwm_proid_low;
pwm_proid = pwm_proid+6;
pwm_proid_hig = (pwm_proid&0xff00)>>8;
pwm_proid_low = (pwm_proid&0x00ff);
PWMA_CNTRH = pwm_proid_hig;
PWMA_CNTRL = pwm_proid_low;
PWMA_CR1 |= 0x01; //1:使能计数器
PWMB_CR1 |= 0x01; //1:使能计数器
SD = 0;
}
}
else if(t_current_change >= 242 && t_current_change < 246)
{
if(f_exactly_once == 1)
{
f_exactly_once=0;
SD = 1;
PWMA_ENO = 0;
PWMB_ENO = 0;
PWMA_CR1 &= ~0x01; //0:禁止计数器
PWMB_CR1 &= ~0x01; //0:禁止计数器
ALL_OFF;
}
}
else if(t_current_change >= 246)
{
if(f_exactly_once == 0)
{
f_exactly_once=1;
PWMA_ENO |= 0x2A;PWMB_ENO |= 0x14; //开启 1 2 3 4 5 路
if(current ==6) //如果还有第六路 那么就同时开启五六路
{
PWMB_ENO |= 0x54;
}
PWMB_CNTRH = pwm_proid_hig;
PWMB_CNTRL = pwm_proid_low;
pwm_proid = pwm_proid+6;
pwm_proid_hig = (pwm_proid&0xff00)>>8;
pwm_proid_low = (pwm_proid&0x00ff);
PWMA_CNTRH = pwm_proid_hig;
PWMA_CNTRL = pwm_proid_low;
PWMA_CR1 |= 0x01; //1:使能计数器
PWMB_CR1 |= 0x01; //1:使能计数器
SD = 0;
f_current_change=0;t_current_change=0;f_exactly_once=0;
}
}
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
}
else if(f_status_change == 1 && f_current_change == 0) //
{
EAXSFR(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
// PWMA_CC1IE_Enable(); //允许PWMA更新中断
PWMB_CNTRH = pwm_proid_hig;
PWMB_CNTRL = pwm_proid_low;
pwm_proid = pwm_proid+6;
pwm_proid_hig = (pwm_proid&0xff00)>>8;
pwm_proid_low = (pwm_proid&0x00ff);
PWMA_CNTRH = pwm_proid_hig;
PWMA_CNTRL = pwm_proid_low;
if(current == 1) {PWMA_ENO |= 0x02;}
else if(current==2) {PWMA_ENO |= 0x0A;}
else if(current==3) {PWMA_ENO |= 0x2A;}
else if(current==4) {PWMA_ENO |= 0x2A;PWMB_ENO |= 0x04;}
else if(current==5) {PWMA_ENO |= 0x2A;PWMB_ENO |= 0x14;}
else if(current==6) {PWMA_ENO |= 0x2A;PWMB_ENO |= 0x54;}
PWMA_CR1 |= 0x01; //1:使能计数器
PWMB_CR1 |= 0x01; //1:使能计数器
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
SD=0;
f_status_change = 0;
pwm_cnt=0;
}
}
if(f_over_flag==0) //超时没有接收到串口数据 终止发PWM
{
t_over_time++;
if(t_over_time>=5000)
{
t_over_time=0;
f_over_flag=1;
SD = 1; //停止74HC244传输数据
EAXSFR(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
PWMA_ENO = 0;
PWMB_ENO = 0;
PWMA_CR1 &= ~0x01; //0:禁止计数器
PWMB_CR1 &= ~0x01; //0:禁止计数器
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
ALL_OFF;
f_current_change = 0;
f_status_change = 0;
}
}
}
这部分代码就是个人纯记录用的了,不清楚产品实际功能的读者是看不明白的。
改变了出PWM路数之后,要实现这样的效果:
就是显出1路波形78ms,然后停5ms,再出12路波形78ms,再停5ms……最终出到设定的路数。上图是从5路改为6路的波形图。
我的实现方法是,首先判断是否为改变了路数的状态,如果是的话,首先是暂停所有路输出3ms,这个是通过f_cnt_stop_out这个标志位来计时3ms。然后进入出波的逻辑过程,首先定义一个时间轴变量t_current_change,在这个时间轴小于78的时候,开启第一路输出(注意f_exactly_once标志位控制只执行一次)(意思是出1路波形78ms),同时判断设定的PWM路数是否大于1,如果等于一那么就没有后面的逻辑了。然后进时间轴大于等于78小于82的时候,停止输出,也是通过f_exactly_once标志位控制只执行一次。然后时间轴大于等于82小于160(78ms)的时候开启1和判断第2路是否开启,以此类推。
如果是没有改变PWM路数,只改了其他参数,那么出波就是所有波形停止3ms之后再根据设置值同时出波:
对应的是这段代码
else if(f_status_change == 1 && f_current_change == 0) //
{
EAXSFR(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
// PWMA_CC1IE_Enable(); //允许PWMA更新中断
PWMB_CNTRH = pwm_proid_hig;
PWMB_CNTRL = pwm_proid_low;
pwm_proid = pwm_proid+6;
pwm_proid_hig = (pwm_proid&0xff00)>>8;
pwm_proid_low = (pwm_proid&0x00ff);
PWMA_CNTRH = pwm_proid_hig;
PWMA_CNTRL = pwm_proid_low;
if(current == 1) {PWMA_ENO |= 0x02;}
else if(current==2) {PWMA_ENO |= 0x0A;}
else if(current==3) {PWMA_ENO |= 0x2A;}
else if(current==4) {PWMA_ENO |= 0x2A;PWMB_ENO |= 0x04;}
else if(current==5) {PWMA_ENO |= 0x2A;PWMB_ENO |= 0x14;}
else if(current==6) {PWMA_ENO |= 0x2A;PWMB_ENO |= 0x54;}
PWMA_CR1 |= 0x01; //1:使能计数器
PWMB_CR1 |= 0x01; //1:使能计数器
EAXRAM(); /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
SD=0;
f_status_change = 0;
pwm_cnt=0;
}
这个就是我实现波形同步的方法,直接操作PWM的计数器,通过手动补偿实现两路波形同步,效果如图:
两路波形只差8ns,对于使用场景来说够了。
另外记录一下这里为什么这样填初值:
PWMB_CNTRH = pwm_proid_hig;
PWMB_CNTRL = pwm_proid_low;
pwm_proid = pwm_proid+6;
pwm_proid_hig = (pwm_proid&0xff00)>>8;
pwm_proid_low = (pwm_proid&0x00ff);
PWMA_CNTRH = pwm_proid_hig;
PWMA_CNTRL = pwm_proid_low;
首先再主函数里面,我已经通过函数取到了要填入的初值pwm_proid_hig和pwm_proid_low,对于PWMB而言,这里直接赋值就好了。
但是对于PWMA而言,还需要补偿,为啥我是pwm_proid = pwm_proid+6; 通过先加总值然后移位再赋值呢。这里也是我一开始没考虑到的地方、
当时的现象是,我原本使用的是PWMA_CNTRL = pwm_proid_low+6;这样的写法,可以看到我注释掉了,这个写法我补偿6,波形滞后非常多,我把+6改为+3,滞后变的只有计时ns,可是一旦加大于4,滞后立马变为了us级。后来想到可能是溢出了,于是按照上图的方式改了滞后,波形恢复正常问题解决。
写到这,项目算是完成了,但是还有不完美的地方,比如改变状态之后,会出现这样的波形
我确实是没时间去深究出现的原因,这里直接丢给客户了,如果没有什么问题,这种bug也不打算修了,就这样把。
还有就是,负载需要阶梯波,其实对于我MCU而言,我给出这种波就行了:
至于负载端的阶梯波,实际上就是,我一路PWM控制一个MOS的G极,MOS的S极对地接一个电阻,各个MOS之间是并联的,导通的MOS越多,接入的电阻越多,并联的阻值越小,电流也就越大,负载端的波形就是一个阶梯形状的波形。
总结
这个项目说难不难,只是STC的弯弯绕绕太多了,如果换用ARM的芯片估计早就调试出来了,用STC这个硬是耗了一个星期时间。还是那句话,如果你准备用或是已经开始用STC了,那么建议你再考虑考虑。