STM32-PWM驱动LED呼吸灯&PWM驱动直流电机(十三)

PWM驱动LED呼吸灯&PWM驱动直流电机

PWM驱动LED呼吸灯

接线图:

在这里我们在 PA0 引脚上插一个 LED 啊,我们准备在这个 PA0 引脚输出一个 PWM 波,用于驱动这个 LED, 并且呈现出不同的亮度。注意一下哈,这个 LED 我们用的是正极接在 PA0 引脚,负极接在接地的驱动方法。这样就是高电平点亮,低电平熄灭。这是正极性的驱动方法,这样接的话观察更直观一些哈,就是占空比越大 LED 越亮,占空比越小 LED 就越暗。

删掉:

新建:

上一小节我们已经把 PWM 的这整个通路都讲完了哈。现在我们只需要把这些模块都打通,那就可以输出 PWM 了是吧?具体的步骤就是,

第一步, RCC 开启时钟,把我们要用的 TIM 外设啊和 GPIO 外设的时钟打开。

第二步,配置时基单元,包括这前面的时钟源选择和这里的时基单元啊,都配置好。这个代码我们之前也写过哈。

第三步,配置输出比较单元,里面包括这个 CCR 的值,输出比较模式,极性选择,输出使能这些在库函数里也是用结构体统一来配置的。

第四步配置 GPIO, 把 PWM 对应的 GPIO 口啊,初始化为复用推挽输出的配置。

这个 PWM 和 GPIO 的对应关系是怎样的呢?可以参考一下引脚定义表哈,这个我们等会再细说。那最后第五步就是运行控启动计数器,这样就能输出 PWM 了。

看一下库函数:

首先看一下这里四个函数, TIM_ OC1_ ITR、 OC2_ ITR、 OC3_ ITR 和 OC4_ ITR。这四个函数就是用来配置输出比较模块的。 OC 就是 output compare 输出比较。所以这一看就知道它是用来配置我们这个结构图的这一块的,对吧?这里输出比较单元有四个

那对应也有四个函数啊, OC1、 OC2、 OC3、 OC4,一个函数配置一个单元。那它的参数呢?第一个 TIMx 选择定时器,第二个结构体啊就是输出比调的那些参数了,这个就清楚了。然后继续看。

下面 TIMOCStruct 的意义的,这个是用来给输出比较结构体赋一个默认值的。那到这里输出比较的配置啊其实就已经可以完成了。

接下来就是一些小功能和运行时更改参数的函数了,我们来看一下哈。这里有四个函数, TIMForceOC1234Config, 这个是用来配置强制输出模式的哈,如果你在运行中想要暂停输出波形,并且强制输出高或低电平,可以用下这个函数,不过一般用的不多哈,因为强制输出高电平,和设置 100 占空比是一样的。强制输出低电平,和设置 0 %占空比也是一样的。所以这四个函数了解一下就行哈,不需要掌握。

然后下面, TIM_ OC1234_ Pro 的 configure, 这四个函数是用来配置 CCR 寄存器的预装功能的。这个预装功能就是影子寄存器哈,之前也介绍过,就是你写入的值不会立即生效,而是在更新事件才会生效,这样可以避免一些小问题。这个一般可以不用哈,也了解下即可,不需要掌握。

然后是 TIMOC1234 fast 的 config,这四个函数是用来配置快速使能的,这个功能手册里单脉冲模式那一节有小段介绍哈,用的也不多,不需要掌握。

接着是 TIM 可利亚 OC1234REF 这个功能在手册里外部事件时清除 REF 信号那一节有介绍,这个也不需要掌握哈。

那刚才看的这些函数都是 STM32 的一些小功能配置,用的都不多,感兴趣的话可以看看手册哈。然后继续看这里, TIM OC1PolarityConfig, OC1NPolarityConfig,然后 OC2 OC2N、 OC3、 OC3N、 OC4,这些就是用来单独设置输出比较的极性的。这里带个 N 的就是高级定时器里互补通道的配置哈, OC4 没有互补通道,所以就没有 OC4N 的函数。那这里有函数可以设置极性哈,在结构体初始化的那个函数里也可以设置极性。这两个地方设置极性的作用是一样的哈,只不过是用结构体是一起初始化的,在这里是一个单独的函数进行修改的。一般来说,结构体里的参数啊,都会有一个单独的函数可以进行更改,这里的函数就是用来单独更改输出极性的哈。

然后下面这里, TIM_ CCx_ Command 和 CCxN_ Command 是用来单独修改输出使能参数的哈。

再下面, TIM_ Select_ OCxM 选择输出比较模式啊,这个是用来单独更改输出比较模式的函数。

然后再往下看,这里, TIM_ SetCompare1234,这四个是用来单独更改 CCR 寄存器值的函数。这四个函数比较重要哈,我们在运行的时候更改占空比就需要用到这四个函数。

好,那到这里有关输出比较的函数就介绍完了哈。

总结一下,这个 OC1_ ITR 的函数是用结构体来初始化输出比较单元的,这个很重要哈,需要掌握。然后中间这里有些函数啊,是定时器的一些小功能,这个了解一下即可,不需要掌握。最后是这些运行时候更改参数的函数。其中这四个 set compare 函数重要,需要掌握,其他的了解一下即可。那函数看完了,我们来开始写代码啊。我们回到 PWM 点 C。

时基单元的代码原来写过,在定时中断中复制过来

接着,在这个实际单元初始化下面,我们来初始化一下输出比较单元哈。我们在这个 TIM 点 h 里找一下函数。在这里有四个初始化函数,对应四个输出比较单元,或者说输出比较通道哈,这都是一个意思。你需要初始化哪个通道,就调用哪个函数。不同的通道对应的 GPIO 口也是不一样的哈,所以这里要按照你 GPIO 口的需求来。这里我使用的 PA0 口,对应的就是第一个输出比较通道,所以这里我就要使用这个 TIM_ OC1_ IT 函数。

通道和 GPIO 的对应关系等会再详细说哈,先别急。那我们先复制一下这个 OC1 一里的函数,然后放在这里。看一下参数哈,第一个是 TIMX, 我们给 TIM2。第二个是用来配置输出比较的结构体,我们转到定义

复制一下这个结构体类型名。接着回来啊,在这上面粘贴,起个变量名叫 TIMOC1InitStruct, 然后复制变量名,在下面列一下结构体成员啊。那这里结构体的成员比较多啊,而且里面有很多参数是高级定时器才用到的,比如这种带个 N 的参数啊,还有这个 Idle state 的参数啊,都是高级定时器才需要用啊

所以这里一般我们就只把我们需要用的参数列出来就行了。那第一个需要用的就是这个 OC mode 设置输出比较的模式。然后是 OC polarity 设置输出比较的极性。 Auto state 设置输出使能。最后是 pulse 这个是用来设置 CCR2 的。最后把结构体的地址放在一例的函数里,这样就行了。

在这里我们这个结构体现在并没有给所有的成员赋值,对吧?那对于这个结构体变量来说,它现在是一个局部变量,如果不给它的成员赋初始值,它成员的值就是不确定的这可能会导致一些问题啊,比如当你想把高级定时器当做通用定时器输出 PWM 时,那你自然就会把这里的 TIM2 改成 TIM1 对吧?这样的话,这个结构体原来用不到的成员现在就需要用了。而这些成员你又没给赋值,那就会导致高级定时器输出 PWM 出现一些奇怪的问题。至于怎么奇怪的呢?比如我之前写了一个代码啊,让高级定时器输出四路 PWM。如果我把初始化函数放在程序的第一行,那就没问题。如果初始化函数之前出现了其他的代码,那四路 PWM 就会有三路不能输出。这个问题就很奇怪是吧?能不能输出 PWM 竟然和初始化函数在哪一行有关。那最终找到的原因呢,就是这里我的结构体成员没有配置完整,而且我也没给结构体赋初始值。所以为了避免程序中出现不确定的因素啊,我们要么就像上面这个一样,把结构体所有的成员都配置完整,有没有用我都给配置一下。要么就先给结构体成员都赋一个初始值,再修改部分的结构体成员。这里先改回来啊。那怎么给结构体赋初始值呢?我们就要用到这里的 struct 一例的函数了。这个 struct 一例的函数就是用来给结构体赋初始值的。

那我们复制一下这个 TIMOCstruct 一例的。放到这个定义结构体的下面一行啊。然后把结构体变量的地址传进去,这样就能给结构体赋初始值了。

我们可以转到这个函数的定义看一下哈。这个给结构体赋初始值的方法也非常简单,就是一个一个手动的给个值哈。右边这里就是它默认给的初始值。

好这是这个结构体赋值的一个注意事项哈,也是这个 Struct 一例的函数的用途。就是如果你不想把所有成员都列一遍赋值,那就可以先用 Struct 一例的赋一个初始值,再更改你想改的值就行了。

接着我们就来看看一下这个 OC 里的几个参数啊,第一个 OC mode 输出比较模式,转到定义看一下哈,它没有跳过去,我们从这里跳试一下。在这里可以看到这个 OC mode 的参数,右边是,这个参数可以是这里的一个值,搜索一下,这里可以看到哈,

这些就是我们上一小节介绍过的那几种输出比较模式,第一个 TIMING 就是冻结模式,第二个 ACTIVE 相等值时有效电平,第三个 Inactive 相等时置无效电平。第四个 Toggle 相等时电平翻转。下面这两个就是 PWM 模式一和 PWM 模式二的。然后下面这里还有 Force 的 Active、 Inactive,这就是强制输出的两种模式啊。不过这两个参数它不让我们初始化的时候使用,那我们也不用它啊,我们主要用的就是这个 TIMOC Mode PWM1,我们复制一下哈,放到这里。

这样输出比较模式就配置好了。接下来, OC polarity 输出比较极性,我们也转下定义。在这里啊,它可以是这里的一个值,我们还是搜索一下哈。

这里参数第一个是 High, 高极性,就是极性不翻转哈, REF 波形直接输出,或者说是有效电平是高电平, REF 有效时输出高电平,这都是一个意思哈。第二个是 Low 低极性就是 REF 电平取反,或者说是有效电平为低电平。这个可以根据你的电路需求来哈,那这里我就选择高极性这个,复制。放到这里,这样极性选择就完成了。

接下来是 Output State 输出状态,就是输出使能哈。跳转一下,在这里哈,然后选择这个,搜索一下

这里就是 Disable 失能和 Enable 使能了,这个不用说,肯定得使能了,复制。放在这里,这样输出使能就完成了。

那最后一个 pulse 这个名字比较奇怪哈, pulse 直译是脉冲的意思,它实际上就是用来设置 CCR 寄存器值。我们可以跳转定义看一下哈

这里注释写的就是指定 pulse 的值会被加载到 Capture Compare Register, 这个就是 CCR 捕获比较寄存器啊。然后这个参数可以是 0~ F F F F 之间的一个值,就是 16 位的范围哈。回到这里,这个 pulse 就是 CCR 的值。前面这里呢预分频器是 PSC, 这里周期是 ARR 哈。这三个值共同决定输出 PWM 的周期和占空比。

计算公式就在 PPT 的这里哈。那这个值我们等会再算哈。现在我们初始化还没有结束,我们还有一个 GPIO 没有初始化,现在我们已经把通道初始化好了,在 TIM2 的 OC1 通道上就可以输出 PWM 波形了,但最终这个波形肯定是要借用一下 GPIO 口才能输出对吧?那这个 TIM2 的 OC1 通道是借了哪个 GPIO 口呢?我们可以打开这个引脚定义表。

这边这一列默认复用功能就是片上外设的端口和 GPIO 的连接关系。在这里可以看到有 TIM2 CH1 ETR, 它是在这个 PA0 这一行的,这就说明 TIM2 的 ETR 引脚和通道一的引脚都是借用了 PA0 这个引脚的位置的。换句话说就是 TIM2 的引脚复用在了 PA0 引脚上。所以说如果我们要使用 TIM2 的 OC1,也就是 CH1 通道输出 PWM, 那它就只能在 PA0 的引脚上输出,而不能任意选择。要输出。同样,如果使用 TIM2 的 CH2 那就只能在 PA1 端口输出。 TIM2 的 CH3 就只能是 PA2, CH4 就只能是 PA3。这些其他的外设也同理哈,比如我们要使用 SPI1 的 MISO 引脚,那就是 PA6。如果要使用 I2C2 的 SCL 引脚,那就是 PB10。这个关系是定死的啊,不能任意更改。不过虽然它是定死的, STM32 还是给了我们一次更改的机会的,这就是重定义啊,或者叫重映射。

比如如果你既要用 USART1 TX 引脚又要用 TIM2 的 CH3 通道,它俩冲突了,没办法同时用。那我们就可以在这个重映射的列表里找一下,比如这里我们找到了 TIM2 的 CH3,那 TIM2 的 CH3 就可以从原来的引脚换到这里的引脚,这样就避免了两个外设引脚的冲突。如果这个重映射的列表里找不到,那外设复用的 GPIO 就不能挪位置,这就是重映射的功能哈。

配置重映射就是用 A FIOL来完成的,这个我们等会给试一下。这些就是外设引脚和 GPIO 引脚的复用关系和重映射的介绍。我们在使用外设的引脚时,需要多参考一下这个引脚定义表哈。那我们现在要使用 TIM2 的 CH1 通道输出 PWM, 通过这个表就知道它只能在 PA0 引脚输出,或者找一下重映射哈,可以看到这里有重映射的位置,所以如果使用重映射,它可以从 PA0 挪到这个 PA15 的引脚上,在其他的引脚就没有机会作为这个通道的输出引脚了。

好这样的话我们就知道该配置哪个 GPIO 口了。我们回到代码,在这上面的位置初始化一下给你一点吧。当然这个初始化的位置随意哈,你可以放在上面也可以放在下面,只不过我们习惯把 GPIO 放在上面哈。那在这里,我们打开这个 LED 点 c 的文件,复制一下这个触发代码,放在这里。

这个开启时钟的也别忘了哈。然后改一下,这个 GPIO,P 改成 GPIO,P 零,下面 GPIO,A 初始化没问题哈。另外这个 GPIO 模式也要更改哈,看一下参数。这里我们需要选择为 GPIO 木的 AFPP 复用推挽输出

为什么选择这个模式呢?

这个我们可以看一下 PPT 的这里啊。对于普通的开漏推挽输出,引脚的控制权是来自于输出数据寄存器的

那如果想让定时器来控制引脚,那就需要使用复用开漏推挽输出的模式。在这里输出数据寄存器将被断开,输出控制权将转移给片上外设。那通过刚才看到引脚定义表哈。我们就知道了,这里片上外设引脚连接的就是 TIM2 的 C H1 通道,所以只有把 GPIO 设置成复用推挽输出,引脚的控制权才能交给片上外设, PWM 的波形才能通过引脚输出。

回到这里,为什么选择复用输出应该就知道了吧?那我们复制这个参数,放到这里。这样输出 PWM 的 GPIO 就配置好了。最后一步, TIMx CMD 已被启动定时器啊, PWM 波形就能通过 PA0 输出了。

现在我们来看一下这里, ARR、 PSC 和 CCR 这三个参数吧,看一下公式哈。通过公式我们知道, PWM 频率等于计数器的更新频率, PWM 占空比等于 CCR 除以 AR 加一, PWM 分辨率等于 AR 加一分之一。如果我现在要产生一个频率为 1000 赫兹,占空比为 50%,分辨率为 1% 的 PWM 波形

那代入公式就是啊,72 兆除以 PSC 加一除以 AR 加一等于 1000, CCR 除以 AR 加一等于 50%。 AR 加一分之一等于 1%,那解一下就很容易得到哈。分辨率 1%, AR 加一就是 100,上面这里也就是 100 哈。占空比 50%, CCR 就是 50。频率 1000, PSC 加一算一下哈,就是 720。那在程序里 ARR 给 100-1, PSC 给 720-1, CCR 给 50。现在就是频率为 1 千赫兹,占空比为 50% 的 PWM 波形了是吧?

我们来试一下看看。那把这个函数的第一行放在头文件里声明一下,编译看看。

此时led已经点亮了,它现在其实是在以 1000 赫兹的频率闪烁,我们看不出来。那怎么看呢?我们可以用一下示波器哈。这个示波器是一个很强大的工具,大家如果有条件的话可以准备一个。但是示波器可能比较贵哈,如果没条件的话,可以看我这个现象哈,也不影响大家理解。那我这里用的虚拟示波器哈,这里可以接探头采集电路波形。然后电脑上有个软件可以显示波形,我们来看一下。这里我把探头的 GND 与 STM32 共地哈,然后探头接在 LED 的正极,就是 PA0 口。

这样就可以采集 PWM 的波形了。我们在电脑端打开探头的通道,可以看到现在就已经出现波形了哈,我们看一下。

可以看出啊,这里到这里是一个周期,时间是一毫秒啊,那频率就是一千赫兹,没问题。然后占空比,高电平,和低电平时间相等,占空比就是百分之五十,也没问题啊。这样就说明我们的参数设置是没问题的。那我们回到代码。

改下这个 CCR1 来调节一下占空比吧。我们可以把它改成 10,那占空比就是 10÷ AR1 加 1=10% 啊。看一下。这是占空比是 10, LED 的亮度就变暗了一些哈。

看一下示波器,可以看到这个高电平的时间就变短了,只占整个周期的 10%。频率仍然不变,还是 1000 赫兹。

那我们再改一下哈。 CR 改成 90,那占空比就是 90%

看一下。现在 LED 就变亮了,看一下示波器,高电平的时间占整个周期的 90%,这就是 PWM 的波形啊和驱动 LED 的效果哈。

那回到代码,我们来完成一下这个工程的最终效果吧,我们想让 LED 呈现呼吸

效果吧,我们想让 LED 呈现呼吸灯的效果,那就是不断更改 CCR 的值就行了,对吧?在运行过程更改 CCR,看一下。那我们就需要用一下这个函数啊, TIM_ SetCompare1 这个函数是用来单独更改通道一的 CCR 值的

我们复制一下哈,回到这里,在下面写个函数,封装一下吧。 void PWM_ SetCompare1 参数是 uint16_ t compare。然后里面调用一下这个 TIM_ SetCompare1 函数,第一个参数给 TIM2,第二个给 compare。这样就分装完了。之前这个初始化设置 CCR 的值啊,我们就不需要了,暂时给 0。

那我们把这个函数放在头文件声明一下啊。

然后到主函数这里来,我们只需要在这个 while (1) 主循环里不断调用 PWM_ SetCompare1 函数,更改 CCR 的值,这样就能完成 LED 呼吸灯的效果了。那我们先定义一个变量, uint8_7 i,然后主循环里 for, i 等于 0, i 小于等于 100 i 加加。现在这个 i 就是从 0~100 不断循环自增啊。然后 PWM set compare 一,参数给 i。这里没有函数提示哈,还有一个警告,是因为没有编译哈,软件还不知道我有这函数

最后别忘了 delay 10 毫秒哈,延时一段时间,要不然太快了。那从 0 变到 100 之后,我们再复制一下这个 for 循环,把这里的 i 改成 100 减 i,那占空比就是从 100 到 0, LED 逐渐变暗,这样就完成了。

再额外说明一下哈,这个 set compare 一函数是设置 CCR 寄存器的值,它并不直接是占空比。占空比是 CCR 和 AR R+一共同决定的,现在我 ARR 加一等于 100, CCR 的值才直接等于占空比。一般情况下都是要和 ARR 共同计算的哈,这个注意一下。

现在 LED 就是在不断的变亮变暗哈,呈现了呼吸灯的效果。再看一下示波器,可以看到占空比在不断变化哈。两个放在一起对照一下啊,这个现象就非常直观了是吧?大家感受一下这个。那这个 LED 呼吸灯的程序哈,我们就写完了。

引脚重映射:

最后我们再看一下之前说的引脚重映射,这个是怎么玩的吧。我们从引脚定义表里看到了,这个 TIM2 的 CG1 可以从 PA0 挪到 PA15 引脚上

怎么操作呢?我们就需要用到 AFIO 了。那首先要使用 AFIO 就要开启 AFIO 的时钟,我们复制一下这一行。这里改成 AFL. AFL 是 APB2 的设备,没有问题啊。

然后我们到这个 GPIO 点 c 区里来。现在我们需要用到这个 GPIO PinRemapConfig 引脚重映射配置,然后放到这里。

转到定义看一下啊,这里第一个参数选项非常多,里面都是重映射的方式啊,每个方式对应的重映射关系是啥呢?

这个可以看一下手册,在 AFL 这一节。有讲引脚重映射的功能哈。这里面有很多表,就是重映射方式和引脚更改的关系。那我们可以找到 TIM2 复用功能重映像的这个表,重映像就是重映射哈。里面有四种重映射的方式。第一种没有重映射,引脚就是 PA0123。第二种部分重映射方式一,引脚就是 PA15 PB3,前两个变了哈,后面两个 PA2 PA3 没有改变。第三种部分重映射方 12,引脚就是后面两个换成 PB10 PB11,前两个没有改变。第四种完全重映射,就是四个引脚都更改位置,这就是是重映射方式和引脚对应关系哈。其他的表也都是这个意思。大家有需要的话可以看一下这个表。

那如果我们想把 PA0 改到 PA15,就可以选择这个部分重映射方式一,或者完全重映射,这都可以哈。回到代码,这里我们就找一下,可以看到这里 TIM2 有部分重映射一,部分重映射二和完全重映射。如果都不使用,那就是没有重映射,对应表里的四种方式哈。

那我们这里选择部分重映射一的参数,然后放到这里。第二个参数 disable,这样就能把 PA0 换到 PA15 了。

但是现在还要注意一个问题,我们回到这个引脚定义表里啊,看一下,在引脚定义表里,这个 PA15 我是没给加粗的,因为它上电后已经默认复用为了调试端口, PTDI。所以如果想让它作为普通的 GPIO 或者复用定时器的通道,那还需要先关闭调试端口的复用啊,怎么关闭呢?

也是用这个 GPIO_ PinRemapConfig 函数。看一下参数啊,这里有三个参数,就是用来解除调试端口的复用的。

SWJ 就是 SWD 和 JTAG 这两种调试方式哈。第一个 No GTRST 就是解除 GTRST 引脚的复用。在引脚定义里看一下,就是这个 N GTRST,也就是 PB4。如果使用这个参数,那么这个 PB4 就变为正常的 GPIO 口了

其他的四个端口仍然是调试端口,不能当做 GPIO 来使用。

然后看第二参数 JTAG disable 这个就是解除 JTAG 调试端口的复用在引脚定义里就是 PA15、 PB3、 PB4 这三个端口变回 GPIO。

上面的 PA13 和 PA14 仍为 SWD 的调试端口。

最后看第三个参数, SWJ disable。

这个参数就是把 SWD 和 JTAG 的调试端口全部解除。在引脚定义里,就是这五个引脚全部变成普通的 GPIO,没有调试功能了。

所以这个参数千万不要随便调用哈,一旦你调用这个参数并且下载程序之后,那么你的调试端口就没有了。这之后再使用 STLink 就下载不进去程序了。这时就只能使用串口下载,下载一个新的,没有解除调试端口的程序,这样才能把调试端口弄回来。所以使用这个参数要小心一点哈。那这些参数和 GPIO 能不能使用的情况啊,在手册里也有写。在这里有个表啊, IO 口能用还是不能用,大家可以看一下,和我刚才描述的内容是一样的。

回到这个引脚定义表啊,在这里如果我们需要用 PA15、 PB3、 PB4 这三个引脚,那通常就是解除 JTAG 的复用,保留 SWD 的复用。

所以这里参数我们就选 SW SWJ TAG disable, 复制,放在这里。然后第二个参数 disable。 这样就可以正常使用 PA15 这个引脚了。

总结一下就是,如果你想让 PA15、 PB3、 PB4 这三个引脚当做 GPIO 来使用的话,那就加一下这里的第一句和第三句,先打开 AFL 时钟,再用 AFL 将 JTAG 复用解除掉,这样就行了。如果你想重映射定时器或者其他外设的复用引脚,那就加一下这里的第一句和第二句,先打开 AFL 时钟,再用 AFL 重映射外设复用的引脚,这样就行了。如果你重映射的引脚又正好是调试端口,那这三句就都得加上打开 AFL 时钟,重映射引脚,解除调试端口,这样才行。

好,那有关重映射和解除调试端口的内容我就讲完了。然后回到代码,现在有了这三句,我们定时器的通道一就从 PA0 挪到 PA15 了。

所以下面 GPIO 初始化这里, GPIO Pin0 也得改成 GPIO Pin15。因为已经挪到 PA15 了,所以就要初始化 PA15 的 GPIO, 而不是 PA0 了。那现在重映射的代码就写好了,我们来试一下哈,编译。下载看一下,现在 PA0 的呼吸灯就不亮了。

我们再插一个 LED 在 PA15 上试试,正极接 PA15,负极接接地。可以看到现在定时器的通道一就已经成功的挪到 PA15 来了,这就是重映射的功能,可以更改复用的引脚。那到这里我们第一个代码就算完成了哈。

PWM驱动直流舵机机

这里是我们套件里的 SG90 舵机,它有三根线哈。第一个 GND 就是棕色线,接在面包板的 GND。 第二个 5V 正极就是红色线啊,这里要接 5V 的电机电源。大家不要把它接在面包板的正极了哈,这个正极只有 3.3V 的电压,而且输出功率不大,带不动电机的。所以我们需要把它接在 STLink 的 5V 输出引脚。从引脚标号看,这最下面的两个引脚都是 5V 输出脚哈,接哪个都行。那我们这里就接在右下角的这个引脚了,这里输出的直接是 USB 的 5V 电源哈,这个功率足够驱动电机的。然后看第三个引脚, PWM 信号就是橙色线,接在 PA1 引脚上。这里我换了个通道哈,上个代码用的是 PA0 的通道一,这里用的是 PA1 的通道二,这都是可以的哈,你想选哪个就选哪个。那最后再在 PB 接一个按键,用来控制舵机。这样这个电路就完成了。

所以我们回到代码,在 PWM 初始化这里改一下。首先我们改下通道,我们现在用的是 PA1 口的通道 2,所以这里 GPIO 初始化就改成 GPIO pin1,上面这个也不需要了啊,先删掉。

然后后面这里 TIMOC1 一立特是通道一的初始化,所以我们要改成 OC2 一立特,这样这个结构体的参数就会配置到通道 2 了。

当然如果你通道一和通道 2 都想要用的话,那就直接在这里加两行代码,把通道一和通道二都初始化了,这样就能同时使用两个通道来输出两个 PWM 的。同理,通道 3 和通道 4 也是可以使用的。

那对于同一个定时器的不同通道输出的 PWM 它们的频率啊,因为不同通道是共用一个计数器的,所以它们的频率必须是一样的。它们的占空比呢,由各自的 CCR 决定,所以占空比可以各自设定。还有就是它们的相位,由于计数器更新啊,所有 PWM 同时跳变,所以它们的相位是同步的。这就是同一个定时器不同通道输出 PWM 的特点。如果驱动多个舵机或者直流电机啊,那使用一个定时器不同通道的 PWM 就完全可以了。然后继续看代码。嘛。这里就只调用 OC2 一例的函数就行了哈。

最后这里 set compare 一改成 compare 二,头文件里也改一下哈。这样就能使用通道二了,编译一下。

然后我们看一下参数,这里 ARR、 PSC 和 CCR 就是我们要设定的参数。还是看一下 PPT 的这里哈。

第一个频率是 72 兆除以 PSC 加一,再除 AR 加一。它要等于舵机要求的频率。

舵机要求的周期是 20 毫秒,那频率就是一除 20 毫秒等于 50 赫兹啊。占空比这里,舵机要求高电平时间是 0.5 毫秒到 2.5 毫秒。要计算占空比的话也是可以算的哈,这个等会再说。那在这里 PSC 和 ARR 的参数并不是固定的哈,这个可以自己多尝试几次,找一个比较方便计算的值。那经过我的尝试呢,我设置 PSC 加一为 72, ARR 加一为 20K, 这样的话满足第一个等式,所以最终频率是 50 赫兹。

同时呢 20K 对应 20 毫秒好,那 CCR 设成 500 就是 0.5 毫秒, CCR 设成 2500 就是 2.5 毫秒。这样的话参数也是非常直观哈。

所以回到程序这里, ARR 的值就给 20K 减一, PSC 就给 72 减一。

下面 CCR 的取值范围是 500~2500,对应的就是 0.5 毫秒~2.5 毫秒。那这里的初始值就给 0 吧,我们使用下面这个函数,在运行过程中设置 CCR。 现在 PWM 就初始化好了,我们来看一下现象。

在主函数这里先把这些删掉哈。我们调用一下 PWM_ SetCompare2,给个 500。那现在舵机用 0~180 度来表示的话,就应该是 0 度的位置。

我们编译试一下哈,没有错误,下载。看一下,现在舵机就移动到 0 度的位置了。

我们稍微掰一下这个输出轴哈,可以感觉有一股力在维持它现在的位置。

然后我们用示波器来验证一下波形看看,把这个示波器探头接在 PA1 引脚,看一下。现在这个频率是 50 赫兹,这里到这里是 20 毫秒,没有问题啊。

然后看一下高电平时间,从这里到这里是 500 微秒,就是 0.5 毫秒啊,都符合要求。

那接着我们改下参数,改为 2500,对应 180 度,试一下啊。

现在可以看到舵机就转到 180 度的位置了

看一下示波器哈,频率还是 50 赫兹,高电平时间为 2.5 毫秒。

再改一下 1500 对应 90 度试一下啊。现在就是在 90 度的位置,

示波器这边呢是 1.5 毫秒,都符合预期啊,这就是舵机的操作方式。

那回到代码,我们来完成一下最终功能哈。

首先我们先给舵机建一个模块,我们想要的函数是舵机设置角度,参数是 0~180 度啊,调用一下就能变为对应的角度,这样才直观方便对吧?而不是这里的 PWM 设置 CCR, 参数是 500~2500,这参数谁知道是啥意思呢?不直观不方便对吧?所以我们再建一个舵机的模块,封装一下这个函数。那这里我快速建一个模块啊

PWM_set compare 二,参数这里就要稍微计算一下了哈。我们来看一下,这里零度对应五百,一百八十度对应两千五百。那如果给一个角度,怎么得到 CCR 的值呢?

这里首先我们要对 angle 进行缩放,0~180 度是 180 的范围,500~2500 是 2000 的范围。所以这里 angle 除以 180 乘以 2000 就得到目标比例了,再加一个偏移哈,加五百。这样就完成了 0~180 到五百到 2500 的映射。验证一下啊,

angle 等于零零除以 180 再乘 2000 等于零,再加 500,得到的是 500,对应 0 度啊。

angle 等于 180, 180 除以 180 再乘 2000 等于 2000,再加 500,得到的是 2500 对应 180 度。由于映射是线性的啊,所以里面的值肯定都是一一对应的,没问题。所以我们就把舵机的代码封装好了。

主函数:

PWM驱动直流电机

这里红色的是 TB6612 电机驱动模块哈。它的第一个引脚 VM 电机电源,同样的也是接在 STLink 的 5 伏引脚。第二个 VCC 逻辑电源,接在面包板 3.3 伏正极哈。第三个 GND 电源负极,接在面包板的负极。之后 AO1 AO2 电机输出端接电机的两根线。这个接线不分正反哈,如果你对调这两根线,那电机旋转的方向就会反过来。然后右边这里是另一路的驱动,如果你需要接两个电机,那就在右边再接一个电机。机,如果只需要一个电机,那就随便选一路哈,另一路空着就行。

那接着继续看上面这里的接线。 STBY 待机控制脚,不需要待机哈,直接接逻辑电源正 3.3 伏。剩下的三个是控制引脚, AIN1 和 AIN2 是方向控制,任意接两个 GPIO 就行了。这里我接的是 PB4 和 PB5 两个脚。

最后一个 PWM A 是速度控制,需要接 PWM 的输出脚,这里我接的是 PA2 这个引脚。这里我又换了个通道哈, PA2 对应的是 TIM2 的通道 3,到时候初始化通道 3 就行了。另外这里还是接一个按键哈,在 PB1 口,用于控制。这就是这个接线图了。

目前这个代码是在通道一输出一个 1000 赫兹的 PWM,我们电机接在了通道三上,所以这里先改一下, GPIO_ Pin_ 0 改成 Pin_ 2,下面 OC1_ IT 改成 OC3_ IT,然后下面 set compare 一改成 compare 三,最后头文件这里一改成三,也别忘了哈,这样就行了。那接下来对于直流电机,我们也给它建立一个模块。

它底层的 PWM 已定了,初始化一下 PWM。 同时在这个电机的模块里还多了一些东西哈,就是电机方向控制的那两个角。所以在这里我们要再额外初始化方向控制的两个角。那我们还是打开 LED 点 C, 复制一下这里的初始化代码,然后放在这里。改下哈,这里开启 GPIOB 时钟没问题。下面 GPIOPin 改成 GPIOPin4 或上 GPIOPin5,之后初始化 GPIOB,这样就把电机方向控制角初始化好了。

那初始化结束,我们接下来就要写设置速度的函数了。在这里, void MotorSetSpeed 参数要给一个带符号的速度变量哈,负数用来表示反转。这个速度值我们定为-100~100 哈,所以这里参数类型就给 int 8 杠 t,名字给 Speed。然后在函数里面,针对正转和反转我们用 if 来分别处理哈。

if Speed 大于等于 0 这里就是正转的逻辑。在里面我们首先把方向控制角设置为一个高电平一个低电平,哪个为高哪个为低不重要哈,就是极性不一样而已。所以我们就设置 GPIOSetBits, GPIOA, GPIOPin4, GPIOResetBits。 GPLA, GPLP 五,这样方向就设置好了。最后是速度, PWM, Set Compare 三,这里应该是三哈,我们还没编译,所以提示有点问题。把 Speed 变量传进去,这样速度就也设置完成了,电机就能转起来了。

接着我们把反转的逻辑也完成一下。 Else,这里 Speed 就是负数,代表反转。那在这里面把上面这三行复制下来,改一下哈。首先正反转,那就 Set 和 Reset 反过来就能反转了。然后还要设置 PWM 这里,因为这时 speed 为负数, set compare 必全正数,所以 speed 的前面要加一个负号,这样就行了。那我们来测试一下,把把这两个函数放在头文件里声明一下。

同时现在可以发现一个问题哈,就是这个电机会发出蜂鸣器的响声。在堵转的时候很明显啊,转起来的话由于电机的声音听不太清。因为电机里面也是线圈和磁铁,所以在 PWM 的驱动下会发出蜂鸣器的声音,这是正常现象哈。如果你不介意的话可以不管,如果介意的话,那怎么避免这个问题呢?

答案就是加大 PWM 频率。当 PWM 频率足够大时,超出人耳的范围,人耳就听不到了。经过查询呢,人耳听到声音的频率范围是 20 赫兹到 20 千赫兹。我们目前给的是 1 千赫兹人耳是能听到的,所以在这里我们可以加大频率。加大频率呢我们可以通过减小预分频器来完成,这样不会影响占空比哈。所以我们给这个预分频器去掉一个 0,现在就是 10 千赫兹了。

当然还不够,我们再把 72 改成 36,变为一半。现在就是二十千赫兹了,我们再试一下看看。下载,现在堵转试一下,就听不到声音了哈。

在这里如果你电机的正反转方向和你想要的方向不一样哈,就是极性反了。这个有很多地方可以调换过来,比如最简单的电机两根线反过来接,或者输入的 IN1 和 IN2 反过来,在程序里也可以哈,把这里的 SET 和 RESET 反过来,这都可以改变极性。另外这里引脚哈,也可以用宏定义给它统一改一下名字,这样如果你换引脚的话,比较方便修改,那这里我就不再演示了哈。好

相关推荐
不脱发的程序猿1 小时前
如何让Skill同时跑在Cursor、Codex和Claude Code里?
单片机·嵌入式硬件·嵌入式
longxiangam2 小时前
esp-idf dsi 屏幕的驱动实现原理—— 关于零拷贝和 DMA 永续刷新
c语言·单片机·嵌入式硬件
星夜夏空992 小时前
FreeRTOS学习(6)——任务创建
单片机·嵌入式硬件·学习
Lance_mu3 小时前
UFS协议学习大纲
嵌入式硬件·七牛云存储
二进制10113 小时前
基于stm32的按键驱动框架的编写
stm32·单片机·嵌入式硬件
VALENIAN瓦伦尼安教学设备3 小时前
激光对中仪应用行业及全球市场份额解析
大数据·人工智能·嵌入式硬件
coward914 小时前
Linux内核驱动初始化流程认识(关于late_initcall和modul_init驱动初始化宏差异)
linux·嵌入式硬件
ACP广源盛139246256734 小时前
GSV2221@ACP# 高带宽低功耗显示转换芯片,赋能 TRAE SOLO 设备高清扩展升级
人工智能·嵌入式硬件·电脑·音视频
bbaydnog4 小时前
嵌入式面试高频题第4弹:函数指针进阶、堆栈分析、Makefile入门,这3个答不上来就悬了
单片机·面试·职场和发展