STM32智能小车(循迹、跟随、避障、测速、蓝牙、wife、4g、语音识别)总结

目录

1.电机模块开发

1.1 让小车动起来

1.2 串口控制小车方向

1.3 如何进行小车PWM调速

1.4 PWM方式实现小车转向

2.循迹小车 

2.1 循迹模块使用

2.2 循迹小车原理

2.3 循迹小车核心代码

2.4 循迹小车解决转弯平滑问题

3.跟随/避障小车

3.1 红外壁障模块分析​编辑

3.2 跟随小车的原理

3.3 跟随小车开发和调试代码

3.4 超声波模块介绍

3.5 舵机模块介绍

3.6 摇头避障小车开发和调试代码

4.测速小车

4.1 测速模块

4.2 测试原理和单位换算

4.3 定时器和中断实现测速开发和调试代码

4.4 小车速度显示在OLED屏

5.远程控制小车

5.1 蓝牙控制小车

5.2 蓝牙控制并测速小车

5.3 wifi控制测速小车

5.4 4g控制小车

6.语音控制小车

6.1语音模块配置:

6.2 语音控制小车开发和调试代码


1.电机模块开发

L9110s概述

接通VCC,GND 模块电源指示灯亮, 以下资料来源官方,具体根据实际调试

IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;

IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;

IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;

IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;

接线参考:

B-1A -- PA0

B-1B -- PB1

A-1A -- PA1

A-1B -- PB10 

1.1 让小车动起来

代码实现:

motor.c

#include "motor.h"
void goForward(void)
{
    // 左轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
    // 右轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void goBack(void)
{
    // 左轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
    // 右轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
}
void goLeft(void)
{
    // 左轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
    // 右轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void goRight(void)
{
    // 左轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
    // 右轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void stop(void)
{
    // 左轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
    // 右轮
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}

motor.h

#ifndef __MOTOR_H__
#define __MOTOR_H__
#include "main.h"
void goForward(void);
void goBack(void);
void goLeft(void);
void goRight(void);
void stop(void);
#endif

main.c

#include "motor.h"

//main函数的while循环部分:
while (1)
{
    goForward();
    HAL_Delay(1000);
    goBack();
    HAL_Delay(1000);
    goLeft();
    HAL_Delay(1000);
    goRight();
    HAL_Delay(1000);
    stop();
    HAL_Delay(1000);
}
1.2 串口控制小车方向
  • 串口分文件编程进行代码整合——通过现象来改代码
  • 接入蓝牙模块,通过蓝牙控制小车
  • 添加点动控制,如果APP支持按下一直发数据,松开就停止发数据(蓝牙调试助手的自定义按键不 能实现),就能实现前进按键按下后小车一直往前走的功能

代码实现:

usart.c

#include "usart.h"

#include "string.h"
#include "stdio.h"
#include "motor.h"

//串口接收缓存(1字节)
uint8_t buf=0;

//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200

// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];

//  接收状态
//  bit15,      接收完成标志
//  bit14,      接收到0x0d
//  bit13~0,    接收到的有效字节数目
uint16_t UART1_RX_STA=0;

#define SIZE 12

char buffer[SIZE];

// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	// 判断中断是由哪个串口触发的
	if(huart->Instance == USART1)
	{
		// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
		if((UART1_RX_STA & 0x8000) == 0)
		{
			// 如果已经收到了 0x0d (回车),
			if(UART1_RX_STA & 0x4000)
			{
				// 则接着判断是否收到 0x0a (换行)
				if(buf == 0x0a)
				{
					// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
					UART1_RX_STA |= 0x8000;

					// 灯控指令
					if(!strcmp(UART1_RX_Buffer, "M1"))
						goForward();
					else if(!strcmp(UART1_RX_Buffer, "M2"))
						goBack();
					else if(!strcmp(UART1_RX_Buffer, "M3"))
						goLeft();
					else if(!strcmp(UART1_RX_Buffer, "M4"))
						goRight();
					else
						stop();
					
					memset(UART1_RX_Buffer, 0, UART1_REC_LEN);
					UART1_RX_STA = 0;
				}
				else
					// 否则认为接收错误,重新开始
					UART1_RX_STA = 0;
			}
			else	// 如果没有收到了 0x0d (回车)
			{
				//则先判断收到的这个字符是否是 0x0d (回车)
				if(buf == 0x0d)
				{
					// 是的话则将 bit14 位置为1
					UART1_RX_STA |= 0x4000;
				}
				else
				{
					// 否则将接收到的数据保存在缓存数组里
					UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
					UART1_RX_STA++;
					
					// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
					if(UART1_RX_STA > UART1_REC_LEN - 1)
						UART1_RX_STA = 0;
				}
			}
		}
		// 重新开启中断
		HAL_UART_Receive_IT(&huart1, &buf, 1);
	}
}

int fputc(int ch, FILE *f)
{      
	unsigned char temp[1]={ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);  
	return ch;
}
1.3 如何进行小车PWM调速

原理

全速前进是LeftCon1A = 0; LeftCon1B = 1;

完全停止是LeftCon1A = 0;LeftCon1B = 0;

那么单位时间内,比如20ms, 有15ms是全速前进,5ms是完全停止, 速度就会比5ms全速前进,15ms完全停止获得的功率多,相应的速度更快!

开发:借用PWM的舵机控制代码

将控制车轮的4个 GPIO 口配置修改如下,否则小车动不起来。

原因:L9110每个控制口需要一高一低才可以动起来,如果PWM有效电平为高电平,则另一个 GPIO口则需要输出低电平才可以驱动轮子。

代码实现:

main.c

// main函数里
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
while (1)
{
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 8);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 8);
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10);
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 15);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 15);
    HAL_Delay(1000);
}
1.4 PWM方式实现小车转向

右转原理: 左轮速度大于右轮

左转原理: 右轮速度大于左轮

左右轮各自调速代码实现:

// main函数里
while (1)
{
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);
    HAL_Delay(1000);
}

2.循迹小车 

2.1 循迹模块介绍
  • TCRT5000传感器的红外发射二极管不断发射红外线
  • 当发射出的红外线没有被反射回来或被反射回来但强度不够大时
  • 红外接收管一直处于关断状态,此时模块的输出端为高电平,指示二极管一直处于熄灭状态
  • 被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和
  • 此时模块的输出端为低电平,指示二极管被点亮
  • 总结就是一句话,没反射回来,D0输出高电平,灭灯

接线方式

  • VCC:接电源正极(3-5V)
  • GND:接电源负极 DO:TTL开关信号输出0、1
  • AO:模拟信号输出(不同距离输出不同的电压,此脚一般可以不接)
2.2 循迹小车原理

由于黑色具有较强的吸收能力,当循迹模块发射的红外线照射到黑线时,红外线将会被黑线吸收,导致 循迹模块上光敏三极管处于关闭状态,此时模块上一个LED熄灭。在没有检测到黑线时,模块上两个LED常亮

总结就是一句话,有感应到黑线,D0输出高电平 ,灭灯

2.3 循迹小车核心代码

硬件接线

  • B-1A -- PA0
  • B-1B -- PB1
  • A-1A -- PA1
  • A-1B -- PB10
  • 循迹模块(左)--  PB3
  • 循迹模块(右) -- PB4

代码示例:

#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)

// main函数里
while (1)
{
    if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
        goForward();
    if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
        goLeft();
    if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
        goRight();
    if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
        stop();
}
2.4 循迹小车解决转弯平滑问题

原理:两轮都有速度且一轮速度大于另一轮

代码实现:

#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
// main函数里
while (1)
{
    if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
    {
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,19);
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,19);
    }
    if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
    {
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);
    }
    if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
    {
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);
    }
    if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
    {
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,0);
        __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,0);
    }
}

3.跟随/避障小车

3.1 红外壁障模块分析

原理和循迹是一样的,循迹红外观朝下,跟随朝前

3.2 跟随小车的原理
  • 左边跟随模块能返回红外,输出低电平,右边不能返回,输出高电平,说明物体在左边,需要左转
  • 右边跟随模块能返回红外,输出低电平,左边不能返回,输出高电平,说明物体在右边,需要右转
3.3 跟随小车开发和调试代码

硬件接线

  • B-1A -- PB0
  • B-1B -- PB1
  • A-1A -- PB2
  • A-1B -- PB10
  • 跟随模块(左) -- PB5
  • 跟随模块(右) -- PB6

代码示例:

#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)
// main函数里
while (1)
{
    if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
        goForward();
    if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
        goRight();
    if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
        goLeft();
    if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
        stop();
}
3.4 超声波模块介绍

使用超声波模块,型号:HC-SR04

  • 怎么让它发送波 Trig ,给Trig端口至少10us的高电平
  • 怎么知道它开始发了 Echo信号,由低电平跳转到高电平,表示开始发送波
  • 怎么知道接收了返回波 Echo,由高电平跳转回低电平,表示波回来了
  • 怎么算时间 Echo引脚维持高电平的时间! 波发出去的那一下,开始启动定时器 波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
  • 怎么算距离 距离 = 速度 (340m/s)* 时间/2

时序图:

3.5 舵机模块介绍

 1. 什么是舵机

如下图所示,最便宜的舵机sg90,常用三根或者四根接线,黄色为PWM信号控制 用处:垃圾桶项目开盖用、智能小车的全比例转向、摄像头云台、机械臂等 常见的有0-90°、0-180°、0-360°

2. 怎么控制舵机

向黄色信号线“灌入”PWM信号

PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右

确定周期/频率:

如果周期为20ms,则 PSC=7199,ARR=199

角度控制

0.5ms-------------0度; 2.5% 对应函数中CCRx为5

1.0ms------------45度; 5.0% 对应函数中CCRx为10

1.5ms------------90度; 7.5% 对应函数中CCRx为15

2.0ms-----------135度; 10.0% 对应函数中CCRx为20

2.5ms-----------180度; 12.5% 对应函数中CCRx为25

3.6 摇头避障小车开发和调试代码

硬件接线

  • sg90 -- PB9

cubeMX配置

代码实现

sg90.c

#include "sg90.h"
#include "gpio.h"
#include "tim.h"
void initSG90(void)
{
    HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); //启动定时器4
    __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度
}
void sgMiddle(void)
{
    __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度
}
void sgRight(void)
{
    __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
}
void sgLeft(void)
{
    __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 25); //将舵机置为180度
}

sg90.h

#ifndef __SG90_H__
#define __SG90_H__

void initSG90(void);
void sgMiddle(void);
void sgRight(void);
void sgLeft(void);

#endif

main.c

initSG90();
HAL_Delay(1000);

while (1)
{
    sgLeft();
    HAL_Delay(1000);
    sgMiddle();
    HAL_Delay(1000);
    sgRight();
    HAL_Delay(1000);
    sgMiddle();
    HAL_Delay(1000);
}

封装超声波传感器

超声波模块接线:

  • Trig       --    PB7
  • Echo     -- PB8

cubeMX配置

代码实现

sr04.c

#include "sr04.h"
#include "gpio.h"
#include "tim.h"

//使用TIM2来做us级延时函数
void TIM2_Delay_us(uint16_t n_us)
{
    /* 使能定时器2计数 */
    __HAL_TIM_ENABLE(&htim2);
    __HAL_TIM_SetCounter(&htim2, 0);
    while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
    /* 关闭定时器2计数 */
    __HAL_TIM_DISABLE(&htim2);
}

double get_distance(void)
{
    int cnt=0;
    //1. Trig ,给Trig端口至少10us的高电平
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);//拉高
    TIM2_Delay_us(20);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);//拉低

    //2. echo由低电平跳转到高电平,表示开始发送波
    //波发出去的那一下,开始启动定时器
    while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET);//等待输入电平拉高
    HAL_TIM_Base_Start(&htim2);
    __HAL_TIM_SetCounter(&htim2,0);

    //3. 由高电平跳转回低电平,表示波回来了
    while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET);//等待输入电平变低
    //波回来的那一下,我们开始停止定时器
    HAL_TIM_Base_Stop(&htim2);

    //4. 计算出中间经过多少时间
    cnt = __HAL_TIM_GetCounter(&htim2);

    //5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
    return (cnt*340/2*0.000001*100); //单位:cm
}

sr04.h

#ifndef __SR04_H__
#define __SR04_H__

double get_distance(void);

#endif

main.c

while (1)
{
    if(dir != MIDDLE){
        sgMiddle();
        dir = MIDDLE;
        HAL_Delay(300);
    }
    disMiddle = get_distance();

    if(disMiddle > 35){
    //前进
    }
    else
    {
        //停止
        //测左边距离
        sgLeft();

        HAL_Delay(300);
        disLeft = get_distance();

        sgMiddle();
        HAL_Delay(300);

        sgRight();
        dir = RIGHT;
        HAL_Delay(300);
        disRight = get_distance();
    }
}

封装电机驱动

代码实现:

while (1)
{
    if(dir != MIDDLE){
        sgMiddle();
        dir = MIDDLE;
        HAL_Delay(300);
    }
    disMiddle = get_distance();

    if(disMiddle > 35){
        //前进
        goForward();
    }else if(disMiddle < 10){
        goBack();
    }else
    {
        //停止
        stop();
        //测左边距离
        sgLeft();
        HAL_Delay(300);
        disLeft = get_distance();

        sgMiddle();
        HAL_Delay(300);

        sgRight();
        dir = RIGHT;
        HAL_Delay(300);
        disRight = get_distance();

        if(disLeft < disRight){
            goRight();
            HAL_Delay(150);
            stop();
        }
        if(disRight < disLeft){
            goLeft();
            HAL_Delay(150);
        stop();
        }
    }
    HAL_Delay(50);
}

4.测速小车

4.1 测速模块

  • 用途:广泛用于电机转速检测,脉冲计数,位置限位等。
  • 有遮挡,输出高电平;无遮挡,输出低电平
  • 接线 :VCC 接电源正极3.3-5V
  • GND 接电源负极 DO TTL开关信号输出
  • AO 此模块不起作用
4.2 测试原理和单位换算
  • 轮子走一圈,经过一个周长,C = 2x3.14x半径= 3.14 x 直径(6.5cm)
  • 对应的码盘也转了一圈,码盘有20个格子,每经过一个格子,会遮挡(高电平)和不遮挡(低电平), 那么一个脉冲就是走了 3.14 * 6.5 cm /20 = 1.0205CM
  • 定时器可以设计成一秒,统计脉冲数,一个脉冲就是1cm
  • 假设一秒有80脉冲,那么就是80cm/s
4.3 定时器和中断实现测速开发和调试代码

测试数据通过串口发送到上位机

硬件接线

测速模块:

  • VCC -- 3.3V 不能接5V,否则遮挡一次会触发3次中断
  • OUT -- PB14

cubeMX配置

代码实现:

unsigned int speedCnt;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_14)
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)
            speedCnt++;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    printf("speed: %d\r\n", speedCnt);
    speedCnt = 0;
}

main函数里:
HAL_TIM_Base_Start_IT(&htim2);
4.4 小车速度显示在OLED屏

OLED模块介绍:STM32 OLED屏幕显示详解

硬件接线

  • SCL -- PB6
  • SDA -- PB7

代码示例:

oled.c

#include "oled.h"
#include "i2c.h"
#include "oledfont.h"

void Oled_Write_Cmd(uint8_t dataCmd)
{
	
	HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
										&dataCmd, 1, 0xff);
}

void Oled_Write_Data(uint8_t dataData)
{
	HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
										&dataData, 1, 0xff);
}

void Oled_Init(void){
	Oled_Write_Cmd(0xAE);//--display off
	Oled_Write_Cmd(0x00);//---set low column address
	Oled_Write_Cmd(0x10);//---set high column address
	Oled_Write_Cmd(0x40);//--set start line address  
	Oled_Write_Cmd(0xB0);//--set page address
	Oled_Write_Cmd(0x81); // contract control
	Oled_Write_Cmd(0xFF);//--128   
	Oled_Write_Cmd(0xA1);//set segment remap 
	Oled_Write_Cmd(0xA6);//--normal / reverse
	Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
	Oled_Write_Cmd(0x3F);//--1/32 duty
	Oled_Write_Cmd(0xC8);//Com scan direction
	Oled_Write_Cmd(0xD3);//-set display offset
	Oled_Write_Cmd(0x00);//
	
	Oled_Write_Cmd(0xD5);//set osc division
	Oled_Write_Cmd(0x80);//
	
	Oled_Write_Cmd(0xD8);//set area color mode off
	Oled_Write_Cmd(0x05);//
	
	Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
	Oled_Write_Cmd(0xF1);//
	
	Oled_Write_Cmd(0xDA);//set com pin configuartion
	Oled_Write_Cmd(0x12);//
	
	Oled_Write_Cmd(0xDB);//set Vcomh
	Oled_Write_Cmd(0x30);//
	
	Oled_Write_Cmd(0x8D);//set charge pump enable
	Oled_Write_Cmd(0x14);//
	
	Oled_Write_Cmd(0xAF);//--turn on oled panel		
}

void Oled_Screen_Clear(void){
	char i,n;
	Oled_Write_Cmd (0x20);                    //set memory addressing mode
	Oled_Write_Cmd (0x02);                    //page addressing mode

	for(i=0;i<8;i++){
		Oled_Write_Cmd(0xb0+i);               
		Oled_Write_Cmd(0x00);                 
		Oled_Write_Cmd(0x10);                 
		for(n=0;n<128;n++)Oled_Write_Data(0x00); 			
	}	
}

void Oled_Show_Char(char row,char col,char oledChar){ //row*2-2
	unsigned int  i;
	Oled_Write_Cmd(0xb0+(row*2-2));                           //page 0
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high	
	for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}

	Oled_Write_Cmd(0xb0+(row*2-1));                           //page 1
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high
	for(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}		
}


/******************************************************************************/
// 函数名称:Oled_Show_Char 
// 输入参数:oledChar 
// 输出参数:无 
// 函数功能:OLED显示单个字符
/******************************************************************************/
void Oled_Show_Str(char row,char col,char *str){
	while(*str!=0){
		Oled_Show_Char(row,col,*str);
		str++;
		col += 8;	
	}		
}

main.c

extern uint8_t buf;
unsigned int speedCnt = 0;
char speedMes[24];  //主程序发送速度数据的字符串缓冲区

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if (GPIO_Pin == GPIO_PIN_14)
		if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)
			speedCnt++;
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	printf("speed: %d\r\n", speedCnt);
	sprintf(speedMes,"speed:%2d cm/s",speedCnt);//串口数据的字符串拼装,speed是格子,每个格子1cm
	Oled_Show_Str(2,2,speedMes);
	speedCnt = 0;
}

5.远程控制小车

5.1 蓝牙控制小车
  • 使用蓝牙模块,串口透传
  • 蓝牙模块,又叫做蓝牙串口模块

串口透传技术:

  • 透传即透明传送,是指在数据的传输过程中,通过无线的方式这组数据不发生任何形式的改变,仿 佛传输过程是透明的一样,同时保证传输的质量,原封不动地到了最终接收者手里。
  • 以太网,蓝牙,Zigbee, GPRS 等模块玩法一样,对嵌入式程序员来说,不需要关心通讯模块内部数据 及协议栈工作原理,只要通过串口编程获得数据即可

代码实现:

整合前面串口控制小车代码,接入蓝牙模块

if (!strcmp(UART1_RX_Buffer, "M1"))
{
    goForward();
    HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M2"))
{
    goBack();
    HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M3"))
{
    goLeft();
    HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M4"))
{
    goRight();
    HAL_Delay(10);
}
else
    stop();
5.2 蓝牙控制并测速小车

原理:运用上面讲到的蓝牙模块和测速模块,将两者代码整合

5.3 wifi控制测速小车

  • Wifi模块-ESP-01s
  • 蓝牙,ESP-01s,Zigbee, NB-Iot等通信模块都是基于AT指令的设计

AT指令介绍:

  • AT指令集是从终端设备(Terminal Equipment,TE)或数据终端设备(Data Terminal Equipment,DTE)向终端适配器(Terminal Adapter,TA)或数据电路终端设备(Data Circuit Terminal Equipment,DCE)发送的。
  • 其对所传输的数据包大小有定义:即对于AT指令的发送,除AT两个字符外,最多可以接收1056个 字符的长度(包括最后的空字符)。
  • 每个AT命令行中只能包含一条AT指令;对于由终端设备主动向PC端报告的URC指示或者response 响应,也要求一行最多有一个,不允许上报的一行中有多条指示或者响应。AT指令以回车作为结 尾,响应或上报以回车换行为结尾。

硬件接线

  • 把esp8266插进串口1

使用方法:

Wifi模块-ESP-01s_esp01s波特率-CSDN博客

5.4 4g控制小车

原理:运用EC03-DNC4G通信模块

模块介绍:

  • 基于串口AT指令的开发方式
  • 有两种工作模式,默认是透传模式,通过其他方式进入AT指令模式
  • 注意插卡不要出错,下图红色位置为SIM卡状态灯,亮才是正常

代码不做修改,直接基于蓝牙小车整合, 4g模块只要做好外网透传就可以了

6.语音控制小车

6.1语音模块配置

使用SU-03T / LD3320

具体介绍看我之前写过的博客:SU-03T语音模块的使用_罗小白的干爹的博客-CSDN博客

  

6.2 语音控制小车开发和调试代码

硬件接线:

 循迹小车: 

  • 循迹模块(左) -- PB3
  • 循迹模块(右) -- PB4

 跟随小车: 

  • 跟随模块(左) -- PA8
  • 跟随模块(右) -- PA9

 避障小车: 

  • sg90:PB9
  • Trig:PA10
  • Echo:PA11

 OLED模块: 

  • SCL -- PB6
  • SDA -- PB7

 语音模块: 

  • A25 -- PA15 (跟随)
  • A26 -- PA13 (避障)
  • A27 -- PA14 (循迹)

cubeMX配置  

代码示例:

#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "gpio.h"

#include "sg90.h"
#include "sr04.h"
#include "motor.h"
#include "oled.h"

#define MIDDLE 0
#define LEFT 1
#define RIGHT 2

#define BZ 1
#define XJ 2
#define GS 3

#define LeftWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)

#define LeftWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8)
#define RightWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)

#define A25 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)
#define A26 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13)
#define A27 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14)

void SystemClock_Config(void);

char dir;

void xunjiMode()
{
	if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ == GPIO_PIN_RESET)
		goForward();
	if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_RESET)
		goLeft();
	if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ == GPIO_PIN_SET)
		goRight();
	if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_SET)
		stop();
}

void gensuiMode()
{
	if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS == GPIO_PIN_RESET)
		goForward();
	if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_RESET)
		goRight();
	if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS == GPIO_PIN_SET)
		goLeft();
	if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_SET)
		stop();
}

void bizhangMode()
{
	double disMiddle;
	double disLeft;
	double disRight;

	if(dir != MIDDLE){
		sgMiddle();
		dir = MIDDLE;
		HAL_Delay(300);
	}
	disMiddle = get_distance();
	
	if(disMiddle > 35){
		//前进
		goForward();
	}else if(disMiddle < 10){
		goBack();
		
	}else
	{
		//停止
		stop();
		//测左边距离
		sgLeft();
		HAL_Delay(300);
		disLeft = get_distance();
		
		sgMiddle();
		HAL_Delay(300);
		
		sgRight();
		dir = RIGHT;
		HAL_Delay(300);
		disRight = get_distance();
		
		if(disLeft < disRight){
			goRight();
			HAL_Delay(150);
			stop();
		}
		if(disRight < disLeft){
			goLeft();
			HAL_Delay(150);
			stop();
		}
	}
	HAL_Delay(50);
}

int main(void)
{
    int mark = 0;

    HAL_Init();


    SystemClock_Config();


    MX_GPIO_Init();
    MX_TIM4_Init();
    MX_TIM2_Init();
    MX_I2C1_Init();

	initSG90();
	HAL_Delay(1000);
	dir = MIDDLE;
	Oled_Init();
	Oled_Screen_Clear();
	Oled_Show_Str(2,2,"-----Ready----");

  while (1)
  {

		//满足寻迹模式的条件
		if(A25 == 1 && A26 == 1 && A27 == 0){
			if(mark != XJ){
				Oled_Screen_Clear();
				Oled_Show_Str(2,2,"-----XunJi----");
			}
			mark = XJ;
			xunjiMode();
		}
		//满足跟随模式的条件
		if(A25 == 0 && A26 == 1 && A27 == 1){
			if(mark != GS){
				Oled_Screen_Clear();
				Oled_Show_Str(2,2,"-----GenSui----");
			}
			mark = GS;
			gensuiMode();
		}
		//满足避障模式的条件
		if(A25 == 1 && A26 == 0 && A27 == 1){
			if(mark != BZ){
				Oled_Screen_Clear();
				Oled_Show_Str(2,2,"-----BiZhang----");
			}
			mark = BZ;
			bizhangMode();
		}
  }
}


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud Gateway实现请求解密和响应加密

文章目录 前言正文一、项目简介二、核心代码2.1 自定义过滤器2.2 网关配置2.3 自定义配置类2.4 加密组件接口2.5 加密组件实现&#xff0c;AES算法2.6 启动类&#xff0c;校验支持的算法配置 三、请求报文示例四、测试结果4.1 网关项目启动时4.2 发生请求时 前言 本文环境使用比…

跨国文件传输为什么要用专业的大文件传输软件?

跨国文件传输是许多跨国企业需要的基础工作&#xff0c;对于传输的质量和速度要求也是很严格的&#xff0c;随着数据量的不断增加&#xff0c;寻常传统的传输方式肯定是不行&#xff0c;需要新的技术和方式来进行传输&#xff0c;大文件传输软件应运而出&#xff0c;那它有什么…

软考中项集成如何画图?计算题怎么考的?

2023下半年软考集成一共考6个批次&#xff0c;10月28日、29日软考集成考了第一、二、三、四批次&#xff0c;11月4日软考集成再考第五批和第六批。 先说一下通过10.28-29得出的软考机考注意事项&#xff1a; 1、草稿纸不能自带&#xff0c;考试现场会发放草稿纸&#xff0c;草…

钡铼技术ARM工控机在机器人控制领域的应用

ARM工控机是一种基于ARM架构的工业控制计算机&#xff0c;用于在工业自动化领域中进行数据采集、监控、控制和通信等应用。ARM&#xff08;Advanced RISC Machine&#xff09;架构是一种低功耗、高性能的处理器架构&#xff0c;广泛应用于移动设备、嵌入式系统和物联网等领域。…

关于pytorch张量维度转换及张量运算

关于pytorch张量维度转换大全 1 tensor.view()2 tensor.reshape()3 tensor.squeeze()和tensor.unsqueeze()3.1 tensor.squeeze() 降维3.2 tensor.unsqueeze(idx)升维 4 tensor.permute()5 torch.cat([a,b],dim)6 torch.stack()7 torch.chunk()和torch.split()8 与tensor相乘运算…

预处理详解(一)

1 预定义符号 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C&#xff0c;其值为1&#xff0c;否则未定义 这些预定义符号都是…

Linux纯C串口开发

为什么要用纯C语言 为了数据流动加速&#xff0c;实现低配CPU建立高速数据流而不用CPU干预&#xff0c;避免串口数据流多次反复上升到软件应用层又下降低到硬件协议层。 关于termios.h 麻烦的是&#xff0c;在 Linux 中使用串口并不是一件最简单的事情。在处理 termios.h 标头…

怎么检测开关电源质量的好坏?测试的方法是什么?

开关电源的工作原理 开关电源(简称SMPS)是常见的一种电源供应器&#xff0c;是高频化的电能转换装置&#xff0c;可以将电压透过不同形式的架构转换为用户端所需求的电压或电流。具有体积小、功耗小、效率高、高可靠性的特点&#xff0c;被广泛应用在工业、军工设备、科研设备、…

机器学习---支持向量机的初步理解

1. SVM的经典解释 改编自支持向量机解释得很好 |字节大小生物学 (bytesizebio.net) 话说&#xff0c;在遥远的从前&#xff0c;有一只贪玩爱搞破坏的妖怪阿布劫持了善良美丽的女主小美&#xff0c;智勇双全 的男主大壮挺身而出&#xff0c;大壮跟随阿布来到了妖怪的住处&…

Docker compose容器编排

Docker compose容器编排 1、Docker compose简介 docker-compose是docker的编排工具&#xff0c;用于定义和运行一个项目&#xff0c;该项目包含多个docker容器&#xff0c;在如今的微服务时代&#xff0c;一个项目会存在多个服务&#xff0c;使用docker一个个部署操作的话就会…

React中的状态管理

目录 前言 1. React中的状态管理 1.1 本地状态管理 1.2 全局状态管理 Redux React Context 2. React状态管理的优势 总结 前言 当谈到前端开发中的状态管理时&#xff0c;React是一个备受推崇的选择。React的状态管理机制被广泛应用于构建大型、复杂的应用程序&#xf…

计算机毕业设计选题推荐-超市售货微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

注册中心ZK、nameServer、eureka、Nacos介绍与对比

前言 注册中心的由来 微服务架构是存在着很多跨服务调用,每个服务都存在着多个节点,如果有多个提供者和消费者,当提供者增加/减少或者消费者增加/减少,双方都需要感知发现。所以诞生了注册中心这个中间件。 市面上有很多注册中心,如 Zookeeper、NameServer、Eureka、Na…

OpenCV 笔记(4):图像的算术运算、逻辑运算

Part11. 图像的算术运算 图像的本质是一个矩阵&#xff0c;所以可以对它进行一些常见的算术运算&#xff0c;例如加、减、乘、除、平方根、对数、绝对值等等。除此之外&#xff0c;还可以对图像进行逻辑运算和几何变换。 我们先从简单的图像加、减、逻辑运算开始介绍。后续会有…

企业 Tomcat 运维 部署tomcat反向代理集群

一、Tomcat 简介 Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c; Tomcat和Nginx、Apache(httpd)、Web服务器一样&#xff0c;具有处理HTML页面的功能不过Tomcat处理静态HTML的能力不如Nginx/Apache服务器 一个tomcat默认并…

ip划分与私公网ip、ip的传递

报文问路&#xff1a;1、不知道跳转默认路由器&#xff0c;2、知道路径&#xff0c;向对应路径发出报文&#xff0c;3、路口路由器&#xff0c;下一步就是目标主机在哪。 想要通信必须同在一个局域网&#xff0c;其实将公网就可以看作一个大型的局域网。 在同一个局域网内发送…

61. 旋转链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;李歘歘的博客 &#x1f3c6; &#x1f33a;每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点&#xff0c;以及职场小菜鸡的生活。&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&am…

恶意软件防范和拦截: 提供防范恶意软件攻击的策略

恶意软件&#xff0c;或者俗称的“病毒”&#xff0c;一直是IT领域的一个严重威胁。这些恶意软件可以窃取敏感信息、损害系统稳定性&#xff0c;甚至对企业和个人造成重大经济损失。在这篇博客文章中&#xff0c;我们将讨论如何防范和拦截恶意软件攻击&#xff0c;包括使用反病…

【unity/vufornia】Duplicate virtual buttons with name.../同一个ImageTarget上多个按钮失灵

问题&#xff1a;在同一个ImageTarget上添加多个按钮时无法触发对应按钮的事件。 解决过程&#xff1a; 1.查看报错&#xff1a;“Duplicate virtual buttons with name...”这一行&#xff0c;顾名思义&#xff0c;命名重复。 2.英文搜索到以下文章&#xff0c;应该在inspe…

算法通过村第十八关-回溯|青铜笔记|什么叫回溯(中篇)

文章目录 前言回溯的核心问题撤销操作解释总结 前言 提示&#xff1a;阳光好的时候&#xff0c;会感觉还可以活很久&#xff0c;甚至可以活出喜悦。 --余秀华 回溯是非常重要的算法思想之一&#xff0c;主要解决一些暴力枚举也搞不定的问题&#xff08;这里埋个坑&#x1f4a3;…