5。STM32裸机开发(3)

news2024/11/25 3:29:54

嵌入式软件开发学习过程记录,本部分结合本人的学习经验撰写,系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维,为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-2-3-4-5)

        1.标号 1 为输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,输入线一般是存在电平变化的信号。

        2.边沿检测电路,EXTI 可以对触发方式进行选择,通过上升沿触发选择寄存器和下降沿触发选择寄存器对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给红色框 3 电路,否则输出无效信号 0。而上升沿和下降沿触发选择这两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。

        3.是一个或门电路,一端输入信号线由标号 2 提供,一端由软件中断事件寄存器提供,只要有一个为有效信号 1,标号 3 电路则输出有效信号 1,否则为无效信号 0。软件中断事件寄存器允许我们使用软件来启动中断/事件线,这个在某些地方非常有用。

        4.是一个与门电路,一端输入信号线由标号 3 电路输出提供,一端由中断屏蔽寄存器提供,只有当两者都为有效信号 1,标号 4 电路才会输出有效信号 1,否则输出无效。这样我们就可以简单的控制中断屏蔽寄存器来实现是否产生中断的目的。当我们把中断屏蔽寄存器设置为 1 时,标号 4 输出就取决于标号3 电路的输出。标号 3 电路输出的信号会被保存到挂起寄存器内,如果确定标号3 电路输出为 1 就会把挂起寄存器对应位置 1。
       
       5.将挂起寄存器内容输入到 NVIC 内,从而实现系统中断事件的控制。
(2)其次,产生事件这条线路(1-2-3-6-7-8),前面 1-2-3 都是一样的,只是在 3 的输出后产生分歧。

        6.是一个与门电路,一端来至标号 3 电路的输出信号,一端来至事件屏蔽寄存器,只有两者都为有效电平 1,标号 6 输出才有效。当事件屏蔽寄存器设置为 0 时,不管标号 3 电路输出为 1 还是 0,标号 6 电路输出均为 0。当事件屏蔽寄存器设置为 1 时,标号 6 电路输出取决于标号 3 电路输出,这样就可以简单的控制事件屏蔽寄存器来实现是否产生事件的目的。

        7.脉冲发生器电路,其输入端只与标号 6 电路输出有关,标号 6 输出有效,脉冲发生器才会输出一个脉冲信号

        8.脉冲信号,由标号 7 脉冲发生器产生,是事件线路的终端,此脉冲信号可供其他外设电路使用,比如定时器、ADC 等。这样的脉冲信号通常用来触发定时器、ADC 等开始转换

        从上面 EXTI 框图可以看出,中断线路最终会输入到 NVIC 控制器中,从而会运行中断服务函数,实现中断内功能,这个是软件级的。而事件线路最后产生的脉冲信号会流向其他的外设电路,是硬件级的。在 EXTI 框图最顶端可以看到,其外设接口时钟是由 PCLK2,即 APB2 提供,所以在后面使能 EXTI 时钟的时候一定要注意。

外部中断/事件线映射

        STM32F10x 的 EXTI 具有 20 个中断/事件线,但EXTI供外部IO口使用的中断线只有16根,而实际使用时GPIO绝不止16个IO口,因此把每个端口的16个IO对应那16根中断线EXTI0-EXTI15.

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_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_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 的每个通用定时器都是完全独立的,没有互相共享的任何资源。

STM32F1 的通用定时器 TIMx (TIM2-TIM5 )具有如下功能:

 

(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.输出比较

通用定时器配置步骤 

(定时器相关库函数在 stm32f10x_tim.c 和 stm32f10x_tim.h 文件中)
(1)使能定时器时钟
        本章定时器实验,我们使用的是通用定时器 TIM4,我们知道 TIM4 是挂接在 APB1 总线上的,所以可以使用 APB1 总线时钟使能函数来使能 TIM4,调用的库函数如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能 APB1 时钟
(2)初始化定时器参数,包含自动重装值分频系数计数方式
要使用定时器功能,必须对定时器内相关参数初始化,其库函数如下:
voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
        函数中第一个参数是用来确定哪个定时器,例如 TIM4;第二个参数是一个结构体指针变量,结构体类型是 TIM_TimeBaseInitTypeDef,其内包含了定时器初始化的成员变量。下面为结构体详情:
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 输出个数。此成员只针对于高级定时器配置,基本定时器与通用定时器不用设置。
(3)设置定时器中断类型,并使能
对定时器中断类型和使能设置的函数如下:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
第一个参数用来选择哪个定时器,例如 TIM4。
第二个参数用来设置定时器中断类型,定时器的中断类型非常多,包括更新中断TIM_IT_Update、触发中断 TIM_IT_Trigger、输入捕获中断等等。
第三个参数用来使能或者失能定时器中断类型,可以为 ENABLE 和 DISABLE。
例如我们要使能定时器 TIM4 更新中断,调用函数如下:
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //开启定时器中断
(4)设置定时器中断优先级,使能定时器中断通道
        在上一节我们已经使能了定时器的更新中断,只要使用到中断,就必需对 NVIC 初始化,NVIC 初始化库函数是 NVIC_Init()。
(5)开启定时器
        前面几个步骤已经将定时器配置好,但还不能正常使用,只有开启定时器了才能让它正常工作,开启定时器的库函数如下:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
第一个参数是用来选择定时器
第二个参数是用来使能或者失能定时器,也就是开启或者关闭定时器功能。 同样可以选择 ENABLE 和 DISABLE。
        例如我们要开启 TIM4,那么调用此函数如下:
TIM_Cmd(TIM4,ENABLE); //开启定时器
(6)编写定时器中断服务函数
        最后我们还需要编写一个定时器中断服务函数,通过中断函数处理定时器产生的相关中断。定时器中断服务函数名在 STM32F1 启动文件内就有,TIM4 中断函数名如下:
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 计数方式,可分为边沿对齐模式和中心对齐模式

通用定时器 PWM 输出配置步骤(与定时器配置对比学习效果更佳)
(1)使能定时器及端口时钟,并设置引脚复用器映射
        因为 PWM 输出也是通用定时器的一个功能,所以需要使能相应定时器时钟。 由于 PWM 输出通道是对应着 STM32F1 芯片的 IO 口,所以需要使能对应的端口时钟,并将对应 IO 口配置为复用输出功能。例如本章 PWM 呼吸灯实验,我们使用的是 TIM3 的 CH1 通道输出 PWM 信号,因此需要使能 TIM3 时钟,调用的库函数如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能 TIM3 时钟
注意: TIM3 的 CH1 通道对应的管脚是 PA6,但是我们开发板上的 LED 灯并没有接在 PA6 引脚上,如果要让这个通道映射到 LED 所接的 IO 口上,则需要使用 GPIO的复用功能重映射。
第一个参数选择是部分重映射还是完全重映射, 第二个参数用来使能还是失能
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;//复用推挽输出
(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
(3)初始化 PWM 输出参数,包含 PWM 模式、输出极性,使能等(此处开始不同)
        初始化定时器后,需要设置对应通道 PWM 的输出参数,比如 PWM 模式、输出极性、是否使能 PWM 输出等。PWM 通道设置函数如下
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
        由于通用定时器有多达 4 路 PWM 输出通道,所以 TIM_OCxInit 函数名中的 x 值可以为 1/2/3/4。函数的第一个参数是用来选择定时器的。第二个参数是一个结构体指针变量
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 是高级定时器才用到的
(4)开启定时器
        前面几个步骤已经将定时器及 PWM 配置好,但 PWM 还不能正常使用,只有开启定时器了才能让它正常工作,开启定时器的库函数如下:
第一个参数是用来选择定时器。
第二个参数是用来使能或者失能定时器,也就是开启或者关闭定时器功能。 同样可以选择 ENABLE 和 DISABLE。
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
TIM_Cmd(TIM3,ENABLE); //开启定时器
(5)修改 TIMx_CCRx 的值控制占空比(调节占空比)
        例如要实现呼吸灯效果,那么就需要调节 TIM3 通道 1 的占空比, 通过修改 TIM3_CCR1 值控制。调节占空比函数是:
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);
//对于其他通道,分别有对应的函数名,函数格式是 TIM_SetComparex(x=1/2/3/4)。
(6)使能 TIMx 在 CCRx 上的预装载寄存器
        第一个参数用于选择定时器,第二个参数用于选择使能还是失能输出比较预装载寄存器,可选择为 TIM_OCPreload_Enable、TIM_OCPreload_Disable。
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
(7)使能 TIMx 在 ARR 上的预装载寄存器允许位
        使能 TIMx 在 ARR 上的预装载寄存器允许位库函数是:第一个参数用于选择定时器,第二个参数用于选择使能还是失能。
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);	
   }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/525635.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【探索SpringCloud】服务发现

前言 今天,我们来聊聊SpringCloud服务发现。主要有如下几个议题: 一、服务发现的概念与方案;二、SpringCloud是如何与各个服务注册厂商进行集成的。 服务发现 在微服务架构中,我们不可避免的需要通过服务间的调用来完成系统功能…

Fourier分析入门——第1章——数学预备知识

第 1 章 学习Fourier分析的数学预备知识 目录 第 1 章 学习Fourier分析的数学预备知识 1.1 引言 1.2 几何和代数的一些相关概念的回顾 1.2.1 标量运算(scalar arithmetic) 1.2.2 向量运算(vector arithmetic) 1.2.3 向量乘法(vector multiplication) 1.2.4 向量长度 …

设计模式(java)-观察者模式

1. 简介 观察者模式,行为型设计模式。观察者模式在实际项目实践中,是一种使用较频繁的设计模式,它主要解决的是信息同步的问题,当多个对象需要从同一个主题中得到自身所需要的信息或状态,并通过这些信息或状态做出相应…

以太网外设ETH

1. 概述 近几年,项目需要,在多款单片机上使用了以太网外设。 本文为阶段知识整理,查缺补漏,方便以后再次遇到相关任务时,可以游刃有余的完成工作。 1.1 修改时间 2023年5月6日创建本文。包含STM32的ETH外设。2023年…

利用CNN对车牌进行智能识别(python代码,解压缩后直接运行)

1.代码流程 该段代码主要利用卷积神经网络(CNN)来识别车牌。下面是代码的主要流程: 导入所需的库和模块,包括matplotlib、numpy、cv2、tensorflow等。 加载用于检测车牌的级联分类器(cascade classifier)…

可见光遥感目标检测(一)任务概要介绍

前言 本篇开始对遥感图像的目标检测进行介绍,介绍了其目标前景、数据集以及评价指标。 本教程禁止转载。同时,本教程来自知识星球【CV技术指南】更多技术教程,可加入星球学习。 Transformer、目标检测、语义分割交流群 欢迎关注公众号CV技…

机器学习13(正则化)

文章目录 简介正则化经验风险和结构风险过拟合正则化建模策略 逻辑回归逻辑回归评估器 练习评估器训练与过拟合实验评估器的手动调参 简介 这一节详细探讨关于正则化的相关内容,并就 sklearn 中逻辑回归(评估器)的参数进行详细解释由于 skle…

javaweb项目实战之myBlog

项目简介 技术栈: Java Mysql Html Ajax Css JS Json 项目说明 :项目使用maven创建,使用MVC架构模式 表示层:通俗讲就是展现给用户的界面和控制器层Servlet,接受请求、封装数据、调用业务 逻辑层,响…

libevent高并发网络编程 - 05_libevent实现http客户端

文章目录 1 http客户端相关的APIevhttp_uri_parse()evhttp_uri_get_scheme()evhttp_uri_get_port()evhttp_uri_get_host()evhttp_uri_get_path()evhttp_uri_get_query()evhttp_connection_base_bufferevent_new()evhttp_request_new()evhttp_make_request()evhttp_request_get_…

刷题刷题,开心

一先来每日一题 在一个仓库里,有一排条形码,其中第 i 个条形码为 barcodes[i]。 请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。 你可以返回任何满足该要求的答案,此题保证存在答案。 示例 1: 输入&…

基于html+css图展示59

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Prompt learning 教学[最终篇]:Chatgpt使用场景推荐、优秀学习资料推荐、AI工具推荐

Prompt learning 教学[最终篇]:Chatgpt使用场景推荐、优秀学习资料推荐、AI工具推荐 1.chatgpt使用场景推荐 各位应该在各种平台看到不少可以尝试使用的场景,我这里仅收录: 有意思的场景:一般比较垂直或者小众,或者出…

CobaltStrike项目实战

环境介绍 模拟内网有三台机器:WEB、PC和DC。 WEB服务器有两个网络适配器,适配器1处于NAT模式用于连接外网,适配器2用于内网。 PC和WEB服务器一样,有两个适配器,能够同时访问外网和内网;DC作为域控制器&…

神经网络的训练过程、常见的训练算法、如何避免过拟合

神经网络的训练是深度学习中的核心问题之一。神经网络的训练过程是指通过输入训练数据,不断调整神经网络的参数,使其输出结果更加接近于实际值的过程。本文将介绍神经网络的训练过程、常见的训练算法以及如何避免过拟合等问题。 神经网络的训练过程 神…

henan Problem E. 矩阵游戏

hunan Problem E. 矩阵游戏 Attachments - 2023 CCPC Henan Provincial Collegiate Programming Contest - Codeforces 思路: 我们考虑用dp,定义f[i][j][k],代表从1,1走到i,j并且使用k次变换操作能够获得的最大 价值,那么类似于01背包,接下…

分布式数据库集成解决方案2

分布式数据库集成解决方案2 扩展阅读内部结构1.表空间(TABLESPACE) # 摘要 : 本文讨论了某公司发货系统的分布式数据库集成解决方案。该公司由于业务的发展,要在另三个城市设立货仓进行发货。为此,需要增加原先的MIS系统实现这一功…

javaweb系列-js函数、数组、字符串

1.4 函数 JavaScript中的函数被设计为执行特定任务的代码块&#xff0c;通过关键字function来定义。JavaScript中定义函数有2种语法。 第一种方法&#xff1a; <script>//定义function add(a,b){return a b;}//调用var a add(1,2);alert(a); </script> 第二种方…

【LeetCode】204.计数质数

204.计数质数&#xff08;中等&#xff09; 思路 埃拉托斯特斯筛法&#xff08;简称埃氏筛法&#xff09;&#xff0c;适用于「判断一个整数是否是质数」&#xff0c;该方法可以在判断一个整数 n 时&#xff0c;同时判断所有小于 n 的整数。 从 1 到 n 进行遍历&#xff0c;假…

内网渗透之权限维持-域控后门-SSPHOOKDSRMSIDSkeleton-Key

权限维持-基于验证DLL加载-SSP 方法一:但如果域控制器重启&#xff0c;被注入内存的伪造的SSP将会丢失。 mimikatz privilege::debug misc::memsspC:\Windows\System32\mimilsa.log 记录登录的账号密码 方法二:使用此方法即使系统重启&#xff0c;也不会影响到持久化的效果…

Plus and Multiply

题目&#xff1a; 题意解析&#xff1a; 有一个无穷大的正整数集合 S&#xff0c;该集合按下面所述方法生成&#xff1a; 数字 1 在集合 S 中。 若数字 x 在该集合中&#xff0c;那么数 xa 和数 xb 均在集合 S 中。&#xff08;其中 a 与 b 为给定常数&#xff09; 现在给出…