STM32——使用TIM输出比较产生PWM波形控制舵机转角

news2024/10/6 2:30:33

一、输出比较简介:

只有高级定时器和通用寄存器才有输入捕获/输出比较电路,他们有四个CCR(捕获/比较寄存器),共用一个CNT(计数器),而输出比较功能是用来输出PWM波形的。
在这里插入图片描述
在这里插入图片描述
红圈部分就是输出比较电路,其中CCR(捕获/比较寄存器)是输入捕获和输出比较共用的,输入捕获和输出比较不能同时进行,当我们进行输出比较时,它就是比较寄存器,我们可以给这个寄存器设定一个值,然后CNT计数器就会不停和这个值进行比较,根据比较结果会输出不同的电平信号(通过输出比较控制器控制,下文讲解),由此可以产生PWM信号,如下图。
在这里插入图片描述

二、PWM简介:

PWM的本质就是一种方法(PWM波形是一种数字信号),通过输出一段变化的波形,这个信号是一个脉冲信号,因为它只有高电平(这是逻辑电平由控制器决定,32是3.3v而51是5v)和低电平(0),而高电平就是脉冲信号,其所占整个周期的比例就是脉冲宽度,通过调整脉冲宽度就能够得到不同的模拟信号,例如右图得到一个近似的正弦信号。而任何信号都可以看作是由一系列的正弦信号合成的。
在这里插入图片描述
PWM(Pulse Width Modulation) 是一种模拟信号的数字化处理方式,通过微处理器的数字输出来对模拟电路进行控制。它通过对一系列脉冲的宽度进行调制,来等效地获得所需要的波形(含形状和幅值),从而达到调整电压和频率的目的。PWM广泛应用于从测量、通信到功率控制与变换的许多领域中,特别在电机控制中表现出色。

  • 频率
    定义:PWM频率是指1秒钟内信号从高电平到低电平再回到高电平的次数,即一秒钟内PWM的周期数。频率越快,其模拟的信号越平稳。

  • 占空比
    定义:占空比是一个脉冲周期内,高电平的时间与整个周期时间的比例。占空比等效为PWM波形等效出来的模拟信号的电压的大小,占空比越大,模拟信号的越趋近于高电平,反之就是趋近于低电平,等效关系一般是线性的。例如高电平是5v,占空比是50%,那模拟信号的电压就近似为2.5v。
    单位:%(0%-100%)

  • 分辨率
    就是例如,占空比以1% 2% 3% 4%…99% 100%这样的情况跳变,那么占空比的变化步距就是1%,因为上述占空比每一次变化就是加1%,这种变化步距越小,说明变化的越细腻。

总结:

  • 1.PWM是一种方法,采用这个方法输出PWM波形是为了使数字输出端口(只能输出高电平和低电平两种,用于控制电机就是只能让它转或者停止)输出模拟信号,这样就能够实现电机调速、控制舵机的转动角度等等。
  • 2.输出比较电路可以说是实现PWM的工具:

(1)如果CRR捕获/比较寄存器的值(红线),ARR自动重装器的值(下图黄线),蓝线就是计数器值的变化,那么黄线和红线之间的差距越小,低电平所占时间就会越短,占空比就越大,通过跳帧占空比,如果是电机,占空比越大转速越快。

(2)占空比可以看作CRR的值比ARR的值。注意这里到30就已经是低电平了,所以高电平的范围是(0~29)30个数。

(3)我们如果让CCR的值每次操作加1,那么占空比每次就加1%,分辨率就是1%。
在这里插入图片描述

三、通用定时器的输出比较模块如何输出PWM波形:

在这里插入图片描述

上图对应的就是下图电路,红色部分输出比较结果OC1REF(reference参考信号),蓝色部分根据比较结果输出比较信号OC1,最后通过黄色部分TIMx_CH1通道输出到GPIO引脚上。
在这里插入图片描述
在这里插入图片描述

1.CNT和CRR两个寄存器的值进行比较之后,其比较结果进入输出模式控制器,然后根据结果输出模式控制器会输出相应的电平OC1_ref

2.信号OC1_ref分别可以进入两条支路,一路进入主模式控制器就能够作为触发输出TRGO输出给其他定时器或者DA/DC转换

3.另一路就进入极性选择器TIMx_CCER,极性选择器置0则信号走上一路不做变换直接输出到输出使能电路,如果置1则让它走下支路可以使信号OC1_ref经过一个非门电路将其反转,高变低,低变高

4.然后再控制输出使能让其通过OC1引脚输出到GPIO引脚上,至于是哪个GPIO可以看引脚定义图
在这里插入图片描述

(1)输出比较控制器执行逻辑:

在这里插入图片描述
通过配置下图红圈的寄存器可以选择不同的模式:
在这里插入图片描述

四、H桥电路:

这是我在抖音搜索的,而输出比较电路一般就接一个这样的电路,这个能够实现电机的正转和反转,中级的圆圈M就代表电机。
在这里插入图片描述

1. 当Q1和Q4导通,当Q2和Q3截止,那么电流从电机的正极流入负极流出,电机正转
在这里插入图片描述2. 当Q2和Q3导通,当Q1和Q4截止,那么电流从电机的负极进入正极流出,电机反转
在这里插入图片描述

五、高级定时器的输出比较电路:

高级定时器的输出比较电路比通用定时器的输出比较电路多了一个死区生成电路,之后会讲解这个电路的作用。
首先,这个电路一般会接一个H桥电路

在这里插入图片描述

(1)死区生成电路的作用:

  • 由于如果接H桥电路,那么对于H桥电路的半桥就是左半边或者由半边,一个MOS管导通另一个MOS管就必须截止,如果同时导通或者截止就会出现问题,那么死区生成电路就是产生一段时间的死区,死区生成电路中的“死区”通常指的是在输入信号进入某个特定范围(即死区)时,电路的输出电压为零;当输入信号脱离这个范围时,电路的输出电压会随输入信号的变化而变化。这个特定范围就是死区。 所以为了防止在半桥电路中,上面MOS管还没完全关断下面的MOS管就已经导通从而出现的两个MOS管同时导通的情况(会产生功率损耗,引起器件发热)就设置了一个死区生成电路,它会在上管关断之后延迟一段时间菜导通下管,保证半桥电路中保持一个MOS管导通另一个MOS管截止的状态。

六、舵机和电机:

(1)舵机:

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

(2)电机:

在这里插入图片描述
注意:
逻辑电平是由控制器决定,例如32单片机为3.3v,89c51单片机为5v
在这里插入图片描述

七、引脚定义:

  • STM32F103C8T6芯片的引脚定义如下图,例如我们代码中要使用的TIM2_OC1就是被默认接在PA0口,但是如果我要用的两个输出通道都被定义在了同一个GPIO口上,就能够通过重映射的功能将其中一个换到另一个引脚上,但不是所有的输出通道都能更换,只有具有重定义功能也就是下图中最右边一列,例如红圈中的TIM2_CH3通道就可以输出到PA2引脚或者PB10引脚上,如果你要使用ADC12_IN2又要使用TIM2_CH3,那么就可以将TIM2_CH3重映射到PB10引脚。
    在这里插入图片描述

八、复用开漏/推挽输出:

  • 首先我们看一下普通的开漏/推挽输出,这个GPIO在这个模式下,其输出电平是根据输出数据寄存器的值决定的,也就是我们给这个输出数据寄存器写什么值,对应就会输出什么电平。
    在这里插入图片描述
  • 再来看复用开漏/推挽输出模式,这个模式下输出数据寄存器和GPIO的输出控制模块是断开的,输出控制模块与单片机的外设连接,在我们这个示例中就是与TIM2_CH1连接,这时GPIO输出的电平由TIM2_CH1通道传输过来的电平信号决定。
    在这里插入图片描述

九、引脚重映射:

(1)方法:

这里要用到AFIO,关于AFIO的库函数被包含在GPIO的文件中。

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
/**
  * @brief  Changes the mapping of the specified pin.
  * @param  GPIO_Remap: selects the pin to remap.
  *   This parameter can be one of the following values:
  *     @arg GPIO_Remap_SPI1             : SPI1 Alternate Function mapping
  *     @arg GPIO_Remap_I2C1             : I2C1 Alternate Function mapping
  *     @arg GPIO_Remap_USART1           : USART1 Alternate Function mapping
  *     @arg GPIO_Remap_USART2           : USART2 Alternate Function mapping
  *     @arg GPIO_PartialRemap_USART3    : USART3 Partial Alternate Function mapping
  *     @arg GPIO_FullRemap_USART3       : USART3 Full Alternate Function mapping
  *     @arg GPIO_PartialRemap_TIM1      : TIM1 Partial Alternate Function mapping
  *     @arg GPIO_FullRemap_TIM1         : TIM1 Full Alternate Function mapping
  *     @arg GPIO_PartialRemap1_TIM2     : TIM2 Partial1 Alternate Function mapping
  *     @arg GPIO_PartialRemap2_TIM2     : TIM2 Partial2 Alternate Function mapping
  *     @arg GPIO_FullRemap_TIM2         : TIM2 Full Alternate Function mapping
  *     @arg GPIO_PartialRemap_TIM3      : TIM3 Partial Alternate Function mapping
  *     @arg GPIO_FullRemap_TIM3         : TIM3 Full Alternate Function mapping
  *     @arg GPIO_Remap_TIM4             : TIM4 Alternate Function mapping
  *     @arg GPIO_Remap1_CAN1            : CAN1 Alternate Function mapping
  *     @arg GPIO_Remap2_CAN1            : CAN1 Alternate Function mapping
  *     @arg GPIO_Remap_PD01             : PD01 Alternate Function mapping
  *     @arg GPIO_Remap_TIM5CH4_LSI      : LSI connected to TIM5 Channel4 input capture for calibration
  *     @arg GPIO_Remap_ADC1_ETRGINJ     : ADC1 External Trigger Injected Conversion remapping
  *     @arg GPIO_Remap_ADC1_ETRGREG     : ADC1 External Trigger Regular Conversion remapping
  *     @arg GPIO_Remap_ADC2_ETRGINJ     : ADC2 External Trigger Injected Conversion remapping
  *     @arg GPIO_Remap_ADC2_ETRGREG     : ADC2 External Trigger Regular Conversion remapping
  *     @arg GPIO_Remap_ETH              : Ethernet remapping (only for Connectivity line devices)
  *     @arg GPIO_Remap_CAN2             : CAN2 remapping (only for Connectivity line devices)
  *     @arg GPIO_Remap_SWJ_NoJTRST      : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST
  *     @arg GPIO_Remap_SWJ_JTAGDisable  : JTAG-DP Disabled and SW-DP Enabled
  *     @arg GPIO_Remap_SWJ_Disable      : Full SWJ Disabled (JTAG-DP + SW-DP)
  *     @arg GPIO_Remap_SPI3             : SPI3/I2S3 Alternate Function mapping (only for Connectivity line devices)
  *                                        When the SPI3/I2S3 is remapped using this function, the SWJ is configured
  *                                        to Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST.   
  *     @arg GPIO_Remap_TIM2ITR1_PTP_SOF : Ethernet PTP output or USB OTG SOF (Start of Frame) connected
  *                                        to TIM2 Internal Trigger 1 for calibration (only for Connectivity line devices)
  *                                        If the GPIO_Remap_TIM2ITR1_PTP_SOF is enabled the TIM2 ITR1 is connected to 
  *                                        Ethernet PTP output. When Reset TIM2 ITR1 is connected to USB OTG SOF output.    
  *     @arg GPIO_Remap_PTP_PPS          : Ethernet MAC PPS_PTS output on PB05 (only for Connectivity line devices)
  *     @arg GPIO_Remap_TIM15            : TIM15 Alternate Function mapping (only for Value line devices)
  *     @arg GPIO_Remap_TIM16            : TIM16 Alternate Function mapping (only for Value line devices)
  *     @arg GPIO_Remap_TIM17            : TIM17 Alternate Function mapping (only for Value line devices)
  *     @arg GPIO_Remap_CEC              : CEC Alternate Function mapping (only for Value line devices)
  *     @arg GPIO_Remap_TIM1_DMA         : TIM1 DMA requests mapping (only for Value line devices)
  *     @arg GPIO_Remap_TIM9             : TIM9 Alternate Function mapping (only for XL-density devices)
  *     @arg GPIO_Remap_TIM10            : TIM10 Alternate Function mapping (only for XL-density devices)
  *     @arg GPIO_Remap_TIM11            : TIM11 Alternate Function mapping (only for XL-density devices)
  *     @arg GPIO_Remap_TIM13            : TIM13 Alternate Function mapping (only for High density Value line and XL-density devices)
  *     @arg GPIO_Remap_TIM14            : TIM14 Alternate Function mapping (only for High density Value line and XL-density devices)
  *     @arg GPIO_Remap_FSMC_NADV        : FSMC_NADV Alternate Function mapping (only for High density Value line and XL-density devices)
  *     @arg GPIO_Remap_TIM67_DAC_DMA    : TIM6/TIM7 and DAC DMA requests remapping (only for High density Value line devices)
  *     @arg GPIO_Remap_TIM12            : TIM12 Alternate Function mapping (only for High density Value line devices)
  *     @arg GPIO_Remap_MISC             : Miscellaneous Remap (DMA2 Channel5 Position and DAC Trigger remapping, 
  *                                        only for High density Value line devices)     
  * @param  NewState: new state of the port pin remapping.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */

(1)第一个参数是重映射的模式,这个可以参考手册
如果我们想将TIM2_CH1_ETR从PA0改成PA15就可以选择第一个部分重映像或者完全重映像

在这里插入图片描述
那么就对应:

GPIO_PartialRemap1_TIM2     //TIM2 Partial1 Alternate Function mapping
或者
GPIO_FullRemap_TIM2         // TIM2 Full Alternate Function mapping

但是注意,PA15的主功能是作为调试端口JTDI,也就是上电之后其默认为调试端口JTDI,还需要先关闭它调试端口的复用,才能够让它作为普通的GPIO口或复用定时器通道。
在这里插入图片描述

(2) 关闭引脚调试功能:

使用的还是GPIO_PinRemapConfig函数:
根据下面这几个参数能够实现

  *     @arg GPIO_Remap_SWJ_NoJTRST      : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST
  *     @arg GPIO_Remap_SWJ_JTAGDisable  : JTAG-DP Disabled and SW-DP Enabled
  *     @arg GPIO_Remap_SWJ_Disable      : Full SWJ Disabled (JTAG-DP + SW-DP)

这几个参数对应的情况可以查看手册:
在这里插入图片描述
因此我们选择:

GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

如果你把所有调试功能的引脚都关闭你就下载不了程序了。不会的不要这么干

十、参数计算:

推荐参数:
ARR = 20K + 1
PSC = 72 + 1
CCR = 500 ~ 2500 对应0° ~ 180°

	//ARR自动重装器的值
	TIM_TIM2_InitStructure.TIM_Period = 20000 - 1;//ARR
	//PSC预分频器的值
	TIM_TIM2_InitStructure.TIM_Prescaler = 72 - 1;//PSC

在这里插入图片描述

十一、采用PWM输出信号控制电机程序实现:

总代码:

主函数在第七步
示例中只让舵机转到0°位置,理解后可以通过TIM_SetCompare1函数结合按键或者其他外设更改CCR的值从而实现不同角度的转换。
在这里插入图片描述

#include "stm32f10x.h"                  // Device header

//初始化舵机
void Steering_EngineInit(void)
{
	//RCC打开TIM2、GPIO、AFIO的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	//引脚重映射
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
	//关闭PA15的调试功能,之后TIM2_CH1就由PA0->PA15
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	
		//初始化GPIOA15:
	GPIO_InitTypeDef GPIO_PA15_InitStructure;
	GPIO_PA15_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_PA15_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_PA15_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_PA15_InitStructure);
	//PB1:
	GPIO_InitTypeDef GPIO_PB1_InitStructure;
	GPIO_PB1_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_PB1_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_PB1_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_PB1_InitStructure);

    //选择时钟源
    TIM_InternalClockConfig(TIM2);
	//配置时基单元
	//初始化定时器2时基单元的结构体
	TIM_TimeBaseInitTypeDef TIM_TIM2_InitStructure;
	/*
	TIM_ClockDivision 是这个结构体中的一个字段,用于设置定时器的时钟分频。
	具体来说,它决定了定时器时钟(TIMxCLK)的频率与内部时钟(CK_INT)之间的关系。
	TIM_CKD_DIV1 表示不进行分频,即 CK_INT = TIMxCLK。
	换句话说,定时器的内部时钟频率与输入的定时器时钟频率相同。
	*/
	TIM_TIM2_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式:选择向上计数模式
	TIM_TIM2_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	//ARR自动重装器的值
	TIM_TIM2_InitStructure.TIM_Period = 20000 - 1;//ARR
	//PSC预分频器的值
	TIM_TIM2_InitStructure.TIM_Prescaler = 72 - 1;//PSC
	//重复计数器的值,高级计数器才有
	TIM_TIM2_InitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2,&TIM_TIM2_InitStructure);
	
	//配置输出比较单元CCR:
	TIM_OCInitTypeDef TIM_OCInitStructure;
	//给TIM2_CH1的初始化结构体先进行一次初始化把用不到的关于高级定时器的成员也初始化
	//然后再对要用到的成员变量进行更改
	TIM_OCStructInit(&TIM_OCInitStructure);
	//输出比较模式
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	//输出比较极性
	//高极性 = 极性不反转;低极性反之,这里选的高极性
	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;
	//输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	//CCR比较寄存器的值
	TIM_OCInitStructure.TIM_Pulse = 0;//CCR
	
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	
	
	
	//启动定时器
	TIM_Cmd(TIM2,ENABLE);
	
}
void PWM_SetCompare(uint16_t compare)
{
	TIM_SetCompare1(TIM2,compare);
}

实现步骤:

g'g'g'g

第一步:使用RCC开启外设的时钟

这里涉及GPIO、AFIO、TIM2三个外设。

//RCC打开TIM2、GPIO、AFIO的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

第二步:配置好时基单元还有时钟源选择

 //选择时钟源
    TIM_InternalClockConfig(TIM2);
	//配置时基单元
	//初始化定时器2时基单元的结构体
	TIM_TimeBaseInitTypeDef TIM_TIM2_InitStructure;
	/*
	TIM_ClockDivision 是这个结构体中的一个字段,用于设置定时器的时钟分频。
	具体来说,它决定了定时器时钟(TIMxCLK)的频率与内部时钟(CK_INT)之间的关系。
	TIM_CKD_DIV1 表示不进行分频,即 CK_INT = TIMxCLK。
	换句话说,定时器的内部时钟频率与输入的定时器时钟频率相同。
	*/
	TIM_TIM2_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式:选择向上计数模式
	TIM_TIM2_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	//ARR自动重装器的值
	TIM_TIM2_InitStructure.TIM_Period = 20000 - 1;//ARR
	//PSC预分频器的值
	TIM_TIM2_InitStructure.TIM_Prescaler = 72 - 1;//PSC
	//重复计数器的值,高级计数器才有
	TIM_TIM2_InitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2,&TIM_TIM2_InitStructure);

第三步:配置输出比较单元

注意:
这里提醒一下输出使能部分:TIM_OCInitStructure.TIM_OutputState

TIM_OCInitStructure.TIM_OutputNState
的区别只有一个N,但是加了N的是高级定时器的部分,所以这里错了舵机会不动。

//配置输出比较单元CCR:
	TIM_OCInitTypeDef TIM_OCInitStructure;
	//给TIM2_CH1的初始化结构体先进行一次初始化把用不到的关于高级定时器的成员也初始化
	//然后再对要用到的成员变量进行更改
	TIM_OCStructInit(&TIM_OCInitStructure);
	//输出比较模式
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	//输出比较极性
	//高极性 = 极性不反转;低极性反之,这里选的高极性
	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;
	//输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	//CCR比较寄存器的值
	TIM_OCInitStructure.TIM_Pulse = 0;//CCR
	
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);

第四步:配置GPIO

把PWM对应的GPIO口初始化为复用推挽输出的配置:
因为这里我们使用的是PA15,而TIM2_CH1M默认是接在PA0,我们需要对引脚进行重映射。

    //引脚重映射,选择部分重映射1
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
	//关闭PA15的调试功能,之后TIM2_CH1就由PA0->PA15
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	
		//初始化GPIOA15:
	GPIO_InitTypeDef GPIO_PA15_InitStructure;
	GPIO_PA15_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_PA15_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_PA15_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_PA15_InitStructure);

第五步:运行控制,启动计数器

//启动定时器
	TIM_Cmd(TIM2,ENABLE);

第六步:更改CCR值的函数TIM_SetCompare1

通过传给compare的值可以更改CCR的值

void PWM_SetCompare(uint16_t compare)
{
	TIM_SetCompare1(TIM2,compare);
}

第七步:在主函数中调用

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

int main()
{
	Steering_EngineInit();
	PWM_SetCompare(500); //对应的是0度
	while(1)
	{
		
 	}
}

十二、按键控制舵机调整角度:

主函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Steering_Engine.h"
#include "KEY.h"
#include "SEVO.h"

float Angle = 0;
int main()
{
	Key_Init();
	OLED_Init();
	SEVO_Init();
	OLED_ShowString(1,1,"Angle:");
	SE_SetAngle(Angle); 
	while(1)
	{
		if(Key_GetNum() == 1)
		{
			Angle += 10;
			if(Angle > 180)
			{
				Angle = 0;
			}
		}
		SE_SetAngle(Angle);
		OLED_ShowNum(1,7,Angle,3);
 	}
}

按键函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB,&GPIO_InitStructure);
}

uint8_t Key_GetNum(void)
{
	uint8_t Key_Num = 0;
	
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0);
		Delay_ms(20);
		Key_Num = 1;
		
	}
	
	return Key_Num;
}

角度设置函数:

这个函数是在原来的舵机函数之上的,只有一点改动

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

void SEVO_Init(void)
{
	Steering_EngineInit();
}

void SE_SetAngle(float Angle)
{
	//把角度分成180分与1~2000中的数对应再加上500,就将角度转换成了对应的CCR值
	PWM_SetCompare(Angle / 180 * 2000 + 500);
}

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

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

相关文章

【知识学习】Unity3D中Shader Graph的概念及使用方法示例

Unity3D中的Shader Graph是一个强大的可视化Shader编辑工具,它允许用户通过拖拽和连接节点的方式来创建Shader,而不是通过传统的编写代码的方式。Shader Graph使得Shader的创建过程更加直观和易于理解,特别是对于那些不熟悉Shader语言编程的美…

gitee配置ssh教程

生成公钥 执行命令: ssh-keygen -t rsa查看公钥 cat ~/.ssh/id_rsa.pub这个公钥就是要复制粘贴到Gitee中的ssh公钥。 配置Gitee SSH公钥 来到Gitee的ssh公钥中,配置

git通过命令方式push代码到远程

本地仓库和远程仓库关联操作 通过关联远程分支可以将本地的分支与远程仓库中的分支进行关联,从而实现本地分支与远程分支的同步和交互。 关联远程分支的步骤如下: 1. 首先,使用git remote -v命令查看当前仓库关联的远程仓库。 git remote …

C#语言编写的仅有8KB大小的简易贪吃蛇开源游戏

前言 今天大姚给大家分享一款由C#语言编写的仅有8KB大小的简易贪吃蛇开源游戏:SeeSharpSnake。 项目特点 该仓库中的项目文件和脚本可以用多种不同的配置构建相同的游戏,每个配置生成的输出大小也不同。 项目源码运行 F5 运行 SeeSharpSnake项目&…

Python 面试【★★★★】

欢迎莅临我的博客 💝💝💝,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

安卓短视频去水印v1.7 简洁好用

各大平台视频无水印提取,登录即永久会员! 无水印提取,图片无水印提取 视频旋转,倒放,转gif等功能 链接:https://pan.baidu.com/s/1buoJmAvSFBiRkBmHc7Nn5w?pwd2fu4 提取码:2fu4

考试如果出现汉诺塔问题怎么办?

对于这道题来说 就按照测试案例里的数字进行输入 测试案例用100 那这三只鸡的具体最多能有多少只鸡呢? 用总数除以这只鸡的单价>>>>>>>即为这只鸡最多有 >>>>>>>> n / 单价 修改后 >>>>> 不只适…

input子系统学习(一)

1、输入子系统框架 2、编写一个简单的设备驱动层代码 #include<linux/module.h> #include<linux/init.h> #include<linux/input.h> #include<linux/time.h>struct input_dev *my_input_dev;static void timer_function(struct timer_list *t); DEFINE…

.NET 一款用于入口打点的免杀WebShell

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

云计算【第一阶段(21)】Linux引导过程与服务控制

目录 一、linux操作系统引导过程 1.1、开机自检 1.2、MBR引导 1.3、GRUB菜单 1.4、加载 Linux 内核 1.5、init进程初始化 1.6、简述总结 1.7、初始化进程centos 6和7的区别 二、排除启动类故障 2.1、修复MBR扇区故障 2.1.1、 实验 2.2、修复grub引导故障 2.2.1、实…

Burpsuite靶场中信息泄露相关的实验通关

目录 第一关&#xff1a;错误消息中的信息披露 第二关&#xff1a;调试页面信息披露 第三关&#xff1a;通过备份文件披露源代码 第四关&#xff1a;通过信息披露绕过身份验证 第五关&#xff1a;版本控制历史中的信息披露 最近看大佬的文章&#xff0c;发现了很对自己没有…

Android 遥控器

遥控器源码 import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RadialGradient; import android.graphics.Region; import android.g…

为什么word生成的PDF内容显示不全?

在现代办公环境中&#xff0c;将文档从一个格式转换为另一个格式是一个常见的任务。然而&#xff0c;有时候我们可能会遇到意想不到的问题&#xff0c;比如使用Word转换成PDF时&#xff0c;生成的PDF文件只显示了整个界面的四分之一内容。这种问题不仅令人困扰&#xff0c;也可…

Search for documents with similar texts

题意&#xff1a;搜索具有相似文本的文档 问题背景&#xff1a; I have a document with three attributes: tags, location, and text. 我有一份文档&#xff0c;包含三个属性&#xff1a;标签、位置和文本。 Currently, I am indexing all of them using LangChain/pgvecto…

Lua: 轻量级多用途脚本语言

Lua 是一种高效而轻量级的脚本语言&#xff0c;具备强大的扩展性和灵活性&#xff0c;广泛应用于游戏开发、嵌入式系统、Web 应用等多个领域。本文将深入探讨 Lua 的特性、应用场景以及如何使用 Lua 进行开发。 1. Lua 的起源与发展 Lua 的发展始于上世纪90年代初&#xff0c;…

GPT-5:AI新时代的领航者与我们的未来

一、引言&#xff1a;GPT-5的崭新时代 在科技的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;领域正迎来一个崭新的纪元。GPT-5&#xff0c;作为OpenAI的最新成果&#xff0c;无疑将成为这个新时代的领航者。从GPT-1到GPT-4&#xff0c;我们见证了AI在自然语言处理领域…

零基础开始学习鸿蒙开发-页面导航栏布局设计

1.设定初始页(Idex.ets) import {find} from ../pages/find import {home} from ../pages/home import {setting} from ../pages/setting Entry Component struct Index {private controller: TabsController new TabsController()State gridMargin: number 10State gridGut…

开源分享:一套完整的直播购物系统源码

直播购物已经成为一种炙手可热的电商模式&#xff0c;吸引了无数商家和消费者的目光。对于开发者来说&#xff0c;构建一个功能齐全、用户体验优良的直播购物系统是一项复杂的任务。本文将分享一套完整的直播购物系统源码&#xff0c;帮助开发者快速搭建自己的直播购物平台。 …

决策树划分属性依据

划分依据 基尼系数基尼系数的应用信息熵信息增益信息增益的使用信息增益准则的局限性 最近在学习项目的时候经常用到随机森林&#xff0c;所以对决策树进行探索学习。 基尼系数 基尼系数用来判断不确定性或不纯度&#xff0c;数值范围在0~0.5之间&#xff0c;数值越低&#x…

IDEA中Maven配置依赖和排除依赖

目录 依赖配置 添加依赖的几种方式&#xff1a; 1.利用中央仓库搜索的依赖坐标 2.利用IDEA工具搜索依赖 3.熟练上手maven后&#xff0c;快速导入依赖 排除依赖 依赖配置 依赖&#xff1a;指当前项目运行所需要的jar包。一个项目中可以引入多个依赖&#xff1a; 例如&am…