(原创)基于MCU的频率可调,占空比可调的PWM实现(MCU,MCS-51/MSP430)
1.Abstract
做这个是受朋友之邀,用在控制电机转动的方面。他刚好在一家好的单位实习,手头工作比较多,无暇分身,所以找我帮忙做个模型。要求很明晰,PWM的频率在0~1KHz范围内,占空比0~99%范围内,二者均可调。抄下指标以后,回到实验室,细细分析以后,决定用MCU来实现一下,毕竟只分析,无实际结果也不是一个好的交代。
2.Content
2.1 理论分析
归根结底来说,是一个时序逻辑,即PWM输出波形是随着时间的推移而变化。用时序图的方式解释更明晰些。
basic_time由内部产生,main_cnt记录的是要输出的PWM占空比,sub_cnt用来输出PWM波形。图中是以占空比为80%为例。从上往下,从左往右看,深色部分表示上一次的状态,当设置为占空比为80%时,在basic_time的上升沿下,子计数器开始从0到99计数,当计数个数满设定的占空比时,PWM引脚输出低电平,直至下一次重新计数开始,PWM引脚恢复高电平。
使用CPLD/FPGA或许更容易实现这个逻辑,使用微控制器就需要转一转思维,将这里的basic_time转换成计数器,在MCU的时钟驱动下逐步计数,计满预定的值以后再重新计数。它的功能正如它的名称,单位时钟产生器。
波形产生原理就如上所述了,还有一个要求就是能对PWM的频率和占空比进行控制,好在一般MCU有串行通信接口,可以避免使用外部资源,再适合不过了。将程序写得完整一点,加入数据正确辨识处理等功能。
2.2 程序编码
2.2.1基于传统MCS-51的MCU程序编码
/* -------------------------------------------------------------- File Name: PWM File Function: 频率、占空比均可调的程序 File Dependency: system library -- intrins.h File Note: 串口进行数据输入,P1.7脚PWM输出 频率范围 0 - 999, 占空比 0 - 99 晶振11.0592M ----------------------------------------------------------------*/ #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 #include <intrins.h> sbit PWM_OUT = P1^7; // PWM 引脚输出口 unsigned int t=0; // time count unsigned char rx[20]=0; // 接收字符存储 unsigned char n=0; // 字符存储 count unsigned char fre=0; // 频率 unsigned char duty=0; // 占空比 unsigned int n_fre=0; // 换算后,频率对应需计数的个数 unsigned int n_duty=0; // 换算后,占空比应需计数的个数 unsigned char rx_end_flag=0; unsigned char rx_full_flag=0; /*------------------------------------------------ 函数声明 ------------------------------------------------*/ void SendStr(unsigned char *s); void MCU_Answer(); void Data_Process(); void Send_LNK(); void LCD_Refresh(); /*------------------------------------------------ Name: Init_UART Function: 串口初始化 Input: None Output: None Note: 通信方式 8-1,baudrate:9600 ------------------------------------------------*/ void Init_UART (void) { SCON = 0x50; // SCON: 模式 1, 8-bit UART, 使能接收 TMOD |= 0x20; // TMOD: timer 1, mode 2, 8-bit 重装 TH1 = 0xFD; // TH1: 重装值 9600 波特率 晶振 11.0592MHz TL1 = 0xFD; TR1 = 1; // TR1: timer 1 打开 EA = 1; //打开总中断 TI = 0; RI = 0; ES = 1; // ES = 1; //打开串口中断 } /*------------------------------------------------- Name: Init_Timer0 Function: 定时器0初始化 Input: None Output: None Note: 11.0592M / 100/ 100 晶振频率/主计数百分化/从计数百分比 --------------------------------------------------*/ void Init_Timer0(void) { TMOD |= 0x02; // Timer 0, Mode 2, 8-bit reload TH0 = 0xA3; TL0 = 0xA3; ET0 = 1; TR0 = 1; } void Short_Delay() //短暂延时,消除串口工作频率太快反应不过来的问题 { unsigned char i=100,j=100; while(--i) --j; } void SendByte(unsigned char dat) // 发送一个字符 { SBUF = dat; } void SendStr(unsigned char *s) // 发送一个字符串 { while(*s != '\0') { SendByte(*s); Short_Delay(); Short_Delay(); Short_Delay(); s ++; } } char CheckCharLegal() { unsigned char i=0; while(i<5) { if((rx[i]>'9')||(rx[i]<'0')) return -1; i++; } if(n==5) return 1; else return -2; } /* ------------------------------------------------ Name: MCU_Answer Function: MCU应答,下位机上传做数据验证 Input: None Output: None Note: 有对数据的检验并提示 -------------------------------------------------*/ void MCU_Answer() // MCU 应答 { // 手动输入频率和占空比以后,单片机做出相应的界面应答 unsigned char i=0; char temp; temp=CheckCharLegal(); n=0; if(temp==1) { SendStr("Input Success!"); SendStr(" Fre: "); i=0; // 输出频率 while(i<3) { SendByte(rx[i]); Short_Delay(); Short_Delay(); Short_Delay(); i++; } SendStr("Hz"); SendStr(" Duty: "); // 输出占空比 while(i<5) { SendByte(rx[i++]); Short_Delay(); Short_Delay(); Short_Delay(); } SendStr("%"); SBUF=0x0A; // 换行 Short_Delay(); // 消除串口工作频率太快 Short_Delay(); Short_Delay(); LCD_Refresh(); } else if(temp == -1) { SendStr("Ops, including ILLEGAL character, for conforming..."); Send_LNK(); SendStr("Input data: "); SendStr(rx); Send_LNK(); SendStr("Error occured, input failure!"); Send_LNK(); } else if(temp == -2) { SendStr("Ops, input data ILLEGAL, format: FFFDDe or FFFDDE"); Send_LNK(); SendStr("Error occured, input failure!"); Send_LNK(); } } /*----------------------------------------------------- Name: Data_Process Function: 将串口接收的数据转为十进制数并离散化 Input: None Output: None Note: None -----------------------------------------------------*/ void Data_Process() // 数据处理 { // 字符转 十进制数 例如 "123"->123fre = ((rx[0]-'0')*100 + (rx[1]-'0')*10 + (rx[2]-'0'))/2;duty = (rx[3]-'0')*10 + (rx[4]-'0'); t=0;// reload // 将频率和占空比转成相应的计数个数n_fre = (unsigned int)((10000.0/(float)fre+0.5));n_fre = n_fre>>1; // 将误差简单处理, 四舍五入n_duty = (unsigned int)(100.0*((float)duty)/((float)fre)+0.5);n_duty = n_duty>>1; } /*------------------------------------------------ Name: UART_SER Function: 串口中服 Input: None Output: None Note: None ------------------------------------------------*/ void UART_SER() interrupt 4 //串行中断服务程序 { unsigned char temp; if(RI) //判断是接收中断产生 { RI=0; temp = SBUF; //标志位清零 if(n<10) { if((temp=='e')||(temp=='E')) //end input char { // n = 0; rx_end_flag=1; } else { rx[n++]=temp; } } else { rx_full_flag=1; } } if(TI) //如果是发送标志位,清零 TI=0; } /* ---------------------------------------------- Name: Timer0_ISR Function: 定时器0中断服务 Input: None Output: None Note: 100us/time -----------------------------------------------*/ void Timer0_ISR() interrupt 1 // 100us/time { if(t >= (n_fre-1)) t=0; if(t<(n_duty)) PWM_OUT = 1; else PWM_OUT = 0; t++; } // 液晶端口定义 sbit RS = P2^4; //定义端口 sbit RW = P2^5; sbit EN = P2^6; #define RS_CLR RS=0 #define RS_SET RS=1 #define RW_CLR RW=0 #define RW_SET RW=1 #define EN_CLR EN=0 #define EN_SET EN=1 #define DataPort P0 /*------------------------------------------------ Name: DelayUs2x Function: 延时2倍的微秒时长 Input: t -- 延时2*t us Output: None Note: None ------------------------------------------------*/ void DelayUs2x(unsigned char t) { while(--t); } /*------------------------------------------------ Name: DelayMs Function: 毫秒延时 Input: t -- 延时时长 Output: None Note: 程序整合时应该去掉 ------------------------------------------------*/ void DelayMs(unsigned char t) { while(t--) { //大致延时1mS DelayUs2x(245); DelayUs2x(245); } } /*------------------------------------------------ Name: LCD_Write_Com Function: 液晶写指令时序 Input: Com -- 指令 Output: None Note: None ------------------------------------------------*/ void LCD_Write_Com(unsigned char com) { // while(LCD_Check_Busy()); //忙则等待 RS_CLR; RW_CLR; EN_SET; DataPort= com; _nop_(); EN_CLR; Short_Delay(); Short_Delay(); Short_Delay(); } /*------------------------------------------------ Name: LCD_Write_Data Function: 液晶写数据时序 Input: Dat -- 写入数据 Output: None Note: None ------------------------------------------------*/ void LCD_Write_Data(unsigned char Data) { // while(LCD_Check_Busy()); //忙则等待 RS_SET; RW_CLR; EN_SET; DataPort= Data; _nop_(); EN_CLR; Short_Delay(); Short_Delay(); Short_Delay(); } /*------------------------------------------------ Name: LCD_Clear Function: 清屏 Input: None Output: None Note: None -----------------------------------------------*/ void LCD_Clear(void) { LCD_Write_Com(0x01); DelayMs(5); } /*------------------------------------------------ Name: LCD_Write_String Function: 向液晶写入字符串 Input: x -- 液晶行 y -- 液晶列 s -- 字符串首地址 Note: None ------------------------------------------------*/ void LCD_Write_String(unsigned char x,unsigned char y,unsigned char *s) { if (y == 0) { LCD_Write_Com(0x80 + x); //表示第一行 } else { LCD_Write_Com(0xC0 + x); //表示第二行 } while (*s) { LCD_Write_Data( *s); s ++; } } /*------------------------------------------------ Name: LCD_Write_Char Function: 向液晶写入一个字符 Input: x -- 液晶行数 y -- 液晶列数 Data -- 写入的字符 Output: None Note: None ------------------------------------------------*/ void LCD_Write_Char(unsigned char x,unsigned char y,unsigned char Data) { if (y == 0) { LCD_Write_Com(0x80 + x); } else { LCD_Write_Com(0xC0 + x); } LCD_Write_Data( Data); } /*------------------------------------------------ Name: LCD_Init Function: 液晶初始化 Input: None Output: None Note: None ------------------------------------------------*/ void LCD_Init(void) { LCD_Write_Com(0x38); /*显示模式设置*/ DelayMs(5); LCD_Write_Com(0x38); DelayMs(5); LCD_Write_Com(0x38); DelayMs(5); LCD_Write_Com(0x38); DelayMs(5); LCD_Write_Com(0x08); /*显示关闭*/ DelayMs(5); LCD_Write_Com(0x01); /*显示清屏*/ DelayMs(5); LCD_Write_Com(0x06); /*显示光标移动设置*/ DelayMs(5); LCD_Write_Com(0x0C); /*显示开及光标设置*/ DelayMs(5); } /* ----------------------------------------------- Name: LCD_Refresh Function: 液晶刷新 Input: None Output: None Note: None -----------------------------------------------*/ void LCD_Refresh() { unsigned char i=0; LCD_Clear(); LCD_Write_String(2,0,"fre: "); while(i<3) { LCD_Write_Char(8+i,0,rx[i++]); Short_Delay(); Short_Delay(); Short_Delay(); } LCD_Write_String(13,0,"Hz"); LCD_Write_String(2,1,"duty: "); while(i<5) { LCD_Write_Char(5+i,1,rx[i++]); Short_Delay(); Short_Delay(); Short_Delay(); } LCD_Write_String(13,1,"%"); } /* ---------------------------------------------- Name: Send_LNK Function: 向终端输出换行符 Input: None Output: None Note: None -----------------------------------------------*/ void Send_LNK() { SBUF=0x0A; Short_Delay(); Short_Delay(); Short_Delay(); } /* -------------------------------------------- Name: ShowWelcomeScreen Function: 显示主界面,辅助使用 Input: None Output: None Note: None ---------------------------------------------*/ void ShowWelcomeScreen() { SendStr(" WELCOME ... "); Send_LNK(); SendStr("Format: HHHDDe or HHHHDDE"); Send_LNK(); SendStr("Example: 10050e means 100HZ, Duty 50%"); Send_LNK(); SendStr("Note: f ranges from 1Hz to 100Hz, Duty Ranges from 1 to 99"); Send_LNK(); SendStr("Insert control data to start!"); Send_LNK(); Send_LNK(); Send_LNK(); Send_LNK(); } /* ----------------------------------------------- Name: SendFullWarning Function: 发出已满警告 Input: None Output: None Note: None ------------------------------------------------*/ void SendFullWarning() { Send_LNK(); Send_LNK(); SendStr("Error occured: Input data overflowed!"); Send_LNK(); SendStr("Please Re-Insert control data..."); Send_LNK(); n=0; } /*------------------------------------------------ Name: Main Function: 主函数,程序入口 Input: None Output: None Note: None ------------------------------------------------*/ void main () { Init_UART(); Init_Timer0(); LCD_Init(); LCD_Clear(); P1 =0x7f; LCD_Write_String(4,0,"Welcome"); LCD_Write_String(0,1,"Status: Stopped!"); ShowWelcomeScreen(); while (1) { if(rx_end_flag == 1) { ET0=0; PWM_OUT = 0; // 关闭PWM输出 MCU_Answer(); Data_Process(); rx_end_flag=0; ET0=1; } if(rx_full_flag == 1) { PWM_OUT = 0; SendFullWarning(); rx_full_flag=0; } } }
这段代码有我写的一部分,也参照了别人的一部分,尤其是液晶那一块儿,液晶的程序已经写了很多回了,但随着计算机的格式化,数据都没有好好保存起来,久而久之文件就都丢失了,索性就直接用别人的代码了。
总体来说,这段代码是非常的冗余的,很不规范,要是实际去用,就得分文件写好了,然后去掉那些大量占用CPU的函数。这段代码是在我深入学习编码规则所写的,更侧重于功能的实现吧。
2.2.2基于新型MSP430的MCU程序编码
刚好手上有一套比较新的MSP430套件——MSP430 LantchPad,核心芯片是MSP430G2553,用新的器件可以将频率做得更高,误差更小。
/*----------------------------------------- File Name: main.c File Function: 实现PWM频率可调,占空比可调测试 File Dependency: system library -- intrinsics.h File Note: 通过串口设置占空比和频率,P1.0设 置为信号输出。输入格式 ################################################# HHHHDDe 或者 HHHHDDE 格式说明: HHHH表示输入频率,四位。范围0000-1000; 如1000表示频率1KHz。 DD 表示占空比,两位。范围00-99;如50表 示占空比为50% E/e 表示输入结束标志符END/end。 ################################################# 输入格式具有位数校验和字符校验,可 避免因输入不当对波形产生影响。 程序仅作测试使用,资源已分配完毕,如 需供其他模块使用,则必要做整合处理。 ---------------------------------------------*/ #include "msp430g2553.h" #include "intrinsics.h" // 宏定义 #define PWM_OUT_HIGH P1OUT |= BIT0 #define PWM_OUT_LOW P1OUT &=~BIT0 #define nop __no_operation() // 参量定义 unsigned int fre=0; // 频率 unsigned char duty=0; // 占空比 unsigned long int n=0; // 计数器 unsigned long int n_fre=0; // 数字化频率 unsigned long int n_duty=0; // 数字化占空比 unsigned char rx[10]; // 接收字符最大宽度 unsigned long int t=0; // 时间计数器 // 函数声明,可单独列文件 /* 发送字符串函数*/ void Send_String(unsigned char *s); /* 发送一个字节 */ void Send_Byte(unsigned char dat); /* 必要短暂延时 */ void Short_Delay(); /* 发送换行符 */ void Send_LNK(); /* ---------------------------- Name: UART_IO_Set Function: 串口引脚配置 Input: None Output: None Note: TXD 设置输出 RXD 设置输入 -----------------------------*/ void UART_IO_Set() { P1SEL |= BIT1 + BIT2; P1SEL2 |= BIT1 + BIT2; P1DIR |= BIT2; // OUTPUT P1DIR &= ~BIT1; // INPUT } /* ---------------------------- Name: UART_Init Function: 串口初始化 Input: None Output: None Note: 8-1-1 baudrate 9600 -----------------------------*/ void UART_Init() { UART_IO_Set(); UCA0CTL0 = 0x00; // 8-1 UCA0CTL1 |= UCSSEL1+UCSSEL0+UCSWRST; //----------------1M------------------- // UCA0BR1 = 0x00; // UCA0BR0 = 0x6D; // UCA0MCTL |= UCBRS1; // Baudrate 9600 //--------------16M-------------------- UCA0BR1 = 0x06; UCA0BR0 = 0x82; UCA0MCTL |= UCBRS2+UCBRS1; UCA0CTL1 &= ~UCSWRST; IE2 |= UCA0TXIE + UCA0RXIE; IFG2 &= ~(UCA0TXIFG+UCA0RXIFG); __enable_interrupt(); } /* ------------------------------------ Name: UCA0_TX_ISR Function: 串口中断接收函数 Input: None Output: None Note: None -------------------------------------*/ #pragma vector=USCIAB0TX_VECTOR __interrupt void UCA0_TX_ISR(void) { IFG2 &= ~UCA0TXIFG; } /* ------------------------------------ Name: MCU_Answer Function: MCU应答,将所收到的数据返回至终端, 供确认 Input: None Output: None Note: None -------------------------------------*/ void MCU_Answer() { unsigned char i=0; Send_String("Input Sucess! Fre: "); while(i<4) { Send_Byte(rx[i++]); } Send_String(" Hz Duty: "); while(i<6) { Send_Byte(rx[i++]); } Send_String("%"); Send_LNK(); } /* ------------------------------------ Name: Data_Process Function: 将接收到的数据转换为十进制数据, 并将所得的十进制数据离散化--转成 计数的个数 Input: None Output: None Note: 数据处理阶段是比较敏感时期,关 闭中断打扰 -------------------------------------*/ void Data_Process() { PWM_OUT_LOW; __disable_interrupt(); t=0; fre = (rx[0]-'0')*1000+(rx[1]-'0')*100+(rx[2]-'0')*10+(rx[3]-'0'); duty= (rx[4]-'0')*10+(rx[5]-'0'); /* n_fre = 200000/fre; n_duty = 2000/fre*duty; */ n_fre = (unsigned long)(200000.0/(double)fre+0.5); n_duty = (unsigned long)(2000.0/((double)fre)*((double)duty)+0.5); //n_duty = n_fre*duty/100; __enable_interrupt(); } /* -------------------------------------- Name: UCA0_RX_ISR Function: 接收数据,存入到接收寄存器,并做 结束符检验 Input: None Output: None Note: 能做数据已满的错误警示 ----------------------------------------*/ #pragma vector=USCIAB0RX_VECTOR __interrupt void UCA0_RX_ISR(void) { unsigned char temp; temp = UCA0RXBUF; // __disable_interrupt(); // disable all interrupt IFG2 &= ~UCA0RXIFG; if(n<10) { if((temp=='e')||(temp=='E')) { n=0; MCU_Answer(); Data_Process(); } else { rx[n++]=temp; } } else { n=0; Send_String("Error: Input Full!"); Send_LNK(); } // __enable_interrupt(); } /* --------------------------------------- Name: Short_Delay Function: 做短时间的延时缓冲 Input: None Output: None Note: 整合程序以后需将这部分精细化,尽量 不要出现在大程序中。 -----------------------------------------*/ void Short_Delay() { unsigned char i,j; i=200;j=200; while(i--)j--; } /* ---------------------------------------- Name: Send_Byte Function: 发送一个字节至终端 Input: dat -- 要发送的字符 Output: None Note: None -----------------------------------------*/ void Send_Byte(unsigned char dat) { unsigned char i; UCA0TXBUF=dat; i=15; while(i--) Short_Delay(); } /* ---------------------------------------- Name: Send_LNK Function: 发送一个换行符至终端 Input: None Output: None Note: None ------------------------------------------*/ void Send_LNK() { unsigned char i=15; UCA0TXBUF=0x0a; while(i--) Short_Delay(); } /* ------------------------------------------ Name: Send_String Function: 向终端输出字符串 Input: str -- 字符串指针 Output: None Note: None ------------------------------------------*/ void Send_String(unsigned char *str) { while(*str != '\0') { Send_Byte(*str); str++; } } /* -------------------------------------- Name: Clock_Init Function: 系统时钟配置 Input: None Output: None Note: 根据数据手册做相应配置,设置为内部 最大时钟16MHz, 并将时钟做输出以便检测; 时钟检测引脚:P1.4 -------------------------------------------*/ void Clock_Init() { DCOCTL = 0xa0; BCSCTL1 = 0x8f; BCSCTL2 = 0x00; BCSCTL3 = 0x04; P1DIR |= BIT4; P1SEL |= BIT4; } /* ---------------------------------------- Name: PWM_IO_Set Function: 设置PWM输出引脚 Input: None Output: None Note: PWM输出引脚为P1.0 -----------------------------------------*/ void PWM_IO_Set() { P1OUT &= ~BIT0; //P1.0 Set as PWM OUT PIN P1DIR |= BIT0; } /* --------------------------------------- Name: ShowWelcomScreen Function: 显示主界面 Input: None Output: None Note: 系统复位后,在终端显示一部分字符, 提示如何使用 ----------------------------------------*/ void ShowWelcomeScreen() { Send_String(" WELCOME ... "); Send_LNK(); Send_String("Format: HHHHDDe or HHHHDDE"); Send_LNK(); Send_String("Example: 100050e means 1000HZ, Duty 50%"); Send_LNK(); Send_String("Note: f ranges from 1Hz to 2000Hz, Duty Ranges from 1 to 99"); Send_LNK(); Send_String("Insert control data to start!"); Send_LNK(); Send_LNK(); Send_LNK(); Send_LNK(); } /* ------------------------------------------------ Name: TimerA0_Init Function: 脉冲单位宽度,最小的计数时间 Input: None Output: None Note: Nmin = 16M / 1000 / 100 / 2 = 80 单位计数 = 时钟/最大频率/百分化/脉冲折半 ---------------------------------------------------*/ void TimerA0_Init() { CCTL0 = CCIE; // CCR0 interrupt enabled CCR0 = 78; // should be 80, for compensate TACTL = TASSEL_2 + MC_1; // SMCLK, upmode __enable_interrupt(); } /* ----------------------------------------------- Name: TimerA0_ISR Function: 定时器0中断服务程序 Input: None Output: None Note: 控制PWM输出,若小于占空比,则输出高,反之, 则输出低 ------------------------------------------------*/ #pragma vector=TIMER0_A0_VECTOR __interrupt void TimerA0_ISR(void) { if(t>=n_fre) t=0; if(t<n_duty) PWM_OUT_HIGH; else PWM_OUT_LOW; t++; } /* ----------------------------------------------- Name: main Function: 程序主入口 Input: None Output: None Note: 系统初始化,总调度入口 -------------------------------------------------*/ void main( void ) { // Stop watchdog timer to prevent time out reset WDTCTL = WDTPW + WDTHOLD; Clock_Init(); UART_Init(); TimerA0_Init(); PWM_IO_Set(); PWM_OUT_HIGH; PWM_OUT_LOW; ShowWelcomeScreen(); while(1) { // add other events here } }
相对编码MCS-51来说,去掉了液晶显示的功能,而且代码也写的比较合理,整体看起来也美观多了(都是自己编码的)。
2.3 验证
用两种单片机写了程序,就要用各自的平台来做测试了。测试仪器需要一台示波器或者逻辑分析仪就可以了。因为对示波器使用得比较习惯,而且借来手续不需要很多,故采用示波器进行数据采集,型号为Tektronix MSO2024 混合信号数字示波器,它是四踪的,但实际只需要用到其中一踪。
2.3.1对MCS-51的程序验证
为了和上位机通讯,晶振采用的是11.5092M,MCU型号为STC89RC54D+。用串口调试助手与MCU进行通信,按照通信格式要求,捕捉一个频率为100Hz,占空比为50%的信号和一个频率为50Hz,占空比为80%的信号。误差在后边一起进行讨论(随机设置,可任意设定)。
2.3.2 对MSP430的程序验证
实现平台是以MSP430G2553为核心芯片的LantchPad开发套件,MSP430G2553可以内部产生可调的时钟,故可以省去外部的晶振,在程序中,设置它的工作频率为16MHz。捕捉一个频率为500Hz,占空比为50%的信号和一个频率为1000Hz,占空比为99%的信号(随机设置,可任意设定)。
2.3.3 误差对比及分析
由上表可以看出,实际做出来的MCS-51的频率误差远小于MSP430,而占空比误差比MSP430大的多。MCS-51随着设置频率的减小频率误差和相位误差均出现明显变化;而MSP430的频率误差和相位误差比较稳定。
因为整个程序是由C语言编写,故与编译器的性能有很大的关系,代码中也用到了部分的延时函数,即使采用定时器的方式来获得精准的时间片,仍会由于晶振源和部分的冗余代码而产生时间偏差。根据在同一段代码执行的条件下,设定不同的频率和占空比,比较它们的误差变化幅度,可以衡量器件的质量。即通过判断误差的稳定性来评价器件的性能。
若需获得更佳的PWM控制信号,可以采取使用汇编的方式进行编码,也可以采用新型高速的MCU来实现,达到减小误差的目的。
3.Conclusion
通过对设计要求的分析,编写了基于两种不同的MCU的代码并进行测试,在可接受误差的范围内实现了频率和占空比可调的PWM输出程序。尽管原理比较简单,将数据测试好并进行细致的分析,做好相关的笔记,算是能对朋友有一个好的交代了。
4.Referece
[1] 单片机技术 何立民
[2] www.stc.com
[3] www.ti.com
5.Platform
1).TimeGen V3.1
2). Keil V3.51
3). IAR Embedded Workbench for MSP430 IDE V5.40.3
4). LY51S
5). TI MSP430 LantchPad
6.Attachment
MCS-51的工程附录 http://i.cnblogs.com/Files.aspx
MSP-430的工程附录 http://i.cnblogs.com/Files.aspx