基于STM32的智能小车

目录

一、硬件清单

二、功能实现

三、相关代码

1、让小车动起来

2、串口控制小车方向

3、串口点动控制小车

4、硬件PWM调速控制小车前进

5、硬件PWM调速控制小车左右转

6、循迹小车

7、解决平滑转弯问题

8.跟随/避障小车

9、摇头避障小车

10、小车测速数据通过串口上传到上位机

11、串口控制小车并使用Oled屏显示速度

12、Wi-Fi测速小车并本地Oled显示

13、语音控制小车


一、硬件清单

1、主控STM32F103C8T6

7044c273cf414157af12f711b1a5b1d2.png

2、L9110S电机模块

247c4523120b446c97805a61f8ffae18.png

3、超声波模块

24b67c280b92411e8b9628d8fd15ac3b.png

4、sg90舵机模块

40d9f0ea3c7c4e879a91e92557149437.png

5、oled屏

57856dcfb46f4de6a74dd6110f493d8b.png

6、测速模块

31a2a48f75c44781b831f82d5e0013e2.png

7、红外模块

30b93da795a84b4b88a1a54c83eaca50.png

8、esp8266模块(wifi模块)

337a32904783478489f1cf071f2a9601.png

9、语音模块

97e2defc061f4f07922ca313e2ffefc9.png

10、循迹模块

d5e5f23357e84b1f823427e7b48bb8eb.png

二、功能实现

左右转与前进后退:通过控制左组轮和右组轮的配合实现。pwm调速使其更平滑

循迹功能:基于光电传感器原理,通过判断黑线和白线来决定左转或者右转

跟随功能:通过超声波测出小车和跟随物的距离决定移动方向

避障功能:通过超声波检测出前方障碍物,做出改变方向的决定

语音模块控制:多种语音改变多种引脚的电平,通过语音控制实现以上多种功能的切换

old屏显示速度:将测速模块的数据通过oled屏显示

远程控制:支持蓝牙、4g以及WiFi控制小车

三、相关代码

1、让小车动起来

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

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

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

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

84ae55d9bea045f2b168e8d4ef1db91c.png

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);
}

motot.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);
}

2、串口控制小车方向

usart.c(在工程配置的usart.c中添加以下代码)

#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;
}
 

注:主函数main.c中需要将中断打开,同时在点开魔术棒将u8勾上 如下:

779d93b82d364c509493538721fbf5b8.png

#include "motor.h"
extern uint8_t buf;
//main函数
HAL_UART_Receive_IT(&huart1, &buf, 1);

3、串口点动控制小车

在usart.c车控指令处进行修改 如下:

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();

注:这里的HAL_Delay函数为滴答定时器 若直接如上修改代码 则在串口助手中发出车控指令时会导致程序阻塞 有以下两种解决方法:

1.在主函数中调用以下函数修改滴答定时器优先级

HAL_NVIC_SetPriority(Systick_IRQn,0,0);

2.通过Cubemx配置滴答定时器优先级

b1833a84c29f46b29507d84aafd3bd82.png

4、硬件PWM调速控制小车前进

c69d4064282a4ca0832053a0a3d605cc.png

caf37676e1d4473c9e2bae0d5842401a.png

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);
}

注意:此时小车轮子并不能正常改变转动速度转动,原因是L9110电机模块每个控制口需要一高一低才可以动起来,如果PWM有效电平为高电平,则另一个 GPIO口则需要输出低电平才可以驱动轮子,解决方法是将电机模块对应的GPIO口设置低电平驱动即可

a922e14882ac4d469c833c99a8719124.png

5、硬件PWM调速控制小车左右转

main.c

// 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);
}

6、循迹小车

循迹原理

fc30659520bb4773aa9075fe8a0d4a86.png

1.左右循迹模块都照射到白色上——红外返回——都输出低电平——指示灯亮——直走

2.左循迹模块照射到黑色跑道上——左边红外被吸收不返回——左边输出高电平——指示灯灭——需要左转

3.右循迹模块照射到黑色跑道上——右边红外被吸收不返回——右边输出高电平——指示灯灭——需要右转

这里循迹模块是一个传感器,需要获取传感器的值进行相应操作,所以Cubemx中需配置传感器对应引脚为输入模式,如下:

88865d4b357d447fb980444a9dd94bc0.png

相关代码

main.c

#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();
}

7、解决平滑转弯问题

#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);
    }
}

8.跟随/避障小车

原理:

  • 左边跟随模块能返回红外,输出低电平,右边不能返回,输出高电平,说明物体在左边,需要左转
  • 右边跟随模块能返回红外,输出低电平,左边不能返回,输出高电平,说明物体在右边,需要右转

相关代码

main.c

#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();
}

9、摇头避障小车

9.1 封装摇头功能

Cubemx配置

f3c108804e164ae9b4d65551f7570d85.png

上图的预分频值和重装值通过公式代入得出,这里设置周期为20ms,公式如下:

6d007f4f0f8846388eb2c81d5ae73d6e.png

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

sg90模块角度控制:

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

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

1.5ms------------90度; 7.5% 对应函数中CCRx为15(这里经过调试发现17比较接近于90°)

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

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

09b2b7e9fb5e4722b43a688c4286a36a.png

相关代码

sg90.c

#include "sg90.h"
#include "gpio.h"
#include "tim.h"
 
void initSG90(void)//初始化90度
{
    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

#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();
}

9.2 封装超声波传感器

Cubrmx配置

b405e93ac1414e5f8c660764f0f01588.png

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

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

char dir;
double disMiddle;
double disLeft;
double disRight;

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();
    }
}     

9.3 封装电机驱动

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);
}

10、小车测速数据通过串口上传到上位机

Cubemx配置

8f6d9b3cc520454790e0ebf5126fae87.png

c08a5baef2494e7cbe736420a0c396f3.png

a718dbbf176b41a8a44051dfa809126d.png

main.c

//中断定时服务函数
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);

11、串口控制小车并使用Oled屏显示速度

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);               //éè??ò3μ??·£¨0~7£?
		Oled_Write_Cmd(0x00);                 //éè????ê??????aáDμíμ??·
		Oled_Write_Cmd(0x10);                 //éè????ê??????aáD??μ??·   
		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;	
	}		
}

显示代码实现

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;
}

12、Wi-Fi测速小车并本地Oled显示

esp8266.c

#include "esp8266.h"
#include "stdio.h"

//1 工作在路由模式
char LYMO[] = "AT+CWMODE=2\r\n";
//2 使能多链接
char DLJ[] = "AT+CIPMUX=1\r\n"; 
//3 建立TCPServer
char JLFW[] = "AT+CIPSERVER=1\r\n"; // default port = 333 


char AT_OK_Flag = 0;				//OK返回值的标志位
char AT_Connect_Net_Flag = 0;		//WIFI GOT IP返回值的标志位
char Client_Connect_Flag = 0;

void initWifi_AP(void)
{
	printf(LYMO);
	while(!AT_OK_Flag) HAL_Delay(50);
	AT_OK_Flag = 0;

	printf(DLJ);
	while(!Client_Connect_Flag) HAL_Delay(50);
	AT_OK_Flag = 0;
}

void waitConnect(void)
{
	printf(JLFW);
	while(!AT_OK_Flag) HAL_Delay(50);
	AT_OK_Flag = 0;	
}

esp8266.h

#ifndef __ESP8266_H_
#define __ESP8266_H_

void initWifi_AP(void);
void waitConnect(void);

#endif

usart.c

#include "motor.h"
#include "stdio.h"
#include "string.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;

extern char AT_OK_Flag;				//OK返回值的标志位
extern char AT_Connect_Net_Flag ;		//WIFI GOT IP返回值的标志位
extern char Client_Connect_Flag;

#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, "WIFI GOT IP"))
											AT_Connect_Net_Flag = 1;
										
										// 查看是否收到 OK
										if(!strcmp(UART1_RX_Buffer, "OK"))
											AT_OK_Flag = 1;
										
										// 查看是否收到 FAIL
										if(!strcmp(UART1_RX_Buffer, "0,CONNECT"))	
											Client_Connect_Flag = 1;
                    
                    if(!strcmp(UART1_RX_Buffer, "+IPD,0,4:M1"))
                        goForward();
                    else if(!strcmp(UART1_RX_Buffer, "+IPD,0,4:M2"))
                        goBack();
                    else if(!strcmp(UART1_RX_Buffer, "+IPD,0,4:M3"))
                        goLeft();
                    else if(!strcmp(UART1_RX_Buffer, "+IPD,0,4:M4"))
                        goRight();
                    else if(!strcmp(UART1_RX_Buffer, "+IPD,0,4:M0"))
                        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;
}

main.c

#include "motor.h"
#include "stdio.h"
#include "oled.h"
#include "esp8266.h"

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

//发送数据
char FSSJ[] = "AT+CIPSEND=0,5\r\n";

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(FSSJ);
	HAL_Delay(50);
	sprintf(speedMes,"%2dcm/s",speedCnt);//串口数据的字符串拼装,speed是格子,每个格子1cm
	printf(speedMes);
	Oled_Show_Str(2,2,speedMes);
	speedCnt = 0;
}

int main(void)
{
	HAL_UART_Receive_IT(&huart1, &buf, 1);
	HAL_TIM_Base_Start_IT(&htim2);
	Oled_Init();
	Oled_Screen_Clear();
	
	HAL_Delay(1000);
	initWifi_AP();
	waitConnect();
}

 这里由于定时器2中用到延时函数,所以需要通过Cubemx配置滴答定时器中断优先级,如下:

13、语音控制小车

e3127e7d7252485ea291d1786834c3b2.png

#include "sg90.h"
#include "sr04.h"
#include "motor.h"
#include "oled.h"
#include "string.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 XJ_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14)
#define GS_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)
#define BZ_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13)
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(XJ_VALUE == GPIO_PIN_RESET && GS_VALUE == GPIO_PIN_SET && BZ_VALUE ==
                GPIO_PIN_SET)
        {
            if(mark != XJ)
            {
                Oled_Screen_Clear();
                Oled_Show_Str(2,2,"-----XunJi----");
            }
            mark = XJ;
            xunjiMode();
        }
        //满足循迹模式的条件
        if(XJ_VALUE == GPIO_PIN_SET && GS_VALUE == GPIO_PIN_RESET && BZ_VALUE ==
                GPIO_PIN_SET)
        {
            if(mark != GS)
            {
                Oled_Screen_Clear();
                Oled_Show_Str(2,2,"-----GenSui----");
            }
            mark = GS;
            gensuiMode();
        }
        //满足避障模式的条件
        if(XJ_VALUE == GPIO_PIN_SET && GS_VALUE == GPIO_PIN_SET && BZ_VALUE ==
                GPIO_PIN_RESET)
        {
            if(mark != BZ)
            {
                Oled_Screen_Clear();
                Oled_Show_Str(2,2,"-----BiZhang----");
            }
            mark = BZ;
            bizhangMode();
        }
        HAL_Delay(50);
    }
}

  • 25
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在STM32上驱动超声波模块,你可以按照以下步骤进行操作: 1. 首先,连接超声波模块到STM32开发板。将VCC引脚连接到5V电源,GND引脚连接到地线,Trig引脚连接到一个输出引脚(用于发射超声波信号),Echo引脚连接到一个输入引脚(用于接收返回的超声波信号)。 2. 在你的STM32工程中,包含"hc_sr04.h"头文件,并在代码中声明一个全局变量`float Dist_cm`,用于存储测量到的距离。 3. 在代码中调用`HC_SR04_Init()`函数,用于初始化超声波模块。 4. 在主循环中,发送一个至少10us的TTL电平信号来触发超声波模块。可以使用STM32的GPIO库来设置输出引脚的电平。 5. 等待一段时间,让超声波模块发出8个40KHZ的脉冲。 6. 接收回响信号,根据高电平的时间来计算出距离。可以使用STM32的GPIO库来读取输入引脚的电平,并根据计算公式`T(s) * 340(m/s) / 2`计算距离。 请注意,具体的代码实现可能会因为使用的STM32型号和开发环境而有所不同。你需要根据你的具体情况进行相应的调整和修改。 #### 引用[.reference_title] - *1* *2* [STM32驱动HC-SR04超声波模块](https://blog.csdn.net/m0_49476241/article/details/130179740)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【STM32篇】驱动HC_SR04超声波测距模块](https://blog.csdn.net/we_long/article/details/128671122)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值