• stm32 - 中断/定时器


    概念

    时钟树

    https://www.bilibili.com/video/BV1th411z7sn?p=13&vd_source=7155082256127a432d5ed516a6423e20

    执行main函数之前,程序中主函数还会执行一个systeminit函数,这个函数就是用来配置时钟树的

    SYSCLK是系统时钟72MHZ
    震荡源:内部震荡源RC振荡器8MHZ,外部震荡源:4~16MHZ(石英晶振),…
    图中与门就是RCC_xxx函数打开时钟

    在这里插入图片描述

    定时器类型

    • 高级定时器:TIM1,TIM8 -> APB2总线,
    • 通用定时器:TIM2,TIM3,TIM4,TIM5 -> APB1总线,
    • 基本定时器:TIM6,TIM7 -> APB1总线,

    针对STM32C8T6:TIM1,TIM2,TIM3,TIM4

    基准时钟(系统时钟)

    stm32主频72MHZ
    RCC_TIMxCLK(主频)-> 内部时钟CK_INT(基本计数时钟) ->控制器 -> CK_PSC ->时基单元
    因此,通向时基单元的计数基准频率是72MHZ

    预分频器 - 时基单元

    对输入的基准频率提前进行一个分频的操作

    对72MHZ的计数时钟进行预分频
    预分频器=0,不分频:输出频率=输入频率=72MHZ
    预分频器=1,2分频:输出频率=输入频率/2=36MHZ
    预分频器=11 ,12分频:输出频率=输入频率/12=6MHZ

    预分频器是16位,最大可以写65535个数,最大是65535+1=65536分频;输出频率=输入频率/65536=1.0986328125KHZ

    CNT计数器 - 时基单元

    对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器值+1
    CNT计数器是16位的,最多计0~65535的数值,再+1就从0开始计数

    实际定时中断,应该是计数器达到目标值时,产生中断

    自动重装寄存器 - 时基单元

    存储计数目标的寄存器
    自动重装寄存器是16位的, 是写入的固定值,当计数器的计数值达到自动重装寄存器的值的时候,表明定时时间到,产生中断信号,并清零计数器开始下一次从0开始计数

    计数器值=自动重装寄存器的值(也叫更新中断),产生中断,产生中断后通往NVIC,再配置号NVIC定时器的通道, 执行中断服务

    基本定时器结构

    在这里插入图片描述
    在这里插入图片描述

    通用定时器

    >

    计数器模式

    向上计数模式:基本定时器只有这一个功能
    向下计数模式:通用计时器
    中央对齐模式:通用计时器

    • 向下计数模式

    从自定义的自动重装值开始,向下自减,减到0后重新回到自动重装值开始计数

    • 中央对齐模式

    0-> 自增 -> 自动重装值 -> 自减 -> 0

    内外时钟源选择

    对于基本定时器,只能选择内部时钟进行定时,即系统频72MHZ
    对于通用定时器,即能选择内部时钟72MHZ,也能选择外部时钟

    在这里插入图片描述

    外部时钟

    TIMx_ETR引脚上的外部时钟(查看引脚定义图)
    在引脚上接一个外部的方波时钟,然后配置内部的电路

    定时中断基本结构

    在这里插入图片描述

    时序

    预分频器时序

    在这里插入图片描述

    计数器时序


    例子

    通用定时器 - 内部定时中断

    main.c

    #include "stm32f10x.h"
    #include "timer.h"
    
    extern uint16_t timer2_num;
    
    int main()
    {
    	OLED_Init();
    	OLED_ShowString(1,1,"helloworld");
    	Timer_Init();
    	while (1) 
    	{
    		OLED_ShowNum(2,1,timer2_num,5);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    timer.h

    #ifndef __TIMER_H__
    #define __TIMER_H__
    #include "stm32f10x.h"
    void Timer_Init();
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    timer.c

    #include "timer.h"
    
    uint16_t timer2_num;
    void Timer_Init()
    {
    	// RCC时钟 系统基准时钟,和外设工作时钟
    	// 通用定时器TIM2是挂载在APB1上的
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); // 时钟树种的与门结构
    	
    	// 时基单元时钟源
    	// 选择内部时钟
    	TIM_InternalClockConfig(TIM2);
    	
    	
    	// 配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	/* 这里定时为1s=1/1HZ; 分频后72MHZ/((7200-1)+1)=10000HZ=10KHZ; 1/10000s记一次数, */
    	TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;					// PSC预分频器的值
    	TIM_TimeBaseInitStructure.TIM_Period=10000-1;					// ARR自动重装器的值  0~65535
    	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;  	// 计数模式,向上计数 
    	/*
    		- TIM_ClockDivision 与时基单元关系不大,时钟分频 不等于 预分频器 
    		- 外部时钟输入,输入引脚后接一个滤波器,滤波器可以过滤信号的抖动干扰 
    		- 在一个固定的时钟频率f下进行采样,如果连续N个采样点都为相同的频率,就代表输入信号稳定,就把这个采样值输出出去 
    		- 如果n个采样点采样值不尽相同,说明信号有抖动,就保持上一次的输出,或者直接输出其他默认的电平 
    		- f和N为滤波器的参数,频率f越低???,采样点数越多,滤波效果越好,但是会导致信号延迟越大
    		- 这个频率f可以由内部时钟而来,也可以是内部时钟分频而来,即TIM_ClockDivision确定
    		-  TIM_CKD_DIV1不分频,x2为2分频,x4为4分频
    	*/
    	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;  // 重复计数器的值,与高级定时器相关 
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    
    	/*
    		预分频器有影子寄存器(缓冲寄存器),写入的值只有在触发更新事件时,才会真正起作用
    		在定时计数溢出之后,伴随产生更新事件,更新事件触发后,会将写入预装载的值加载到对应的影子寄存器中
    		自动重装寄存器->对应的影子寄存器;预分频器->对应的影子寄存器
    		
    		针对ARR的ARPE位用于控制ARR自动重载寄存器是否具有缓冲作用,
    		如果具有缓冲作用,只有当等到更新事件触发后,才会写入影子寄存器并生效
    		如果不具有缓冲作用,那么立即写入影子寄存器并生效
    	*/ 
    	/*从源码可知,为了防止初始化完成立即进入中断,使得中断标志位置1,这里手动清理标志位,避免进入中断*/
    	TIM_ClearFlag(TIM2,TIM_FLAG_Update);	
    
    	// 配置中断输出控制,使能中断,开启更新中断(而不是更新事件)
    	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);  // 中断到NVIC的通路
    	
    	// 配置NVIC,打开定时器中断的通道,分配优先级
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;		// 中断输出通道
    	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;	// 抢占优先级(在抢占中断中)
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;		// 响应优先级 (在响应中断中)
    	NVIC_Init(&NVIC_InitStructure);
    	
    	// 启动定时器
    	TIM_Cmd(TIM2,ENABLE);
    }
    
    void TIM2_IRQHandler() // 更新中断服务
    {
    	if (TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) // 判断中断标志位
    	{
    		// 清楚中断标志位
    		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    		
    		timer2_num++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    通用定时器 - 外部时钟定时中断

    main.c

    #include "stm32f10x.h"
    #include "timer.h"
    
    extern uint16_t timer2_num;
    
    int main()
    {
    	OLED_Init();
    	OLED_ShowString(1,1,"helloworld");
    	Timer_Init();
    	while (1) 
    	{
    		OLED_ShowNum(2,1,timer2_num,5);
    		OLED_ShowNum(3,1,Timer_GetCounter(),5);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    timer.h

    #ifndef __TIMER_H__
    #define __TIMER_H__
    #include "stm32f10x.h"
    void Timer_Init();
    uint16_t Timer_GetCounter();
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    timer.c

    #include "timer.h"
    
    uint16_t timer2_num;
    void Timer_Init()
    {
    	// RCC时钟 系统基准时钟,和外设工作时钟
    	// 通用定时器TIM2是挂载在APB1上的
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	
    	// 时基单元时钟源
    	// TIM_InternalClockConfig(TIM2); // 选择内部时钟
    	// 不需要外部触发预分频器
    	// 外部时钟触发的极性: 反向:低电平或下降沿有效;不反向:高电平或上升沿有效
    	// 外部时钟触发滤波器:0x00~0xFF ,这个值决定外部计数的滤波器的频率f和采样点n,见手册有对应的关系; 这里暂时不用滤波器
    	TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00); // 外部时钟模式2
    	
    	// 外设时钟,需要使用GPIO
    	// 初始化APB2外设时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); 
    	
    	// 配置GPIO - ODR/CLR
    	GPIO_InitTypeDef GPIO_InitStructure;			// 配置结构体
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; 	// GPIO_Mode_IPU 上拉输入,因为是外部时钟方波输入,所以是输入模式	
    	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0;		// GPIOA的PA0号引脚
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // 50MHZ 
    	
    	GPIO_Init(GPIOA,&GPIO_InitStructure); 			// A0推挽输出
    	
    	// 配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;					// PSC预分频器的值
    	TIM_TimeBaseInitStructure.TIM_Period=10-1;						// ARR自动重装器的值 
    	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;  	// 计数模式,向上计数    
    	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;		// 关系不大,时钟分频    
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;  // 重复计数器的值,与高级定时器相关 
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	/*
    		预分频器有影子寄存器(缓冲寄存器),写入的值只有在触发更新事件时,才会真正起作用
    		在定时计数溢出之后,伴随产生更新事件,更新事件触发后,会将写入预装载的值加载到对应的影子寄存器中
    		自动重装寄存器->对应的影子寄存器;预分频器->对应的影子寄存器
    		
    		针对ARR的ARPE位用于控制ARR自动重载寄存器是否具有缓冲作用,
    		如果具有缓冲作用,只有当等到更新事件触发后,才会写入影子寄存器并生效
    		如果不具有缓冲作用,那么立即写入影子寄存器并生效
    	*/ 
    	/*从源码可知,为了防止初始化完成立即进入中断,使得中断标志位置1,这里手动清理标志位,避免进入中断*/
    	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
    	
    	// 配置中断输出控制,使能中断
    	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);  // 中断到NVIC的通路
    	
    	// 配置NVIC,打开定时器中断的通道,分配优先级
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;		// 中断输出通道
    	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;	// 抢占优先级(在抢占中断中)
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;		// 响应优先级 (在响应中断中)
    	NVIC_Init(&NVIC_InitStructure);
    	
    	// 启动定时器
    	TIM_Cmd(TIM2,ENABLE);
    }
    
    uint16_t Timer_GetCounter()
    {
    	return TIM_GetCounter(TIM2); // 中断后自动归零
    }
    
    
    void TIM2_IRQHandler()
    {
    	if (TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) // 判断中断标志位
    	{
    		// 清楚中断标志位
    		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    		
    		timer2_num++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    定时器 - 输出捕获

    输出比较可以通过比较CNT和CCR寄存器值的关系,来对输出电平进行置1、0或翻转的操作用于输出一定频率和占空比的PWM波形
    每个高级定时器和通用定时器都拥有4个输出比较通道
    CCR是给定的值

    PWM

    https://www.bilibili.com/video/BV1Mb411e7re?p=33&vd_source=7155082256127a432d5ed516a6423e20

    注意:输出PWM不需要中断申请

    脉冲宽度调制
    使用PWM波形实现模拟信号的输出

    频率越快,等效模拟的信号越平稳,但是性能开销越大
    占空比:T_on/T_s:高电平/整个周期的时间比例
    占空比决定了PWM等效出的模拟电压的大小
    占空比越大,等效的模拟电压越趋近与高电平,反之亦然
    分辨率:占空比变化步距
    在这里插入图片描述

    输出比较通道 - 通用定时器

    oc1ref是一个参考信号电平
    在这里插入图片描述
    在这里插入图片描述

    注意:输出PWM不需要中断申请

    在这里插入图片描述

    PWM频率:等于计数器的更新频率
    在这里插入图片描述

    例子 - 呼吸灯

    main.c

    #include "stm32f10x.h"
    #include "Delay.h"
    #include "LED.h"
    #include "key.h"
    #include "Buzzer.h"
    #include "PhotoSensor.h"
    #include "OLED.h"
    #include "infrCountSensor.h"
    #include "encoder.h"
    #include "timer.h"
    #include "PWM.h"
    
    static uint16_t pwmCompare1Num=0;
    int main()
    {
    	OLED_Init();
    	OLED_ShowString(1,1,"helloworld");
    	PWM_Init();
    	while (1) 
    	{
    		for (uint16_t i=0;i<=100;i++)
    		{
    			PWM_SetCompare1(i);
    			Delay_ms(10);
    		}
    		for (uint16_t i=0;i<=100;i++)
    		{
    			PWM_SetCompare1(100-i);
    			Delay_ms(10);
    		}
    	}
    } 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    PWM.h

    #ifndef __PWM_H__
    #define __PWM_H__
    #include "stm32f10x.h"                  // Device header
    void PWM_Init();
    void PWM_SetCompare1(uint16_t compare);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    PWM.c

    #include "PWM.h"
    
    void PWM_Init()
    {
    	// RCC时钟 系统基准时钟,和外设工作时钟
    	// 通用定时器TIM2是挂载在APB1上的
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	
    	// 时基单元时钟源
    	TIM_InternalClockConfig(TIM2); // 选择内部时钟
    	
    	// 外设时钟,需要使用GPIO
    	// 初始化APB2外设时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); 
    	
    	// 配置GPIO - ODR/CLR
    	// 注意针脚GPIO复用和重映射的问题,见手册针脚定义
    	GPIO_InitTypeDef GPIO_InitStructure;			// 配置结构体
    	/*
    		对于普通的开漏和推挽输出,引脚的控制权是来自输出数据寄存器的
    		如果想让定时器控制引脚,就需要使用复用开漏/推挽输出的模式,使得输出输出寄存器断开,输出控制权转移到片上外设 这里是TIM2_CH1通道 
    	*/
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; 	// 	GPIO_Mode_AF_PP 复用推挽输出
    	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0;		// GPIOA的PA0号引脚
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // 50MHZ 
    	
    	GPIO_Init(GPIOA,&GPIO_InitStructure); 			// A0推挽输出
    	
    	// 配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_Prescaler=100-1;					// PSC预分频器的值 // 72MHZ/((100-1)+1)=720KHZ
    	TIM_TimeBaseInitStructure.TIM_Period=720-1;						// ARR自动重装器的值  // 720*(1/720KHZ)=1/1KHZ=1ms
    	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;  	// 计数模式,向上计数    
    	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;		// 关系不大,时钟分频    
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;  // 重复计数器的值,与高级定时器相关 
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	// 无需配置中断相关内容
    	
    	// 无需 配置中断输出控制,使能中断
    	
    	// 无需 配置NVIC,打开定时器中断的通道,分配优先级
    
    	
    	// 配置输出比较单元
    	// TIM_OCxInit output compare 输出比较模块
    	// 配置好之后,就可以在TIM_OC1通道上输出PWM波形,然后借用GPIO口进行输出 ,TIM2_CH1_ETR引脚和PA0引脚复用,见引脚定义手册
    	TIM_OCInitTypeDef TIM_OCInitStruct;
    	TIM_OCStructInit(&TIM_OCInitStruct); 						// 默认初始值
    	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;    			// 设置输出比较的模式,一共8个模式  
    	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;   	// 设置输出使能,输出控制器
    	TIM_OCInitStruct.TIM_Pulse=0;      							// 设置CCR   0~0xFFFF,这里频率1kHZ,占空比50%,1%的分辨率 
    	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;    	// 设置输出比较的极性,这里REF有效电平为高电平(这里应该不是对应REF的极性选择器)
    	TIM_OC1Init(TIM2,&TIM_OCInitStruct);
    	
    	// 启动定时器
    	TIM_Cmd(TIM2,ENABLE);  
    }
    
    void PWM_SetCompare1(uint16_t compare)
    {
    	TIM_SetCompare1(TIM2,compare);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    例子 - 呼吸灯-端口重映射

    PWM.c

    PA0重映射到PA15

    #include "PWM.h"
    
    void PWM_Init()
    {
    	// RCC时钟 系统基准时钟,和外设工作时钟
    	// 通用定时器TIM2是挂载在APB1上的
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	
    	// 时基单元时钟源
    	TIM_InternalClockConfig(TIM2); // 选择内部时钟
    	
    	// 外设时钟,需要使用GPIO
    	// 初始化APB2外设时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); 
    	
    	
    	// 配置AFIO 实现引脚重映射
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE); 	// 见手册,部分重映射
    	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); // 恢复PA15的正常GPIO端口功能,因为
    	
    	// 配置GPIO - ODR/CLR
    	// 注意针脚GPIO复用和重映射的问题,见手册针脚定义
    	GPIO_InitTypeDef GPIO_InitStructure;			// 配置结构体
    	/*
    		对于普通的开漏和推挽输出,引脚的控制权是来自输出数据寄存器的
    		如果想让定时器控制引脚,就需要使用复用开漏/推挽输出的模式,使得输出输出寄存器断开,输出控制权转移到片上外设 这里是TIM2_CH1通道 
    	*/
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; 	// 	GPIO_Mode_AF_PP 复用推挽输出
    	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_15;		// GPIOA的PA15号引脚,重映射之后需要使用PA15的端口
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // 50MHZ 
    	
    	GPIO_Init(GPIOA,&GPIO_InitStructure); 			// A0推挽输出
    	
    
    	
    	
    	// 配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_Prescaler=720-1;					// PSC预分频器的值 // 72MHZ/((720-1)+1)=100KHZ
    	TIM_TimeBaseInitStructure.TIM_Period=100-1;						// ARR自动重装器的值  // 100*(1/100KHZ)=1/1KHZ=1ms
    	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;  	// 计数模式,向上计数    
    	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;		// 关系不大,时钟分频    
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;  // 重复计数器的值,与高级定时器相关 
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	     
    	// 无需配置中断相关内容
    	
    	// 无需 配置中断输出控制,使能中断
    	
    	// 无需 配置NVIC,打开定时器中断的通道,分配优先级
    
    	
    	// 配置输出比较单元
    	// TIM_OCxInit output compare 输出比较模块
    	// 配置好之后,就可以在TIM_OC1通道上输出PWM波形,然后借用GPIO口进行输出 ,TIM2_CH1_ETR引脚和PA0引脚复用,见引脚定义手册
    	TIM_OCInitTypeDef TIM_OCInitStruct;
    	TIM_OCStructInit(&TIM_OCInitStruct); 						// 默认初始值
    	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;    			// 设置输出比较的模式,一共8个模式  
    	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;   	// 设置输出使能,输出控制器
    	TIM_OCInitStruct.TIM_Pulse=0;      							// 设置CCR   0~0xFFFF,这里频率1kHZ,占空比50%,1%的分辨率 
    	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;    	// 设置输出比较的极性,这里REF有效电平为高电平(这里应该不是对应REF的极性选择器)
    	TIM_OC1Init(TIM2,&TIM_OCInitStruct);
    	
    	// 启动定时器
    	TIM_Cmd(TIM2,ENABLE);  
    }
    
    void PWM_SetCompare1(uint16_t compare)
    {
    	TIM_SetCompare1(TIM2,compare);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    例子 - 控制舵机 / 连续舵机有问题???

    按键控制舵机角度

    main.c

    #include "stm32f10x.h"
    #include "Delay.h"
    #include "LED.h"
    #include "key.h"
    #include "Buzzer.h"
    #include "PhotoSensor.h"
    #include "OLED.h"
    #include "infrCountSensor.h"
    #include "encoder.h"
    #include "timer.h"
    #include "PWM.h"
    #include "servo.h"
    
    static uint16_t pwmCompare1Num=0;
    uint8_t keyNum;
    float ServoAngle;
    int main()
    {
    	OLED_Init();
    	OLED_ShowString(1,1,"helloworld");
    	Servo_Init();
    	Key_Init();
    	
    	while (1) 
    	{	
    		uint8_t keyNum=key_getNum();
    		if (keyNum==12)
    		{
    			ServoAngle+=30;
    		}
    		if (ServoAngle>180)
    		{
    			ServoAngle=0;
    		}
    		
    		Servo_SetAngle(ServoAngle);
    	}
    } 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    servo.h

    #ifndef __SERVO_H__
    #define __SERVO_H__
    #include "PWM.h"
    void Servo_Init();
    void Servo_SetAngle(float angle);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    servo.c

    #include "servo.h"
    
    void Servo_Init()
    {
    	PWM_Init();
    }
    
    void Servo_SetAngle(float angle)
    {
    	/*
    		0->500;
    		180->2500;
    	*/
    	uint16_t angleScaled=(float)(angle/180)*2000+500;
    	PWM_SetCompare2(angleScaled);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    PWM.h

    #ifndef __PWM_H__
    #define __PWM_H__
    #include "stm32f10x.h"                  // Device header
    void PWM_Init();
    void PWM_SetCompare1(uint16_t compare);
    void PWM_SetCompare2(uint16_t compare);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    PWM.c

    使用通道2

    #include "PWM.h"
    
    void PWM_Init()
    {
    	// RCC时钟 系统基准时钟,和外设工作时钟
    	// 通用定时器TIM2是挂载在APB1上的
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	
    	// 时基单元时钟源
    	TIM_InternalClockConfig(TIM2); // 选择内部时钟
    	
    	// 外设时钟,需要使用GPIO
    	// 初始化APB2外设时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); 
    	
    	// 配置GPIO - ODR/CLR
    	// 注意针脚GPIO复用和重映射的问题,见手册针脚定义
    	GPIO_InitTypeDef GPIO_InitStructure;			// 配置结构体
    	/*
    		对于普通的开漏和推挽输出,引脚的控制权是来自输出数据寄存器的
    		如果想让定时器控制引脚,就需要使用复用开漏/推挽输出的模式,使得输出输出寄存器断开,输出控制权转移到片上外设 这里是TIM2_CH2通道 
    	*/
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; 	// 	GPIO_Mode_AF_PP 复用推挽输出
    	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_1;		// GPIOA的PA1号引脚
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // 50MHZ 
    	
    	GPIO_Init(GPIOA,&GPIO_InitStructure); 			// A0推挽输出
    	
    	// 配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_Prescaler=72-1;					// PSC预分频器的值 // 72MHZ/((72-1)+1)=1000KHZ=1MHZ
    	TIM_TimeBaseInitStructure.TIM_Period=20000-1;					// ARR自动重装器的值  // 20k*(1/1MHZ)=2/100HZ=20ms,舵机的周期要求20ms
    	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;  	// 计数模式,向上计数    
    	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;		// 关系不大,时钟分频    
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;  // 重复计数器的值,与高级定时器相关 
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	     
    	// 无需配置中断相关内容
    	
    	// 无需 配置中断输出控制,使能中断
    	
    	// 无需 配置NVIC,打开定时器中断的通道,分配优先级
    
    	
    	// 配置输出比较单元
    	// TIM_OCxInit output compare 输出比较模块
    	// 配置好之后,就可以在TIM_OC2通道上输出PWM波形,然后借用GPIO口进行输出 ,TIM2_CH2引脚和PA1引脚复用,见引脚定义手册
    	TIM_OCInitTypeDef TIM_OCInitStruct;
    	TIM_OCStructInit(&TIM_OCInitStruct); 						// 默认初始值
    	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;    			// 设置输出比较的模式,一共8个模式  
    	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;   	// 设置输出使能,输出控制器
    	TIM_OCInitStruct.TIM_Pulse=0;      							// 设置CCR   0~0xFFFF,这里频率1kHZ,占空比50%,1%的分辨率 
    	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;    	// 设置输出比较的极性,这里REF有效电平为高电平(这里应该不是对应REF的极性选择器)
    	TIM_OC2Init(TIM2,&TIM_OCInitStruct);
    	
    	/* 
    		不同通道共用一个时基单元,所以频率必须是一样的
    		但是占空比可以由各自的CCR决定
    		各个通道,相位是同步的
    	*/
    	// TIM_OC1Init(TIM2,&TIM_OCInitStruct); 同时初始化,可以同时输出两个PWM波形
    	
    	// 启动定时器
    	TIM_Cmd(TIM2,ENABLE);  
    }
    
    void PWM_SetCompare1(uint16_t compare)
    {
    	TIM_SetCompare1(TIM2,compare);
    }
    
    void PWM_SetCompare2(uint16_t compare)
    {
    	TIM_SetCompare2(TIM2,compare);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    key.c

    #include "stm32f10x.h"
    #include "key.h"
    #include "Delay.h"
    
    void Key_Init()
    {
    	// 初始化APB2外设时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    	
    	// 配置GPIO - ODR/CLR
    	GPIO_InitTypeDef GPIO_InitStructure;			// 配置结构体
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; 	// GPIO_Mode_IPU 上拉输入 - 根据原理图(内部 上拉输入配置)
    	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_12 | GPIO_Pin_13;			// GPIOB的12,13号引脚
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // 50MHZ 仅与输入有关
    	
    	GPIO_Init(GPIOB,&GPIO_InitStructure); 			// B0推挽输出
    }
    
    uint8_t key_getNum()
    {
    	uint8_t keyNum=0;
    	
    	/*
    	 * GPIO_ReadInputDataBit 读取输入数据寄存器某一端口(引脚)的输入值,高低电平
    	 * GPIO_ReadInputData   读取整个输入数据寄存器
    	 * GPIO_ReadOutputDataBit 读取输出数据寄存器某一个位,输出模式下,用来看一下自己输出的是什么
    	 * GPIO_ReadOutputData 读取整个输出数据寄存器
    	 */ 
    	
    	// 输入数据
    	if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0)
    	{
    		Delay_ms(20);
    		while (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0);
    		Delay_ms(20);
    		
    		keyNum=12;
    	}
    	if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0)
    	{
    		Delay_ms(20);
    		while (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0);
    		Delay_ms(20);
    		
    		keyNum=13;
    	}
    	
    	return keyNum;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    输入捕获

    https://www.bilibili.com/video/BV1zg411n7HX/?spm_id_from=333.337.search-card.all.click&vd_source=7155082256127a432d5ed516a6423e20
    https://www.bilibili.com/video/BV17W4y1u7cN?p=11&vd_source=7155082256127a432d5ed516a6423e20 - 正点原子

    注意:输入捕获和输出比较功能,同时只能使用一个
    输入捕获模式下,当通道输入引脚出现指定电平跳变时(上升沿或下降沿),当前CNT的值被锁存在CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
    通用和高级定时器都拥有4个输入捕获通道
    可配置PWMI模式,同时测量频率和占空比

    输入捕获基本电路

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    主从触发模式

    在这里插入图片描述

    输入捕获基本结构/PWMI输入捕获结构

    在这里插入图片描述
    在这里插入图片描述

    例子 - 输入捕获频率

    main.c

    #include "stm32f10x.h"
    #include "Delay.h"
    #include "OLED.h"
    #include "timer.h"
    #include "PWM.h"
    #include "IC.h"
    
    int main()
    {
    	OLED_Init();
    	OLED_ShowString(1,1,"helloworld");
    	OLED_ShowString(2,1,"Freq");
    	PWM_Init();
    	IC_Init();
    	
    	PWM_SetPrescaler(7200-1); 	// Freq=72M/(PSC+1)/(ARR+1) ARR=100-1
    	PWM_SetCompare1(50); 		// Duty=CCR/(ARR+1) ARR=100-1	
    	while (1) 
    	{	
    		OLED_ShowNum(2,6,IC_GetReq(),6);
    	}
    } 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    PWM.h

    #ifndef __PWM_H__
    #define __PWM_H__
    #include "stm32f10x.h"                  // Device header
    void PWM_Init();
    void PWM_SetCompare1(uint16_t compare);
    void PWM_SetCompare2(uint16_t compare);
    void PWM_SetCompare3(uint16_t compare);
    void PWM_SetPrescaler(uint16_t prescaler); // 仅通过调节PSC来调节频率
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    PWM.c

    #include "PWM.h"
    
    void PWM_Init()
    {
    	// RCC时钟 系统基准时钟,和外设工作时钟
    	// 通用定时器TIM2是挂载在APB1上的
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	
    	// 时基单元时钟源
    	TIM_InternalClockConfig(TIM2); // 选择内部时钟
    	
    	// 外设时钟,需要使用GPIO
    	// 初始化APB2外设时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); 
    	
    	// 配置GPIO - ODR/CLR
    	// 注意针脚GPIO复用和重映射的问题,见手册针脚定义
    	GPIO_InitTypeDef GPIO_InitStructure;			// 配置结构体
    	/*
    		对于普通的开漏和推挽输出,引脚的控制权是来自输出数据寄存器的
    		如果想让定时器控制引脚,就需要使用复用开漏/推挽输出的模式,使得输出输出寄存器断开,输出控制权转移到片上外设 这里是TIM2_CH3通道 
    	*/
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; 	// 	GPIO_Mode_AF_PP 复用推挽输出
    	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0;		// GPIOA的PA0号引脚
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // 50MHZ 
    	
    	GPIO_Init(GPIOA,&GPIO_InitStructure); 			// A0推挽输出
    	
    	// 配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_Prescaler=720 -1;					// PSC预分频器的值 // 72MHZ/((72-1)+1)=1000KHZ=1MHZ
    	TIM_TimeBaseInitStructure.TIM_Period=100-1;						// ARR自动重装器的值  // 20k*(1/1MHZ)=2/100HZ=20ms,舵机的周期要求20ms
    	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;  	// 计数模式,向上计数    
    	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;		// 关系不大,时钟分频    
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;  			// 重复计数器的值,与高级定时器相关 
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	     
    	// 无需配置中断相关内容
    	
    	// 无需 配置中断输出控制,使能中断
    	
    	// 无需 配置NVIC,打开定时器中断的通道,分配优先级
    
    	
    	// 配置输出比较单元
    	// TIM_OCxInit output compare 输出比较模块
    	// 配置好之后,就可以在TIM_OC2通道上输出PWM波形,然后借用GPIO口进行输出 ,TIM2_CH2引脚和PA1引脚复用,见引脚定义手册
    	TIM_OCInitTypeDef TIM_OCInitStruct;
    	TIM_OCStructInit(&TIM_OCInitStruct); 						// 默认初始值
    	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;    			// 设置输出比较的模式,一共8个模式  
    	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;   	// 设置输出使能,输出控制器
    	TIM_OCInitStruct.TIM_Pulse=0;      							// 设置CCR   0~0xFFFF,这里频率1kHZ,占空比50%,1%的分辨率 
    	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;    	// 设置输出比较的极性,这里REF有效电平为高电平(这里应该不是对应REF的极性选择器)
    	TIM_OC1Init(TIM2,&TIM_OCInitStruct);
    	
    	/* 
    		不同通道共用一个时基单元,所以频率必须是一样的
    		但是占空比可以由各自的CCR决定
    		各个通道,相位是同步的
    	*/
    	// TIM_OC2Init(TIM2,&TIM_OCInitStruct); 同时初始化,可以同时输出两个PWM波形
    	
    	// 启动定时器
    	TIM_Cmd(TIM2,ENABLE);  
    }
    
    void PWM_SetCompare1(uint16_t compare)
    {
    	TIM_SetCompare1(TIM2,compare);
    }
    
    void PWM_SetCompare2(uint16_t compare)
    {
    	TIM_SetCompare2(TIM2,compare);
    }
    
    void PWM_SetCompare3(uint16_t compare)
    {
    	TIM_SetCompare3(TIM2,compare);
    }
    
    void PWM_SetPrescaler(uint16_t prescaler)  // 仅通过调节PSC来调节频率
    {
    	TIM_PrescalerConfig(TIM2,prescaler, TIM_PSCReloadMode_Update);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    IC.h

    #ifndef __IC_H__
    #define __IC_H__
    #include "PWM.h"
    #include "stm32f10x.h"
    
    void IC_Init();
    uint32_t IC_GetReq();
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    IC.c

    #include "IC.h"
    
    void IC_Init()
    {
    	// 配置GPIO,及其时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	GPIO_InitTypeDef GPIO_InitStruct;
    	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
    	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
    	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);  
    	
    	// 配置TIM,及其时钟,
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); // TIM2负责输出PWM,因此不能同时在使用输入捕获功能
    														// 这里使用TIM3
    	
    	// 配置时基单元,内部时钟
    	TIM_InternalClockConfig(TIM3);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    	TIM_TimeBaseInitStruct.TIM_Prescaler=72-1; // PSC,使得标准频率为1MHZ
    	TIM_TimeBaseInitStruct.TIM_Period=65536-1; // ARR
    	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
    	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
    	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
    	
    	// 配置输入捕获单元: 滤波器、边沿检测、极性选择、直连/交叉通道 分频器
    	TIM_ICInitTypeDef TIM_ICInitStruct;
    	TIM_ICInitStruct.TIM_Channel=TIM_Channel_1; 
    	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising; 		// 极性选择,选择上升沿触发 
    	TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI;  // 触发信号从哪个引脚输入,可以选择直连通道或交叉通道
    	TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1; 			// 分频器,不分频就是每次触发都有效,2分频就是每隔一次有效一次
    	TIM_ICInitStruct.TIM_ICFilter=0xF;					 		// 设置滤波器,滤除高频噪声,不会影响原信号的频率
    	TIM_ICInit(TIM3,&TIM_ICInitStruct);
    	
    	// 配置从模式,及其触发源
    	// 设置从模式,输入触发源
    	// 有信号触发之后,在从模式下CNT就会自动清零 
    	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1); // 选择触发源,一共8个触发源
    	// 设置从模式执行的操作
    	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset); // 设置从模式要执行的动作
    	
    	// 开启定时器
    	TIM_Cmd(TIM3,ENABLE);
    }
    
    uint32_t IC_GetReq()
    {
    	return 1*1000*1000/TIM_GetCapture1(TIM3); // xHZ
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    例子 - 输入捕获频率/占空比

    main.c

    #include "stm32f10x.h"
    #include "OLED.h"
    #include "timer.h"
    #include "PWM.h"
    #include "IC.h"
    
    int main()
    {
    	OLED_Init();
    	OLED_ShowString(1,1,"helloworld");
    	OLED_ShowString(2,1,"Freq: ");
    	OLED_ShowString(3,1,"Duty: ");
    	PWM_Init();
    	IC_Init();
    	
    	PWM_SetPrescaler(7200-1); 	// Freq=72M/(PSC+1)/(ARR+1) ARR=100-1
    	PWM_SetCompare1(80); 		// Duty=CCR/(ARR+1) ARR=100-1	
    	while (1) 
    	{	
    		OLED_ShowNum(2,6,IC_GetReq(),6);
    		OLED_ShowNum(3,6,IC_GetDuty(),2);
    	}
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    PWM.c/PWM.h 见上个例子 没改动

    IC.h

    #ifndef __IC_H__
    #define __IC_H__
    #include "PWM.h"
    #include "stm32f10x.h"
    
    void IC_Init();
    uint32_t IC_GetReq();
    uint32_t IC_GetDuty();
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    IC.c

    #include "IC.h"
    
    void IC_Init()
    {
    	// 配置GPIO,及其时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	GPIO_InitTypeDef GPIO_InitStruct;
    	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
    	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
    	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);  
    	
    	// 配置TIM,及其时钟,
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); // TIM2负责输出PWM,因此不能同时在使用输入捕获功能
    														// 这里使用TIM3
    	
    	// 配置时基单元,内部时钟
    	TIM_InternalClockConfig(TIM3);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    	TIM_TimeBaseInitStruct.TIM_Prescaler=72-1; // PSC,使得标准频率为1MHZ
    	TIM_TimeBaseInitStruct.TIM_Period=65536-1; // ARR
    	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
    	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
    	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
    	
    	// 配置输入捕获单元: 滤波器、边沿检测、极性选择、直连/交叉通道 分频器
    	TIM_ICInitTypeDef TIM_ICInitStruct;
    	TIM_ICInitStruct.TIM_Channel=TIM_Channel_1; 
    	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising; 		// 极性选择,选择上升沿触发 
    	TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI;  // 触发信号从哪个引脚输入,可以选择直连通道或交叉通道
    	TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1; 			// 分频器,不分频就是每次触发都有效,2分频就是每隔一次有效一次
    	TIM_ICInitStruct.TIM_ICFilter=0xF;					 		// 设置滤波器,滤除高频噪声,不会影响原信号的频率
    	TIM_ICInit(TIM3,&TIM_ICInitStruct);
    	
    	// version 1 重新配置一遍
    	// 配置第二个通道 检测占空比
    //	TIM_ICInitStruct.TIM_Channel=TIM_Channel_2; 
    //	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Falling; 		// 极性选择,选择下将沿触发 
    //	TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_IndirectTI;  // 触发信号从哪个引脚输入,可以选择直连通道或交叉通道
    //	TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1; 			// 分频器,不分频就是每次触发都有效,2分频就是每隔一次有效一次
    //	TIM_ICInitStruct.TIM_ICFilter=0xF;					 		// 设置滤波器,滤除高频噪声,不会影响原信号的频率
    //	TIM_ICInit(TIM3,&TIM_ICInitStruct);
    	
    	// version 2 调用库函数 配置第二个通道
    	TIM_PWMIConfig(TIM3,&TIM_ICInitStruct);
    	
    	
    	// 配置从模式,及其触发源
    	// 设置从模式,输入触发源
    	// 有信号触发之后,在从模式下CNT就会自动清零 
    	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1); // 选择触发源,一共8个触发源
    	// 设置从模式执行的操作
    	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset); // 设置从模式要执行的动作
    	
    	// 开启定时器
    	TIM_Cmd(TIM3,ENABLE);
    }
    
    uint32_t IC_GetReq()
    {
    	return 1*1000*1000/TIM_GetCapture1(TIM3); // xHZ
    }
    
    uint32_t IC_GetDuty()
    {
    	return TIM_GetCapture2(TIM3)*100/TIM_GetCapture1(TIM3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
  • 相关阅读:
    人员定位系统如何实现危化企业安全生产?
    java数据类型——基本数据类型&引用数据类型
    KT148A语音芯片按键版本一对一触发播放功能描述_V4
    SaaS CRM系统的优势,与本地部署相比哪个更方便?
    C++学习笔记(17)
    驱动开发:内核物理内存寻址读写
    sql根据子表批量更新主表
    7.1 为什么要用函数
    LVS之DR模式(最常见的LVS负载方式,直接路由模式)
    SpringBoot+PageHelper+Vue+Element从零开始实现分页功能(包含前后端源码)
  • 原文地址:https://blog.csdn.net/L_fengzifei/article/details/133499924