茂名本土网站建设公司百分百营销软件官网
目录
前言
技术实现
原理图
接线图
代码实现
内容要点
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 headervoid 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_15GPIO_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模式1TIM_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,捕获/比较器,本工程中有封装的函数用于改变占空比,此处先设置为0TIM_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_Hvoid 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_Hvoid 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,// 00x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 10x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 20x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 30x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 40xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 50x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 60x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 70x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 80x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 90x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 100x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 110x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 120x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 130x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 140x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 150x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 160x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 170x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 180x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 190x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 200x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 210x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 220x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 230x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 240x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 250x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 260x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 270x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 280x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 290x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 300x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 310xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 320x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 330x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 340xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 350x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 360x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 370x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 380xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 390x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 400x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 410x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 420x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 430x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 440x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 450x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 460xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 470x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 480xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 490x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 500x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 510x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 520x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 530x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 540xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 550x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 560x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 570x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 580x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 590x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 600x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 610x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 620x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 630x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 640x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 650x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 660x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 670x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 680x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 690x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 700x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 710x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 720x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 730x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 740x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 750x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 760x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 770x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 780x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 790x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 800x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 810x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 820x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 830x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 840x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 850x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 860x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 870x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 880x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 890x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 900x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 910x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 920x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 930x00,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_15GPIO_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模式1TIM_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,捕获/比较器,本工程中有封装的函数用于改变占空比,此处先设置为0TIM_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不在同一个部分。