一、小车1.0——基本蓝牙小车(仅蓝牙遥控小车运动方向,本篇)
二、小车2.0——蓝牙小车PLUS(可以蓝牙控制方向+蓝牙直接调节车速)
三、小车3.0——避障小车(超声波+舵机云台)
四、小车4.0——无线手柄方向感知操控小车(mpu6050+双蓝牙透传)
五、双轮自平衡小车(HAL库版)——点击此处
更多有意思的文章点击“我的主页”
--------😐
更多有意思的视频 -----> B站 @想要亿只独角兽
--------😐
文章目录
本篇文章介绍的是基于STM32C8T6 HAL库的蓝牙遥控小车。
可能内容有点多,但绝不是水文,程序也有详细的解释。
这篇文章主要介绍小车代码的实现,代码是由HAL库编写,相较于函数库,HAL库更容易入门,也是ST现在主推的,我用的是Cube IDE进行调试和编写代码的,当然使用CubeMX+Keil5编写也是一样的,毕竟Cube IDE中集成有CubeMX
文中要用到的串口调试助手以及蓝牙调试器我会在文末放上链接供大家使用
先附上小车的图片吧。。。

演示视频:
简易蓝牙遥控小车(STM32/HAL)
完整工程(CubeIDE)链接---->蓝牙小车完整工程(设置了收费望理解,其实代码已经在博客里完全展示)

| 元件序号 | 元件名称 (数量) |
|---|---|
| 01 | STM32最小系统板(1个) |
| 02 | L298N电机驱动模块(2个) |
| 03 | 12V 锂电池+充电器(1个) |
| 04 | HC-08 蓝牙模块 (1个) |
| 05 | 四驱小车底座带电机(1个) |
| 06 | 转接头(1个)一般买电池会送 |
| 07 | 船型开关(1个) |
| 08 | 杜邦线、面包板 (若干) |
分别称1号L298N的两个使能端口为ENA1,ENB1,控制电机正反转的端口记为IN1,IN2,IN3,IN4,同理2号L298N的为ENA2,ENB2,IN11,IN22,IN33,IN44。
将ENA1,ENB1,ENA2,ENB2 这四个使能端口通过杜邦线与面包板统一接到STM32C8T6的PA0,这样做的目的是对四个直流电机统一进行PWM调速控制。
四个电机的正负极分别接两个L298N的INx端口,至于到底哪个接正极,哪个接负极,根据你电机安装的方式而定,建议先把电机的两根线焊上,然后把底盘安装起来,这样电机的安装方式就确定了,先随便把两个L298N的4个INx接口跟电机相接,等到把其他信号线接好后,再判断对错并调节。
调节方法如下:在程序中让小车往前跑,观察车轮的转向,往前转的车轮的线不用变,把往后转的电机对应的L298N的INx接口的两根线换一下就行了,例如左前电机往后转即IN1与IN2互换。
注意稳压模块至少有1V的压降,所以L298N输出的5V电压经过5V-3.3V模块后接到stm32的3.3V供电端,
下图更正一下—>稳压模块的OUT+端接stm32的3.3V供电端
接线如图所示: 选用L298N模块对小车的四轮进行驱动,两个L298N并联,由12V锂电池进行供电

HC-08蓝牙模块的TXD与STM32C8T6的USART_RX连接。RXD与STM32C8T6的USART_TX连接。
蓝牙模块的TXD——PA3
蓝牙模块的RXD——PA2
GND与单片机共地
(PA2,PA3的配置会在CubeMX中的设置中具体说明)
(1)点击RCC开启HSE和LSE,并选择RC或晶体作为时钟源

(2)配置时钟树

最终最右侧显示为

利用STM32C8T6定时器TIM2的PWM功能,将PA0设置为TIM2_CH1,即小车的PWM调速是由TIM2的通道1来实现的。
(1)点击TIM2,在Mode选项中设置Clock Source为Internal Clock。设置Channel1为PWM Generation CH1,其余默认即可。

(2)设置Configuration选项中Parameter Settings的参数
这里的参数设置见下文代码讲解中的PWM代码讲解

(3)TIM2_CH1 的GPIO参数设置


PA8 PA12 PA13 PA14 PA15 PB0 PB1 PB2都设置为输出功能


8个INx与PA11的参数设置一致,但是User Label不一样,对应如下:
IN1——PA8
IN2——PA12
IN3——PA13
IN4——PA14
IN11——PA15
IN22——PB0
IN33——PB1
IN44——PB2
最终结果为:

目的是让蓝牙与单片机之间发送和接收数据
(1)点击Connectivity–>USART2,开启异步模式(Asynchronous)

(2)Parameter Settings的参数设置
注意配置中的波特率要与蓝牙的波特率一致,否则通信不能正常进行

(3)NVIC参数设置
开启串口2中断,蓝牙通信需要

最终配置引脚图:

自此全部的基础配置就都完成了,点击生成代码即可,下面就要自己编写驱动代码了

motor.h代码如下:
#ifndef MOTOR_MOTOR_H_
#define MOTOR_MOTOR_H_
#include "stm32f1xx_hal.h" //HAL库文件声明
#include <main.h>
void L_MOTOR_GO(); //小车左侧两个电机正转
void R_MOTOR_GO();
void L_MOTOR_BACK(); //小车左侧两个电机反转
void R_MOTOR_BACK();
void L_MOTOR_STOP();
void R_MOTOR_STOP(); //小车左侧两个电机停转
motor.c代码如下:
#include "motor.h"
void L_MOTOR_GO()
{
HAL_GPIO_WritePin(GPIOA, IN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, IN2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, IN11_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, IN22_Pin, GPIO_PIN_RESET);
}
void R_MOTOR_GO()
{
HAL_GPIO_WritePin(GPIOA, IN3_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, IN4_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, IN33_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, IN44_Pin, GPIO_PIN_RESET);
}
void L_MOTOR_BACK()
{
HAL_GPIO_WritePin(GPIOA, IN1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, IN2_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, IN11_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, IN22_Pin, GPIO_PIN_SET);
}
void R_MOTOR_BACK()
{
HAL_GPIO_WritePin(GPIOA, IN3_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, IN4_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, IN33_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, IN44_Pin, GPIO_PIN_SET);
}
void L_MOTOR_STOP()
{
HAL_GPIO_WritePin(GPIOA, IN1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, IN2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, IN11_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, IN22_Pin, GPIO_PIN_RESET);
}
void R_MOTOR_STOP()
{
HAL_GPIO_WritePin(GPIOA, IN3_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, IN4_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, IN33_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, IN44_Pin, GPIO_PIN_RESET);
}
control.h代码如下:
#ifndef CONTROL_CONTROL_H_
#define CONTROL_CONTROL_H_
#include "stm32f1xx_hal.h" //HAL库文件声明
#include <main.h>
#include "../motor/motor.h"
extern TIM_HandleTypeDef htim2;//声明TIM2的HAL库结构体
void CAR_GO(); //小车前进
void CAR_BACK(); //小车后退
void CAR_LGO(); //小车原地左拐
void CAR_RGO(); //小车原地右拐
void CAR_STOP(); //小车停止
#endif /* CONTROL_CONTROL_H_ */
control.c代码如下:
#include"control.h"
void CAR_GO()
{
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1,1200);
L_MOTOR_GO();
R_MOTOR_GO();
}
void CAR_BACK()
{
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1,1200);
L_MOTOR_BACK();
R_MOTOR_BACK();
}
void CAR_LGO()
{
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1,500);
L_MOTOR_BACK();
R_MOTOR_GO();
}
void CAR_RGO()
{
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1,500);
L_MOTOR_GO();
R_MOTOR_BACK();
}
void CAR_STOP()
{
L_MOTOR_STOP();
R_MOTOR_STOP();
}


usart.h代码如下:
#ifndef INC_USART_H_
#define INC_USART_H_
#include "stm32f1xx_hal.h" //HAL库文件声明
extern UART_HandleTypeDef huart2;//声明USART2的HAL库结构体
#define USART2_REC_LEN 200//定义USART2最大接收字节数
extern uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为校验和
extern uint16_t USART2_RX_STA;//接收状态标记
extern uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//串口中断回调函数声明
#endif /* INC_USART_H_ */
usart.c代码如下:
#include "usart.h"
uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART2_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14~0:接收到的有效字节数目
uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart ==&huart2)
{
if((USART2_RX_STA&0x8000)==0)//接收未完成
{
if(USART2_NewData==0x5A)//接收到了0x5A
{
USART2_RX_STA|=0x8000; //接收完成了,将USART2_RX_STA中的bit15(15位)置1
}
else
{
USART2_RX_BUF[USART2_RX_STA&0X7FFF]=USART2_NewData; /*将收到的数据放入数组,
例如按下按键1(前进):
USART2_RX_BUF[0]=0xA5
USART2_RX_BUF[1]=0x01
USART2_RX_BUF[2]=0x01
虽然蓝牙模块发送的数据包有4个字节但是包尾0x5A不会存
入USART2_RX_BUF中,当单片机接收到包尾的0x5A时会将USART2_RX_STA的最高位置1*/
USART2_RX_STA++; //数据长度计数加1
if(USART2_RX_STA>(USART2_REC_LEN-1))USART2_RX_STA=0;//接收数据错误,重新开始接收
}
}
HAL_UART_Receive_IT(&huart2,(uint8_t *)&USART2_NewData,1); //因为每执行完一次中断回调函数会将接收中断功能关闭,所以最后需要再开启接收中断
}
}
我用的是HC-08蓝牙模块,网上都有卖初学者套餐的,不贵。
需要用到的蓝牙调试器,其他蓝牙调试器的方法也大同小异



设定4个按键所要链接的数据以及参数

注:
步骤6仅添加一个byte数据即可,其余数据类型不添加
步骤1的蓝牙名称可以在串口调试助手中用AT指令集“AT+NAME=xxx”来改变
1.设置完后,打开串口调试助手,按照下图所示将蓝牙设备通过USB连接到电脑

2.之后在设备管理器中查看对应的CH340的端口号,我的是COM3

3.观察接收到的数据包
第三步的目的是可以更加直观地观察到蓝牙向单片机发送的数据包是什么,便于程序usart.c的编写。
先对串口助手进行设置,通过上一步我们知道了串口号,波特率是HC-08默认的,设为9600,这一点在USART2的设置中也要特别注意。其他按下图设置,设置完后点击“打开”,分别按下按键1,2,3,4观察接收到的数据包。

总的来说就是蓝牙发送的数据包结构是由4个数据组成,即包头、原数据、校验码、包尾各一个字节,后续单片机只要判断“原数据”这个字节就可以知道我们在手机上按下或松开了哪一个按键

main.c代码如下:
我只写了需要自己编写的驱动代码,其余main.c中的内容会由CubeMX自动生成
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "../../icode/control/control.h"
#include "../../icode/usart/usart.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); //开启TIM2通道1的PWM,给直流电机进行PWM调速
HAL_UART_Receive_IT(&huart2,(uint8_t *)&USART2_NewData,1); //开启接收中断
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(USART2_RX_STA&0x8000) //判断中断接收标志位(蓝牙模块使用USART2)
{
if((USART2_RX_STA&0x7FFF) ==3 //判断接收数量3个
&& USART2_RX_BUF[0]==0xA5 //判断接收第1个数据是不是包头0xA5
&& USART2_RX_BUF[2]==(USART2_RX_BUF[1])%0x100) //判断接收校验码是不是原数据之和的低8位
{
switch(USART2_RX_BUF[1]) //接收并读取蓝牙发送过来的第2个数据
{
case(0x01):CAR_GO();break;
case(0x02):CAR_LGO();break;
case(0x03):CAR_RGO();break;
case(0x04):CAR_BACK();break;
case(0x00):CAR_STOP();break;
default:break;
}
}
USART2_RX_STA=0;//标志位清0,准备下次接收
}
}
/* USER CODE END 3 */
}
链接:https://pan.baidu.com/s/1ukD9jpGayvIKT9EHDjQdIQ?pwd=xapn
提取码:xapn
链接:https://pan.baidu.com/s/1_eNSOYhsWu_y8R1Nn9tnUw?pwd=lnx4
提取码:lnx4
以上就是今天要讲的内容,理解掌握上述就可以做出自己的一辆蓝牙遥控小车了,但是这款小车不同于市面上的遥控车,这辆车每次只能按一个按键,并不能既按前进又按左转来控制小车,因为并没有转向轴,两种车的转弯原理不同。
本人是一枚大二在读通信专业的学生,利用课余时间通过学习自己做出来了一辆入门的遥控车,当然我也是通过CSDN这个很好的平台学习了51智能小车的做法,于是乎想着制作一个32控制的小车。这篇文章主要分享以及记录学习中的感悟,可能还有不足,还望大佬们在评论区提出,大家相互学习与进步。
这篇文章也算是我在CSDN的首作。
更多有意思的文章 点击CSDN“我的主页”
更多有意思的视频 -----> B站 @想要亿只独角兽
完整工程(CubeIDE)链接---->蓝牙小车完整工程(设置了收费望理解,其实代码已经在博客里完全展示)
码字不易,希望喜欢的小伙伴别忘了==点赞+收藏+关注==,你们的肯定就是我创作的动力
欢迎大家积极交流,本文未经允许谢绝转载!!!
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主
1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva
LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L
目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式
目录一、安装包链接二、安装详细步骤1.安装Wireshark和WinPcap2.安装OracleVMVirtualBox3.安装ensp三、安装后注册四、启动路由器出现40错误怎么解决一、安装包链接二、安装详细步骤链接:https://pan.baidu.com/s/1QbUUYMOMIV2oeIKHWP1SpA?pwd=xftx提取码:xftx1.安装Wireshark和WinPcap找到Wireshark安装包所在文件夹,双击它,按照以下步骤安装。2.安装OracleVMVirtualBox找到OracleVMVirtualBox安装包所在文件夹,双击它,按照以下步骤安装。注:可自定义安装
Nginx安装1.官网下载Nginx2.使用XShell和Xftp将压缩包上传到Linux虚拟机中3.解压文件nginx-1.20.2.tar.gz4.配置nginx5.启动nginx6.拓展(修改端口和常用命令)(一)修改nginx端口(二)常用命令1.官网下载Nginxhttp://nginx.org/en/download.html这里我下载的是1.20.2版本,大家按需下载对应稳定版即可2.使用XShell和Xftp将压缩包上传到Linux虚拟机中没有XShell可以参考《Linux操作系统CentOS7连接XShell》3.解压文件nginx-1.20.2.tar.gz1)检查是否存
因学习需要用到keras,通过查找较多资料最终完成Anaconda、TensorFlow和Keras的简单安装。因为网上的相关资料较多但大部分不够全面,查找起来不太方便,因此自己记录一下成功下载安装的详细过程,顺便推荐一下借鉴的写的很好的相关教程文章。keras需要在TensorFlow之上才能运行,所以要先安装TensorFlow,而TensorFlow只能在3.7以前的python版本中运行,所以需要先创建一个基于python3.6的虚拟环境,因此便需要先下载Anaconda。一、Anaconda3下载和安装Anaconda下载安装教程原文链接:https://blog.csdn.net/
【动态规划】一、背包问题1.背包问题总结1)动规四部曲:2)递推公式总结:3)遍历顺序总结:2.01背包1)二维dp数组代码实现2)一维dp数组代码实现3.完全背包代码实现4.多重背包代码实现一、背包问题1.背包问题总结暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!背包问题是动态规划(DynamicPlanning)里的非常重要的一部分,关于几种常见的背包,其关系如下:在解决背包问题的时候,我们通常都是按照如下五部来逐步分析,把这五部都搞透了,算是对动规来理解深入了。1)动规四部曲:(1)确定dp数组及其下标的含义(2)确定递推公式(3)dp数组的初始化(4)确定遍历顺