呼吸灯实验
实验目标与原理(铁头山羊)
实验目标:在面包板上用两颗开发套件包中的 LED(红、蓝)连接定时器一通道一,产生互补 PWM 信号驱动 LED 实现呼吸灯效果。
工作原理:让 LED 亮度连续变化类似正弦函数,将标准正弦函数 y = sin2πt 通过加 1、乘以 0.5 变换到 0 到 1 范围,通过调节 PWM 占空比模拟正弦信号,使 LED 两端电压按此函数变化。
周期设定:PWM 信号周期设为 1 毫秒,通过配置定时器让其输出该周期且占空比按规律变化的 PWM 波。
我们将占空比设置为这个正弦函数(这样就可以实现呼吸灯的效果),并且假设周期为1ms。
- 输出 PWM 波形分析
- 电路结构:通用定时器输出比较部分电路左边是 CNT 和 CCR 比较结果,右边是输出比较电路,通过 Tim CH1 输出到 GPL 引脚,有四个同样单元分别输出到 CH2、CH3、CH4。
- 输出模式控制器:输入是 CNT 和 CCR 大小关系,输出是 REF 高低电平,有 8 种模式可通过寄存器配置,如冻结模式可暂停输出,匹配时电平翻转模式可输出频率可调占空比 50% 的 PWM 波形,PWM 模式一和 PWM 模式 2 可输出频率和占空比都可调的 PWM 波形。
- 工作流程:以 PWM 模式一为例,配置好定时器单元使 CNT 自增,CNT 与 CCR 比较,根据大小关系改变 REF 电平,经极性选择输出到 GPL 口完成 PWM 波形输出,占空比受 CCR 值调控。
参数计算
配置IO口
先查看要配置CH1和CH1N对应的IO口。
找到IO口之后,查看GPIO口的配置模式。
因为标准的正弦函数,频率为1HZ,是使PWM信号的占空比为这个正弦函数。
占空比等于 CRR1 / 周期 ,周期为 1000 ,所以CRR1等于 这个数值。
代码编写初始化
初始化函数声明:在模板工程的 main.c 文件中声明 app_pwm_init 函数初始化 PWM 波产生。
IO 引脚初始化:开启 GPLA 时钟,声明变量设置 PA8 引脚模式为 GPL_mode_AFPP,最大输入速度两兆赫兹,调用 GPO_init 初始化;PB13 初始化代码类似,修改端口等参数。
定时器 1 初始化:开启定时器一时钟 ; 配置 时基单元参数,PSC 设为 71,arr 设为 999,计数方向上计数,RCR 设为 0;开启 arr 寄存器预加载;闭合开关使时基单元工作。
- 输出比较配置:配置输出比较通道一参数,模式选 PWM1,极性都选正,使能正常输出和互补输出开关;闭合 Moe 主开关。
设置CCR1的值:前面有进行过计算,设置为值之后 调用下面的比较函数就可以了。
完整的main.c函数
#include "stm32f10x.h" // Device header
#include "math.h"
#include "delay.h"
#include "OLED.h"
void App_Pwm_Init(void);
int arr[11]= {0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};
int main(void)
{
App_Pwm_Init();
OLED_Init();
int i = 0;
while(1)
{
// float t = GetTick()* 1.0e-3f;
// OLED_ShowNum(1, 1, GetTick(), 6);
// float duty = 0.5* (sin( 2*3.14*t)+1);
// uint16_t CRR1 = duty * 1000;
// OLED_ShowNum(1, 1, CRR1, 6);
TIM_SetCompare1(TIM1, arr[(i++)%11]);
Delay_ms(200);
}
}
void App_Pwm_Init(void)
{
//1. 配置GPIO口
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
//配置 GPIOA - pin8
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
//配置GPIOB - pin13
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
//2. 配置时基单元参数
//2.1 开启定时器1时钟信号
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
//2.2 配置时基单元参数(TIM)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Prescaler = 71;
TIM_TimeBaseInitStruct.TIM_Period = 999;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);
//2.3 开启自动重载寄存器预加载
TIM_ARRPreloadConfig(TIM1, ENABLE);
//2.4 开启总开关
TIM_Cmd(TIM1, ENABLE);
//配置输出比较模块
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
//设置互补输出的极性
TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_High;
//设置正常输出的极性
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
//使能后面的开关
TIM_OCInitStruct.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
//捕获比较寄存器的初值 设置为0
TIM_OCInitStruct.TIM_Pulse = 0;
TIM_OC1Init(TIM1, &TIM_OCInitStruct);
//3.闭合MOE总开关
TIM_CtrlPWMOutputs(TIM1, ENABLE);
// 开启比较寄存器预加载
TIM_CCPreloadControl(TIM1, ENABLE);
}
江协科技PWM呼吸灯
设计思路:
PWM 模块建立
- 添加文件:在 hardware 文件夹右键添加名为 PWM 的 c 文件和 h 文件。
- 文件初始化:在 c 文件中添加 STDM32 的头文件,开始编写 PWM 初始化函数 void PWM init void 。
PWM 初始化步骤
- 开启时钟:通过 RCC 开启 TIM 外设和 GPIO 外设的时钟。
- 配置时机单元:包括时钟源选择和时基单元配置,可参考之前定时中断的代码进行复制修改。
- 配置输出比较单元:使用 Tim OC1EMIT 等四个函数配置输出比较模块,用 Tim OC struct init 给输出比较结构体赋默认值,设置输出比较模式、极性、使能等参数。(一般有的参数这个模块中没有,可以使用这个结构体初始化函数,就如时基单元结构体中定时器一为高级定时器,有个功能其他定时器没有,就不需要配置,但是如果为高级定时器使用就会出现问题)
- 配置 GPIO:将 PWM 对应的 GPIO 口初始化为复用推挽输出,根据引脚定义表确定 PWM 与 GPO 的对应关系。
- 运行控制:启动计数器,输出 PWM。
库函数介绍
- 输出比较配置函数:Tim OC1init 等四个函数用于配置输出比较模块,Tim OC struct init 用于给输出比较结构体赋默认值。
- 其他小功能函数:
- TIM_ForcedOC1Config 1234 等函数用于配置强制输出模式等小功能,一般了解即可。
- TIM_OC1PreloadConfig 输出比较函数预加载功能配置
- TIM_OC1FastConfig配置定时器的输出比较快速模式
- TIM_ClearOC1Ref 清除定时器通道 1 的参考信号(OC1REF)状态。该函数主要用于高级定时器(如 TIM1、TIM8)的互补输出控制,在故障处理或特定应用场景下需要手动清除参考信号时使用。
- TIM_OC1NPolarityConfig 高级定时器互补通道的极性 TIM_OC1PolarityConfig 正常模式的极性
- TIM_GetCapture1 会将当前计数器的值锁存到捕获寄存器(CCR1)中。
TIM_GetCapture1
函数的作用就是读取这个锁存值,从而计算外部信号的时间参数 就是上升沿或者下降沿时的捕获数据可以用来计算PWM脉宽 和 频率等数据
- 运行更改参数函数:TIM_SetCompare11234 这四个函数用于单独更改 CCR 寄存器值,运行时更改占空比需要用到,较为重要。
代码编写
- 实际单元初始化:复制定时器定时中断工程的初始化代码,去掉打开中断和配置 NV IC 的部分,保留启动定时器部分。
- 输出比较单元初始化:使用 Tim OCE init 函数初始化 PA0 口对应的第一个输出比较通道,给结构体变量赋初始值并配置相关参数。
- GPR 初始化:复制 LED 点 c 文件的初始化代码,更改 GPL PIN 和 GPL 模式为复用推挽输出。
- 设置 PWM 参数:根据公式计算并设置 ARR、PSC 和 CCR 参数,产生特定频率、占空比和分辨率的 PWM 波形。
- 验证参数:通过示波器采集波形验证参数设置是否正确,调节 CCR 值可改变占空比和 LED 亮度。
- 实现呼吸灯效果:封装 Tim set compare 一函数,在主循环中通过不断改变 CCR 值实现 LED 呼吸灯效果。
引脚重映射
开启时钟:使用 AFIO 进行引脚重映射,需先开启 AFIO 的时钟。
重映射配置:使用 gplo pin remap config 函数进行重映射配置,根据手册选择合适的重映射方式,如将 Tim 2 的 CG1 从 PA0 挪到 PA15 可选择部分重映射一或完全重映射。
解除调试端口复用:若重映射引脚是调试端口,需使用 GPIO PIN remap configure 函数的相关参数 如果引脚默认有其他功能解除调试端口复用,下面几个函数使用需谨慎。
- 第一个函数 关闭JTRST调试 使用后 PB4变为正常的Io口
- 第二个 关闭 JTAG 使用后 PA15 PB3 Pb4 变为正常的Io口
- 第三个 关闭所有调试功能 SWD 和 JTAG 调试端口全部解除 就没有调试功能 下载之后就没有调试端口 ,之后就不能使用STLink下载进去程序 就要使用串口,下载有调试端口的程序 才可以恢复
验证重映射:编译下载代码,验证引脚重映射是否成功,成功后 PA0 的呼吸灯不亮,PA15 接上 LED 可实现呼吸灯效果 。
// PWM.c #include "stm32f10x.h" // Device header void Pwm_Init(void) { //配置时基单元和比较输出 //定时器2在总线1上 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //将定时器配置为 内部时钟 ,也可以不写 默认为内部时钟 TIM_InternalClockConfig(TIM2); //配置时钟选择模式为 内部时钟模式 TIM_InternalClockConfig(TIM2); //配置时基单元结构体 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //滤波的原理是固定f的频率下进行猜样,如果连续N个采样点都相同就说明输入稳定, //如果不相同就说明不稳定就会进行处理,f的来源有两个 内部时钟直接而来,一个就是经过分频器分频来 //所以和时基单元关系不大 TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //这里要配置 1KHZ 占空比为50% 频率为1%的PWM波形 TIM_TimeBaseInitStruct.TIM_Period = 100-1; // ARR TIM_TimeBaseInitStruct.TIM_Prescaler = 720-1; // PSC TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct); //TIM_TimeBaseInit 会触发一次update事件 将值写入,所以函数过后将中断标志位清除 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //开始时钟使能 TIM_Cmd(TIM2, ENABLE); //配置比较输出模块 TIM_OCInitTypeDef TIM_OCInitStruct; TIM_OCStructInit(&TIM_OCInitStruct); //配置比较输出模式 TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; //ccr 比较器的值 后面会变化 所以配置为0 TIM_OCInitStruct.TIM_Pulse = 0; //CCR TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OC1Init(TIM2, &TIM_OCInitStruct); TIM_CCPreloadControl(TIM2, ENABLE); //配置GPIo口 查看引脚 对应GPIo A0 //配置GPIO口 定时器2的ETR外部时钟模式2 的gpio引脚映射是gpioA0 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; //查看Io配置 需要将io口配置为 推挽复用输出 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); } void Pwm_compare(uint16_t compare) { TIM_SetCompare1(TIM2, compare); }
//main.c #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Pwm.h" uint16_t i; int main(void) { OLED_Init(); Pwm_Init(); while(1) { //周期为100 for(i = 0; i < 100; i++) { Pwm_compare(i); Delay_ms(20); } //周期为100 for(i = 0; i < 100; i++) { Pwm_compare(100-i); Delay_ms(20); } } }