STM32单片机入门学习——第16节: [6-4] PWM驱动LED呼吸灯PWM驱动舵机PWM驱动直流电机

news2025/4/9 2:25:29

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!

本文写于:2025.04.05

STM32开发板学习——第16节: [6-4] PWM驱动LED呼吸灯&PWM驱动舵机&PWM驱动直流电机

  • 前言
  • 开发板说明
  • 引用
  • 解答和科普
  • 一、PWM驱动呼吸灯
  • 二、PWM驱动舵机
  • 问题
  • 三、PWM驱动直流电机
  • 总结

前言

   本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
   欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
   在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
   另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!

开发板说明

   本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
   原理图如下
1、开发板原理图
在这里插入图片描述
2、STM32F103C6和51对比
在这里插入图片描述
3、STM32F103C6核心板
在这里插入图片描述

视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。

下图是实物图
在这里插入图片描述

引用

【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
在这里插入图片描述
数据手册
在这里插入图片描述

解答和科普

一、PWM驱动呼吸灯

在这里插入图片描述
PA0插入一个LED,PA0引脚输出一个PWM波,用于驱动LED,并且呈现不同的亮度,这个LED正极接在PA0引脚,负极接在GND的驱动方法,这样就是高电平点亮,低电平熄灭,这是正极性驱动的方法,更直观一些。占空比越大,LED越亮,占空比越小,LED就越暗,
在这里插入图片描述
1、 RCC开启时种,把我们需要的TIM外设和GPIO外设的时钟打开
2、 配置时基单元,包括前面的时钟源选择和时基单元
3、 配置输出比较单元,包括这个CCR的值,输出比较模式、极性选择、输出使能这些参数
4、 配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出的配置
5、 运行控制,启动计数器,这样就能输出PWM

在这里插入图片描述

void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

给输出比较结构体赋一个默认值的;到这里输出比较就配置完成了;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用的不多
在这里插入图片描述那这里有函数可以设置极性。在结构体初始化的那个函数里也可以设置极性, 这两个地方设置极性的作用是一样的。只不过是用结构体是一起初始化的, 这里是一个单独的函数进行修改的, 一般来说结结构体里的参数,都会有一个单独的函数可以进行更改。
在这里插入图片描述

void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);

单独更改输出比较模式的函数

在这里插入图片描述

总结一下:

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

用结构体初始化输出比较单元;
在这里插入图片描述
运行时改变参数的

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

代码配置

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

	TIM_InternalClockConfig(TIM2);		//好多人不写,默认的是内部时钟
	
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;			//滤波的分频关系不大
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;		//向上计数
	TIM_TimeBaseInitStructure.TIM_Period=7200-1;						//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler=10000-1;					//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	TIM_OCInitTypeDef   TIM_OCInitStructure;
	TIM_OCInitStructure.TIM_OCMode=;
	TIM_OCInitStructure.TIM_OCPolarity=;
	TIM_OCInitStructure.TIM_OutputState=;
	TIM_OCInitStructure.TIM_Pulse=;
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	
	TIM_Cmd(TIM2,ENABLE);
}

因为有些参数是高级定时器才有的,我们只配置了我们需要用的,
在这里我们这个结构体现在并没有给所有的成员赋值对吧, 对结构体来说是局部变量,如果不给它的成员赋初始值。它成员的值就是不确定的,这可能会导致一些问题:比如当你想把高级定时器当做通用定时器输出PWM时,那你自然就会把这里的TIM2改为TIM1对吧,这样的话,这个结构体原本用不到的成员,现在就需要用了,而这些成员你又没给赋值,那就会导致高级定时器输出PWM会出现一些奇怪的问题。
让高级定时器输出4路PWM,若果我把初始化函数放在程序的第一行,那就没问题。如果初始化函数之前出现了其他的代码,那4路PWM就会有3路不能输出,奇怪,能不能输出PWM,竟然和初始化函数在哪一行有关,要么配置完整,每个结构体成员都要配置一下,不管有没有用,要么先给结构体都赋初始值,再修改部分的结构体成员。

void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

这个函数用来给结构体赋初始值的,如果你不想给每个结构体成员都赋值,你可以先用这个函数赋给结构体初始值,然后再更改你想设置的结构体成员;

/*配置比较单元*/
	TIM_OCInitTypeDef   TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;			//输出模式PWM1
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;		//	极性选择
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; //输出使能						
	TIM_OCInitStructure.TIM_Pulse=;												//CCR
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);			//比较通道初始化完成
	

在这里插入图片描述
TIM2的引脚复用在了PA0引脚上,如果我们要使用TIM2的OC1也就是CH1通道,输出PWM,那它就只能在PA0的引脚上输出,而不能任意选择引脚输出,同样如果使用TIM2的CH2,那就只能在PA1端口输出,其他的外设也是同理,关系是定死的;
在这里插入图片描述
虽然是定死的,STM32还是给我们一次更改的机会的,这就是重定义,或者叫重映射,比如你既要用USART2的TX引脚,又要用TIM2的CH3通道,它俩冲突了,没法同时用,那我们就可以在这个重映射的列表里找一下,比如这里我们找到了TIM2的CH3,那么TIM2-CH3就可以换到这里,避免了两个外设引脚的冲突,如果这个重映射的列表找不到,那外设复用的GOIO就不能挪动位置,这就是重映射功能,配置重映射是用AFIO来完成的。

因此我吗可以配置PA0或者重映射到PA15的引脚上,其他的引脚就没有机会作为这个通道的输出引脚了。初始化引脚的位置可以随意,你可以放在上面也可以放在下面,只不过我们习惯GOIO放在上面,

	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;

为什么选择这个模式
在这里插入图片描述
对应普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的;
在这里插入图片描述
如果想让定时器控制引脚,就需要使用复用开漏.推挽输出的模式,这这里输出数据寄存器将被断开,输出控制权转移给片上外设,所以这里的片上外设连接的就是TIM2的CH1通道,所以只有把GPIO设置成复用推挽输出,引脚的控制权才能交给片上外设,PWM的波形才能通过引脚输出。

配置GPIO

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef   GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	

如果现在想要产生一个频率为1Khz,占空比为50%,分辨率为1%的PWM波形,
72M/(PSC+1)/(ARR+1)=1000;
CCR/(ARR+1)=50%;
1/(ARR+1)=1%; ARR=100-1,CCR=50,PSC=720-1;

在这里插入图片描述

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef   GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	

	TIM_InternalClockConfig(TIM2);		//好多人不写,默认的是内部时钟
	
	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_OCInitTypeDef   TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;			//输出模式PWM1
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;		//	极性选择
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; //输出使能						
	TIM_OCInitStructure.TIM_Pulse=50;											/* CCR*/
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);			//比较通道初始化完成
	
	TIM_Cmd(TIM2,ENABLE);							//启动定时器
}

如果现在想要产生一个频率为1Khz,占空比为50%,分辨率为1%的PWM波形,
72M/(PSC+1)/(ARR+1)=1000;
CCR/(ARR+1)=50%;
1/(ARR+1)=1%; ARR=100-1,CCR=50,PSC=720-1;

实验现象
频率1Khz,占空比50%
在这里插入图片描述
占空比10%
在这里插入图片描述

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

在这里插入图片描述
LED呈现呼吸灯
我们想让LED呈现呼吸灯的效果,那就是不断更改CCR的值就行了,在运行工程更改CCR,需要用下面的函数;

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);

这个函数是用来单独更改通道1的CCR的值的,把它封装为函数,我们就可以调用这个函数来改变PWM中CCR的值;

oid PWM_SetCompare1(uint16_t  Compare)
{
	TIM_SetCompare1(TIM2,Compare);
}

我们只需要在while(1)主循环里,不断调用PWM_SetCompare1函数更改CCR的值,这样就能完成LED呼吸灯的效果了。


	uint8_t i;
		for(i=0; i<=100;i++)		//占空比从0到100
		{
			PWM_SetCompare1(i);			//设置CCR的值,并不是直接设置占空比,占空比是CCR/(ARR+1)决定
			Delay_ms(10);
		}
		
		for(i=0; i<=100;i++)		//占空比从100到0
		{
			PWM_SetCompare1(100-i);
			Delay_ms(10);
		}

设置CCR的值,并不是直接设置占空比,占空比是CCR/(ARR+1)决定;
实验现象

LED呼吸灯

PWM占空比变化

占空比变化和呼吸灯效果

引脚重映射
在这里插入图片描述
这个TIM2的CH1可以从PA0挪到PA15引脚上,这里需要用到AFIO了
那首先使用AFIO,就要开启AFIO的时钟,

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

引脚重映射配置

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

在这里插入图片描述
在这里插入图片描述
部分重映射1、部分重映射2、和完全重映射,如果都不使用,那就是没有重映射;对应表里4中情况;
如果我们想把PAO改到PA15,就可以选择这个部分重映射方式1,或者完全重映射,这都可以。

	 GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);

PA15没有加粗,因为它上电默认复用为了调试端口JTDI,所以如果想让他作为普通的GPIO或者复用定时器的通道,那还需要先关闭调试端口的复用,怎么关闭:也是用这个

 GPIO_PinRemapConfig();

在这里插入图片描述
这三个参数是用来解除调速端口复用的,SWJ就是SWD和JTAG这两种调试方式,第一个SWJ_NoJTRST,就是解除JTRST引脚的复用,在引脚定义里看一下,就是这个NJTREST也就是PB4
在这里插入图片描述
如果使用这个参数,那么这个PB4就变为正常的GPIO口了,其他的四个端口仍然是调试端口。不能当做GPIO来使用;

SWJ_JTAGDisable :这个就是解除JTAG调试端口的复用,在引脚定义里就是,PA15、PB3、PB4、这三个端口变回GPIO,上面的PA13和PA14,仍然为SWD的调试端口;
在这里插入图片描述

SWJ_Disable :这个参数就是把SWD和JTAG的调试端口全部解除,在引脚定义里就是这5个引脚全部变为普通GPIO口了,没有调速功能了,所以这个参数千万不要随便调用,一但调用这个参数并且下载到程序之后,那么你的调试端口就没有了,这之后再使用STLINK就下载不进去程序了,这时就只能使用串口下载,下载一个新的、没有解除调试端口的程序,这样才能把调试端口能回来。
在这里插入图片描述

在这里插入图片描述
如果我们需要使用PA15、PB3、PB4这三个引脚,那通常就是解除JATG的复用,保留SWD的复用,所以这里,参数就选择

 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

这样就可以正常使用PA15这个引脚了;
总结一下,如果你想让PA15、PB3、PB4、这三个引脚当做GPIO来使用的话,那就加一下这里的第一句和第三局,先打开AFIO时钟,再用GPIO_PinRemapConfig,用AFIO将JATG复用解除掉了,这样就行了。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

如果你想重映射定时器或者其他外设的复用引脚,那就加一下,第一句和第二句,先打开AFIO时钟,再用AFIO重映射外设复用的引脚,这样就行了,如果你重映射的引脚又正好是调试端口,那这三句都得加上:打开AFIO时钟,重映射引脚,解除调速端口,这样才行;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

有了这三句,我们定时器的通道1,就从PA0挪到PA15了,所以下面GPIO初始话这里就,GPIO_Pin_0也得改成GPIO_Pin_15,

GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;

那现在重映射的代码写好了。
在这里插入图片描述
呼吸灯代码
main

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "PWM.h"
#include "OLED.h"

uint8_t i;

int main(void)
{
							
	OLED_Init();
	OLED_ShowString(1,2,"Hello STM32 MCU");
	PWM_Init();
	
	while(1)
	{
		for(i=0; i<=100;i++)		//占空比从0到100
		{
			PWM_SetCompare1(i);			//设置CCR的值,并不是直接设置占空比,占空比是CCR/(ARR+1)决定
			Delay_ms(10);
		}
		
		for(i=0; i<=100;i++)		//占空比从100到0
		{
			PWM_SetCompare1(100-i);
			Delay_ms(10);
		}
		

	}
}

PWM.CH

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	/*重映射设置*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	
	GPIO_InitTypeDef   GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	

	TIM_InternalClockConfig(TIM2);		//好多人不写,默认的是内部时钟
	
	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_OCInitTypeDef   TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;			//输出模式PWM1
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;		//	极性选择
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; //输出使能						
	TIM_OCInitStructure.TIM_Pulse=90;											/* CCR*/
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);			//比较通道初始化完成
	
	TIM_Cmd(TIM2,ENABLE);							//启动定时器
}

void PWM_SetCompare1(uint16_t  Compare)
{
	TIM_SetCompare1(TIM2,Compare);
	
}

#ifndef  __PWM_H
#define  __PWM_H

void PWM_Init(void);
void PWM_SetCompare1(uint16_t  Compare);

#endif

实验现象
重映射

重映射TIM2_CH1从PA0到PA15

二、PWM驱动舵机

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

在这里插入图片描述
所以要改为OC2lnit,这样这个结构体参数就会配置到通道2了

	TIM_OC2Init(TIM2,&TIM_OCInitStructure);	

当然如果你通道1和通道2都想要用,那就再这里加两行代码,通道1和2都初始化,这样就能同时使用两个通道来输出两个PWM了,同理通道3和通道4也是可以使用的,那对于同一个定时器的不同通道输出的PWM,它们的频率,因为不同通道是共用一个计数器的,所以它们的频率必须是一样的,它们的占空比,由各自的CCR决定,所以占空比可以各自设定,还有就是它们的相位,由于计数器更新,所有PWM同时跳变,所以它们的相位是同步的,这就是同一个定时器不同通道输出PWM的特点,,如果驱动多个舵机或者直流电机,那使用一个定时器不同通道的PWM,就完全可以了。
在这里插入图片描述

在这里插入图片描述
舵机要求的周期是20ms,那频率就是1/20ms=50Hz;
占空比这里,舵机要求高电平时间是0.5ms ~ 2.5ms ,
PSC和ARR的值不是固定的,可以尝试几次,找一个方便计算的值;
PSC+1=72,ARR+1=20K;这样的话,满足第一个等式,所以最终频率为50HZ,同时20k对应20ms,那CCR设置500,就是0.5ms;CCR设置2500,那就是2.5ms;

			PWM_SetCompare2(500);
		

这里就是1.5ms也就0度的位置;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

改为2500对应180°

PWM_SetCompare2(2500);

在这里插入图片描述
改为1500对应90°
在这里插入图片描述

首先,给舵机建一个模块,我们想要的函数是,舵机设置角度,参数是0到180度,调用一下,就能变为对应的角度,这样才直观方便。而不是去设置PWM中的CCR,参数是500到2500。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Servo.h"
#include "OLED.h"
#include "Key.h"

uint8_t KeyNum;
float Angle;

int main(void)
{
							
	OLED_Init();
	OLED_ShowString(1,2,"Hello STM32 MCU");
	OLED_ShowString(2,1,"Angle:");
	Key_Init();
	Servo_Init();
	
	while(1)
	{
		Servo_SetAngle(0);
		KeyNum=Key_GetNum();
		if(KeyNum==1)
		{
			Angle +=30;
			if(Angle>180)
			{
				Angle=0;
				
			}
		}
		Servo_SetAngle(Angle);
		OLED_ShowNum(2,7,Angle,3);
	}
}

Servo.ch

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

void Servo_Init(void)		//底层初始化
{
	PWM_Init();
}

/*
0 		 500
180 	 2500


*/
void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2(Angle/180*2000+500);			//完成映射
}

#ifndef  __SERVO_H
#define  __SERVO_H

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

PWM.ch

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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);	
	

	TIM_InternalClockConfig(TIM2);		//好多人不写,默认的是内部时钟
	
	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_OCInitTypeDef   TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;			//输出模式PWM1
	TIM_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_Cmd(TIM2,ENABLE);							//启动定时器
}

void PWM_SetCompare2(uint16_t  Compare)
{
	TIM_SetCompare2(TIM2,Compare);
	
}

#ifndef  __PWM_H
#define  __PWM_H

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

#endif

Key,ch

#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 KeyNum	=0;
	if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
		Delay_ms(20);
		KeyNum=1;
	}
	
	if (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_11)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_11)==0);
		Delay_ms(20);
		KeyNum=2;
	}
	
	return  KeyNum;
}

#ifndef    __KEY_H
#define    __KEY_H

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


#endif

实验现象

舵机控制角度失败版

0度
在这里插入图片描述
30度
在这里插入图片描述
60度
在这里插入图片描述
控制不住
在这里插入图片描述

问题

1、白色背景的话:需要打开反相
在这里插入图片描述

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

三、PWM驱动直流电机

在这里插入图片描述
红色是TB6612电机驱动模块,它的第一个引脚VM,电机电源,同样的,也是接在STlink的5V引脚,第二个VCC,逻辑电源,接在面包板3.3V正极,第三个GND,电源负极,接在面包板的负极,之后AO1、AO2、电机输出端,接电机的两根线,这个接线不分正反,如果你对调这两根线,那电机旋转的方向就会反过来,然后右边是另一路的驱动,如果你需要驱动两个电机,就接两路,上面是STBY,待机控制脚,不需要待机,直接接逻辑电源正3.3V,剩下的三个是控制引脚,AN1和AN2是方向控制,任意接两个GPIO就行了,这里接的是PA4和PA5两个引脚,最后一个PWMA是速度控制,需要接PWM的输出脚,这里接的是PA2这个引脚,PA2对应的是TIM2的通道3,到时候初始化通道3就行了,另外接了一个按键,在PB1口,用于控制。
在这里插入图片描述

在转动时候会有响声,如何避免这个问题呢,可以通过加大PWM频率,当PWM 频率足够大时,超出人耳的范围就听不到了,20hz-20khz,我们目前是1KHZ,是能听到的;
加大频率可以通过减小预分频器来完成,这样不会影响占空比,所以我们给预分频器去掉一个0,现在就是10kHZ了,还可以再把76改成36,变为一半,现在就是20Khz了,

实验现象

直流电机调速

代码
main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Motor.h"
#include "OLED.h"
#include "Key.h"

uint8_t KeyNum;
int8_t	Speed;


int main(void)
{
							
	OLED_Init();
	OLED_ShowString(1,2,"Hello STM32 MCU");
	OLED_ShowString(2,1,"Speed:");
	Motor_Init();
	Key_Init();
	
	
	
	while(1)
	{
			KeyNum=Key_GetNum();
			if(KeyNum==1)
			{
				Speed+=20;
					if(Speed>100)
					{
						Speed=-100;
						
					}
			}
			Motor_SetSpeed(Speed);
			OLED_ShowSignedNum(2,7,Speed,3);
	}
}

PWM.C H

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	/*重映射设置*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	
	GPIO_InitTypeDef   GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	

	TIM_InternalClockConfig(TIM2);		//好多人不写,默认的是内部时钟
	
	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=36-1;								/* PSC*/
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	/*配置比较单元*/
	TIM_OCInitTypeDef   TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;			//输出模式PWM1
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;		//	极性选择
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; //输出使能						
	TIM_OCInitStructure.TIM_Pulse=0;											/* CCR*/
	TIM_OC3Init(TIM2,&TIM_OCInitStructure);			//比较通道初始化完成
	
	TIM_Cmd(TIM2,ENABLE);							//启动定时器
}

void PWM_SetCompare3(uint16_t  Compare)
{
	TIM_SetCompare3(TIM2,Compare);
	
}

#ifndef  __PWM_H
#define  __PWM_H

void PWM_Init(void);
void PWM_SetCompare3(uint16_t  Compare);

#endif

Motor.Ch

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

void Motor_Init(void)
{
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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);	
	PWM_Init();
}

void Motor_SetSpeed(int8_t Speed)
{
		if(Speed>=0)
		{
			GPIO_SetBits(GPIOA,GPIO_Pin_4);
			GPIO_ResetBits(GPIOA,GPIO_Pin_5);
			
			PWM_SetCompare3(Speed);
		}
		else
		{
			GPIO_SetBits(GPIOA,GPIO_Pin_5);
			GPIO_ResetBits(GPIOA,GPIO_Pin_4);
			
			PWM_SetCompare3(-Speed);
		
		}


}

#ifndef  __MOTOR_H
#define  __MOTOR_H

void Motor_Init(void);
void Motor_SetSpeed(int8_t Speed);
#endif

Key.CH(主要初始化了PB1)

#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 KeyNum	=0;
	if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
		Delay_ms(20);
		KeyNum=1;
	}
	
	if (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_11)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_11)==0);
		Delay_ms(20);
		KeyNum=2;
	}
	
	return  KeyNum;
}

#ifndef    __KEY_H
#define    __KEY_H

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


#endif

OLED.CH

#include "stm32f10x.h"
#include "OLED_Font.h"

/*引脚配置*/
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

/*引脚初始化*/
void OLED_I2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);
	OLED_W_SCL(1);
	OLED_W_SDA(0);
	OLED_W_SCL(0);
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		OLED_W_SDA(!!(Byte & (0x80 >> i)));
		OLED_W_SCL(1);
		OLED_W_SCL(0);
	}
	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
}

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x00);		//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x40);		//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}

#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

总结

本节课主要是进行了定时器比较的功能,把上一节课定时器比较进行了实现,不仅要对时基进行配置,还要对比较单元进行配置,后来这就是真个流程,后来把PWM模块封装起来,之后的都是用PWM做底层代码实现其他的功能,
1、如果现在想要产生一个频率为1Khz,占空比为50%,分辨率为1%的PWM波72M/(PSC+1)/(ARR+1)=1000;
CCR/(ARR+1)=50%;1/(ARR+1)=1%; ARR=100-1,CCR=50,PSC=720-1;还学会了引脚重映射,
2、如果你想重映射定时器或者其他外设的复用引脚,那就加一下,第一句和第二句,先打开AFIO时钟,再用AFIO重映射外设复用的引脚,这样就行了,如果你重映射的引脚又正好是调试端口,那这三句都得加上:打开AFIO时钟,重映射引脚,解除调速端口,这样才行;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

有了这三句,我们定时器的通道1,就从PA0挪到PA15了
3、用PWM为底层,完成了LED呼吸灯、PWM驱动舵机、PWM驱动直流电机,舵机实现的不太好,不知道什么原因,是不是供电的问题有待于考虑
4、学会了基本的示波器的操作,感觉用起来很好玩。

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

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

相关文章

基础框架系列分享:一个通用的Excel报表生成管理框架

由于我们系统经常要生成大量的Excel报表&#xff08;Word&#xff0c;PDF报表也有&#xff0c;另行分享&#xff09;&#xff0c;最初始他们的方案是&#xff0c;设计一个表&#xff0c;和Excel完全对应&#xff0c;然后读表&#xff0c;把数据填进去&#xff0c;这显然是非常不…

Ansible(4)—— Playbook

目录 一、Ansible Playbook : 1、Play &#xff1a; 2、Playbook&#xff1a; 二、Ansible Playbook 格式&#xff1a; 1、空格&#xff1a; 2、破折号&#xff08; - &#xff09;&#xff1a; 3、Play 格式&#xff1a; 三、查找用于任务的模块&#xff1a; 1、模块…

自学-C语言-基础-数组、函数、指针、结构体和共同体、文件

这里写自定义目录标题 代码环境&#xff1a;&#xff1f;问题思考&#xff1a;一、数组二、函数三、指针四、结构体和共同体五、文件问题答案&#xff1a; 代码环境&#xff1a; Dev C &#xff1f;问题思考&#xff1a; 把上门的字母与下面相同的字母相连&#xff0c;线不能…

蓝桥云客--团队赛

2.团队赛【算法赛】 - 蓝桥云课 问题描述 蓝桥杯最近推出了一项团队赛模式&#xff0c;要求三人组队参赛&#xff0c;并规定其中一人必须担任队长。队长的资格很简单&#xff1a;其程序设计能力值必须严格大于其他两名队友程序设计能力值的总和。 小蓝、小桥和小杯正在考虑报名…

C-S模式之实现一对一聊天

天天开心&#xff01;&#xff01;&#xff01; 文章目录 一、如何实现一对一聊天&#xff1f;1. 服务器设计2. 客户端设计3. 服务端代码实现4. 客户端代码实现5. 实现说明6.实验结果 二、改进常见的服务器高并发方案1. 多线程/多进程模型2. I/O多路复用3. 异步I/O&#xff08;…

[Deep-ML]Transpose of a Matrix(矩阵的转置)

Transpose of a Matrix&#xff08;矩阵的转置&#xff09; 题目链接&#xff1a; Transpose of a Matrix&#xff08;矩阵的转置&#xff09;https://www.deep-ml.com/problems/2 题目描述&#xff1a; 难度&#xff1a; easy&#xff08;简单&#xff09;。 分类&#…

智慧节能双突破 强力巨彩谷亚VK系列刷新LED屏使用体验

当前全球节能减排趋势明显&#xff0c;LED节能屏作为显示技术的佼佼者&#xff0c;正逐渐成为市场的新宠。强力巨彩谷亚万境VK系列节能智慧屏凭借三重技术保障、四大智能设计以及大师臻彩画质&#xff0c;在实现节能效果的同时&#xff0c;更在智慧显示领域树立新的标杆。   …

html 给文本两端加虚线自适应

效果图&#xff1a; <div class"separator">文本 </div>.separator {width: 40%;border-style: dashed;display: flex;align-items: center;color: #e2e2e2;font-size: 14px;line-height: 20px;border-color: #e2e2e2;border-width: 0; }.separator::bef…

leetcode4.寻找两个正序数组中的中位数

思路源于 LeetCode004-两个有序数组的中位数-最优算法代码讲解 基本思路是将两个数组看成一个数组&#xff0c;然后划分为两个部分&#xff0c;若为奇数左边部分个数多1&#xff0c;若为偶数左边部分等于右边部分个数。i表示数组1划分位置&#xff08;i为4是索引4也表示i的左半…

0101安装matplotlib_numpy_pandas-报错-python

文章目录 1 前言2 报错报错1&#xff1a;ModuleNotFoundError: No module named distutils报错2&#xff1a;ERROR:root:code for hash blake2b was not found.报错3&#xff1a;**ModuleNotFoundError: No module named _tkinter**报错4&#xff1a;UserWarning: Glyph 39044 …

OSCP - Proving Grounds- SoSimple

主要知识点 wordpress 插件RCE漏洞sudo -l shell劫持 具体步骤 依旧是nmap 起手&#xff0c;只发现了22和80端口&#xff0c;但80端口只能看到一张图 Nmap scan report for 192.168.214.78 Host is up (0.46s latency). Not shown: 65533 closed tcp ports (reset) PORT …

C语言求3到100之间的素数

一、代码展示 二、运行结果 三、感悟思考 注意: 这个题思路他是一个试除法的一个思路 先进入一个for循环 遍历3到100之间的数字 第二个for循环则是 判断他不是素数 那么就直接退出 这里用break 是素数就打印出来 在第一个for循环内 第二个for循环外

【2025】物联网发展趋势介绍

目录 物联网四层架构感知识别层网络构建层管理服务层——**边缘存储**边缘计算关键技术&#xff1a;综合应用层——信息应用 物联网四层架构 综合应用层&#xff1a;信息应用 利用获取的信息和知识&#xff0c;支持各类应用系统的运转 管理服务层&#xff1a;信息处理 对数据进…

如何查看 MySQL 的磁盘空间使用情况:从表级到数据库级的分析

在日常数据库管理中&#xff0c;了解每张表和每个数据库占用了多少磁盘空间是非常关键的。这不仅有助于我们监控数据增长&#xff0c;还能为性能优化提供依据。 Google Gemini中国版调用Google Gemini API&#xff0c;中国大陆优化&#xff0c;完全免费&#xff01;https://ge…

汇编学习之《移位指令》

这章节学习前需要回顾之前的标志寄存器的内容&#xff1a; 汇编学习之《标志寄存器》 算数移位指令 SAL (Shift Arithmetic Left)算数移位指令 : 左移一次&#xff0c;最低位用0补位&#xff0c;最高位放入EFL标志寄存器的CF位&#xff08;进位标志&#xff09; OllyDbg查看…

Nature Communications上交、西湖大学、复旦大学研发面向机器人多模式运动的去电子化刚弹耦合高频自振荡驱动单元

近年来&#xff0c;轻型仿生机器人因其卓越的运动灵活性与环境适应性受到国际机器人领域的广泛关注。然而&#xff0c;现有气动驱动器普遍受限于低模量粘弹性材料的回弹滞后效应与能量耗散特性&#xff0c;加之其"非刚即柔"的二元结构设计范式&#xff0c;难以同时满…

对备忘录模式的理解

对备忘录模式的理解 一、场景1、题目【[来源](https://kamacoder.com/problempage.php?pid1095)】1.1 题目描述1.2 输入描述1.3 输出描述1.4 输入示例1.5 输出示例 2、理解需求 二、不采用备忘录设计模式1、代码2、问题3、错误的备忘录模式 三、采用备忘录设计模式1、代码1.1 …

【数据结构】图的基本概念

图的定义 通俗来说一堆顶点被一堆线连在一起&#xff0c;这一坨顶点与线的集合 目录 图的定义 术语 有向图与无向图 简单图与多重图 度、入度与出度 路径与回路 路径长度与距离 子图 连通、连通图与连通分量 强连通、强连通图与强连通分量 完全图 生成树与生成森林 权…

激光加工中平面倾斜度的矫正

在激光加工中&#xff0c;加工平面的倾斜度矫正至关重要&#xff0c;直接影响加工精度和材料处理效果。以下是系统的矫正方法和步骤&#xff1a; 5. 验证与迭代 二次测量&#xff1a;加工后重新检测平面度&#xff0c;确认残余误差。 反馈优化&#xff1a;根据误差分布修正补偿…

rdiff-backup备份

目录 1. 服务器备份知识点 1.1 备份策略 1.2 备份步骤和宝塔面板简介 1.3 CentOS7重要目录 2. 备份工具 2.1 tar -g 备份演示 2. rsync 备份演示 3. rdiff-backup 备份演示 4. 差异和优缺点 3. rdiff-backup安装和使用 3.1 备份命令rdiff-backup 3.2 恢复命令--…