STM32-智能小车项目

server/2025/3/6 21:21:30/

项目框图

ST-link接线

实物图:

正面:

 反面:

相关内容

使用L9110S电机模块

电机驱动模块L9110S详解 | 良许嵌入式

测速模块

 

语音模块SU-03T

网站:智能公元/AI产品零代码平台

一、让小车动起来

新建文件夹智能小车项目

在里面复制19-串口打印功能

重命名为01-让小车动起来

新建文件夹motor,如下图所示

打开项目

加载文件

先更改引脚

然后分装函数

前进,后退,左转,右转,停止函数

PB0、PB1引脚控制的是左轮:

当PB0引脚为高电平,PB1引脚为低电平的时候,左轮向前走

当PB0引脚为低电平,PB1引脚为高电平的时候,左轮向后走

当PB0引脚与PB1引脚电平相同的时候,左轮停止

PB2、PB10引脚控制的是右轮

当PB2引脚为高电平,PB10引脚为低电平的时候,右轮向前走

当PB2引脚为低电平,PB10引脚为高电平的时候,右轮向后走

当PB2引脚与PB10引脚电平相同的时候,右轮停止

#include "motor.h"//初始化电机函数
void motor_init(void)
{GPIO_InitTypeDef gpio_initstruct;//打开时钟__HAL_RCC_GPIOB_CLK_ENABLE();                           // 使能GPIOB时钟//调用GPIO初始化函数gpio_initstruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_10;          // 两个LED对应的引脚gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;             // 推挽输出gpio_initstruct.Pull = GPIO_PULLUP;                     // 上拉gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;           // 高速HAL_GPIO_Init(GPIOB, &gpio_initstruct);motor_stop();
}
//前进
void motor_go_forward(void)
{//左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);//右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
}
//后退
void motor_go_backward(void)
{//左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);//右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
}
//左转
void motor_go_left(void)
{//左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);//想要一个轮子不动,就让它两个引脚都等于reset或者setHAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);//右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
}
//右转
void motor_go_right(void)
{//左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);//右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
}
//停止
void motor_stop(void)
{//左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); //右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
}

二、串口控制小车

注意:串口控制小车与蓝牙控制小车类似

想要实现的效果是:

M1:前进

M2:后退

M3:左转

M4:右转

其他M:停止

复制01-让小车动起来

重命名为02-串口控制小车

将蓝牙模块的文件粘贴到改项目中进行修改

打开项目文件

加载文件

将蓝牙模块的串口2代码粘贴到02项目中

将接收到的串口2的数据打印到串口1中

编译

调试代码

烧录

记得把串口2连接上,用两个串口助手显示结果

串口助手1->连接串口1->用于接收串口2发送过来的数据->波特率是115200

串口助手2->连接串口2->用于发送数据(M1\M2\M3\M4\其他数据)->波特率是9600

注意:在烧录完代码之后,一切准备就绪,发现在串口2中发送输入,串口1接收不到,这时候不要慌,先把两个串口助手断开,然后再次选择串口号,连接串口,这时候在输入数据,就会有反应了。

注意:如果你的电脑中usb接口不足,板子选择使用面包板接电就会出现一个小错误,面包板中只有一个3V3在供电,这个时候,使用杜邦线给32板子接电,就会出现在串口2中输入M1前进之后再输入M2后退,小车会停下来,串口助手1中会显示hello word。如下图所示:

再次输入M2小车会后退,这是因为供电不足,电压不稳,所以想要达到输入M1就是前进,输入M2就是后退,解决办法:需要使用USB外接电源供电。

三、蓝牙控制小车

将上述的串口控制小车中的串口2所接的引脚替换成蓝牙模块

然后手机连接蓝牙

输入数据,小车就会行动

要注意蓝牙所设置的波特率,不然不能正确接收数据

蓝牙当初所设置的波特率是115200,所以要在主函数中调用蓝牙初始化函数时更改波特率

 

四、点动控制小车

        当摁下前进按键的时候,小车就会疯狂的向前走,那么现在要实现,当摁一下前进,小车就前进一下,实现点动控制小车。

复制项目文件02,重命名为,点动控制小车

打开项目

只添加这两行代码即可

剩下的在软件上完成

原理:

在主函数的while函数中,有一个停止的函数,小车最开始的状态就是停止状态,想让小车开始移动,就要打断这个while循环,所以就要用到中断,所以点动的代码要在中断函数中写,所以在中断回调函数中,有让其移动的代码,将这个代码延时一下,比如10ms。那么程序在执行完移动代码之后就会执行这个延时函数,然后再回到while循环中执行停止小车的函数,所以就实现了,10ms的点动,而,需要长时间的移动小车,就要在软件中找补了。

在串口助手中

(把蓝牙摘下去换上串口2,记得串口2的波特率已经改为115200了)

串口助手2可以使用数据流循环发送,设置好发送的间隔,就可以实现长时间的移动。

设置的时间太长,没办法将小车动作连续起来

时间太短,小车跑的太快

其实这个整套下来,像是将片段的小车移动整合起来变成一个连续的小车移动

将小车独立出来的接线:

现在小车的串口2连接蓝牙模块

ST-LINK和串口1可以在烧录完代码之后拔掉

32的5V电压和GND可以连接面包板的正负极

面包板的电压计算:四块1.5V的电压=1.5*4=6V

五、PWM调速控制小车

小车接收到不同的电压值,跑的速度也是不一样的

电压越大,速度越快(并不是电压越大越好,小心烧掉板子)

为了避免小车一开始速度就很快可以使用PWM调速

小车的电机与32板子连接的电压是3.3V,所以小车全速前进的时候电压是3.3V,如果把电压降低至2V小车的速度就会降下来,1.5V更慢。

复制01项目,重命名为04-PWM调速控制小车

复制呼吸灯实验中BSP文件中的pwm文件夹

粘贴到04-PWM调速控制小车的BSP文件中

打开项目,加载文件

记得加载HAL库中的定时器文件tim.c,tim_er.c

编译无错

修改pwm文件

将这个的TIM4修改为TIM2,TIM2的两个通道在这里用到

因为TIM4的两个通道GPIO口可能会占用到

这里就要注意,做项目前要对资源进行合理的分配,这很重要

这里需要更改电机的引脚

想要达成每个电机使用不同的通道

这里不用看PB引脚的通道,因为左轮用一个通道,右轮用一个通道,所以PB引脚的通道并不重要,不需要关注。

这里注意,通用定时器


代码修改如下:

这里注意,原来我pwm_init函数中的参数是psc在前,arr在后,然后主函数中声明pwm_init();时写的参数20-1,72-1,这里传参的顺序错了,所以在这里pwm文件中调换了psc和arr的顺序。

复习CCR是什么?

主函数代码如下:

产生的疑问?

        STM33F103C8T6智能小车项目中使用循迹+PWM调速小车时,小车电机实际接线的引脚是PA0接A-1A、PB1接A-1B、PA1接B-1A、PB10接B-1B和PWM定义的接口引脚是PA0和PA1,但是在小车电机文件中定义的引脚是PB0接A-1A、PB1接A-1B、PB2 接B-1A、PB10接B-1B,这时候使用pwm调节CRR的值可以控制小车车速,但是为什么项目所定义的电机文件中的引脚和小车实际接线的引脚不一致的情况下,小车还可以动?

DeepSeek回答

关于STM32F103C8T6智能小车项目中,电机实际接线与代码中定义的引脚不一致,但小车仍然可以动的原因。我需要仔细分析这个问题,并给出合理的解释。

首先,用户提到实际接线是PA0接A-1A,PB1接A-1B,PA1接B-1A,PB10接B-1B。而代码中定义的引脚是PB0接A-1A,PB1接A-1B,PB2接B-1A,PB10接B-1B。但使用PWM调节CCR(应该是PWM的捕获/比较寄存器)来控制车速,小车却能动。这看起来矛盾,因为引脚定义不一致,按理说电机可能不会正常工作,或者完全不动。

我需要考虑几个可能性:

‌硬件接线错误,但代码中错误地配置了其他引脚‌:比如,虽然代码定义的是PB0,但实际可能错误地配置了PA0,导致两者巧合地对应。这可能是因为代码中的初始化部分错误地将PB0配置为PA0,或者存在其他配置问题。

‌复用功能或引脚映射‌:STM32的某些引脚可能有复用功能,或者通过重映射功能改变了实际控制的引脚。例如,定时器的通道可能被映射到不同的引脚,导致代码中定义的PB0实际上输出到了PA0。

‌代码中的引脚配置与实际不符,但部分引脚正确‌:例如,虽然A-1A和B-1A的引脚定义错误,但A-1B和B-1B的PB1和PB10正确,这样可能有一半的电机驱动正确,而另一半可能被其他因素(如共地或短路)导致电机仍能转动,但表现异常。

‌PWM信号通过其他方式传递‌:可能存在飞线或电路板上的短路,导致PWM信号从代码定义的引脚传输到了实际接线的引脚。

接下来,我需要进一步分析这些可能性。首先检查代码中的GPIO初始化部分,查看是否正确配置了引脚。例如,如果代码中错误地将PB0配置为PWM输出,但实际硬件连接到了PA0,但PA0可能被配置为其他功能(如普通GPIO输出),这时候如果PA0被设置为高电平,可能电机一直全速运行,而PWM调节无效。但用户提到可以通过调节CCR控制车速,这说明PWM可能确实在起作用。

另一种可能是,虽然代码中定义的引脚是PB0、PB2,但实际使用的定时器通道对应的引脚是PA0和PA1。例如,定时器2的通道1和2可能默认映射到PA0和PA1,而代码中错误地配置了PB0和PB2,但由于没有重映射,实际输出仍然在PA0和PA1。这种情况下,即使代码中试图配置PB0和PB2为PWM,但由于这些引脚对应的定时器通道不同,可能无法输出,但用户的情况却可以工作,这似乎矛盾。

或者,用户可能使用了不同的定时器,例如,PA0和PA1属于定时器2,而PB0、PB1属于定时器3,但代码中错误地配置了定时器3到PB0,但实际硬件连接到了定时器2的PA0,这时候如果代码中错误地配置了定时器3,但实际没有启用,而PA0可能被其他代码(如直接GPIO控制)驱动,这可能无法解释PWM调速的有效性。

可能需要考虑代码中的具体配置,比如使用的定时器和通道,以及引脚重映射情况。例如,STM32的某些定时器通道可以重映射到其他引脚,如果代码中启用了重映射,那么实际输出引脚会改变。例如,定时器3的通道1默认是PA6,但部分重映射后可能是PB4,完全重映射可能是PC6。如果用户代码中配置了重映射,但实际硬件连接到了默认引脚,或者相反,可能导致引脚不一致但信号仍然输出。

另外,用户提到调节CCR可以控制车速,这说明PWM信号确实在正确的引脚上输出。因此,可能的情况是,虽然代码中定义的引脚名称和实际接线不一致,但实际配置的定时器通道对应的物理引脚是正确的。例如,代码中定义PB0,但实际配置的是定时器3通道1,而该通道的引脚可能是PB0,但硬件连接到了PA0,这似乎矛盾,除非存在硬件错误或飞线。

或者,用户可能混淆了引脚的定义,例如在代码中错误地命名了引脚,但实际配置的是正确的引脚。比如,代码中将电机A的PWM引脚定义为PB0,但在初始化时实际配置的是PA0,这可能是代码中的注释或变量名错误,而实际代码正确。

另一个可能性是,电机驱动模块的使能端被固定启用,例如ENA和ENB被接高电平,此时电机的方向由IN1和IN2控制,而PWM信号实际上控制的是速度,但若方向引脚配置正确,即使PWM引脚错误地输出高电平,电机也会全速运转。但用户提到可以调节速度,所以PWM必须有效。

综合来看,最可能的解释是代码中实际配置的引脚与变量名或注释中的定义不一致,但初始化部分正确配置了实际接线的PA0和PA1。例如,虽然在代码的注释或变量名中写的是PB0和PB2,但初始化函数中正确地设置了PA0和PA1作为PWM输出。这可能是一个代码书写错误,但实际配置正确,导致电机正常运行。

或者,可能存在引脚复用的情况,例如PA0和PB0都被配置为同一个定时器的不同通道,但这种情况在STM32中不太可能,除非使用了不同的定时器。

需要进一步考虑STM32F103的定时器通道和引脚映射。例如,查看PA0和PB0分别属于哪个定时器的哪个通道。PA0对应定时器2的通道1,而PB0可能属于定时器3的通道3,或者其他定时器。如果代码中配置了定时器2的通道1,那么无论变量名如何,实际输出会在PA0,而如果变量名错误地写成了PB0,但初始化代码正确,那么电机就能正常工作。

因此,问题可能出在代码中的引脚定义变量名与实际初始化时使用的引脚不一致,但初始化部分正确配置了实际引脚。例如:

在代码中可能有:
#define MOTOR_A_PWM_PIN PB0
#define MOTOR_B_PWM_PIN PB2

但在初始化时,错误地配置了PA0和PA1:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 实际是PA0
或者使用了正确的定时器配置,例如Timer2的通道1和2,对应的PA0和PA1,而变量名错误地写成了PB0和PB2。

因此,尽管代码中的定义和实际接线不一致,但由于初始化部分正确配置了PA0和PA1,所以PWM信号正确输出,电机能够运行,并且通过调节CCR(即定时器的捕获/比较寄存器)改变占空比,从而控制速度。

总结起来,可能的原因是代码中引脚定义的变量名或注释错误,但实际的硬件初始化和定时器配置正确,导致虽然文档或变量名不一致,但实际信号输出正确,因此小车可以正常运行。

📅 ‌时效性分析‌:用户问题不依赖时效性数据,但为确保准确性,当前分析基于 ‌2025年02月25日‌ 的技术背景。

🚗 ‌问题核心‌

在引脚定义与实际接线不一致的情况下,小车仍能运行的根本原因在于 ‌代码与硬件实际信号流的匹配性‌。以下是关键分析:

🔧 ‌原因解析‌
1️⃣ ‌PWM信号实际生效的引脚与代码定义不同‌
‌可能场景‌:
代码中虽然名义上定义引脚为 PB0 和 PB2,但底层驱动(如定时器配置)可能错误/隐式地指向了 PA0 和 PA1。
‌示例‌:
c
Copy Code
// 代码中名义定义引脚为 PB0(错误)
#define MOTOR_A_PWM_PIN PB0
// 实际初始化时误配置为 PA0(正确)
TIM_OC1Init(TIM2, &pwm_config);  // TIM2_CH1 默认映射到 PA0

‌结果‌:PWM信号通过 PA0/PA1 输出,与硬件接线一致,因此可调速。
2️⃣ ‌部分引脚功能重合或短路‌
‌可能场景‌:
若 PB0 与 PA0 在硬件上被短接(如PCB设计错误或飞线),代码对 PB0 的操作会实际影响 PA0。
部分电机驱动模块(如L298N)的使能端(ENA/ENB)直接接高电平,此时方向引脚(如A-1A/A-1B)的PWM占空比仍可调速。
3️⃣ ‌代码未严格绑定引脚定义‌
‌可能场景‌:
电机驱动代码通过 ‌定时器通道‌(如 TIM2_CH1)控制PWM,而非直接依赖引脚名。
‌示例‌:
c
Copy Code
// 实际控制PA0的是TIM2_CH1,无论引脚名如何定义
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);  // PA0输出PWM

📊 ‌验证方法‌
‌示波器/逻辑分析仪检测‌:
检查 PA0 和 PB0 的PWM波形,确认实际生效的引脚。
‌代码调试‌:
跟踪PWM初始化代码,验证定时器通道与引脚的映射关系(参考《STM32F103参考手册》的定时器复用功能表)。
‌硬件排查‌:
检查是否存在 PB0 与 PA0、PB2 与 PA1 的意外短路。
🛠️ ‌解决方案建议‌
‌统一代码与硬件定义‌:
修改代码中的引脚定义,使其与实际接线一致(如将 PB0 改为 PA0)。
‌明确定时器通道映射‌:
在代码中显式配置引脚重映射(如需),避免隐式依赖默认映射。
‌示例‌:
c
Copy Code
GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE);  // 显式重映射TIM2通道

‌添加注释与文档‌:
在代码和硬件手册中明确记录引脚对应关系,减少后续维护成本。
✅ ‌总结‌

引脚定义不一致但小车能运行,本质是 ‌信号流在硬件或代码层被隐式修正‌。需通过硬件检测和代码审计定位具体原因,最终确保定义与实物的严格一致。

六、循迹小车

循迹模块详解 | 良许嵌入式

注意这里的接线改回去了,要是不改回去会出错,小车会不要电的一直跑

复制01重命名05-循迹小车

循迹模块和红外传感器模块很像,所以复制智能台灯的ia_sensor

粘贴到05文件的BSP文件中,并且重命名为track

打开文件

加载文件

七、循迹+PWM调速

        上面循迹小车中,可能会出现由于地图太小,小车太大,太沉,电机电压够足导致小车速度太快从而小车在拐弯的时候,冲出地图跑道。

        为了避免这这种情况的发生,可以选择换一张大地图、把小车重量减轻、体积减轻、让小车变得更灵活,或者把小车电机电压减少,小车速度放慢。

这里使用PWM调速小车

使用PWM的接线

复制04pwm文件重命名为06-循迹+pwm调速控制小车

将05中的track文件粘贴到06中

打开工程,加载文件

这里注意改变arr和psc的值

因为原来的20-1,72-1,步子太大了

上述中小车可以设置的pwm波为0-20,如果设置9太快了,如果设置8太慢了,所以让它步子小一点,这样可以设置的值就更精确了

具体的数值,需要结合实际情况自己设定,这里95的速度只符合我这里

八、跟随小车

跟随小车要达到的效果是:

当两边的跟随模块都检测到东西的时候,就向前走,当右边检测到东西的时候,就向右走,否则向左走,当都未检测到东西的时候,就停止。

复制01,重命名为07-跟随小车

将循迹模块的track重命名为followu

打开项目,加载文件,将所有的track重命名为follow

更改引脚

代码如下:

九、避障小车

复制项目文件01,重命名为08-避障小车

复制超声波实验和舵机实验的文件,粘贴至如下图所示

接线如图所示:

打开项目

加载文件

要加入定时器文件

舵机代码可以分装成左转,右转,中间,方便看

超声波的代码就是按照上面的接线,改一下引脚,就可以直接用了

主函数代码如下:

#include "sys.h"
#include "delay.h"
#include "uart1.h"
#include "motor.h"
#include "sg90.h"
#include "hcsr04.h"int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */uart1_init(115200);motor_init();sg90_init();hcsr04_init();printf("hello word!\r\n");double dis_left = 0, dis_middle = 0, dis_right = 0;sg90_turn_middle();while(1) { dis_middle = hcsr04_get_lenght();//接收超声波所测的距离if(dis_middle > 35)//判断距离是否大于35?是向前走motor_go_forward();else if(dis_middle < 15)//否则判断距离是否小于15?是后退motor_go_backward();else //否则就是距离>15并且<35{motor_stop();  //先停下来//向左看并测量左侧距离sg90_turn_left();delay_ms(500);  //给个时间进行测量dis_left = hcsr04_get_lenght();   //存储下左侧的长度//居中sg90_turn_middle();   //超声波传感器回到中间的位置delay_ms(500);//向右看并测量右侧距离sg90_turn_right();delay_ms(500);dis_right = hcsr04_get_lenght();//根据距离判断左转还是右转if(dis_left < dis_right)motor_go_right();elsemotor_go_left();delay_ms(500);  //给个时间反应一下motor_stop();  //电机停一下sg90_turn_middle();  //舵机回到中间的位置}delay_ms(100);  //给个时间反应一下}
}

十、测速模块

接线如下图所示:

复制02-串口控制小车

重命名为09-测速小车

新建speed文件

打开项目,加载文件

别忘了把定时器的驱动文件加载进来

写头文件

注意:测速模块要用到中断,每次感受到一个下降沿就中断一次,目的为了更好的测速

然后使用定时器,每一秒定时一次

代码如下:

#include "speed.h"
#include "delay.h"
#include "stdio.h"uint16_t speed_cnt = 0;
TIM_HandleTypeDef timer_handle = {0};//定义结构体//gpio口初始化
void speed_gpio_init(void)
{//使能GPIO时钟GPIO_InitTypeDef gpio_initstruct;//定义一个结构体gpio_initstruct//打开时钟-使能GPIO时钟__HAL_RCC_GPIOB_CLK_ENABLE();//设置GPIO输入模式//设置AFIO(开启时钟,IO口映射)//设置EXTI(屏蔽,上/下沿)//调用GPIO初始化函数gpio_initstruct.Mode = GPIO_MODE_IT_FALLING;//下降沿输出//结构体函数的四种类型,引脚模式、引脚、引脚上拉下拉、引脚速度gpio_initstruct.Pin = GPIO_PIN_12;//LED1对应的引脚gpio_initstruct.Pull = GPIO_NOPULL;//上拉HAL_GPIO_Init(GPIOB,&gpio_initstruct);//设置NVIC(优先级分组,设置优先级,使能中断)HAL_NVIC_SetPriority(EXTI15_10_IRQn,2,0);HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
//定时器中的初始化
void speed_timer_init(uint16_t arr,uint16_t psc)
{timer_handle.Instance = TIM2; //选择使用的定时器timer_handle.Init.Prescaler = psc; //PSCtimer_handle.Init.Period = arr; //ARRtimer_handle.Init.CounterMode = TIM_COUNTERMODE_UP;//配置成向上的,默认向上的timer_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;  //自动重装载,禁用自动重装载HAL_TIM_Base_Init(&timer_handle);HAL_TIM_Base_Start_IT(&timer_handle);//设置使能更新中断,和启动计数器
}
//初始化speed函数
void speed_init(void)
{speed_gpio_init();speed_timer_init(10000-1,7200-1);//一秒中断一次的定时器
}//设置中断服务函数
void EXTI15_10_IRQHandler(void)
{HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
}
//回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{delay_ms(20);if(GPIO_Pin == GPIO_PIN_12){if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12) == GPIO_PIN_RESET){speed_cnt++;printf("speed = %d\r\n",speed_cnt);  //测量出格子的个数}}
}
//定时器msp函数
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2){__HAL_RCC_TIM2_CLK_ENABLE();HAL_NVIC_SetPriority(TIM2_IRQn,2,2);//设置优先级HAL_NVIC_EnableIRQ(TIM2_IRQn);//设置使能中断}
}//定时器中断服务函数 
void TIM2_IRQHandler(void)
{//一般还会调用一个公共处理的服务函数HAL_TIM_IRQHandler(&timer_handle); }
//定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2){//测量到每一秒转了多少个格子printf("speed = %d\r\n",speed_cnt);speed_cnt = 0;}
}

主函数如下:

#include "sys.h"
#include "delay.h"
#include "uart1.h"
#include "motor.h"
#include "bluetooth.h"
#include "speed.h"int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */uart1_init(115200);motor_init();speed_init();bt_init(9600);printf("hello word!\r\n");motor_go_forward(); //让小车前进while(1) { }
}

十一、测速小车oled显示并上传蓝牙

复制09,重命名为10-测速小车oled显示并上传蓝牙

粘贴oled文件

打开项目

加载文件

想把获取到的速度,打印到oled上

我们得到的值是数字,要将这个数字转换成字符串打印到oled屏幕上

数字转字符串我们使用sprintf

定义一个字符数组

使用memset()先把这个字符数组清空,免得之后每次中断之后传进来的数据对其进行干扰

修改oled引脚

编译,无错

将蓝牙模块的这个函数的这个位置修改成这样

 因为原来使用的sizeof打印子啊手机上间距太大,不正常,如下所示

所以,换成strlen,就变成了如下所示:

 十二、wifi控制小车速度

将蓝牙模块换成esp8266模块

让电脑可以看见小车的速度买也可以使用电脑控制小车前进后退

将esp8266接到串口2 的tx和rx中

复制10,重命名11-WiFi控制小车并显示速度

打开项目,加载文件

将蓝牙的相关文件删除

编译不出错

使用网络调试助手架构一个服务器

连接esp8266

 

#include "sys.h"
#include "delay.h"
#include "uart1.h"
#include "motor.h"
#include "speed.h"
#include "oled.h"
#include "esp8266.h"
#include "string.h"int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */uart1_init(115200);motor_init();speed_init();oled_init();esp8266_init(115200); printf("hello word!\r\n");motor_go_forward(); //让小车前进char recv_data[ESP8266_RX_BUF_SIZE];while(1) { //esp8266发送数据esp8266_receive_data(recv_data);if(strstr(recv_data, "M1") != NULL)motor_go_forward();else if(strstr(recv_data, "M2") != NULL)motor_go_backward();else if(strstr(recv_data, "M3") != NULL)motor_go_left();else if(strstr(recv_data, "M4") != NULL)motor_go_right();elsemotor_stop();}
}

 

这里没有用到空闲中断,不把它注释掉反而会出错

十三、4G控制小车

4G模块引脚接线,tx、rx接串口2,vcc接串口1中的5v,gnd随便找个地方接一下就可以

使用4G模块要使用花生壳进行内网穿透,获取外网地址

设置链路

对于4G模块的代码可以直接复用10-测速小车oled显示并上传蓝牙

其中只需要注意4G模块的波特率就好,你的4G模块是什么波特率就改什么波特率

连接上4G模块之后,

这段代码就不是透传到蓝牙模块上了,而是透传到4G模块

所以就可以在网络调试助手上看见了

十四、语音控制小车

接线如下图所示:

配置SU-03T,点开网站:智能公元/AI产品零代码平台

语言选择中文,下一步

设置三次

 等待SDK烧录完成之后,就可以下载固件了

将语音模块接线之后

进行烧录

然后再烧录的exe工具中,选择如下文件

记住:这个文件不要放到中文路径下,路径也不要太长

 

 至此,语音文件就烧录完成了

下面是代码部分的编写:

复制01,重命名12-语音控制小车

 打开项目,加载文件

记得加入定时器文件

编译,无错之后进行下一步

在主函数中写入头文件和初始化函数,编译,无错,进行下一步

记得每次往主函数中融合其他模块的时候,要编译测试一下,减少错误率

主函数代码如下:

#include "sys.h"
#include "delay.h"
#include "uart1.h"
#include "motor.h"
#include "follow.h"
#include "hcsr04.h"
#include "sg90.h"
#include "track.h"double dis_left = 0, dis_middle = 0, dis_right = 0;//循迹模式
void track_mode(void)
{if(track_left_flag() == TRUE && track_right_flag() == TRUE)motor_go_forward(); //前进else if(track_left_flag() == FALSE && track_right_flag() == TRUE)  //左边压线motor_go_left(); //左转else if(track_left_flag() == TRUE && track_right_flag() == FALSE)  //右边压线motor_go_right(); //右转else if(track_left_flag() == FALSE && track_right_flag() == FALSE) // 全部为黑色motor_stop(); //停止
}
//跟随模式
void follow_mode(void)
{if(follow_left_flag() == TRUE && follow_right_flag() == TRUE)motor_go_forward();else if(follow_left_flag() == FALSE && follow_right_flag() == TRUE)//右边有目标motor_go_right();else if(follow_left_flag() == TRUE && follow_right_flag() == FALSE)//左边有目标motor_go_left();else if(follow_left_flag() == FALSE && follow_right_flag() == FALSE)motor_stop();
}
//避障模式
void avoid_mode(void)
{dis_middle = hcsr04_get_lenght();//接收超声波所测的距离if(dis_middle > 35)//判断距离是否大于35?是向前走motor_go_forward();else if(dis_middle < 15)//否则判断距离是否小于15?是后退motor_go_backward();else //否则就是距离>15并且<35{motor_stop();  //先停下来//向左看并测量左侧距离sg90_turn_left();delay_ms(500);  //给个时间进行测量dis_left = hcsr04_get_lenght();   //存储下左侧的长度//居中sg90_turn_middle();   //超声波传感器回到中间的位置delay_ms(500);//向右看并测量右侧距离sg90_turn_right();delay_ms(500);dis_right = hcsr04_get_lenght();//根据距离判断左转还是右转if(dis_left < dis_right)motor_go_right();elsemotor_go_left();delay_ms(500);  //给个时间反应一下motor_stop();  //电机停一下sg90_turn_middle();  //舵机回到中间的位置}delay_ms(100);  //给个时间反应一下
}int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */uart1_init(115200);motor_init();follow_init();hcsr04_init();sg90_init();track_init();printf("hello word!\r\n");sg90_turn_middle();while(1) { }
}

新建一个voice文件,加载文件

voice.c代码如下:

voice.h文件

主函数代码如下:

#include "sys.h"
#include "delay.h"
#include "uart1.h"
#include "motor.h"
#include "follow.h"
#include "hcsr04.h"
#include "sg90.h"
#include "track.h"
#include "voice.h"double dis_left = 0, dis_middle = 0, dis_right = 0;//循迹模式
void track_mode(void)
{if(track_left_flag() == TRUE && track_right_flag() == TRUE)motor_go_forward(); //前进else if(track_left_flag() == FALSE && track_right_flag() == TRUE)  //左边压线motor_go_left(); //左转else if(track_left_flag() == TRUE && track_right_flag() == FALSE)  //右边压线motor_go_right(); //右转else if(track_left_flag() == FALSE && track_right_flag() == FALSE) // 全部为黑色motor_stop(); //停止
}
//跟随模式
void follow_mode(void)
{if(follow_left_flag() == TRUE && follow_right_flag() == TRUE)motor_go_forward();else if(follow_left_flag() == FALSE && follow_right_flag() == TRUE)//右边有目标motor_go_right();else if(follow_left_flag() == TRUE && follow_right_flag() == FALSE)//左边有目标motor_go_left();else if(follow_left_flag() == FALSE && follow_right_flag() == FALSE)motor_stop();
}
//避障模式
void avoid_mode(void)
{dis_middle = hcsr04_get_lenght();//接收超声波所测的距离if(dis_middle > 35)//判断距离是否大于35?是向前走motor_go_forward();else if(dis_middle < 15)//否则判断距离是否小于15?是后退motor_go_backward();else //否则就是距离>15并且<35{motor_stop();  //先停下来//向左看并测量左侧距离sg90_turn_left();delay_ms(500);  //给个时间进行测量dis_left = hcsr04_get_lenght();   //存储下左侧的长度//居中sg90_turn_middle();   //超声波传感器回到中间的位置delay_ms(500);//向右看并测量右侧距离sg90_turn_right();delay_ms(500);dis_right = hcsr04_get_lenght();//根据距离判断左转还是右转if(dis_left < dis_right)motor_go_right();elsemotor_go_left();delay_ms(500);  //给个时间反应一下motor_stop();  //电机停一下sg90_turn_middle();  //舵机回到中间的位置}delay_ms(100);  //给个时间反应一下
}int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */uart1_init(115200);motor_init();follow_init();hcsr04_init();sg90_init();track_init();voice_init();printf("hello word!\r\n");sg90_turn_middle();while(1) { if(TRACK_PIN == GPIO_PIN_RESET && FOLLOW_PIN == GPIO_PIN_SET && AVOID_PIN == GPIO_PIN_SET)track_mode();//循迹模式else if(TRACK_PIN == GPIO_PIN_SET && FOLLOW_PIN == GPIO_PIN_RESET && AVOID_PIN == GPIO_PIN_SET)follow_mode();//跟随模式else if(TRACK_PIN == GPIO_PIN_SET && FOLLOW_PIN == GPIO_PIN_SET && AVOID_PIN == GPIO_PIN_RESET)avoid_mode();//避障模式}
}

编译无错


http://www.ppmy.cn/server/172976.html

相关文章

【华为OD机考】华为OD笔试真题解析(20)--投篮大赛

题目描述 你现在是一场采用特殊赛制投篮大赛的记录员。这场比赛由若干回合组成&#xff0c;过去几回合的得分可能会影响以后几回合的得分。 比赛开始时&#xff0c;记录是空白的&#xff0c;你会得到一个记录操作的字符串列表ops&#xff0c;其中ops[i]是你需要记录的第i项操…

1分钟从零开始搭建机器人管理系统(WindSurf)

1. 软件安装 可以直接安装作为IDE或者作为插件到其它IDE https://codeium.com/download 对话方式构建系统&#xff08;可以更换Claude 3.7、DeepSeek R1等模型&#xff09; 创建一个BS架构的机器人远程操控系统&#xff0c;具备机器人状态及位置实时更新&#xff0c;可以实…

前端实现word文档的生成和下载

一 前提 应项目需求&#xff0c;需要把前端生成word文档并下载。此项目我使用的是vue框架。本篇文章主要是记录自己在实现中遇到的问题以及最终使用方式。 二 实现方式 我的方式是将 html 转为word文档并下载。现在网上最常见的是使用 html-docx-js 配合 file-saver 使用…

flask 安装后不能识别

Windows 11 上&#xff0c;系统能够识别 Python 但无法识别 Flask, 使用python -m flask 方式可以 但是很麻烦 百度查询 认为 环境变量未配置 即使 Flask 已正确安装&#xff0c;如果其路径未添加到系统的环境变量中&#xff0c;系统也无法识别 flask 命令。可以通过以下步骤将…

Mysql中使用sql语句生成雪花算法Id

?? 简介&#xff1a;java系列技术分享(??持续更新中…??) ?? 初衷:一起学习、一起进步、坚持不懈 ?? 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正?? ?? 希望这篇文章对你有所帮助,欢迎点赞 ?? 收藏 留言 ?? ?? 更多文章请点击 [这里是图片002]…

【SegRNN 源码理解】PMF的多步并行预测

位置编码 elif self.dec_way "pmf":if self.channel_id:# m,d//2 -> 1,m,d//2 -> c,m,d//2# c,d//2 -> c,1,d//2 -> c,m,d//2# c,m,d -> cm,1,d -> bcm, 1, dpos_emb torch.cat([self.pos_emb.unsqueeze(0).repeat(self.enc_in, 1, 1),self.cha…

计算机网络:Socket网络编程 Udp与Tcp协议 第一弹

目录 1.IP地址和端口号 1.1 如何通信 1.2 端口号详解 1.3 理解套接字socket 2. 网络字节序 3. socket接口 3.1 socket类型设计 3.2 socket函数 3.3 bind函数 4. UDP通信协议 4.1 UDP服务端类 4.2 Udp服务类InitServer函数 4.3 Udp服务类Start函数 4.4 Udp服务主函…

windows环境执行composer install出错

现在的项目环境都是要求比较新的版本&#xff0c;就比如今天部署测试一个新框架遇到了下面这些问题&#xff0c;报错原因有以下几点&#xff1a; PHP版本低了&#xff0c;现在的新项目都是要求PHP8以上版本&#xff1b;指令废弃&#xff0c;配置文件禁用即可&#xff1b;切换P…