STM32基础教程——PWM驱动舵机

news2025/3/30 20:26:02

目录

前言

技术实现 

原理图

接线图

代码实现 

内容要点

PWM基本结构

开启外设时钟

配置GPIO端口

配置时基单元

 初始化输出比较单元

调整PWM占空比

输出比较通道重映射 

 舵机角度设置

 实验结果

 问题记录

前言

舵机(Servo)是一种位置(角度)伺服驱动器,由电机、减速齿轮组、控制电路和位置传感器(如电位器或编码器)组成,通过闭环控制精确调节输出轴的角度或速度。它接收脉宽调制(PWM)等信号,驱动电机旋转并经齿轮组减速增矩后输出目标角度,同时传感器实时反馈位置信息以校正误差,确保稳定定位。常见于航模、机器人、自动化设备等领域,根据需求可分为模拟舵机、数字舵机、金属齿轮舵机等,选型时需考虑扭矩、旋转范围(如180°或360°)、负载能力及环境适应性,其核心优势在于精准的角度控制与自锁能力,但需避免过载或侧向力导致的损坏。

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

 控制原理 

        控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。当然我们可以不用去了解它的具体工作原理,知道它的控制原理就够了。就象我们使用晶体管一样,知道可以拿它来做开关管或放大管就行了,至于管内的电子具体怎么流动是可以完全不用去考虑的。

技术实现 

原理图

接线图

代码实现 

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"						//延时函数
#include "OLED.h"
#include "PWM.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNum;										//按键值
float Angle;

int main(void)
{
	OLED_Init();									//OLED初始化
	Servo_Init();									//舵机初始化
	Key_Init();										//按键初始化
	OLED_ShowString(1,1,"Angle:");					//在OLED的第一行第一列显示字符串“Angle:”
	while(1)
	{
		KeyNum = Key_GetNum();						//读取按键值
		if(KeyNum == 1)								
		{
			Angle += 30;							//舵机角度递增
			if(Angle > 180)							
			{
				Angle = 0;							//重置舵机角度
			}
		}
		Servo_SetAngle(Angle);						//设置舵机的角度
		OLED_ShowNum(1,7,Angle,3);					//显示当前的角度
	}
}

 PWM.h

#ifndef __PWM_H__
#define __PWM_H__

#include "stm32f10x.h"                  // Device header


void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);

#endif

PWM.c 

#include "PWM.h"


/**
  * @brief	PWM初始化函数
  * @param  None
  * @retval None
  * @note   输出频率为1kHz,占空比为50%,分辨率为1%的PWM波形
**/
void PWM_Init(void)
{
	/*
		开启时钟
	*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);						//开启TIM2时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);					//开启GPIOA时钟
	/*
		配置端口
	*/
	GPIO_InitTypeDef GPIO_InitStruct;								 		//定义结构体
	
	GPIO_InitStruct.GPIO_Pin 	= GPIO_Pin_1;				 				//选择PA0    //重映射到GPIO_Pin_15
	GPIO_InitStruct.GPIO_Mode 	= GPIO_Mode_AF_PP;							//复用推挽输出,将引脚的控制权交给片上外设
	GPIO_InitStruct.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	/*
		选择TIM时钟
	*/
	TIM_InternalClockConfig(TIM2);
	/*
	
		配置时基单元
	*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;							//定义一个TIM_TimeBaseInitTypeDef类型的结构体用于初始化时基单元
	
	TIM_TimeBaseInitStruct.TIM_ClockDivision     = TIM_CKD_DIV1;			//设置时钟分频模式为不分
	TIM_TimeBaseInitStruct.TIM_CounterMode       = TIM_CounterMode_Up;		//计数方式设置为向上计数模式
	/*
		输出的PWM波形的频率为1kHz,分辨率为1%
		PWM频率:	Freq = CK_PSC / (PSC + 1) / (ARR + 1)
		PWM占空比:	Duty = CCR / (ARR + 1)
		PWM分辨率:	Reso = 1 / (ARR + 1)

	*/
	TIM_TimeBaseInitStruct.TIM_Period            = 20000-1;					//ARR,自动加载重装寄存器,要写入自动重装值
	TIM_TimeBaseInitStruct.TIM_Prescaler         = 72-1;					//PSC,预分频器的分频值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;						//重复计数,只有高级定时器才会使用,通用定时器用不到
	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);							//初始化时基单元
	
	/*
		初始化输出比较单元
	*/
	TIM_OCInitTypeDef TIM_OCInitStruct;										//定义一个TIM_OCInitTypeDef类型的结构体用以输出比较单元初始化
	TIM_OCStructInit(&TIM_OCInitStruct);									//给每个结构体成员赋初始值,防止因未使用到的结构体成员未初始化而导致程序出现错误
	TIM_OCInitStruct.TIM_OCMode 		= TIM_OCMode_PWM1;					//选择PWM模式1
	TIM_OCInitStruct.TIM_OCPolarity 	= TIM_OCPolarity_High;				//OC1有效电平为高电平
	TIM_OCInitStruct.TIM_OutputState 	= TIM_OutputState_Enable;			//输出比较使能
	/*
		输出PWM的波形占空比
		PWM占空比:	Duty = CCR / (ARR + 1)

	*/
	TIM_OCInitStruct.TIM_Pulse 			= 0;								//CCR,捕获/比较器,本工程中有封装的函数用于改变占空比,此处先设置为0
	TIM_OC2Init(TIM2,&TIM_OCInitStruct);
	
	TIM_Cmd(TIM2,ENABLE);													//启动TIM2
}

/**
  * @brief	指定CCR寄存器的值改变PWM Duty
  * @param  None
  * @retval None
  * @note   None
**/
void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2,Compare);
}

Servo.h 

#ifndef __SERVO_H__
#define __SERVO_H__

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Servo_Init(void);
void Servo_SetAngle(float Angle);


#endif

Servo.c 

#include "Servo.h"

/**
  * @brief	舵机初始化
  * @param  None
  * @retval None
  * @note   None
**/
void Servo_Init(void)
{
	PWM_Init();
}

/**
  * @brief	设置舵机角度
  * @param  Angle 舵机旋转的角度
  * @retval None                                                                                                                                                                                                                                                
  * @note   None
**/
void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2((Angle/180*2000)+500);
}
	

Key.h 

#ifndef __KEY_H__
#define __KEY_H__

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void Key_Init(void);
uint8_t Key_GetNum(void);

#endif

Key.c 

#include "Key.h"

/**
  * @brief	设置高速总线APB2外围时钟
  *			配置GPIO端口
  * @param  None
  * @retval None
  */
void Key_Init(void)
{
	/*
		高速总线APB2外围时钟设置
	*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*
		配置端口
	*/
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;							//选择上拉输入模式
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB,&GPIO_InitStruct);
}

/**
  * @brief	读取按键的值
  * @param  None
  * @retval 按键的值
  */
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;
	
	if(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1))
	{
		Delay_ms(20);
		while(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1));
		Delay_ms(20);
		KeyNum = 1;
	}
	
	if(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11))
	{
		Delay_ms(20);
		while(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11));
		Delay_ms(20);
		KeyNum = 2;
	}
	return KeyNum;
}

OLED.h 

#ifndef __OLED_H
#define __OLED_H

void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);

#endif

 OLED.c

#ifndef __OLED_H
#define __OLED_H

void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);

#endif

OLED_Font.h 

#ifndef __OLED_FONT_H
#define __OLED_FONT_H

/*OLED字模库,宽8像素,高16像素*/
const uint8_t OLED_F8x16[][16]=
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//  0
	
	0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
	
	0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
	
	0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
	0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
	
	0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
	0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
	
	0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
	0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
	
	0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
	0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
	
	0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
	
	0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
	0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
	
	0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
	0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
	
	0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
	0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
	
	0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
	0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
	
	0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
	0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
	
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
	
	0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
	
	0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
	0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
	
	0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
	0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
	
	0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
	0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
	
	0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
	0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
	
	0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
	0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
	
	0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
	
	0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
	0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
	
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
	
	0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
	0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
	
	0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,
	0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
	
	0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
	0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
	
	0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
	0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
	
	0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
	0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
	
	0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
	0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
	
	0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
	0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
	
	0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
	0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
	
	0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
	
	0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
	0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
	
	0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
	
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
	
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
	
	0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
	0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
	
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
	
	0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
	
	0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
	0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
	
	0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
	0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
	
	0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
	
	0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
	0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
	
	0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
	
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
	
	0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
	0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
	
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
	
	0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
	
	0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
	0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
	
	0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
	
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
	
	0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
	0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
	
	0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
	0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
	
	0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
	0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
	
	0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
	
	0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
	
	0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
	0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
	
	0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
	
	0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
	0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
	
	0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
	
	0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
	
	0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
	0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
	
	0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
	
	0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
	
	0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
	
	0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
	
	0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
	
	0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
	0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
	
	0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
	
	0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
	
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
	
	0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
	
	0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
	0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
	
	0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
	0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
	
	0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
	
	0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
	0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
	
	0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
	
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
	
	0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
	0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
	
	0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
	
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
	
	0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
	
	0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
	0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
	
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
	
	0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
	0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
	
	0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};


#endif

内容要点

PWM基本结构

开启外设时钟

/*
		开启时钟
	*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);						//开启TIM2时钟

本实验使用TIM2输出调制PWM波,TIM2属于APB1外设,调用RCC_APB1PeriphClockCmd()函数来开启APB1外设时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);					//开启GPIOA时钟

根据STM32引脚的定义,PA0的默认复用功能为TIM2的输出比较通道之一TIM2_CH1,GPIOA隶属于APB2外设,调用RCC_APB2PeriphClockCmd()函数开启APB2外设时钟。

配置GPIO端口

/*
		配置端口
	*/
	GPIO_InitTypeDef GPIO_InitStruct;								 		//定义结构体
	
	GPIO_InitStruct.GPIO_Pin 	= GPIO_Pin_1;				 				//选择PA0    //重映射到GPIO_Pin_15
	GPIO_InitStruct.GPIO_Mode 	= GPIO_Mode_AF_PP;							//复用推挽输出,将引脚的控制权交给片上外设
	GPIO_InitStruct.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);

GPIO初始化中,初始化结构体GPIO_InitStruct结构体成员,将GPIO引脚设置为PA0 Pin,将GPIO模式设置为复用推挽输出,因为GPIO的主功能为PA0 IO口,这里使用其默认复用功能,故应将GPIO模式设置为复用推挽,使用普通的推挽输出无法输出PWM波形。

配置时基单元

/*
		选择TIM时钟
	*/
	TIM_InternalClockConfig(TIM2);
	

选择TIM时钟为内部时钟

/*
	
		配置时基单元
	*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;							//定义一个TIM_TimeBaseInitTypeDef类型的结构体用于初始化时基单元
	
	TIM_TimeBaseInitStruct.TIM_ClockDivision     = TIM_CKD_DIV1;			//设置时钟分频模式为不分
	TIM_TimeBaseInitStruct.TIM_CounterMode       = TIM_CounterMode_Up;		//计数方式设置为向上计数模式
	/*
		输出的PWM波形的频率为1kHz,分辨率为1%
		PWM频率:	Freq = CK_PSC / (PSC + 1) / (ARR + 1)
		PWM占空比:	Duty = CCR / (ARR + 1)
		PWM分辨率:	Reso = 1 / (ARR + 1)

	*/
	TIM_TimeBaseInitStruct.TIM_Period            = 20000-1;					//ARR,自动加载重装寄存器,要写入自动重装值
	TIM_TimeBaseInitStruct.TIM_Prescaler         = 72-1;					//PSC,预分频器的分频值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;						//重复计数,只有高级定时器才会使用,通用定时器用不到
	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);							//初始化时基单元

输出PWM,舵机周期20ms,即2000us,ARR(自动重装寄存器)和PSC(预分频器)的值分别设置为20000和72。

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

 初始化输出比较单元

/*
		初始化输出比较单元
	*/
	TIM_OCInitTypeDef TIM_OCInitStruct;										//定义一个TIM_OCInitTypeDef类型的结构体用以输出比较单元初始化
	TIM_OCStructInit(&TIM_OCInitStruct);									//给每个结构体成员赋初始值,防止因未使用到的结构体成员未初始化而导致程序出现错误
	TIM_OCInitStruct.TIM_OCMode 		= TIM_OCMode_PWM1;					//选择PWM模式1
	TIM_OCInitStruct.TIM_OCPolarity 	= TIM_OCPolarity_High;				//OC1有效电平为高电平
	TIM_OCInitStruct.TIM_OutputState 	= TIM_OutputState_Enable;			//输出比较使能
	/*
		输出PWM的波形占空比
		PWM占空比:	Duty = CCR / (ARR + 1)

	*/
	TIM_OCInitStruct.TIM_Pulse 			= 0;								//CCR,捕获/比较器,本工程中有封装的函数用于改变占空比,此处先设置为0
	TIM_OC1Init(TIM2,&TIM_OCInitStruct);

初始化输出比较单元时,在初始化TIM输出比较结构体之前调用TIM_OCStructInit()函数给结构体的每个成员初始化赋默认值,防止在实验中未使用到输出比较单元中的某些功能且未初始化这些功能对应的结构体成员时,发生意外错误。

将输出比较单元的输出模式设置为PWM模式1。

设置输出极性为高电平有效,即PWM模式1下计数器的值小于CCR时为高电平,反之为低电平。

此处是通过设置捕获比较使能寄存器TIMx_CCER位1CC1P设置输入/捕获1输出极性

此处将CCR寄存器的值设置为0,实验中使用封装的函数 void PWM_SetComparel(uint16_t Compare)来调用TIM_SetCompare1()函数来改变CCR寄存器的值实现占空比的改变。

/**
  * @brief	指定CCR寄存器的值改变PWM Duty
  * @param  None
  * @retval None
  * @note   None
**/
void PWM_SetComparel(uint16_t Compare)
{
	TIM_SetCompare1(TIM2,Compare);
}

调整PWM占空比

在初始化输出比较单元中,结构体TIM_OCInitStruct为TIM_OCInitTypeDef类型,用于初始化输出比较单元。初始化结构体成员时,将成员TIM_Pulse的值初始化为0。单独封装一个函数用于改变CCR寄存器的值。

/**
  * @brief	指定CCR寄存器的值改变PWM Duty
  * @param  None
  * @retval None
  * @note   None
**/
void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2,Compare);
}

在程序中将修改CCR寄存器的操作封装成函数void PWM_SetCompare2(uint16_t Compare),在函数中调用TIM_SetCompare2()函数修改CCR寄存器的值,用于动态调整占空比。 

输出比较通道重映射 

//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);						//开启AFIO时钟
//	
//	/*
//		引脚重映射
//	*/
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);					//将TIM2的CH1从PA0重映射到PA15
//	/*
//		解除引脚复用
//	*/
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);					//PA15引脚作为JTD1功能使用,此处解除AFIO的JTAG复用

在开启TIM2和GPIOA时钟后,开启AFIO时钟,调用GPIO_PinRemapConfig()将TIM2_CH1从PA0引脚重映射到PA15引脚。由于PA15默认为JTAG调试功能,调用GPIO_PinRemapConfig()函数解除引脚复用,PA15引脚作为JTD1功能使用,解除AFIO的JTAG复用。(如果不使用重映射功能,则忽略这部分)

 舵机角度设置

 输入信号的脉冲宽度和舵机输出轴的角度的关系如下:

将其转化为如下对应关系:

由于舵机的转轴角度是由PWM占空比来决定的,在初始化时基单元时将ARR寄存器的值设置为了20000-1,Duty = CCR / (ARR+1), 将脉冲宽度和周期全部转化为以us为单位,方便计算。这样只要传入对应的值就可以控制PWM的占空比来控制舵机的转轴角度。

在控制舵机转轴角度时,传入对应角度,通过计算将对应的CCR寄存器的值写入。(Angle的值为 上图500~2500)

(Angle/180*2000)+500

 将计算结果传给函数TIM_SetCompare2()修改CCR寄存器的值。

 实验结果

按下按键改变舵机旋转轴的角度,当旋转轴角度到达180°时,按下按键舵机旋转轴角度归为0°

 问题记录

1.CCR寄存器的值是在初始化输出比较单元中,由TIM_OCInitTypeDef 类型的结构体TIM_OCInitStruct的成员TIM_Pulse来指定CCR寄存器的值,而不是在配置时基单元时初始化的TIM_TimeBaseInitTypeDef类型的结构体TIM_TimeBaseInitStruct成员TIM_RepetitionCounter,后者是用于指定高级定时器的重复计数功能。在编程时注意区分CCR和ARR and PSC不属于同一个部分

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

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

相关文章

odata 搜索帮助

参考如下链接&#xff1a; FIORI ELement list report 细节开发&#xff0c;设置过滤器&#xff0c;搜索帮助object page跳转等_fiori element label 变量-CSDN博客 注&#xff1a;odata搜索帮助可以直接将值带出来&#xff0c;而不需要进行任何的重定义 搜索帮助metedata配置…

Docker基本命令VS Code远程连接

Docker基本命令 创建自己的docker容器&#xff1a;docker run --net host --name Container_name --gpus all --shm-size 1t -it -v Your_Path:Your_Dir mllm:mac /bin/bashdocker run&#xff1a;用于创建并启动一个新容器-name&#xff1a;为当前新建的容器命名-gpus&#x…

大疆上云api直播功能如何实现

概述 流媒体服务器作为直播画面的中转站,它接收推流端的相机画面,同时拉流端找它获取相机的画面。整个流程如下: 在流媒体服务器上创建流媒体应用(app),一个流媒体服务器上面可以创建多个流媒体应用约定推拉流的地址。假设流媒体服务器工作在1935端口上面,假设创建的流…

理解文字识别:一文读懂OCR商业化产品的算法逻辑

文字识别是一项“历久弥新”的技术。早在上世纪初&#xff0c;工程师们就开始尝试使用当时有限的硬件设备扫描并识别微缩胶片、纸张上的字符。随着时代和技术的发展&#xff0c;人们在日常生活中使用的电子设备不断更新换代&#xff0c;文字识别的需求成为一项必备的技术基础&a…

使用 Cursor、MCP 和 Figma 实现工程化项目自动化,提升高达 200% 效率

直接上手不多说其他的&#xff01; 一、准备动作 1、Cursor下载安卓 1.1访问官方网站 打开您的网络浏览器&#xff0c;访问 Cursor 的官方网站&#xff1a;https://www.cursor.com/cn 1.2开始下载: 点击"Download for free" 根据您的浏览器设置&#xff0c;会自…

Arduino、ESP32驱动GUVA-S12SD UV紫外线传感器(光照传感器篇)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 UV紫外线传感器是一个测试紫外线总量的最佳传感器,它不需要使用波长滤波器,只对紫外线敏感。 Arduino UV紫外线传感器,直接输出对应紫外线指数(UV INDEX)的线性电压,输出电压范围大约0~1100mV(对应UV INDEX值…

PTA 1097-矩阵行平移

给定一个&#x1d45b;&#x1d45b;nn的整数矩阵。对任一给定的正整数&#x1d458;<&#x1d45b;k<n&#xff0c;我们将矩阵的奇数行的元素整体向右依次平移1、……、&#x1d458;、1、……、&#x1d458;、……1、……、k、1、……、k、……个位置&#xff0c;平移…

Notepad++ 替换 换行符 为 逗号

多行转一行&#xff0c;逗号分隔 SPO2025032575773 SPO2025032575772 SPO2025032575771 SPO2025032575771 SPO2025032575770为了方便快速替换&#xff0c;我们需要先知道这样类型的数据都存在哪些换行符。 点击【视图】-【显示符号】-【显示行尾符】 对于显示的行尾换行符【C…

使用飞书API自动化更新共享表格数据

飞书API开发之自动更新共享表格 天马行空需求需求拆解1、网站数据爬取2、飞书API调用2.1 开发流程2.2 创建应用2.3 配置应用2.4 发布应用2.5 修改表格权限2.6 获取tenant_access_token2.7 调用API插入数据 总结 天马行空 之前一直都是更新的爬虫逆向内容&#xff0c;工作中基本…

使用vscode搭建pywebview集成vue项目示例

文章目录 前言环境准备项目源码下载一、项目说明1 目录结构2 前端项目3 后端项目获取python安装包(选择对应版本及系统) 三、调试与生成可执行文件1 本地调试2 打包应用 四、核心代码说明1、package.json2、vite.config.ts设置3、main.py后端入口文件说明 参考文档 前言 本节我…

蓝桥杯嵌入式十六届模拟三

由硬件框图可以知道我们要配置LED 和按键 一.LED 先配置LED的八个引脚为GPIO_OutPut,锁存器PD2也是,然后都设置为起始高电平,生成代码时还要去解决引脚冲突问题 二.按键 按键配置,由原理图按键所对引脚要GPIO_Input 生成代码,在文件夹中添加code文件夹,code中添加fun.…

onedav一为导航批量自动化导入网址(完整教程)

OneNav作为一个功能强大的导航工具,支持后台管理、加密链接、浏览器书签批量导入等功能,能够帮助用户轻松打造专属的导航页面。今天,我将为大家详细介绍如何实现OneNav导航站的批量自动化导入网址。 1、建立要批量导入的表格 格局需要创建表格,表格的要求是一定要有需要,…

Linux之编辑器vim命令

vi/vim命令&#xff1a; 终端下编辑文件的首选工具&#xff0c;号称编辑器之神 基本上分为三种模式&#xff0c;分别是 命令模式&#xff08;command mode&#xff09;>输入vi的命令和快捷键&#xff0c;默认打开文件的时候的模式插入模式&#xff08;insert mode&#x…

备赛蓝桥杯之第十六届模拟赛2期职业院校组第四题:地址识别

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

多模态自动驾驶混合渲染HRMAD:将NeRF和3DGS进行感知验证和端到端AD测试

基于3DGS和NeRF的三维重建技术在过去的一年中取得了快速的进步&#xff0c;动态模型也变得越来越普遍&#xff0c;然而这些模型仅限于处理原始轨迹域内的对象。 HRMAD作为一种混合方案&#xff0c;将传统的基于网格的动态三维神经重建和物理渲染优势结合&#xff0c;支持在任意…

mac m3 pro 部署 stable diffusion webui

什么是Stable Diffusion WebUI &#xff1f; Stable Diffusion WebUI 是一个基于Stable Diffusion模型开发的图形用户界面&#xff08;GUI&#xff09;工具。通过这个工具&#xff0c;我们可以很方便的基于提示词&#xff0c;描述一段文本来指导模型生成相应的图像。相比较通过…

多层感知机实现

激活函数 非线性 ReLU函数 修正线性单元 rectified linear unit relu(x)max(0,x) relu的导数&#xff1a; sigmoid函数 s i g m o i d ( x ) 1 1 e − x sigmoid(x)\frac{1}{1e^{-x}} sigmoid(x)1e−x1​ 是一个早期的激活函数 缺点是&#xff1a; 幂运算相对耗时&…

Linux笔记---动静态库(使用篇)

目录 1. 库的概念 2. 静态库&#xff08;Static Libraries&#xff09; 2.1 静态库的制作 2.2 静态库的使用 2.2.1 显式指定库文件及头文件路径 2.2.2 将库文件安装到系统目录 2.2.3 将头文件安装到系统目录 3. 动态库 3.1 动态库的制作 3.2 动态库的使用 3.2.1 显式…

手机销售终端MPR+LTC项目项目总体方案P183(183页PPT)(文末有下载方式)

资料解读&#xff1a;手机销售终端 MPRLTC 项目项目总体方案 详细资料请看本解读文章的最后内容。在当今竞争激烈的市场环境下&#xff0c;企业的销售模式和流程对于其发展起着至关重要的作用。华为终端正处于销售模式转型的关键时期&#xff0c;波士顿 - 华为销售终端 MPRLTC …

【Python LeetCode Patterns】刷力扣,15 个学习模式总结

1. 前缀和&#xff08;Prefix Sum&#xff09;—— 查询子数组中元素和303. 区域和检索 - 数组不可变304. 二维区域和检索 - 矩阵不可变 2. 双指针&#xff08;Two Pointers&#xff09;—— 移向彼此或远离彼此3. 滑动窗口&#xff08;Sliding Window&#xff09;—— 找到满足…