STM32小实验2

定时器实验

TIM介绍

  • TIMTimer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

STM32F103C8T6定时器资源:TIM1TIM2TIM3TIM4

高级定时器

高级控制定时器(TIM1)可以被看成是分配到6个通道的三相PWM发生器,它具有带死区插入的互补 PWM输出,还可以被当成完整的通用定时器。四个独立的通道可以用于:
● 输入捕获
● 输出比较
● 产生PWM(边缘或中心对齐模式)
● 单脉冲输出
配置为16位标准定时器时,它与TIMx定时器具有相同的功能。配置为16位PWM发生器时,它具有全 调制能力(0~100%)。 在调试模式下,计数器可以被冻结,同时PWM输出被禁止,从而切断由这些输出所控制的开关。 很多功能都与标准的TIM定时器相同,内部结构也相同,因此高级控制定时器可以通过定时器链接功 能与TIM定时器协同操作,提供同步或事件链接功能。

通用定时器

STM32F103xx增强型产品中,内置了多达3个可同步运行的标准定时器(TIM2、TIM3和TIM4)。每个 定时器都有一个16位的自动加载递加/递减计数器、一个16位的预分频器和4个独立的通道,每个通 道都可用于输入捕获、输出比较、PWM和单脉冲模式输出,在最大的封装配置中可提供最多12个输 入捕获、输出比较或PWM通道。 它们还能通过定时器链接功能与高级控制定时器共同工作,提供同步或事件链接功能。在调试模式 下,计数器可以被冻结。任一标准定时器都能用于产生PWM输出。每个定时器都有独立的DMA请求 机制。 这些定时器还能够处理增量编码器的信号,也能处理1至3个霍尔传感器的数字输出。

基本定时器

这个定时器是专用于实时操作系统,也可当成一个标准的递减计数器。它具有下述特性:
● 24位的递减计数器
● 自动重加载功能
● 当计数器为0时能产生一个可屏蔽系统中断
● 可编程时钟源

定时中断基本结构

预分频时序

计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

计数器时序

计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
                                         = CK_PSC / (PSC + 1) / (ARR + 1)

计数器无预装时序

计数器有预装时序

RCC时钟树

定时器定时中断

#include "stm32f10x.h"                  // Device header/*** 函    数:定时中断初始化* 参    数:无* 返 回 值:无*/
void Timer_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟/*配置时钟源*/TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	/*中断输出配置*/TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位//TIM_TimeBaseInit函数末尾,手动产生了更新事件//若不清除此标志位,则开启中断后,会立刻进入一次中断//如果不介意此问题,则不清除此标志位也可TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设/*TIM使能*/TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
*/
#ifndef __TIMER_H
#define __TIMER_Hvoid Timer_Init(void);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"uint16_t Num;			//定义在定时器中断里自增的变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化 OLED代码在实验1Timer_Init();		//定时中断初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:while (1){OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量}
}/*** 函    数:TIM2中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断{Num ++;												//Num变量自增,用于测试定时中断TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}

定时器外部时钟

#include "stm32f10x.h"                  // Device header/*** 函    数:定时中断初始化* 参    数:无* 返 回 值:无* 注意事项:此函数配置为外部时钟,定时器相当于计数器*/
void Timer_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA0引脚初始化为上拉输入/*外部时钟配置*/TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);//选择外部时钟模式2,时钟从TIM_ETR引脚输入//注意TIM2的ETR引脚固定为PA0,无法随意更改//最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;					//计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;				//预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	/*中断输出配置*/TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位//TIM_TimeBaseInit函数末尾,手动产生了更新事件//若不清除此标志位,则开启中断后,会立刻进入一次中断//如果不介意此问题,则不清除此标志位也可TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设/*TIM使能*/TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}/*** 函    数:返回定时器CNT的值* 参    数:无* 返 回 值:定时器CNT的值,范围:0~65535*/
uint16_t Timer_GetCounter(void)
{return TIM_GetCounter(TIM2);	//返回定时器TIM2的CNT
}/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
*/
#ifndef __TIMER_H
#define __TIMER_Hvoid Timer_Init(void);
uint16_t Timer_GetCounter(void);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"uint16_t Num;			//定义在定时器中断里自增的变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化  OLED代码在实验1Timer_Init();		//定时中断初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:OLED_ShowString(2, 1, "CNT:");			//2行1列显示字符串CNT:while (1){OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量OLED_ShowNum(2, 5, Timer_GetCounter(), 5);		//不断刷新显示CNT的值}
}/*** 函    数:TIM2中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断{Num ++;												//Num变量自增,用于测试定时中断TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}

TIM输出比较

输出比较介绍

  • OCOutput Compare)输出比较
  • 输出比较可以通过比较CNTCCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
  • 每个高级定时器和通用定时器都拥有4个输出比较通道
  •           高级定时器的前3个通道额外拥有死区生成和互补输出的功能

PWM介绍

  • PWMPulse Width Modulation)脉冲宽度调制
  • 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
  • PWM参数:   频率 = 1 / TS            占空比 = TON / TS           分辨率 = 占空比变化步距

输出比较通道高级

输出比较通道通用

输出比较模式

PWM基本结构

参数计算

  • PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1)
  • PWM占空比:  Duty = CCR / (ARR + 1)
  • PWM分辨率:  Reso = 1 / (ARR + 1)

PWM驱动LED呼吸灯

#include "stm32f10x.h"                  // Device header/*** 函    数:LED初始化* 参    数:无* 返 回 值:无*/
void LED_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA1和PA2引脚初始化为推挽输出/*设置GPIO初始化后的默认电平*/GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);				//设置PA1和PA2引脚为高电平
}/*** 函    数:LED1开启* 参    数:无* 返 回 值:无*/
void LED1_ON(void)
{GPIO_ResetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为低电平
}/*** 函    数:LED1关闭* 参    数:无* 返 回 值:无*/
void LED1_OFF(void)
{GPIO_SetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为高电平
}/*** 函    数:LED1状态翻转* 参    数:无* 返 回 值:无*/
void LED1_Turn(void)
{if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平{GPIO_SetBits(GPIOA, GPIO_Pin_1);					//则设置PA1引脚为高电平}else													//否则,即当前引脚输出高电平{GPIO_ResetBits(GPIOA, GPIO_Pin_1);					//则设置PA1引脚为低电平}
}/*** 函    数:LED2开启* 参    数:无* 返 回 值:无*/
void LED2_ON(void)
{GPIO_ResetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为低电平
}/*** 函    数:LED2关闭* 参    数:无* 返 回 值:无*/
void LED2_OFF(void)
{GPIO_SetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为高电平
}/*** 函    数:LED2状态翻转* 参    数:无* 返 回 值:无*/
void LED2_Turn(void)
{if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平{                                                  GPIO_SetBits(GPIOA, GPIO_Pin_2);               		//则设置PA2引脚为高电平}                                                  else                                               		//否则,即当前引脚输出高电平{                                                  GPIO_ResetBits(GPIOA, GPIO_Pin_2);             		//则设置PA2引脚为低电平}
}
#ifndef __LED_H
#define __LED_Hvoid LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED1_Turn(void);
void LED2_ON(void);
void LED2_OFF(void);
void LED2_Turn(void);#endif
#include "stm32f10x.h"                  // Device header/*** 函    数:PWM初始化* 参    数:无* 返 回 值:无*/
void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO重映射*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	//受外设控制的引脚,均需要配置为复用模式		/*配置时钟源*/TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1/*TIM使能*/TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}/*** 函    数:PWM设置CCR* 参    数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比*           占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare1(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}
#ifndef __PWM_H
#define __PWM_Hvoid PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);#endif
#include "stm32f10x.h"/*** @brief  微秒级延时* @param  xus 延时时长,范围:0~233015* @retval 无*/
void Delay_us(uint32_t xus)
{SysTick->LOAD = 72 * xus;				//设置定时器重装值SysTick->VAL = 0x00;					//清空当前计数值SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器while(!(SysTick->CTRL & 0x00010000));	//等待计数到0SysTick->CTRL = 0x00000004;				//关闭定时器
}/*** @brief  毫秒级延时* @param  xms 延时时长,范围:0~4294967295* @retval 无*/
void Delay_ms(uint32_t xms)
{while(xms--){Delay_us(1000);}
}/*** @brief  秒级延时* @param  xs 延时时长,范围:0~4294967295* @retval 无*/
void Delay_s(uint32_t xs)
{while(xs--){Delay_ms(1000);}
} 
#ifndef __DELAY_H
#define __DELAY_Hvoid Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"uint8_t i;			//定义for循环的变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化PWM_Init();			//PWM初始化while (1){for (i = 0; i <= 100; i++){PWM_SetCompare1(i);			//依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮Delay_ms(10);				//延时10ms}for (i = 0; i <= 100; i++){PWM_SetCompare1(100 - i);	//依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗Delay_ms(10);				//延时10ms}}
}

舵机的介绍

  • 舵机是一种根据输入PWM信号占空比来控制输出角度的装置
  • 输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms

 PWM驱动舵机

#include "stm32f10x.h"                  // Device header/*** 函    数:PWM初始化* 参    数:无* 返 回 值:无*/
void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA1引脚初始化为复用推挽输出	//受外设控制的引脚,均需要配置为复用模式/*配置时钟源*/TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;				//计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/ TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2/*TIM使能*/TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}/*** 函    数:PWM设置CCR* 参    数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比*           占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare2(uint16_t Compare)
{TIM_SetCompare2(TIM2, Compare);		//设置CCR2的值
}
#ifndef __PWM_H
#define __PWM_Hvoid PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);#endif
#include "stm32f10x.h"                  // Device header
#include "PWM.h"/*** 函    数:舵机初始化* 参    数:无* 返 回 值:无*/
void Servo_Init(void)
{PWM_Init();									//初始化舵机的底层PWM
}/*** 函    数:舵机设置角度* 参    数:Angle 要设置的舵机角度,范围:0~180* 返 回 值:无*/
void Servo_SetAngle(float Angle)
{PWM_SetCompare2(Angle / 180 * 2000 + 500);	//设置占空比//将角度线性变换,对应到舵机要求的占空比范围上
}
#ifndef __SERVO_H
#define __SERVO_Hvoid Servo_Init(void);
void Servo_SetAngle(float Angle);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"uint8_t KeyNum;			//定义用于接收键码的变量
float Angle;			//定义角度变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化 OLED代码在实验1Servo_Init();		//舵机初始化Key_Init();			//按键初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Angle:");	//1行1列显示字符串Angle:while (1){KeyNum = Key_GetNum();			//获取按键键码if (KeyNum == 1)				//按键1按下{Angle += 30;				//角度变量自增30if (Angle > 180)			//角度变量超过180后{Angle = 0;				//角度变量归零}}Servo_SetAngle(Angle);			//设置舵机的角度为角度变量OLED_ShowNum(1, 7, Angle, 3);	//OLED显示角度变量}
}

直流电机及驱动的介绍

  • 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
  • TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

PWM驱动直流电机

#include "stm32f10x.h"                  // Device header
#include "PWM.h"/*** 函    数:直流电机初始化* 参    数:无* 返 回 值:无*/
void Motor_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA4和PA5引脚初始化为推挽输出	PWM_Init();													//初始化直流电机的底层PWM
}/*** 函    数:直流电机设置速度* 参    数:Speed 要设置的速度,范围:-100~100* 返 回 值:无*/
void Motor_SetSpeed(int8_t Speed)
{if (Speed >= 0)							//如果设置正转的速度值{GPIO_SetBits(GPIOA, GPIO_Pin_4);	//PA4置高电平GPIO_ResetBits(GPIOA, GPIO_Pin_5);	//PA5置低电平,设置方向为正转PWM_SetCompare3(Speed);				//PWM设置为速度值}else									//否则,即设置反转的速度值{GPIO_ResetBits(GPIOA, GPIO_Pin_4);	//PA4置低电平GPIO_SetBits(GPIOA, GPIO_Pin_5);	//PA5置高电平,设置方向为反转PWM_SetCompare3(-Speed);			//PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数}
}
#ifndef __MOTOR_H
#define __MOTOR_Hvoid Motor_Init(void);
void Motor_SetSpeed(int8_t Speed);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"/*** 函    数:按键初始化* 参    数:无* 返 回 值:无*/
void Key_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB1和PB11引脚初始化为上拉输入
}/*** 函    数:按键获取键码* 参    数:无* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手*/
uint8_t Key_GetNum(void)
{uint8_t KeyNum = 0;		//定义变量,默认键码值为0if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键1按下{Delay_ms(20);											//延时消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	//等待按键松手Delay_ms(20);											//延时消抖KeyNum = 1;												//置键码为1}if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)			//读PB11输入寄存器的状态,如果为0,则代表按键2按下{Delay_ms(20);											//延时消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	//等待按键松手Delay_ms(20);											//延时消抖KeyNum = 2;												//置键码为2}return KeyNum;			//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
#ifndef __KEY_H
#define __KEY_Hvoid Key_Init(void);
uint8_t Key_GetNum(void);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"uint8_t KeyNum;		//定义用于接收按键键码的变量
int8_t Speed;		//定义速度变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化Motor_Init();		//直流电机初始化Key_Init();			//按键初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:while (1){KeyNum = Key_GetNum();				//获取按键键码if (KeyNum == 1)					//按键1按下{Speed += 20;					//速度变量自增20if (Speed > 100)				//速度变量超过100后{Speed = -100;				//速度变量变为-100//此操作会让电机旋转方向突然改变,可能会因供电不足而导致单片机复位//若出现了此现象,则应避免使用这样的操作}}Motor_SetSpeed(Speed);				//设置直流电机的速度为速度变量OLED_ShowSignedNum(1, 7, Speed, 3);	//OLED显示速度变量}
}

输入捕获介绍

  • ICInput Capture)输入捕获
  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
  • 可配置为PWMI模式,同时测量频率和占空比
  • 可配合主从触发模式,实现硬件全自动测量

频率测量

  • 测频法:在闸门时间T内,对上升沿计次,得到N,则频率
  • 𝑓𝑥=𝑁 / 𝑇f_x=N / T
  • 测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率
  • 𝑓𝑥=𝑓𝑐 / 𝑁f_x=f_c  / N
  • 中界频率:测频法与测周法误差相等的频率点
  • 𝑓𝑚=𝑓𝑐 / 𝑇f_m=√(f_c  / T)

输入捕获通道

主从触发模式

输入捕获基本结构

PWNI基本结构

输入捕获模式测频率

#include "stm32f10x.h"                  // Device header/*** 函    数:PWM初始化* 参    数:无* 返 回 值:无*/
void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO重映射*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	//受外设控制的引脚,均需要配置为复用模式		/*配置时钟源*/TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1/*TIM使能*/TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}/*** 函    数:PWM设置CCR* 参    数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比*           占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare1(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}/*** 函    数:PWM设置PSC* 参    数:Prescaler 要写入的PSC的值,范围:0~65535* 返 回 值:无* 注意事项:PSC和ARR共同决定频率,此函数仅设置PSC的值,并不直接是频率*           频率Freq = CK_PSC / (PSC + 1) / (ARR + 1)*/
void PWM_SetPrescaler(uint16_t Prescaler)
{TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);		//设置PSC的值
}
#ifndef __PWM_H
#define __PWM_Hvoid PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetPrescaler(uint16_t Prescaler);#endif
#include "stm32f10x.h"                  // Device header/*** 函    数:输入捕获初始化* 参    数:无* 返 回 值:无*/
void IC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入/*配置时钟源*/TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元/*输入捕获初始化*/TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性,选择为上升沿触发捕获TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频,选择不分频,每次信号都触发捕获TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉,选择直通,不交叉TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道/*选择触发源及从模式*/TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位//即TI1产生上升沿时,会触发CNT归零/*TIM使能*/TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}/*** 函    数:获取输入捕获的频率* 参    数:无* 返 回 值:捕获得到的频率*/
uint32_t IC_GetFreq(void)
{return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
}
#ifndef __IC_H
#define __IC_Hvoid IC_Init(void);
uint32_t IC_GetFreq(void);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化PWM_Init();			//PWM初始化IC_Init();			//输入捕获初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000Hz/*使用PWM模块提供输入捕获的测试信号*/PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100while (1){OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率}
}

PWMI模式测频率占空比

#include "stm32f10x.h"                  // Device header/*** 函    数:PWM初始化* 参    数:无* 返 回 值:无*/
void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO重映射*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	//受外设控制的引脚,均需要配置为复用模式		/*配置时钟源*/TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1/*TIM使能*/TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}/*** 函    数:PWM设置CCR* 参    数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比*           占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare1(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}/*** 函    数:PWM设置PSC* 参    数:Prescaler 要写入的PSC的值,范围:0~65535* 返 回 值:无* 注意事项:PSC和ARR共同决定频率,此函数仅设置PSC的值,并不直接是频率*           频率Freq = CK_PSC / (PSC + 1) / (ARR + 1)*/
void PWM_SetPrescaler(uint16_t Prescaler)
{TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);		//设置PSC的值
}
#ifndef __PWM_H
#define __PWM_Hvoid PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetPrescaler(uint16_t Prescaler);#endif
#include "stm32f10x.h"                  // Device header/*** 函    数:输入捕获初始化* 参    数:无* 返 回 值:无*/
void IC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入/*配置时钟源*/TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元/*PWMI模式初始化*/TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性,选择为上升沿触发捕获TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频,选择不分频,每次信号都触发捕获TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉,选择直通,不交叉TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);						//将结构体变量交给TIM_PWMIConfig,配置TIM3的输入捕获通道//此函数同时会把另一个通道配置为相反的配置,实现PWMI模式/*选择触发源及从模式*/TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位//即TI1产生上升沿时,会触发CNT归零/*TIM使能*/TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}/*** 函    数:获取输入捕获的频率* 参    数:无* 返 回 值:捕获得到的频率*/
uint32_t IC_GetFreq(void)
{return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
}/*** 函    数:获取输入捕获的占空比* 参    数:无* 返 回 值:捕获得到的占空比*/
uint32_t IC_GetDuty(void)
{return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);	//占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}
#ifndef __IC_H
#define __IC_Hvoid IC_Init(void);
uint32_t IC_GetFreq(void);
uint32_t IC_GetDuty(void);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化PWM_Init();			//PWM初始化IC_Init();			//输入捕获初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000HzOLED_ShowString(2, 1, "Duty:00%");			//2行1列显示字符串Duty:00%/*使用PWM模块提供输入捕获的测试信号*/PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100while (1){OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率OLED_ShowNum(2, 6, IC_GetDuty(), 2);	//不断刷新显示输入捕获测得的占空比}
}

编码器接口介绍

  • Encoder Interface 编码器接口
  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
  • 每个高级定时器和通用定时器都拥有1个编码器接口
  • 两个输入引脚借用了输入捕获的通道1和通道2

正交编码器

编码器接口基本结构

工作模式

实例(均不反向)

实例(TI1反向)

编码器接口测速

#include "stm32f10x.h"                  // Device header/*** 函    数:定时中断初始化* 参    数:无* 返 回 值:无*/
void Timer_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟/*配置时钟源*/TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	/*中断输出配置*/TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位//TIM_TimeBaseInit函数末尾,手动产生了更新事件//若不清除此标志位,则开启中断后,会立刻进入一次中断//如果不介意此问题,则不清除此标志位也可TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设/*TIM使能*/TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
*/
#ifndef __TIMER_H
#define __TIMER_Hvoid Timer_Init(void);#endif
#include "stm32f10x.h"                  // Device header/*** 函    数:编码器初始化* 参    数:无* 返 回 值:无*/
void Encoder_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6和PA7引脚初始化为上拉输入/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;                //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元/*输入捕获初始化*/TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量TIM_ICStructInit(&TIM_ICInitStructure);							//结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;				//选择配置定时器通道2TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道/*编码器接口配置*/TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//配置编码器模式以及两个输入通道是否反相//注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相//此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置/*TIM使能*/TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}/*** 函    数:获取编码器的增量值* 参    数:无* 返 回 值:自上此调用此函数后,编码器的增量值*/
int16_t Encoder_Get(void)
{/*使用Temp变量作为中继,目的是返回CNT后将其清零*/int16_t Temp;Temp = TIM_GetCounter(TIM3);TIM_SetCounter(TIM3, 0);return Temp;
}
#ifndef __ENCODER_H
#define __ENCODER_Hvoid Encoder_Init(void);
int16_t Encoder_Get(void);#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"int16_t Speed;			//定义速度变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化Timer_Init();		//定时器初始化Encoder_Init();		//编码器初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:while (1){OLED_ShowSignedNum(1, 7, Speed, 5);	//不断刷新显示编码器测得的最新速度}
}/*** 函    数:TIM2中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断{Speed = Encoder_Get();								//每隔固定时间段读取一次编码器计数增量值,即为速度值TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}

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

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

相关文章

HTB:Timelapse[WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 提取并保存靶机TCP开放端口号 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用nmap对靶机UD…

【贵州省】乡镇界arcgis格式shp数据乡镇名称和编码内容下载测评

shp数据字段乡镇名称和编码&#xff0c;坐标是wgs84&#xff0c;数据为SHP矢量格式&#xff0c;下载下来直接加载进ArcMap即可使用 下载地址&#xff1a;https://download.csdn.net/download/zhongguonanren99/14928126

[免费]微信小程序(高校就业)招聘系统(Springboot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序(高校就业)招聘系统(Springboot后端Vue管理端)&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序(高校就业)招聘系统(Springboot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项目介绍…

“AI智能实训系统:让学习更高效、更轻松!

大家好&#xff0c;作为一名资深产品经理&#xff0c;今天我来跟大家聊聊一款备受瞩目的产品——AI智能实训系统。在这个人工智能技术飞速发展的时代&#xff0c;AI智能实训系统应运而生&#xff0c;为广大学习者提供了全新的学习体验。那么&#xff0c;这款产品究竟有哪些亮点…

Linux下字符设备驱动编写(RK3568)

文章目录 一 基础知识概念特点常见应用场景 二 linux 下的字符设备字符设备在 /dev 目录下用 ls -l 命令查看字符设备文件类型主设备号和次设备号 三 字符驱动模块的编写1. 头文件引入2. 定义错误码枚举3. 设备操作函数定义4. 关键结构体与变量定义5. 驱动入口函数&#xff08;…

【ROS2】RViz2加载URDF模型文件

1、RViz2加载URDF模型文件 1)运行RViz2 rviz22)添加组件:RobotModel 3)选择通过文件添加 4)选择URDF文件,此时会报错,需要修改Fixed Frame为map即可 5)因为没有坐标转换,依然会报错,下面尝试解决 2、运行坐标转换节点 1)运行ROS节点:robot_state_publishe

大数据组件(三)快速入门实时计算平台Dinky

大数据组件(三)快速入门实时计算平台Dinky Dinky 是一个开箱即用的一站式实时计算平台&#xff08;同样&#xff0c;还有StreamPark&#xff09;&#xff0c;以 Apache Flink 为基础&#xff0c;连接数据湖仓等众多框架&#xff0c;致力于流批一体和湖仓一体的建设与实践。 Di…

TANGO - 数字人全身动作生成

文章目录 一、关于 TANGO演示视频&#xff08;YouTube&#xff09;&#x1f4dd;发布计划 二、⚒️安装克隆存储库构建环境 三、&#x1f680;训练和推理1、推理2、为自定义字符创建图形 一、关于 TANGO TANGO 是 具有分层音频运动嵌入 和 扩散插值的共语音手势视频再现 由东…

1月9日星期四今日早报简报微语报早读

1月9日星期四&#xff0c;农历腊月初十&#xff0c;早报#微语早读。 1、上海排查47家“俄罗斯商品馆”&#xff1a;个别店铺被责令停业&#xff0c;立案调查&#xff1b; 2、西藏定日县已转移受灾群众4.65万人&#xff0c;检测到余震646次&#xff1b; 3、国家发改委&#x…

Zemax 序列模式下的扩束器

扩束器结构原理 扩束器用于增加准直光束&#xff08;例如激光束&#xff09;的直径&#xff0c;同时保持其准直。它通常用于激光光学和其他需要修改光束大小或发散度的应用。 在典型的扩束器中&#xff0c;输入光束是准直激光器&#xff0c;或光束进入第一个光学元件。当光束开…

【TI毫米波雷达】DCA1000不使用mmWave Studio的数据采集方法,以及自动化实时数据采集

【TI毫米波雷达】DCA1000不使用mmWave Studio的数据采集方法&#xff0c;以及自动化实时数据采集 mmWave Studio提供的功能完全够用了 不用去纠结用DCA1000低延迟、无GUI传数据 速度最快又保证算力无非就是就是Linux板自己写驱动做串口和UDP 做雷达产品应用也不会采用DCA1000的…

Kubernetes Gateway API-4-TCPRoute和GRPCRoute

1 TCPRoute 目前 TCP routing 还处于实验阶段。 Gateway API 被设计为与多个协议一起工作&#xff0c;TCPRoute 就是这样一个允许管理TCP流量的路由。 在这个例子中&#xff0c;我们有一个 Gateway 资源和两个 TCPRoute 资源&#xff0c;它们按照以下规则分配流量&#xff1…

提升决策支持:五大报表软件功能全面评测

本文将为大家介绍五款功能强大的报表软件&#xff0c;包括山海鲸报表、JReport、Power BI、Zoho Analytics 和 SAP Crystal Reports。这些工具各具特色&#xff0c;能够帮助企业快速生成数据报表并进行深度分析。无论是数据可视化、报表定制、自动化生成还是与其他系统的集成&a…

Backend - C# EF Core 执行迁移 Migrate

目录 一、创建Postgre数据库 二、安装包 &#xff08;一&#xff09;查看是否存在该安装包 &#xff08;二&#xff09;安装所需包 三、执行迁移命令 1. 作用 2. 操作位置 3. 执行&#xff08;针对visual studio&#xff09; 查看迁移功能的常用命令&#xff1a; 查看…

GESP202312 四级【小杨的字典】题解(AC)

》》》点我查看「视频」详解》》》 [GESP202312 四级] 小杨的字典 题目描述 在遥远的星球&#xff0c;有两个国家 A 国和 B 国&#xff0c;他们使用着不同的语言&#xff1a;A 语言和 B 语言。小杨是 B 国的翻译官&#xff0c;他的工作是将 A 语言的文章翻译成 B 语言的文章…

【软考网工笔记】计算机基础理论与安全——网络规划与设计

HFC 混合光纤同轴电缆网 HFC: Hybrid Fiber - Coaxial 的缩写&#xff0c;即混合光纤同轴电缆网。是一种经济实用的综合数字服务宽带网接入技术。 HFC 通常由光纤干线、同轴电缆支线和用户配线网络三部分组成&#xff0c;从有线电视台出来的节目信号先变成光信号在干线上传输…

KubeVirt 进阶:设置超卖比、CPU/MEM 升降配、在线磁盘扩容

前两篇文章&#xff0c;我们分别介绍 Kubevirt 的安装、基本使用 以及 将 oVirt 虚拟机迁移到 KubeVirt&#xff0c;我们留了两个ToDo&#xff0c;一个是本地磁盘的动态分配&#xff0c;一个是固定 IP 的需求&#xff0c;本期我们先解决第一个&#xff0c;本地磁盘的动态分配。…

自动化脚本本地可执行但是Jenkins上各种报错怎么解决

作者碎碎念&#xff1a; 测试环境 Jenkinsdockerpythonunittest&#xff0c; 测试问题&#xff1a;本人在写关于SAP4Me网站的自动化脚本时遇到一个问题 本地怎么都跑的通 但是一上Jenkins会出现各种各样的问题 因为在Jenkins里面脚本是放在docker环境里面跑的 所以环境的差异…

TaskBuilder前端组件简介

3.3.3.1前端组件的分类 前端页面是由众多组件层层嵌套构成的&#xff0c;这些组件是任讯信息自主研发的一套前端UI组件&#xff0c;称为tfp组件&#xff0c;这些组件根据其功能和特点又分为几大类&#xff0c;它们的继承关系如下图所示&#xff1a; 从图中可知&#xff0c;所…

linux下shell中使用上下键翻出历史命名时出现^[[A^[[A^[[A^[[B^[[B的问题解决

前言 今天在使用linux的时候&#xff0c;使用上下键想翻出历史命令时&#xff0c;却出现[[A[[A[[A[[B^[[B这种东东&#xff0c;而tab键补全命令的功能也无法使用。最终发现是由于当前用户使用的shell是/bin/sh的原因。 解决方法 运行以下命令&#xff0c;将默认 shell 设置为…