首页 > 代码库 > (原创)基于MCU的频率可调,占空比可调的PWM实现(MCU,MCS-51/MSP430)

(原创)基于MCU的频率可调,占空比可调的PWM实现(MCU,MCS-51/MSP430)

1.Abstract

    做这个是受朋友之邀,用在控制电机转动的方面。他刚好在一家好的单位实习,手头工作比较多,无暇分身,所以找我帮忙做个模型。要求很明晰,PWM的频率在0~1KHz范围内,占空比0~99%范围内,二者均可调。抄下指标以后,回到实验室,细细分析以后,决定用MCU来实现一下,毕竟只分析,无实际结果也不是一个好的交代。

2.Content

  2.1 理论分析

    归根结底来说,是一个时序逻辑,即PWM输出波形是随着时间的推移而变化。用时序图的方式解释更明晰些。

image FIG2.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:                PWMFile Function:         频率、占空比均可调的程序File Dependency:    system library -- intrins.hFile 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;                        // 字符存储 countunsigned 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_UARTFunction:        串口初始化Input:            NoneOutput:            NoneNote:            通信方式 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_Timer0Function:        定时器0初始化Input:            NoneOutput:            NoneNote:            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_ProcessFunction:        将串口接收的数据转为十进制数并离散化Input:            NoneOutput:            NoneNote:            None -----------------------------------------------------*/void Data_Process()                          // 数据处理{// 字符转 十进制数 例如 "123"->123   
 fre  = ((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_SERFunction:        串口中服Input:            NoneOutput:            NoneNote:            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_ISRFunction:        定时器0中断服务Input:            NoneOutput:            NoneNote:            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:            DelayUs2xFunction:        延时2倍的微秒时长Input:            t -- 延时2*t usOutput:            NoneNote:            None------------------------------------------------*/void DelayUs2x(unsigned char t){    while(--t);}/*------------------------------------------------Name:            DelayMsFunction:        毫秒延时Input:            t -- 延时时长Output:            NoneNote:            程序整合时应该去掉------------------------------------------------*/void DelayMs(unsigned char t){      while(t--) {     //大致延时1mS     DelayUs2x(245);     DelayUs2x(245); }}/*------------------------------------------------Name:            LCD_Write_ComFunction:        液晶写指令时序Input:            Com -- 指令Output:            NoneNote:            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_DataFunction:        液晶写数据时序Input:            Dat -- 写入数据Output:            NoneNote:            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_ClearFunction:    清屏Input:        NoneOutput:        NoneNote:        None-----------------------------------------------*/ void LCD_Clear(void) {  LCD_Write_Com(0x01);  DelayMs(5);}/*------------------------------------------------Name:            LCD_Write_StringFunction:        向液晶写入字符串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_CharFunction:        向液晶写入一个字符Input:            x -- 液晶行数                y -- 液晶列数                Data -- 写入的字符Output:            NoneNote:            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_InitFunction:        液晶初始化Input:            NoneOutput:            NoneNote:            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_LNKFunction:        向终端输出换行符Input:            NoneOutput:            NoneNote:            None -----------------------------------------------*/void Send_LNK(){    SBUF=0x0A;    Short_Delay();    Short_Delay();    Short_Delay();}/* --------------------------------------------Name:            ShowWelcomeScreenFunction:        显示主界面,辅助使用Input:            NoneOutput:            NoneNote:            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:            SendFullWarningFunction:        发出已满警告Input:            NoneOutput:            NoneNote:            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:            MainFunction:        主函数,程序入口Input:            NoneOutput:            NoneNote:            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的函数。这段代码是在我深入学习编码规则所写的,更侧重于功能的实现吧。

2014_11_16 002 FIG2.2 MCS-51验证平台

    2.2.2基于新型MSP430的MCU程序编码

    刚好手上有一套比较新的MSP430套件——MSP430 LantchPad,核心芯片是MSP430G2553,用新的器件可以将频率做得更高,误差更小。

/*-----------------------------------------File Name:      main.cFile Function:  实现PWM频率可调,占空比可调测试File Dependency: system library -- intrinsics.hFile 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_SetFunction: 串口引脚配置Input:    NoneOutput:   NoneNote:     TXD  设置输出          RXD  设置输入 -----------------------------*/void UART_IO_Set(){   P1SEL   |= BIT1 + BIT2;   P1SEL2  |= BIT1 + BIT2;      P1DIR   |= BIT2;  // OUTPUT   P1DIR   &= ~BIT1; // INPUT}/* ----------------------------Name:     UART_InitFunction: 串口初始化Input:    NoneOutput:   NoneNote:     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_ISRFunction: 串口中断接收函数Input:    NoneOutput:   NoneNote:     None -------------------------------------*/#pragma vector=USCIAB0TX_VECTOR__interrupt void UCA0_TX_ISR(void){     IFG2  &= ~UCA0TXIFG;}/* ------------------------------------Name:     MCU_AnswerFunction: MCU应答,将所收到的数据返回至终端,         供确认Input:    NoneOutput:   NoneNote:     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_ProcessFunction: 将接收到的数据转换为十进制数据,        并将所得的十进制数据离散化--转成        计数的个数Input:    NoneOutput:   NoneNote:     数据处理阶段是比较敏感时期,关        闭中断打扰 -------------------------------------*/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_ISRFunction: 接收数据,存入到接收寄存器,并做        结束符检验Input:    NoneOutput:   NoneNote:     能做数据已满的错误警示 ----------------------------------------*/#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_DelayFunction: 做短时间的延时缓冲Input:    NoneOutput:   NoneNote:     整合程序以后需将这部分精细化,尽量        不要出现在大程序中。 -----------------------------------------*/void Short_Delay(){  unsigned char i,j;  i=200;j=200;  while(i--)j--;}/* ----------------------------------------Name:     Send_ByteFunction: 发送一个字节至终端Input:    dat -- 要发送的字符Output:   NoneNote:     None -----------------------------------------*/void Send_Byte(unsigned char dat){  unsigned char i;  UCA0TXBUF=dat;      i=15;    while(i--)      Short_Delay();}/* ----------------------------------------Name:     Send_LNKFunction: 发送一个换行符至终端Input:    NoneOutput:   NoneNote:     None ------------------------------------------*/void Send_LNK(){  unsigned char i=15;  UCA0TXBUF=0x0a;  while(i--)    Short_Delay();}/* ------------------------------------------Name:     Send_StringFunction: 向终端输出字符串Input:    str -- 字符串指针Output:   NoneNote:     None ------------------------------------------*/void Send_String(unsigned char *str){  while(*str != \0)  {    Send_Byte(*str);      str++;  }}/* --------------------------------------Name:     Clock_InitFunction: 系统时钟配置Input:    NoneOutput:   NoneNote:     根据数据手册做相应配置,设置为内部       最大时钟16MHz, 并将时钟做输出以便检测;       时钟检测引脚:P1.4   -------------------------------------------*/void Clock_Init(){  DCOCTL = 0xa0;  BCSCTL1 = 0x8f;  BCSCTL2 = 0x00;  BCSCTL3 = 0x04;    P1DIR |= BIT4;  P1SEL |= BIT4;  }/* ----------------------------------------Name:     PWM_IO_SetFunction: 设置PWM输出引脚Input:    NoneOutput:   NoneNote:     PWM输出引脚为P1.0 -----------------------------------------*/void PWM_IO_Set(){  P1OUT &= ~BIT0; //P1.0 Set as PWM OUT PIN  P1DIR |= BIT0;}/* ---------------------------------------Name:     ShowWelcomScreenFunction: 显示主界面Input:    NoneOutput:   NoneNote:     系统复位后,在终端显示一部分字符,        提示如何使用 ----------------------------------------*/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_InitFunction:  脉冲单位宽度,最小的计数时间Input:     NoneOutput:    NoneNote:      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_ISRFunction: 定时器0中断服务程序Input:    NoneOutput:   NoneNote:     控制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:     mainFunction: 程序主入口Input:    NoneOutput:   NoneNote:     系统初始化,总调度入口-------------------------------------------------*/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来说,去掉了液晶显示的功能,而且代码也写的比较合理,整体看起来也美观多了(都是自己编码的)。

2014_11_16 005 FIG2.3 LantchPad 验证平台

  2.3 验证

    用两种单片机写了程序,就要用各自的平台来做测试了。测试仪器需要一台示波器或者逻辑分析仪就可以了。因为对示波器使用得比较习惯,而且借来手续不需要很多,故采用示波器进行数据采集,型号为Tektronix MSO2024 混合信号数字示波器,它是四踪的,但实际只需要用到其中一踪。

  2.3.1对MCS-51的程序验证

    为了和上位机通讯,晶振采用的是11.5092M,MCU型号为STC89RC54D+。用串口调试助手与MCU进行通信,按照通信格式要求,捕捉一个频率为100Hz,占空比为50%的信号和一个频率为50Hz,占空比为80%的信号。误差在后边一起进行讨论(随机设置,可任意设定)。

05080 FIG 2.4 上位机发送数据

TEK00009 FIG2.5 频率为100Hz,占空比为50%的信号捕捉

tek00012 FIG2.6 频率为50Hz,占空比为80%的信号捕捉

  2.3.2 对MSP430的程序验证

    实现平台是以MSP430G2553为核心芯片的LantchPad开发套件,MSP430G2553可以内部产生可调的时钟,故可以省去外部的晶振,在程序中,设置它的工作频率为16MHz。捕捉一个频率为500Hz,占空比为50%的信号和一个频率为1000Hz,占空比为99%的信号(随机设置,可任意设定)。

TEK00001 FIG2.7 频率为500Hz,占空比为50%的信号捕捉

TEK00004 FIG2.8 频率为1000Hz,占空比为99%的信号捕捉

  2.3.3 误差对比及分析

image FIG2.9 误差对比

    由上表可以看出,实际做出来的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

(原创)基于MCU的频率可调,占空比可调的PWM实现(MCU,MCS-51/MSP430)