嵌入式软件开发学习过程记录,本部分结合本人的学习经验撰写,系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维,为RTOS做铺垫(本部分基于库函数版实现),如有不足之处,敬请批评指正。
(3)中主要讲解中断系统,包括按键中断与定时器中断,并扩展了基于定时器的PWM波输出
一 STM中断系统
中断是指处理器执行某个程序时,突然接收到来自硬件或软件的请求,需要暂时中断程序的执行,转而去响应这个请求。中断可以高效地响应设备的事件,例如按键、定时器、串口传输等,以及异常情况,例如除零、非法指令等。在嵌入式系统中,中断技术是实现实时性、提高系统可靠性的一种重要手段。
中断可以分为硬件中断和软件中断两种类型:
- 硬件中断:由硬件发起的中断请求,例如外部设备(如传感器、键盘、鼠标)的事件触发,或者CPU本身的异常(如非法指令、除零等)。在硬件中断请求发生时,CPU会立即将当前的执行现场保存下来,然后跳转到中断服务程序去处理中断。
- 软件中断:由软件程序发起的中断请求,通常使用近似于硬件中断的机制实现。软件中断常常用于进行任务切换和协程操作。在软件中断请求发生时,CPU同样会将当前的执行现场保存下来,并跳转到对应的中断服务程序去处理中断。
总之,中断技术是处理器与外部设备交互的重要手段,它可以高效地响应设备的事件,并实现实时性、提高系统可靠性。在嵌入式系统中,中断技术得到广泛应用,是实现各种实时应用的重要基础。
NVIC与EXTI
NVIC和EXTI都是嵌入式系统中常用的中断机制,但它们在使用方式和应用场景上有所不同。
NVIC(Nested Vectored Interrupt Controller)是ARM Cortex-M微控制器中一个内置的中断控制器,可以管理所有可屏蔽中断源,包括外部中断、内部中断和异常(如PendSV、SysTick等)。通过NVIC的配置,我们可以设置中断优先级、使能/禁止中断、触发中断等,从而实现不同的应用场景。NVIC擅长处理基于软件的中断和多任务调度,例如对定时器的计时、串口数据接收等操作进行中断处理。
EXTI(External Interrupt)则是针对特定的外部中断源定义的一种中断机制,通常由芯片厂商提供统一的API接口,方便开发人员调用。使用EXTI,开发人员通常需要先指定中断源的类型(例如下降沿触发、上升沿触发等),然后才能进行中断处理。EXTI擅长处理和外部硬件相关的中断,例如按键、传感器等设备的状态变化触发中断。
总之,NVIC和EXTI都是嵌入式系统中常用的中断机制,但根据使用方式和应用场景的不同,在具体的应用中选择合适的中断机制是非常重要的。
STM32F10x 外部中断/事件控制器(EXTI)包含多达 20 个用于产生事件/中断请求的边沿检测器。EXTI 的每根输入线都可单独进行配置,以选择类型(中断或事件)和相应的触发事件(上升沿触发、下降沿触发或边沿触发),还可独立地被屏蔽。
EXTI 分为两大部分功能,一个产生中断,另一个产生事件,这两个功能从硬件上就有所差别,这个在框图中也有体现。从图中标号 3 的位置处就分出了两条线路,一条是 3-4-5 用于产生中断,另一条是 3-6-7-8 用于产生事件。
1.标号 1 为输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,输入线一般是存在电平变化的信号。
2.边沿检测电路,EXTI 可以对触发方式进行选择,通过上升沿触发选择寄存器和下降沿触发选择寄存器对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给红色框 3 电路,否则输出无效信号 0。而上升沿和下降沿触发选择这两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
3.是一个或门电路,一端输入信号线由标号 2 提供,一端由软件中断事件寄存器提供,只要有一个为有效信号 1,标号 3 电路则输出有效信号 1,否则为无效信号 0。软件中断事件寄存器允许我们使用软件来启动中断/事件线,这个在某些地方非常有用。
6.是一个与门电路,一端来至标号 3 电路的输出信号,一端来至事件屏蔽寄存器,只有两者都为有效电平 1,标号 6 输出才有效。当事件屏蔽寄存器设置为 0 时,不管标号 3 电路输出为 1 还是 0,标号 6 电路输出均为 0。当事件屏蔽寄存器设置为 1 时,标号 6 电路输出取决于标号 3 电路输出,这样就可以简单的控制事件屏蔽寄存器来实现是否产生事件的目的。
7.脉冲发生器电路,其输入端只与标号 6 电路输出有关,标号 6 输出有效,脉冲发生器才会输出一个脉冲信号。
从上面 EXTI 框图可以看出,中断线路最终会输入到 NVIC 控制器中,从而会运行中断服务函数,实现中断内功能,这个是软件级的。而事件线路最后产生的脉冲信号会流向其他的外设电路,是硬件级的。在 EXTI 框图最顶端可以看到,其外设接口时钟是由 PCLK2,即 APB2 提供,所以在后面使能 EXTI 时钟的时候一定要注意。
外部中断/事件线映射
EXTI 配置步骤(EXTI 相关库函数在 stm32f10x_exti.c 和 stm32f10x_exti.h 文件中)
接下来就可以利用EXTI中断和按键,实现一个按键中断的案例,映射到实际工作中就是按键触发事件的例子
1. 首先,使能IO口时钟,配置IO口模式为输入
2. 开启 AFIO 时钟,设置 IO 口与中断线的映射关系:接下来需要将 GPIO 映射到对应的中断线上,只要使用到外部中断,就必须先使能 AFIO 时钟,前面已经说了它是挂接在 APB2 总线上的,所以:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能AFIO时钟
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);//将GPIO映射到对应的中断线上//以将中断线0映射到GPIOA端口为例:GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
3. 配置中断分组(NVIC),使能中断:在前面介绍EXTI原理时,我们了解到 EXTI 产生中断线路最终是流向 NVIC 控制器的,由 NVIC 调用中断服务函数,因此需要对 NVIC 进行配置,配置 NVIC 范例如下:具体包括设置中断通道,设置抢占优先级和响应优先级,通道使能
值得一提的是,中断优先级数字越低,优先级越高
第 0 组:所有 4 位用于指定响应优先级第 1 组:最高 1 位用于指定抢占式优先级,最低 3 位用于指定响应优先级第 2 组:最高 2 位用于指定抢占式优先级,最低 2 位用于指定响应优先级第 3 组:最高 3 位用于指定抢占式优先级,最低 1 位用于指定响应优先级第 4 组:所有 4 位用于指定抢占式优先级
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//EXTI0 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
4. 初始化EXTI,选择触发方式:配置好 NVIC 后,我们还需要对中断线上的中断初始化,EXTI 初始化库函数如下:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
函数形参是有一个结构体EXTI_InitTypeDef 类型的指针变量,EXTI_InitTypeDef 结构体成员变量如下:
typedef struct{uint32_t EXTI_Line; //中断/事件线EXTIMode_TypeDef EXTI_Mode; //EXTI 模式EXTITrigger_TypeDef EXTI_Trigger; //EXTI 触发方式FunctionalState EXTI_LineCmd; //中断线使能或失能}EXTI_InitTypeDef;其中:EXTI_Line:EXTI 中断/事件线选择,可配置参数为 EXTI0-EXTI20。EXTI_Mode:EXTI 模式选择,可以配置为中断模式 EXTI_Mode_Interrupt 和 事件模式 EXTI_Mode_Event。EXTI_Trigger : 触发方式选择,可以配置为上升沿触发EXTI_Trigger_Rising、下降沿触发 EXTI_Trigger_Falling、上升沿和下降沿触发 EXTI_Trigger_Rising_Falling。EXTI_LineCmd:中断线使能或者失能,配置 ENABLE 为使能,DISABLE 为失能,我们这里要使用外部中断,所以需使能。
5. 初始化配置完成后编写EXTI中断服务函数:所有中断函数都在 STM32F1 启动文件中,不知道中断函数名的可以打开启动文件查找。这里我们使用到的是外部中断,其函数名如下:
EXTI0_IRQHandlerEXTI1_IRQHandlerEXTI2_IRQHandlerEXTI3_IRQHandlerEXTI4_IRQHandlerEXTI9_5_IRQHandlerEXTI15_10_IRQHandler
从函数名可以看到,前面 0-4 个中断线都是独立的函数,中断线 5-9 共用一个 函 数 EXTI9_5_IRQHandler , 中断线10-15 也共用一个函数EXTI15_10_IRQHandler,所以要在编写对应中断服务函数时要注意。
具体软件设计(按键中断)
在APP文件夹中创建exti文件夹,进一步创建exti.c和exti.h文件,具体为:
#include "zong.h"
#include "exti.h"
#include "key.h"
#include "led.h"
void My_EXTI_Init(void)//因为EXTI_Init()库函数已经定义了,初始化EXTI,选择触发方式
{
//需要——>设置输出模式,使能时钟,配置NVIC,配置EXTI,开启中断功能
//不需要设置I/O口为输入模式,在key.c文件中设置过了
NVIC_InitTypeDef NVIC_InitStructure;//定义结构体变量
EXTI_InitTypeDef EXTI_InitStructure;//定义结构体变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//开启AFI0时钟的使能
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);//设置I0口与中断线的映射关系,选择GPIO管脚用作外部中断线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
//因为需要四个按键,所以按照硬件电路配置四个中断
//EXTI0 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
//因为分了两组,抢占优先级为2/子优先级约小,优先级越高
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器//取地址
//EXTI2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;//EXTI2中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//EXTI3 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//EXTI3中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//EXTI4 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;//EXTI4中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//配置完成内部NVIC后,初始化EXTI,选择触发方式
EXTI_InitStructure.EXTI_Line=EXTI_Line0;//中断/事件线
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//EXTI模式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; //EXTI触发模式(低电平变高电平了——上升沿触发)这是因为K_UP是高电平有效
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//中断线使能或失能
EXTI_Init(&EXTI_InitStructure);//流程化了
EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//(高电平变低电平了——下降沿触发)
//因为这三个按键是低电平有效
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
//编写 四个 中断触发程序
void EXTI0_IRQHandler(void)//编写EXTI中断服务函数//注意此函数名不能随便更改,改了不会报错但程序无法运行
{
if(EXTI_GetITStatus(EXTI_Line0)==1)//获取EXTI中断标志位状态
{
delay_ms(10);//消除抖动
if(K_UP==1)//双重判断!!!
{
led2=0;
}
}EXTI_ClearITPendingBit(EXTI_Line0);//清除中断标志位
}
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)==1)
{
delay_ms(10);
if(K_LEFT==0)
{
led3=1;
}
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)==1)
{
delay_ms(10);
if(K_DOWN==0)
{
led2=1;
}
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
void EXTI4_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line4)==1)
{
delay_ms(10);
if(K_RIGHT==0)
{
led3=0;
}
}
EXTI_ClearITPendingBit(EXTI_Line4);
}
二 定时器中断函数
中断是用于在有外部信号输入时触发相应任务的,上一节介绍了基于按键输入的,此处介绍基于周期性定时器的、
STM32F1 包含 2 个基本定时器(TIM6、TIM7)、4 个通用定时器(TIM2-TIM5)和 2 个高级定时器(TIM1、TIM8),共计 8 个
STM32F1 的通用定时器包含一个 16 位自动重载计数器(CNT),该计数器由可编程预分频器(PSC)驱动。STM32F1 的通用定时器可用于多种用途,包括测量输入信号的脉冲宽度(输入捕获)或者生成输出波形(输出比较和 PWM)等。使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32F1 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
(1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。(2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。(3)4 个独立通道(TIMx_CH1-4),这些通道可以用来作为:A.输入捕获B.输出比较C. PWM 生成(边缘或中间对齐模式)D.单脉冲模式输出(4)可使用外部信号(TIMx_ETR)控制定时器,且可实现多个定时器互连 (可以用 1 个定时器控制另外一个定时器)的同步电路。(5)发生如下事件时产生中断/DMA 请求:A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)C.输入捕获D.输出比较(6)支持针对定位的增量(正交)编码器和霍尔传感器电路(7)触发输入作为外部时钟或者按周期的电流管理
1.时钟源选择:内部/外部时钟
2.控制器
3.时基单元
4.输入捕获
5.输出比较
通用定时器配置步骤
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能 APB1 时钟
voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
typedef struct{uint16_t TIM_Prescaler; //定时器预分频器uint16_t TIM_CounterMode; //计数模式uint32_t TIM_Period; //定时器周期uint16_t TIM_ClockDivision; //时钟分频uint8_t TIM_RepetitionCounter; //重复计数器} TIM_TimeBaseInitTypeDef;详情如下:TIM_Prescaler:定时器的预分频器系数,时钟源经过该预分频器后输出的才是定时器时钟,设置值范围:0-65535,分频系数由于是除数,分母不能为 0, 所以会自动加 1,最后实现 1-65536 分频。TIM_CounterMode:定时器计数方式,前面讲解过,可以设置为向上、向下、 中心对齐计数方式。比较常用的是向上计数模式(TIM_CounterMode_Up)和向下计数模式(TIM_CounterMode_Down)。TIM_Period:设置定时器自动重载计数周期值,在事件产生时更新到影子寄存器。可设置范围为 0 至 65535。TIM_ClockDivision:时钟分频因子,设置定时器时钟 CK_INT 频率与数字滤波器采样时钟频率分频比。TIM_RepetitionCounter:重复计数器,通过此参数可以非常简单的控制 PWM 输出个数。此成员只针对于高级定时器配置,基本定时器与通用定时器不用设置。
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //开启定时器中断
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
TIM_Cmd(TIM4,ENABLE); //开启定时器
TIM4_IRQHandler
读取定时器中断状态标志位的函数,通过状态寄存器的值判断此次中断是哪种类型,然后做出相应的控制
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
#include "time.h"
#include "led.h"
#include "zong.h"
void TIM4_Init(u16 per,u16 psc)
{ //中断触发条件,设置定时器中断优先级,中断服务函数
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//TIM_TimeBaseInit定义结构体变量
//自动装载值,分频系数,计数方式
NVIC_InitTypeDef NVIC_InitStructure;//定义中断变量
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
//GPIO一般在APB2/TIM4一般在APB1/开启TIM4时钟(使用第4定时器)
//初始化定时器部分-TIM4,选择触发方式
TIM_TimeBaseInitStructure.TIM_Period=per; //自动装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数(1—65536)
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频一般为1分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式
//从0开始计数到设定的自动装载值就会溢出,然后产生中断
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);//初始化TIM4
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //设置定时器中断类型,并使能
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//清除中断标志位
//设置定时器中断优先级,使能定时器中断通道
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;//TIM4中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级//因为分了两组,抢占优先级为2/子优先级约小,优先级越高
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器//取地址
TIM_Cmd(TIM4,ENABLE); //开启定时器
}
void TIM4_IRQHandler(void)//编写定时器中断服务函数
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==1)//读取定时器中断状态标志位
{
led2=!led2;
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//清除定时器中断标志位
}
三 利用定时器输出PWM波
STM32F1 除了基本定时器 TIM6 和 TIM7,其他定时器都可以产生 PWM 输出。 其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出。
PWM 的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号(改变占空比),信号频率是由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定。PWM 模式根据计数器 CNT 计数方式,可分为边沿对齐模式和中心对齐模式。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能 TIM3 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启时钟void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);//选择 TIM3_CH1 完全重映射GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);//改变指定管脚的映射// 将 PF9 管脚模式配置为复用推挽输出GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
TIM_OCInitTypeDef 成员变量:typedef struct{uint16_t TIM_OCMode; //比较输出模式uint16_t TIM_OutputState; //比较输出使能uint16_t TIM_OutputNState; //比较互补输出使能uint32_t TIM_Pulse; //脉冲宽度uint16_t TIM_OCPolarity; //输出极性uint16_t TIM_OCNPolarity; //互补比较输出极性uint16_t TIM_OCIdleState; //空闲状态下比较输出状态uint16_t TIM_OCNIdleState; //空闲状态下比较输出状态} TIM_OCInitTypeDef;成员变量讲解:TIM_OCMode:比较输出模式选择,总共有 8 种,最常用的是 PWM1 和 PWM2。TIM_OutputState:比较输出使能,用来使能 PWM 输出到 IO 口。TIM_OCPolarity:输出极性,用来设定输出通道电平的极性,是高电平还是低电平。结 构 体 内 其 他 的 成 员 变 量 TIM_OutputNState, TIM_OCNPolarity, TIM_OCIdleState 和 TIM_OCNIdleState 是高级定时器才用到的
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);TIM_Cmd(TIM3,ENABLE); //开启定时器
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);//对于其他通道,分别有对应的函数名,函数格式是 TIM_SetComparex(x=1/2/3/4)。
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
#include "pwm.h"
//定时器初始化、pwm初始化、端口时钟使能、管脚完全复用
void IIM3_CH1_PWM_Init(u16 per,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//初始化定时器参数,包含自动重装值,分频系数,计数方式等
TIM_OCInitTypeDef TIM_OCInitStructure;
//初始化PWM输出参数,包含PWM模式、输出极性(设置为低电平,则输出有效就为低电平,无效为高电平),使能等
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能定时器及端口时钟,并设置引脚复用器映射(复用功能)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//使能led时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能AFIO的端口时钟
//对led进行配置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6; //选择你要设置的IO口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //设置了复用推挽输出模式
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
GPIO_Init(GPIOC,&GPIO_InitStructure); /* 初始化GPIO */
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);//设置管脚完全映射GPIO_FullRemap_TIM3(将TIM3映射到PC6)、使能
//初始化“定时器”参数,包含自动重装值,分频系数,计数方式等
TIM_TimeBaseInitStructure.TIM_Period=per; //自动装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数(1—65536)
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频一般为1分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式
//从0开始计数到设定的自动装载值就会溢出,然后产生中断
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
//初始化PWM输出参数,包含PWM模式、输出极性(设置为低电平,则输出有效就为低电平,无效为高电平),使能等
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//使用PWM1模式
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//设置输出极性为低电平(输出有效就为低电平,无效为高电平)
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//使能pwm输出
TIM_OC1Init(TIM3,&TIM_OCInitStructure); //输出比较通道1初始化(惯例了)
TIM_Cmd(TIM3,ENABLE); //开启定时器
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能TIMx在 CCR1 上的预装载寄存器
//第一个参数用于选择定时器,第二个参数用于选择使能还是失能输出比较预装载寄存器,
//可选择为TIM_OCPreload_Enable、TIM 0CPre load_ Disable。
TIM_ARRPreloadConfig(TIM3,ENABLE);//使能TIMx在 ARR 上的预装载寄存器
//第一个参数用于选择定时器,第二个参数用于选择使能还是失能。
//因为是ARR与CCR"比较寄存器"进行比较
}
#include "zong.h"
#include "led.h"
#include "time.h"
#include "pwm.h"
int main()
{
u16 i=0;
u8 fx=0;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断分组(NVIC) ,使能中断
LED_Init();
IIM3_CH1_PWM_Init(500,72-1);//per自动装载值//psc 分频系数(1—65536)
//定时500ms——Tout= ((per自动装载寄存器的值)* (psc分频系数+1))/Tclk72MHz;
//500*(72-1+1)/72
while(1)//这是一个循环,卡在这,不让程序结束!
{
if(fx==0)
{
i++;//暗变亮
if(i==300)//300以内能看到明显变化,不要超过500
{
fx=1;
}
}
else
{
i--;
if(i==0)
{
fx=0;
}
}//构成了一个往复循环!!!i在内部不断加减循环
TIM_SetCompare1(TIM3,i); //将不断加减的i值读入,从而不断改变占空比
//i值最大可以取499,因为ARR最大值是499.
delay_ms(10);
}
}