(原创)基于MCU的频率可调,占空比可调的PWM实现(MCU,MCS-51/MSP430)
1.Abstract
做这个是受朋友之邀,用在控制电机转动的方面。他刚好在一家好的单位实习,手头工作比较多,无暇分身,所以找我帮忙做个模型。要求很明晰,PWM的频率在0~1KHz范围内,占空比0~99%范围内,二者均可调。抄下指标以后,回到实验室,细细分析以后,决定用MCU来实现一下,毕竟只分析,无实际结果也不是一个好的交代。
2.Content
2.1 理论分析
归根结底来说,是一个时序逻辑,即PWM输出波形是随着时间的推移而变化。用时序图的方式解释更明晰些。
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 != '\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的函数。这段代码是在我深入学习编码规则所写的,更侧重于功能的实现吧。
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 != '\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来说,去掉了液晶显示的功能,而且代码也写的比较合理,整体看起来也美观多了(都是自己编码的)。
FIG2.3 LantchPad 验证平台
2.3 验证
用两种单片机写了程序,就要用各自的平台来做测试了。测试仪器需要一台示波器或者逻辑分析仪就可以了。因为对示波器使用得比较习惯,而且借来手续不需要很多,故采用示波器进行数据采集,型号为Tektronix MSO2024 混合信号数字示波器,它是四踪的,但实际只需要用到其中一踪。
2.3.1对MCS-51的程序验证
为了和上位机通讯,晶振采用的是11.5092M,MCU型号为STC89RC54D+。用串口调试助手与MCU进行通信,按照通信格式要求,捕捉一个频率为100Hz,占空比为50%的信号和一个频率为50Hz,占空比为80%的信号。误差在后边一起进行讨论(随机设置,可任意设定)。
FIG 2.4 上位机发送数据
FIG2.5 频率为100Hz,占空比为50%的信号捕捉
FIG2.6 频率为50Hz,占空比为80%的信号捕捉
2.3.2 对MSP430的程序验证
实现平台是以MSP430G2553为核心芯片的LantchPad开发套件,MSP430G2553可以内部产生可调的时钟,故可以省去外部的晶振,在程序中,设置它的工作频率为16MHz。捕捉一个频率为500Hz,占空比为50%的信号和一个频率为1000Hz,占空比为99%的信号(随机设置,可任意设定)。
FIG2.7 频率为500Hz,占空比为50%的信号捕捉
FIG2.8 频率为1000Hz,占空比为99%的信号捕捉
2.3.3 误差对比及分析
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
(原创)基于MCU的频率可调,占空比可调的PWM实现(MCU,MCS-51/MSP430)的更多相关文章
- PCL—低层次视觉—点云滤波(基于点云频率)
1.点云的频率 今天在阅读分割有关的文献时,惊喜的发现,点云和图像一样,有可能也存在频率的概念.但这个概念并未在文献中出现也未被使用,谨在本博文中滥用一下“高频”一词.点云表达的是三维空间中的一种信息 ...
- cc2530 timer 3 PWM <可调占空比>
前提: 开始用的是 cc2530 timer 1来做PWM的,已经可调占空比了,但是由于硬件的改动,需要用timer 3 和 timer 4 代替.由于调试过程中出了些小问题,于是自己把这个贴出来.关 ...
- PCL—点云滤波(基于点云频率) 低层次点云处理
博客转载自:http://www.cnblogs.com/ironstark/p/5010771.html 1.点云的频率 今天在阅读分割有关的文献时,惊喜的发现,点云和图像一样,有可能也存在频率的概 ...
- iOS-Core-Animation-Advanced-Techniques/12-性能调优/性能调优.md
性能调优 代码应该运行的尽量快,而不是更快 - 理查德 在第一和第二部分,我们了解了Core Animation提供的关于绘制和动画的一些特性.Core Animation功能和性能都非常强大,但如果 ...
- sql让时间调前,调后的语句
时间调前,调后 select billid,DATEADD(mm,2,billdate) from bi_Bill 注:用dateadd(/时间年/月/日,调前或后多少,字段) mm为月份,2为调前两 ...
- (原创)基于FPGA的调光流水灯(Verilog,CPLD/FPGA)
1.Abstract 前几天做了一个呼吸灯,觉得确实挺有意思的:可惜的是只有一个灯管亮,板子上有四个灯,要是能让这些灯有序地亮起来,那应该更有趣味了!跟传统的一样,逻辑上做成一个流水灯的样式, ...
- 51单片机产生1Hz-5kHz可调占空比方波
学校的课程设计,总结一下. 注意 1.高低电平的改变不适合在主函数的while循环中,因为要有数码管动态显示的延时和其它逻辑处理,时间太长会不能及时改变高低电平值. 2.中断的执行时间一定是不能超过定 ...
- [原创]基于VueJs的前后端分离框架搭建之完全攻略
首先请原谅本文标题取的有点大,但并非为了哗众取宠.本文取这个标题主要有3个原因,这也是写作本文的初衷: (1)目前国内几乎搜索不到全面讲解如何搭建前后端分离框架的文章,讲前后端分离框架思想的就更少了, ...
- STM32 一个定时器产生4路 独立调频率,占中比可调,脉冲个数可以统计。
实现这个功能,基本原理是利用STM32 的输出比较功能. 1.其它设置就是普通定时器的设置这里开启,四个输出比较中断,和一个更新中断, 更新中断这里不需要开也可以达到目的,我这里开启是做其它的用处的. ...
随机推荐
- DataSet读取XML
string file = File.ReadAllText("c://123.xml", Encoding.Default); using (DataSet ds = new D ...
- java使用xsd校验xml样例
知识点:XSD文件是指XML结构定义 ( XML Schemas Definition )文件,是DTD的替代品.可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其 ...
- JSP、HTML标签
<%@ ...%> 表示是指令,主要用来提供整个JSP 网页相关的信息,并且用来设定JSP网页的相关属性,例如:网页的编码方式.语法.信息等.起始符号为: <%@ 终止符号为: %& ...
- 【转】提高VR渲染速度的关键
提高VR渲染速度的关键,这个教程比以往的教程都要重要很多,如果你是刚刚步入学习和上升阶段那么这将是你必须要看的东西,他会让你迅速提升技能达到比你死看书本好很多的效果,不多说上教程 VR的基本渲染方 ...
- IO同步、异步与阻塞、非阻塞
一.同步与异步同步/异步, 它们是消息的通知机制 1. 概念解释A. 同步所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回. 按照这个定义,其实绝大多数函数都是同步调用(例如si ...
- mongodb 的安装和使用
官方网址 http://www.mongodb.org 1.下载mongodb-win32-i386-latest.zip 解压 mongodb 3.1.5 需要 win7 下 下载安装内存补丁 ht ...
- 学习笔记—Fragement +Actionbar
这里实现的是tabhost类型的菜单选项,还有下拉菜单选项的 http://www.cnblogs.com/hanyuan/archive/2012/04/11/android_actionbar_a ...
- C#算法之向一个集合中插入随机不重复的100个数
一道非常经典的C#笔试题: 需求:请使用C#将一个长度为100的int数组,插入1-100的随机数,不能重复,要求遍历次数最少. 1.最简单的办法 var rd = new Random(); Lis ...
- 命令行启动win7系统操作部分功能
control.exe /name microsoft.folderoptions 启动资源管理器的 文件夹属性 选项卡 control.exe /name Microsoft.AddHardware ...
- vc++ 如何添加右键弹出菜单
一.创建新工程 二.编辑菜单资源 1.添加菜单 按"Ctrl+R",双击"Menu"图标 2.于菜单编辑器内编辑菜单 四.添加代码(红色部分) void CCM ...