• (原创)基于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:                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 != '')
         {
             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"->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_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的函数。这段代码是在我深入学习编码规则所写的,更侧重于功能的实现吧。

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

        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 != '')
      {
        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来说,去掉了液晶显示的功能,而且代码也写的比较合理,整体看起来也美观多了(都是自己编码的)。

    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

    6.Attachment

    MCS-51的工程附录 http://i.cnblogs.com/Files.aspx

    MSP-430的工程附录 http://i.cnblogs.com/Files.aspx

  • 相关阅读:
    如何在android studio 1.0 启动时设置代理【解决WARN
    【转】如何阅读android源码
    【Android】 BroadcastReceiver详解
    如何在Android app中支持多主题
    从源码角度剖析 setContentView() 背后的机制
    【转】Android EditText的使用及值得注意的地方
    【转】Android应用程序打包时,出现错误:"XXX" is not translated in "af" (Afrikaans), "am" (Amharic), "ar" (Arabic).....
    【转】Fragment对用户可见的判断实践,亲测有效
    【转】从java1到java9每个版本都有什么新特性?
    【转】贝塞尔曲线介绍
  • 原文地址:https://www.cnblogs.com/hechengfei/p/4114486.html
Copyright © 2020-2023  润新知