当前位置: 首页 > news >正文

STM32-智能小车项目

项目框图

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();//避障模式}
}

编译无错

相关文章:

STM32-智能小车项目

项目框图 ST-link接线 实物图&#xff1a; 正面&#xff1a; 反面&#xff1a; 相关内容 使用L9110S电机模块 电机驱动模块L9110S详解 | 良许嵌入式 测速模块 语音模块SU-03T 网站&#xff1a;智能公元/AI产品零代码平台 一、让小车动起来 新建文件夹智能小车项目 在里面…...

Python:字符串常见操作

find(子字符串&#xff0c;开始位置下标&#xff0c;结束位置下标) 注意&#xff1a;开始位置和结束位置下标可以省略&#xff0c;表示在整个字符串中查找 stasdfghjkl print(st.find(a))#输出结果为0&#xff0c;表明a在第一个位置默认从零开始&#xff0c;找不到则返回-1 …...

Redis 哈希(Hash)

Redis 哈希(Hash) 概述 Redis 哈希&#xff08;Hash&#xff09;是一种特殊的键值对类型&#xff0c;它允许存储结构化的数据&#xff0c;例如一个对象或记录。每个哈希值可以包含多个字段&#xff0c;每个字段又可以存储一个字符串值。这使得Redis哈希非常适合用于存储对象的…...

Windows对比MacOS

Windows对比MacOS 文章目录 Windows对比MacOS1-环境变量1-Windows添加环境变量示例步骤 1&#xff1a;打开环境变量设置窗口步骤 2&#xff1a;添加系统环境变量 2-Mac 系统添加环境变量示例步骤 1&#xff1a;打开终端步骤 2&#xff1a;编辑环境变量配置文件步骤 3&#xff1…...

react 路由跳转的几种方式

在 React 中&#xff0c;路由跳转通常通过 react-router-dom&#xff08;或类似的路由库&#xff09;来实现。以下是几种常见的路由跳转方式&#xff1a; 1. 使用 <Link> 组件 <Link> 是最简单的路由跳转方式&#xff0c;它会生成一个 <a> 标签&#xff0c…...

2.你有什么绝活儿?—Java能做什么?

1、Java的绝活儿&#xff1a;要问Java有什么绝活&#xff0c;我觉得它应该算是一位魔法师&#xff0c;会的绝活儿有很多&#xff0c;要说最能拿得出手的当属以下三个。 1.1 平台无关性&#xff1a;Java可以在任何地方施展魔法&#xff0c;无论是Windows、Linux还是Mac&#xf…...

2025年2月文章一览

2025年2月编程人总共更新了17篇文章&#xff1a; 1.2025年1月文章一览 2.《Operating System Concepts》阅读笔记&#xff1a;p2-p8 3.《Operating System Concepts》阅读笔记&#xff1a;p9-p12 4.《Operating System Concepts》阅读笔记&#xff1a;p13-p16 5.《Operati…...

C++ | 面向对象 | 类

&#x1f47b;类 &#x1f47e;语法格式 class className{Access specifiers: // 访问权限DataType variable; // 变量returnType functions() { } // 方法 };&#x1f47e;访问权限 class className {public:// 公有成员protected:// 受保护成员private:// 私有成员 }…...

leetcode:2164. 对奇偶下标分别排序(python3解法)

难度&#xff1a;简单 给你一个下标从 0 开始的整数数组 nums 。根据下述规则重排 nums 中的值&#xff1a; 按 非递增 顺序排列 nums 奇数下标 上的所有值。 举个例子&#xff0c;如果排序前 nums [4,1,2,3] &#xff0c;对奇数下标的值排序后变为 [4,3,2,1] 。奇数下标 1 和…...

Visionpro cogToolBlockEditV2.Refresh()

在 C# 中使用 cogToolBlockEditV2.Refresh() 方法主要用于刷新 CogToolBlockEditV2 控件的显示状态&#xff0c;适用于动态更新界面或重新加载工具块&#xff08;ToolBlock&#xff09;的场景。以下是具体说明和典型应用场景。 基本作用 刷新控件显示&#xff1a;当修改了与 C…...

Apache Spark中的依赖关系与任务调度机制解析

Apache Spark中的依赖关系与任务调度机制解析 在Spark的分布式计算框架中,RDD(弹性分布式数据集)的依赖关系是理解任务调度、性能优化及容错机制的关键。宽依赖(Wide Dependency)与窄依赖(Narrow Dependency)作为两种核心依赖类型,直接影响Stage划分、Shuffle操作及容…...

网络基础III

目录 一、网络层 1.1IP协议 1.2网段划分&#xff08;&#x1f53a;&#xff09; 1.3特殊的ip地址 1.4ip地址的数量限制 1.5私有ip和公网ip 1.6路由 二、数据链路层 2.1认识以太网 2.2以太网帧格式 2.3认识mac地址 2.4mac地址和ip地址 2.5认识MTU 2.6MTU对IP协议的…...

【SpringBoot】自动配置原理与自定义启动器

Spring Boot 自动配置原理与自定义启动器 目录标题 Spring Boot 自动配置原理与自定义启动器摘要1. 引言2. Spring Boot自动配置原理分析2.1 自动配置的核心流程2.2 核心注解与配置文件解析2.2.1 EnableAutoConfiguration2.2.2 spring.factories 文件 2.3 自动配置类剖析2.4 配…...

Element实现el-dialog弹框移动、全屏功能

1、在Vue项目中src/utils目录中创建dialog.js&#xff0c;用来定义draggable-dialog&#xff1b; import Vue from vue Vue.directive(draggable-dialog, { // 属性名称draggable-dialog&#xff0c;前面加v- 使用bind(el, binding, vnode) {const dialogHeaderEl el.querySe…...

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(11)

详解&#xff08;11&#xff09; 初始化配置解析上下文 senv environ;ngx_memzero(&conf, sizeof(ngx_conf_t));/* STUB: init array ? */conf.args ngx_array_create(pool, 10, sizeof(ngx_str_t));if (conf.args NULL) {ngx_destroy_pool(pool);return NULL;}conf.te…...

千峰React:案例一

做这个案例捏 因为需要用到样式&#xff0c;所以创建一个样式文件&#xff1a; //29_实战.module.css .active{text-decoration:line-through } 然后创建jsx文件&#xff0c;修改main文件&#xff1a;导入Todos&#xff0c;写入Todos组件 import { StrictMode } from react …...

部署Joplin私有云服务器postgres版-docker compose

我曾经使用过一段时间 Joplin&#xff0c;官方版本是收费的&#xff0c;而我更倾向于将数据掌握在自己手中。因此&#xff0c;在多次权衡后&#xff0c;我决定自己搭建 Joplin 服务器并进行尝试。 个人搭建的版本与数据库直连&#xff0c;下面是使用 Docker Compose 配置数据库…...

rust学习笔记6-数组练习704. 二分查找

上次说到rust所有权看看它和其他语言比有什么优势&#xff0c;就以python为例 # Python3 def test():a [1, 3, -4, 7, 9]print(a[4])b a # 所有权没有发生转移del b[4]print(a[4]) # 由于b做了删除&#xff0c;导致a再度访问报数组越界if __name__ __main__:test() 运行结…...

Jsmoke-一款强大的js检测工具,浏览器部署即用,使用方便且高效

目录标题 Jsmoke &#x1f6ac;&#x1f6ac; by Yn8rt使用方式界面预览功能特性支持的敏感信息类型 Jsmoke &#x1f6ac;&#x1f6ac; by Yn8rt ​ 该插件由 Yn8rt师傅 开发&#xff0c;插件可以理解为主动版的hae和apifinder&#xff0c;因为其中的大多数规则我都引用了&a…...

PyCharm中通过命令行执行`pip`命令下载到哪里了:虚拟环境目录下

PyCharm中通过命令行执行pip命令下载到哪里了:虚拟环境目录下 在PyCharm中通过命令行执行pip命令安装工具包,包的下载位置取决于多种因素 虚拟环境 如果项目使用了虚拟环境(通常是推荐的做法): Windows:虚拟环境通常位于项目目录下的.venv文件夹(默认情况)或你指定…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...

Qt的学习(二)

1. 创建Hello Word 两种方式&#xff0c;实现helloworld&#xff1a; 1.通过图形化的方式&#xff0c;在界面上创建出一个控件&#xff0c;显示helloworld 2.通过纯代码的方式&#xff0c;通过编写代码&#xff0c;在界面上创建控件&#xff0c; 显示hello world&#xff1b; …...

EEG-fNIRS联合成像在跨频率耦合研究中的创新应用

摘要 神经影像技术对医学科学产生了深远的影响&#xff0c;推动了许多神经系统疾病研究的进展并改善了其诊断方法。在此背景下&#xff0c;基于神经血管耦合现象的多模态神经影像方法&#xff0c;通过融合各自优势来提供有关大脑皮层神经活动的互补信息。在这里&#xff0c;本研…...

初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)

零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...