定时器中断应用 HC-SR04超声波测距模块、定时器输出PWM应用 控制SG90舵机

目录

  • [1. 定时器中断应用](#1. 定时器中断应用)
    • [1.1 内部时钟选择](#1.1 内部时钟选择)
    • [1.2 计数器模式](#1.2 计数器模式)
    • [1.3 HC-SR04超声波测距模块](#1.3 HC-SR04超声波测距模块)
      • [1.3.1 工作原理](#1.3.1 工作原理)
      • [1.3.2 硬件接线](#1.3.2 硬件接线)
      • [1.3.3 控制超声波测距步骤](#1.3.3 控制超声波测距步骤)
  • [2. 定时器输出PWM应用](#2. 定时器输出PWM应用)
    • [2.1 通用PWM原理](#2.1 通用PWM原理)
    • [2.2 输出比较通道](#2.2 输出比较通道)
    • [2.3 PWM模式](#2.3 PWM模式)
    • [2.4 定时器PWM库函数配置](#2.4 定时器PWM库函数配置)
    • [2.5 SG90舵机](#2.5 SG90舵机)
      • [2.5.1 SG90工作原理](#2.5.1 SG90工作原理)
      • [2.5.2 SG90引脚](#2.5.2 SG90引脚)
      • [2.5.3 控制方法](#2.5.3 控制方法)
    • [2.6 控制舵机配置步骤](#2.6 控制舵机配置步骤)

1. 定时器中断应用

1.1 内部时钟选择

除非APB1的分频系数是1,否则通用定时器的时钟等于APB1时钟的2倍。

默认调用SystemInit函数情况下:

SYSCLK=72M、AHB时钟=72M、APB1时钟=36M

所以APB1的分频系数=AHB/APB1时钟=2,通用定时器时钟CK_INT=2*36M=72M

1.2 计数器模式

  • 向下计数模式 (时钟分频因子=1)
  • 向上计数
  • 中央对齐模式

1.3 HC-SR04超声波测距模块

1.3.1 工作原理

  • 采用IO触发测距 ,给至少10μs的高电平信号
  • 模块自动发送8个40kHz的方波自动检测是否有信号返回
  • 有信号返回 ,通过IO输出一高电平高电平持续时间就是超声波从发射到返回的时间 /声波从发射到返回的时间
  • HC-SR04超声波测距模块提供2cm~400cm的测距功能,精度达3mm。

1.3.2 硬件接线

四根杜邦线,VCC连接单片机3.3-5V(推荐5V供电),GND接到板子的GND,Trig为外部触发信号输入,输入一个10μs的高电平即可触发模块测距,Echo即为回响信号输出,测距结束时此管引脚输出一个低电平,电平宽度反映超声波往返时间之和。

1.3.3 控制超声波测距步骤

  • 初始化超声波时钟定时器时钟
  • 初始化超声波引脚定时器中断配置
  • 编写定时器中断函数测距

    高电平的时间 = 中断次数 + 不足一个中断的次数
c 复制代码
// tim.h
#ifndef TIM_H
#define	TIM_H
void Base_TIM_Init(void);
void HC04_Init(void);
float Get_Length(void);
#endif
c 复制代码
// tim.c
#include "stm32f10x.h"
#include "tim.h"

uint16_t mscount = 0;

void delay_us(uint32_t us){
	us *= 8;
	while(us--);
}

void delay_ms(uint32_t ms){
	while(ms--){
		delay_us(1000);
	}
}

// 定时器函数
void Base_TIM_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeInitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;

	// 优先级分组配置
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

	TIM_TimeInitStruct.TIM_Prescaler = 72-1;	// 分频因子 不能超过65535
	TIM_TimeInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数模式
	TIM_TimeInitStruct.TIM_Period = 1000-1;	// 自动重装载值 不能超过65535
	TIM_TimeInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;	// 外部输入时钟分频因子
	TIM_TimeInitStruct.TIM_RepetitionCounter = 0;	// 重复计数值 高级定时器专用
	TIM_TimeBaseInit(TIM2, &TIM_TimeInitStruct);
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);	// 配置定时器的中断使能
	TIM_Cmd(TIM2, DISABLE);	// 定时器先关着 用的时候再打开

	
	NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
}

// HC-SR04 超声波初始化函数
void HC04_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruction;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// Trig引脚 B11 输出
	GPIO_InitStruction.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStruction.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStruction.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &GPIO_InitStruction);
	
	// Echo引脚 B10 输入
	GPIO_InitStruction.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruction.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &GPIO_InitStruction);
}

// 开启定时器
void Open_Tim(void)
{
	TIM_SetCounter(TIM2, 0);	// 计数器 CNT 设置为0
	mscount = 0;	// 中断次数为 0
	TIM_Cmd(TIM2, ENABLE);
}

// 关闭定时器
void Close_Tim(void)
{
	TIM_Cmd(TIM2, DISABLE);
}

// 获取定时器中断的次数, 定时器中断函数
void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
	{
		mscount++;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

// 获取高电平的总时间
int Get_Echo_Time(void)
{
	uint16_t t=0;
	
	t = mscount * 1000;
	t += TIM_GetCounter(TIM2);	// 获取 CNT 计数器的值
	TIM_SetCounter(TIM2, 0);	// 计数器 CNT 设置为 0
	delay_ms(50);
	
	return t;
}

float Get_Length(void)
{
	uint16_t t=0;
	float length=0;
	int i=0;
	float sum=0;
	
	while(i != 5)
	{
		// 1.HC04 工作
		GPIO_SetBits(GPIOB, GPIO_Pin_11);
		delay_us(20);
		GPIO_ResetBits(GPIOB, GPIO_Pin_11);
		
		// 2.定时器计算高电平时间
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0);	// 等待 Echo 引脚低电平到高电平 跳变
		Open_Tim();
		i++;
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 1);	// 等待 Echo 引脚高电平到低电平
		Close_Tim();
		t = Get_Echo_Time();
	
		// 3.时间换算成距离
		// 声速 ≈ 340 m/s → 往返 1cm 需要 ≈ 58.8 μs
		// 所以 距离(cm) = 时间(μs) / 58 是行业惯例
		length = ((float)t/58.0);
		sum += length;
	}
	length = sum/5.0;
	return length;
}
c 复制代码
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "LED.h"
#include "USART.h"
#include "tim.h"
#include "stdio.h"

void delay(uint16_t time)
{
	uint16_t i=0;
	while(time--)
	{
		i = 12000;
		while(i--);
	}
}

int  main()
{
	float length = 0;
	
	LED_Init();
	Base_TIM_Init();
	My_Usart1_Init();
	HC04_Init();
	
	while(1)
		{
			length = Get_Length();
			printf("%lf\r\n", length);
		}
}

2. 定时器输出PWM应用

2.1 通用PWM原理

PWM,英文名Pulse width modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是通过调节占空比的变化来调节信号能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占整个信号周期的百分比,例如方波的占空比就是50%。PWM的功能有很多种,比如控制呼吸灯、控制直流电机或者舵机等驱动原件等等,是单片机的一个十分重要的功能。

即PWM模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。其框图如下图所示:

横坐标是时间变量 ,纵坐标是CNT计数值 ,CNT计数值随着时间的推进会不断经历从0到ARR清零复位再到ARR 的这一过程。这之中还有一个数值是CCRx比较值 ,通过比较值和输出配置 可以使它输出高低电平逻辑 ,这样就产生了PWM波形 。通过调节ARR的值可以调节PWM的周期调节CCRx的值大小可以调节PWM占空比

2.2 输出比较通道

2.3 PWM模式

PWM有PWM模式1模式2 两种模式 ,它们之间的区别用寄存器 TIMx_CCMR1 的 OC1M[2:0] 位 来分析:

PWM模式1和模式2的定义和区别,可以简单理解为:PWM模式1 的情况下,当前值小于比较值 为有效电平;PWM模式2 的情况下,当前值大于比较值为有效电平。

2.4 定时器PWM库函数配置

  • 输出库函数配置
c 复制代码
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
  • 结构体成员
c 复制代码
typedef struct
{
  uint16_t TIM_OCMode;			// PWM模式1 或者 模式2
  uint16_t TIM_OutputState;		// 输出使能 OR使能
  uint16_t TIM_OutputNState;	// 高级定时器控制互补通道N的状态
  uint16_t TIM_Pulse;			// 比较值,写CCRx
  uint16_t TIM_OCPolarity;		// 比较输出极性
  uint16_t TIM_OCNPolarity;		// 高级定时器控制互补输出通道N的极性
  uint16_t TIM_OCIdleState;		// 空闲状态下的电平
  uint16_t TIM_OCNIdleState;	// 高级定时器控制互补输出通道空闲状态下的电平
} TIM_OCInitTypeDef;
  • 设置比较值函数
c 复制代码
void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);
  • 使能输出比较预装载
c 复制代码
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
  • 使能自动重装载的预装载寄存器允许位
c 复制代码
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

2.5 SG90舵机

2.5.1 SG90工作原理

舵机的工作原理比较简单。舵机内部有一个基准电压,单片机产生的PWM信号通过信号线进入舵机,与舵机内部的基准电压作比较,获得电压差输出。电压差的正负输出到电机驱动芯片上,从而决定正反转。开始旋转的时候,舵机内部通过级联减速齿轮带动电位器旋转,使得电压差为零,电机停止转动。

2.5.2 SG90引脚

sg90有三个引脚,分别是 红线(VCC)棕线(GND)橙线(信号线) 。通常使用5V供电,信号线接单片机引脚,用来接收单片机发送的PWM。

2.5.3 控制方法

控制sg90舵机旋转也比较简单,只需要给它输出PWM波修改占空比就可以调整角度 。sg90的控制一般需要一个周期为20ms左右的时基脉冲 ,脉冲的高电平部分一般在0.5ms~2.5ms 。高电平持续时间与旋转角度的对应关系如下
T = 20ms = (ARR+1)*(PSC+1)/72 000 000
200 * 7200 / 72000 000 = 20ms = 0.02s = 1/50s

195 5
190 10
185 15
180 20
175 25
高电平持续时间设置为 比较值

高电平持续时间/ms 舵机角度/° 占空比
0.5 2.5%
1.0 45° 5%
1.5 90° 7.5%
2.0 135° 10%
2.5 180° 12.5%

2.6 控制舵机配置步骤

  • 使能定时器和相关IO时钟
  • 初始化IO为复用模式
  • 初始化定时器
  • 初始化输出比较参数
  • 使能预装载寄存器
  • 使能定时器
  • 改变比较值CCR x,达到不同的占空比效果
c 复制代码
// sg90.h
#ifndef SG90_H
#define SG90_H
#include "stm32f10x.h"
void SG90_Init(void);
void SG90_Angle(uint16_t angle);
#endif
c 复制代码
// sg90.c
#include "sg90.h"
#include "stm32f10x.h"

void SG90_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_InitStruct;
	TIM_OCInitTypeDef TIMOC_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	// 使能复用功能的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	// 使能TIM3定时器的时钟
	// 配置TIM3定时器的通道从默认引脚切换到手册中定义的"部分重映射引脚"上
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
	
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	TIM_InitStruct.TIM_Prescaler = 7200-1;
	TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数模式,从0开始,逐次递增
	TIM_InitStruct.TIM_Period = 200-1;
	TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM3, &TIM_InitStruct);
	
	TIMOC_InitStruct.TIM_OCMode = TIM_OCMode_PWM1;	// 比比较值低为有效电平
	TIMOC_InitStruct.TIM_OutputState = TIM_OutputState_Enable;	// 定时器输出使能
	TIMOC_InitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;	// 低电平有效
	TIM_OC2Init(TIM3, &TIMOC_InitStruct);
	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);	// 使能定时器通道2的输出比较预装载寄存器
	TIM_Cmd(TIM3, ENABLE);	// 启动定时器
}


void SG90_Angle(uint16_t angle)
{
	switch(angle)
	{
		case 180: TIM_SetCompare2(TIM3, 175); break;	// 设置比较值
		case 135: TIM_SetCompare2(TIM3, 180); break;
		case 90: TIM_SetCompare2(TIM3, 185); break;
		case 45: TIM_SetCompare2(TIM3, 190); break;
		case 0: TIM_SetCompare2(TIM3, 195); break;
	}
}
c 复制代码
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "sg90.h"

void delay(uint16_t time)
{
	uint16_t i=0;
	while(time--)
	{
		i = 12000;
		while(i--);
	}
}

int  main()
{
	SG90_Init();
	while(1)
		{
			SG90_Angle(135);
			delay(1000);
			SG90_Angle(0);
			delay(1000);
		}
}
相关推荐
神明不懂浪漫2 小时前
【第十三章】操作符详解,预处理指令详解
c语言·开发语言·经验分享·笔记
进击的横打2 小时前
【车载开发系列】C语言浮点数入门
c语言·车载系统
水饺编程3 小时前
Windows 编程基础:wsprintf 函数
c语言·c++·windows·visual studio
白太岁3 小时前
操作系统开发:(7) 优先级反转与继承、TLS 及核亲和性
c语言·单片机·系统架构
橘色的喵3 小时前
C++17 vs C 编译产物体积:工业嵌入式场景的实测与分析
c语言·c++·c++17
爱编码的小八嘎4 小时前
第1章 程序点滴-1.4 开放性思维(2)
c语言
嵌入式×边缘AI:打怪升级日志4 小时前
环境监测传感器从设备程序设计(ADC采集与输出控制)
单片机·嵌入式硬件·fpga开发
lpfasd1235 小时前
Zig 简介:C 的现代化继任者
c语言·开发语言
流云鹤5 小时前
2026牛客寒假算法基础集训营1(B C E G K L)
c语言·算法