基于51单片机的SPWM波(数码管)原理图、流程图、物料清单、仿真图、源代码.预计算SPWM正弦表的占空比核心公式
基于51单片机的SPWM波(数码管)
基于at89c1单片机。
要求能够输出SPWM并且测量输入正弦波的频率并显示。
直流电压通过DC-AC电路转为方波,搭建检测电路进行滤波和调节,得到正弦波,单片机采集该正弦波的频率,并显示。
#include "reg51.h"
sbit out=P3^7;//输出
unsigned int pwm=0;
unsigned char code table[]={ //正弦编码
128,130,132,134,136,139,141,143,145,148,150,152,154,156,158,161,163,165,167,169,171,173,175,177,180,182,184,186,188,190,191,
193,195,197,199,201,203,204,206,208,210,211,213,215,216,218,220,221,223,224,226,227,228,230,231,232,234,235,236,237,238,239,
240,242,243,243,244,245,246,247,248,249,249,250,251,251,252,252,253,253,254,254,254,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,254,254,254,253,253,252,252,251,251,250,249,249,248,247,246,245,244,244,243,242,241,240,238,237,236,235,
234,232,231,230,228,227,226,224,223,221,220,218,217,215,213,212,210,208,206,205,203,201,199,197,195,194,192,190,188,186,184,
182,180,178,176,174,171,169,167,165,163,161,159,156,154,152,150,148,146,143,141,139,137,134,132,130,128,125,123,121,119,117,
114,112,110,108,105,103,101,99,97,95,92,90,88,86,84,82,80,78,76,74,72,70,68,66,64,62,60,58,56,54,52,51,49,47,45,44,42,40,39,
37,36,34,33,31,30,28,27,25,24,23,22,20,19,18,17,16,15,14,13,12,11,10,9,8,7,7,6,5,5,4,3,3,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,1,1,1,2,2,3,3,4,4,5,6,6,7,8,9,10,10,11,12,13,14,15,16,18,19,20,21,22,24,25,26,28,29,31,32,34,35,37,38,40,42,43,45,47,48,
50,52,54,56,57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,88,90,92,94,96,98,101,103,105,107,109,112,114,116,118,120,123,125};
void delay()//延时,并产生SPWM
{
unsigned int i=0;
for(i=0;i<255;i++)
{
if(i<table[pwm])
out=1;
else
out=0;
}
}
void main()
{
IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开INT0的中断允许。
TMOD|=0X20;
TH1=118;
TL1=118;
ET1=1;//打开定时器0中断允许
EA=1;//打开总中断
TR1=1;
while(1)
{
delay();
}
}
void Timer1() interrupt 3//定时器1中断
{
if(pwm<359)//SPWM占空比控制
pwm++;
else
pwm=0;
}
————————————————
版权声明:本文为CSDN博主「cqtianxingkeji」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cqtianxingkeji/article/details/136224471
以下是一个基于单片机的SPWM(正弦脉宽调制)程序实现思路及示例代码框架,适用于常见的嵌入式平台(如STM32、51单片机等):
SPWM生成原理
通过调整PWM波的占空比,使其随时间按正弦规律变化,再经过滤波后可得到近似正弦波的输出。核心步骤包括:
- 生成正弦采样表:预先计算一个周期内不同角度对应的占空比值。
- 定时器中断更新占空比:通过定时器周期性更新PWM的占空比,实现正弦调制。
程序实现步骤
1. 生成正弦表
#define PI 3.1415926
#define SAMPLE_POINTS 200 // 一个周期的采样点数(影响输出波形精度)
uint16_t sin_table[SAMPLE_POINTS]; // 存储正弦波占空比值的数组
void generate_sin_table(void) {
float amplitude = 0.9; // 幅值(0~1,避免占空比溢出)
for (int i = 0; i < SAMPLE_POINTS; i++) {
float angle = 2 * PI * i / SAMPLE_POINTS;
sin_table[i] = (uint16_t)( (1 + sin(angle)) * amplitude * (PWM_ARR / 2) );
// PWM_ARR为定时器的自动重装载值(如999)
}
}
2. 配置PWM定时器
以STM32为例,配置定时器为PWM模式:
// 初始化PWM定时器(如TIM1)
TIM_HandleTypeDef htim_pwm;
htim_pwm.Instance = TIM1;
htim_pwm.Init.Prescaler = 71; // 预分频,决定PWM频率
htim_pwm.Init.CounterMode = TIM_COUNTERMODE_UP;
htim_pwm.Init.Period = PWM_ARR; // ARR值,决定PWM分辨率
htim_pwm.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim_pwm);
// 配置PWM通道(如通道1)
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // 初始占空比
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim_pwm, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim_pwm, TIM_CHANNEL_1); // 启动PWM
3. 配置定时中断更新占空比
// 初始化另一个定时器(如TIM2)用于触发中断
TIM_HandleTypeDef htim_int;
htim_int.Instance = TIM2;
htim_int.Init.Prescaler = 71; // 预分频,决定SPWM频率
htim_int.Init.CounterMode = TIM_COUNTERMODE_UP;
htim_int.Init.Period = 100; // 中断周期 = (Prescaler+1)*Period / 主频
HAL_TIM_Base_Init(&htim_int);
HAL_TIM_Base_Start_IT(&htim_int); // 启动定时器中断
// 中断服务函数中更新占空比
uint16_t pwm_index = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
__HAL_TIM_SET_COMPARE(&htim_pwm, TIM_CHANNEL_1, sin_table[pwm_index]);
pwm_index = (pwm_index + 1) % SAMPLE_POINTS;
}
}
关键参数说明
- PWM频率:由定时器的
Prescaler
和ARR
决定,公式:
[ f_{PWM} = \frac{主频}{(Prescaler + 1) \times (ARR + 1)} ] - SPWM频率:由中断频率和采样点数决定,公式:
[ f{正弦波} = \frac{f{中断}}{SAMPLE_POINTS} ]
注意事项
- 根据实际硬件调整定时器配置和引脚映射。
- 正弦表幅值需小于1,避免PWM占空比超过ARR值。
- 高频SPWM需优化中断服务函数,或使用DMA传输提升性能。
可根据具体单片机型号(如STM32CubeMX或Arduino)调整代码细节。
https://www.elecfans.com/zt/1078833/
在SPWM(正弦脉宽调制)中,预先计算一个周期内不同角度对应的占空比值是核心步骤,通过生成正弦表实现精确波形控制。以下是具体计算方法:
计算公式
占空比计算公式为:
DutyCycle = (Amplitude × sin(2 × π × 角度 / 周期) + Offset) × (ARR + 1) / 2
其中:
- Amplitude 为正弦波幅值(0~1之间);
- Offset 为偏移量(通常为1,使占空比在0%~100%范围内);
- ARR 为定时器自动重装载值(即PWM周期);
- 角度 为当前点在周期中的位置(0~周期长度)。 1
单极性与双极性区别
- 单极性SPWM:使用一个通道,占空比在0%~100%之间变化;
- 双极性SPWM:需两个互补通道,占空比需对称(例如一个通道为正弦波,另一个为反向正弦波),计算公式为:
DutyCycle1 = (1 + M × sin(2 × π × 角度 / 周期)) × (ARR + 1) / 2
DutyCycle2 = (1 - M × sin(2 × π × 角度 / 周期)) × (ARR + 1) / 2
其中 M 为调制比(0 < M < 1)。 12
实现步骤
- 配置定时器:设置为PWM模式,选择中心对齐模式以减少谐波;
- 计算正弦表:根据周期长度(如200点)计算每个角度对应的占空比;
- 中断更新:通过定时器中断实时更新占空比寄存器。 13
SPWM(正弦脉宽调制)通过预先计算正弦周期内各角度对应的占空比值,生成按正弦规律变化的PWM波形。具体实现步骤如下:
1. 正弦表生成原理
核心公式为:DutyCycle = (Amplitude * sin(2π * i / N) + Offset) * (ARR + 1) / 2
其中:
Amplitude
为幅值(0~1),避免过调制;Offset
通常取1,使占空比范围覆盖0%~100%(单极性)1;ARR
为定时器自动重装载值,决定PWM分辨率17。
2. 实现步骤
-
确定参数:
- 载波频率(PWM频率)与调制波频率(如50Hz正弦波)1;
- 采样点数N(如200点/周期)6;
- 幅值Amplitude(推荐0.8以下)6。
-
计算正弦表:
对每个采样点i(0≤i<N),计算归一化正弦值并映射到CCR寄存器值:cCopy Code
CCR_Value = (sin(2π * i / N) * 0.5 + 0.5) * ARR; // 单极性SPWM
双极性需使用互补通道,公式调整为对称占空比15。
-
定时器配置:
- 设置PWM模式(如STM32的TIMx_ARR=999,Prescaler=71,生成1kHz载波)7;
- 通过中断或DMA更新CCR值,动态调整占空比6。
3. 优化建议
- 查表法:预计算正弦表存储于数组,减少实时计算开销4;
- 对称采样:采用平均对称规则采样法提高电压利用率5;
- 滤波处理:输出端添加RC低通滤波还原正弦波7。
示例代码片段(STM32 HAL库):
uint16_t SIN_TABLE[200]; // 预生成正弦表
for (int i = 0; i < 200; i++) {
SIN_TABLE[i] = (sin(2 * M_PI * i / 200) * 0.5 + 0.5) * 999; // ARR=999
}
// 定时器中断中更新CCR
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, SIN_TABLE[counter++ % 200]);
我们使用STM32 HAL库生成SPWM波形,核心是生成一个正弦波形的脉宽调制序列。SPWM(Sinusoidal PWM)是通过比较正弦调制波和三角载波来生成脉冲的,但在微控制器中,我们通常通过预先计算正弦表(一个周期内不同角度对应的占空比值)并定时更新PWM占空比来实现。
公式方法:
1.确定载波频率(PWM频率)和调制波频率(正弦波频率)。
2.确定每个PWM周期内需要更新的点数(即正弦表的大小N)。
3.计算正弦表:对于每个点i(0到N-1),计算正弦值,然后映射到PWM的占空比。
占空比的计算公式:DutyCycle = (Amplitude * sin(2 * π * i / N) + Offset) * (ARR +1)/2其中:-Amplitude:正弦波的幅值(0到1之间,通常小于1,以避免过调制)-Offset:偏移量,通常为1(使正弦值在0到2之间),这样占空比范围在0%到100%之间(对于单极性 SPWM)或正负交替(对于双极性SPWM,需要另外处理)-ARR:PWM定时器的自动重装载值(即计数周期)
注意:对于单极性SPWM,我们通常使用一个通道,占空比在0%到100%之间变化。对于双极性SPWM,通常使用两个互补的通道,并且
如何计算产生SPWM所需要的占空比
[cpp] view plain copy
- /** @author Mei Jilin
- @date 2013/9/2
- @brief 加入生成SPWM部分
- **/
- #include "timer.h"
- #include "led.h"
- static uint16_t CCR3_Val = 1800;
- static uint16_t PrescalerValue = 0;
- /*PWM输出配置说明,*/
- /* - Prescaler = (TIM3CLK / TIM3 counter clock) - 1 选择TIMER工作在36MHZ
- The TIM3 is running at 36 MHz: TIM3 Frequency = TIM3 counter clock/(ARR + 1)
- = 36 MHz / 3600 = 10 KHz
- TIM3 Channel1 duty cycle = (TIM3_CCR1/ TIM3_ARR)* 100 */
- /*转换周期10K*/
- void PWM_Init(void)
- {
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- TIM_OCInitTypeDef TIM_OCInitStructure;
- GPIO_InitTypeDef GPIO_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
- /* TIM3 clock enable */
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
- /* GPIOA and GPIOB clock enable */
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|
- RCC_APB2Periph_AFIO, ENABLE);
- /* GPIOA Configuration:TIM3 Channel1, 2, 3 and 4 as alternate function push-pull */
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //TIM1_CH1N
- GPIO_Init(GPIOB, &GPIO_InitStructure);
- /* Compute the prescaler value */
- PrescalerValue = (uint16_t) (SystemCoreClock / 36000000) - 1; //TIMER2 - 36MHZ
- /* Time base configuration */
- TIM_TimeBaseStructure.TIM_Period = 3600; /*TIM3_ARR = 2879,12.5Khz*/
- TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
- TIM_TimeBaseStructure.TIM_ClockDivision = 0;
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
- TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
- TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
- /* Channel 1, 2,3 and 4 Configuration in PWM mode */
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
- TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //关闭互补输出
- TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
- TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
- TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
- TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Set;
- TIM_OC1Init(TIM1, &TIM_OCInitStructure);
- TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //这句的功能是让改变CCR2之后马上有效
- TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
- NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 6;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStructure);
- TIM_Cmd(TIM1, ENABLE);
- /* TIM1 Main Output Enable */
- TIM_CtrlPWMOutputs(TIM1, ENABLE); //TIM1需要加上这句,貌似低级定时器不需要
- }
- uint16_t Duty[] = {1800,1856,1912,1968,2023,2078,2131,2183,2233,2282,2329,2373,2416,2456,2493,
- 2528,2559,2588,2614,2636,2655,2671,2684,2692,2698,2699,2698,2692,2684,2671,2655,2636,2614,
- 2588,2559,2528,2493,2456,2416,2373,2329,2282,2233,2183,2131,2078,2023,1968,1912,1856,1800,
- 1743,1687,1631,1576,1521,1468,1416,1366,1317,1270,1226,1183,1143,1106,1071,1040,1011,985,
- 963,944,928,915,907,901,900,901,907,915,928,944,963,985,1011,1040,1071,1106,1143,1183,1226,
- 1270,1317,1366,1416,1468,1521,1576,1631,1687,1743,1799,1799};
- uint16_t count = 0;
- uint16_t num = sizeof(Duty)/(sizeof(Duty[0]));
- void TIM1_CC_IRQHandler(void)
- {
- if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET)
- {
- TIM1->SR = (uint16_t)~TIM_IT_CC1;
- TIM1->CCR1 = Duty[count];
- count++;
- if(count==num)
- {
- count=0;
- }
- }
- }
在上一篇文章里粘贴了STM32产生SPWM的代码,我在编写这些代码时最大的问题就是如何得到占空比,就是代码中的数组 Duty[]。他的思想就是对正弦波采样,在采样点出用PWM的占空比来代替正弦波在该点的数值。最容易想到就是用定时器的ARR值乘以sin(2*pi*f*t)(为什么它可以代替?2025.9.18),就可以得到对应的寄存器CCR值。但是CCR值不能为负,所以要把sin(2*pi*f*t)变为
A*sin(2*pi*f*t)+B的形式,取A=B=0.5;那么计算公式为ARR*(0.5*sin(2*pi*f*t)+0.5),上传一个C++写的exe文件,可以帮助产生Duty[]数组.
http://download.csdn.NET/detail/mjlsuccess/6205567
软件使用vs2010编写的可能在有些电脑上无法运行,如果你装有VS2010,我可以把原工程发给你
转载于http://blog.csdn.net/mjlsuccess/article/details/11019805
自己思考:10KZ为载波(锯齿波)的频率,最终输入给电机为100HZ正弦波,则载波比为100,也就是100个数据,同时ccr不能为负值,故进行抬升,原本应该为0-1800,进行抬升1800的0.5倍
更多推荐
所有评论(0)