STM32之智能小车,手把手从0到1,模块化编程

Posted haozigegie

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32之智能小车,手把手从0到1,模块化编程相关的知识,希望对你有一定的参考价值。

小车介绍

本博文将会从0到1实现一个智能小车,该小车实现功能:1. 摇头避障模式、2. 跟随模式、3. 循迹模式、4. 小车测速并显示在OLED屏幕、5. 语音控制小车等等。

硬件组成

STM32F103开发板、小车套件、L9110S电机模块、超声波模块(HC-SR04)、sg90舵机、测速模块、循迹模块、红外避障模块等等(下面有详细介绍)

模块化编程

小车采用模块化编程循序渐进,即每实现新的功能小车需要复制前一种功能小车的文件夹来进行修改,否则会因为丢失某些文件导致小车功能缺失。

移动小车

让小车动起来。

硬件组成

L9110S电机模块

L9110S电机模块用来驱动两个电机,如果需要控制四个电机则需要两个L9110S电机模块。

  • 当B-1A为高电平,B-2A为低电平时,电机反转或正转。

  • 当B-1A为低电平,B-2A为高电平时,电机正转或反转。

  • 当B-1A为低电平,B-2A为低电平时,电机不转。

  • A-1A、A-1B同理。

  • 电机的正转和反转与跟电机的接线不同而不同,注意自己调试。

L9110S电机模块与STM32F103板子接线

  • B-1A <-> PB0

  • B-2A <-> PA0

  • A-1A <-> PB2

  • A-1B <-> PA1

STM32CubeMX相关配置

配置SYS

配置RCC

配置GPIO

配置PA0引脚、PA1引脚、PB0引脚、PB2引脚输出高电平。

文件编写

添加文件car.c

#include "gpio.h"

void car_goForward()

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);


void car_goBack()

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);


void car_goStop()

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);


void car_goRight()

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);


void car_goLeft()

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);

添加文件car.h

void car_goForward(void);

void car_goBack(void);

void car_goStop(void);

void car_goRight(void);
    
void car_goLeft(void);

main.c文件编写

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "car.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* 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();
    /* USER CODE BEGIN 2 */

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    
        /* USER CODE END WHILE */

        car_goForward();
        HAL_Delay(1000);
        car_goStop();
        HAL_Delay(1000);

        /* USER CODE BEGIN 3 */
    
    /* USER CODE END 3 */


/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)

    RCC_OscInitTypeDef RCC_OscInitStruct = 0;
    RCC_ClkInitTypeDef RCC_ClkInitStruct = 0;

    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    
        Error_Handler();
    

    /** Initializes the CPU, AHB and APB buses clocks
     */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    
        Error_Handler();
    


/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void)

    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1)
    
    
    /* USER CODE END Error_Handler_Debug */


#ifdef USE_FULL_ASSERT
/**
 * @brief  Reports the name of the source file and the source line number
 *         where the assert_param error has occurred.
 * @param  file: pointer to the source file name
 * @param  line: assert_param error line source number
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line)

    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number,
       ex: printf("Wrong parameters value: file %s on line %d\\r\\n", file, line) */
    /* USER CODE END 6 */

#endif /* USE_FULL_ASSERT */

USART小车

利用USART接收串口信息,实现蓝牙遥控小车等功能。

STM32CubeMX相关配置

配置USART1

配置NVIC

使用Micro库

只要映射了printf用来发送数据去串口都要使用这个库。

文件编写

修改文件usart.c

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file    usart.c
 * @brief   This file provides code for the configuration
 *          of the USART instances.
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"

/* USER CODE BEGIN 0 */

#include <stdio.h>
#include <string.h>

#include "car.h"

#define USART_REC_LEN 200

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

uint8_t UART1_RX_Buffer[USART_REC_LEN]; // 接收缓冲,串口接收的数据存放地点

// 串口接收状态,16位
uint16_t UART1_RX_STA = 0;
// bit15: 如果是1表示接收完成
// bit14: 如果是1表示接收到回车(0x0d)
// bit13~bit0: 接收到的有效字节数目

/* USER CODE END 0 */

UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(void)


    /* USER CODE BEGIN USART1_Init 0 */

    /* USER CODE END USART1_Init 0 */

    /* USER CODE BEGIN USART1_Init 1 */

    /* USER CODE END USART1_Init 1 */
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    
        Error_Handler();
    
    /* USER CODE BEGIN USART1_Init 2 */

    /* 开启串口1的接收中断 */
    HAL_UART_Receive_IT(&huart1, &buf, 1); /* 每接收一个串口数据调用一次串口接收完成回调函数 */

    /* USER CODE END USART1_Init 2 */


void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle)


    GPIO_InitTypeDef GPIO_InitStruct = 0;
    if (uartHandle->Instance == USART1)
    
        /* USER CODE BEGIN USART1_MspInit 0 */

        /* USER CODE END USART1_MspInit 0 */
        /* USART1 clock enable */
        __HAL_RCC_USART1_CLK_ENABLE();

        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**USART1 GPIO Configuration
        PA9     ------> USART1_TX
        PA10     ------> USART1_RX
        */
        GPIO_InitStruct.Pin = GPIO_PIN_9;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        /* USART1 interrupt Init */
        HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        /* USER CODE BEGIN USART1_MspInit 1 */

        /* USER CODE END USART1_MspInit 1 */
    


void HAL_UART_MspDeInit(UART_HandleTypeDef *uartHandle)


    if (uartHandle->Instance == USART1)
    
        /* USER CODE BEGIN USART1_MspDeInit 0 */

        /* USER CODE END USART1_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_USART1_CLK_DISABLE();

        /**USART1 GPIO Configuration
        PA9     ------> USART1_TX
        PA10     ------> USART1_RX
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9 | GPIO_PIN_10);

        /* USART1 interrupt Deinit */
        HAL_NVIC_DisableIRQ(USART1_IRQn);
        /* USER CODE BEGIN USART1_MspDeInit 1 */

        /* USER CODE END USART1_MspDeInit 1 */
    


/* USER CODE BEGIN 1 */

/* 重写stdio.h文件中的prinft()里的fputc()函数 */
int fputc(int my_data, FILE *p)

    unsigned char temp = my_data;
    // 改写后,使用printf()函数会将数据通过串口一发送出去
    HAL_UART_Transmit(&huart1, &temp, 1, 0xffff); // 0xfffff为最大超时时间
    return my_data;


/* 串口接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

    // 判断中断是哪个串口触发的
    if (huart->Instance == USART1)
    

        // 判断接收是否完成,即判断UART1_RX_STA的bit15是否为1
        if (!(UART1_RX_STA & 0x8000))
         // 如果没接收完成就进入接收流程

            // 判断是否接收到回车0x0d
            if (UART1_RX_STA & 0x4000)
            

                // 判断是否接收到换行0x0a
                if (buf == 0x0a)
                

                    // 如果回车和换行都接收到了,则表示接收完成,即把bit15拉高
                    UART1_RX_STA |= 0x8000;
                
                else
                 // 如果接收到回车0x0d没有接收到换行0x0a

                    // 则认为接收错误,重新开始接收
                    UART1_RX_STA = 0;
                
            
            else
             // 如果没有接收到回车0x0d

                // 则判断收到的这个字符是否是回车0x0d
                if (buf == 0x0d)
                

                    // 如果这个字符是回车,则将将bit14拉高,表示接收到回车
                    UART1_RX_STA |= 0x4000;
                
                else
                 // 如果不是回车

                    // 则将这个字符存放到缓存数组中
                    UART1_RX_Buffer[UART1_RX_STA & 0x3ffff] = buf;
                    UART1_RX_STA++;

                    // 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
                    if (UART1_RX_STA > USART_REC_LEN - 1)
                    
                        UART1_RX_STA = 0;
                    
                
            
        
        // 如果接收完成则重新开启串口1的接收中断
        HAL_UART_Receive_IT(&huart1, &buf, 1);
    


/* 对串口接收数据的处理 */
void usart1_receive_data_handle()

    /* 判断判断串口是否接收完成 */
    if (UART1_RX_STA & 0x8000)
    
        printf("接收完成\\r\\n");

        // 串口接收完数据后,对串口数据进行处理
        if (!strcmp((const char *)UART1_RX_Buffer, "forward"))
        
            printf("前进\\r\\n");
            car_goForward();
        
        else if (!strcmp((const char *)UART1_RX_Buffer, "back"))
        
            printf("后退\\r\\n");
            car_goBack();
        
        else if (!strcmp((const char *)UART1_RX_Buffer, "left"))
        
            printf("左转\\r\\n");
            car_goLeft();
        
        else if (!strcmp((const char *)UART1_RX_Buffer, "right"))
        
            printf("右转\\r\\n");
            car_goRight();
        
        else if (!strcmp((const char *)UART1_RX_Buffer, "stop"))
        
            printf("停止\\r\\n");
            car_goStop();
        
        // 接收到其他数据,进行报错
        else
        
            if (UART1_RX_Buffer[0] != '\\0')
            
                printf("%s\\r\\n", "输入错误,请重新输入");
            
        

        // 换行,重新开始下一次接收
        memset(UART1_RX_Buffer, 0, USART_REC_LEN);
        // printf("\\r\\n");
        UART1_RX_STA = 0;
    


/* USER CODE END 1 */

修改文件usart.h

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file    usart.h
 * @brief   This file contains all the function prototypes for
 *          the usart.c file
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__

#ifdef __cplusplus
extern "C"

#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

    /* USER CODE BEGIN Includes */

    /* USER CODE END Includes */

    extern UART_HandleTypeDef huart1;

    /* USER CODE BEGIN Private defines */

    /* USER CODE END Private defines */

    void MX_USART1_UART_Init(void);

    /* USER CODE BEGIN Prototypes */

    void usart1_receive_data_handle(void);

    /* USER CODE END Prototypes */

#ifdef __cplusplus

#endif

#endif /* __USART_H__ */

main.c文件编写

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>
#include "car.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_USART1_UART_Init();
    /* USER CODE BEGIN 2 */

    printf("haozige\\r\\n");

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */

        usart1_receive_data_handle(); /* 对串口读取的数据进行处理 */
        HAL_Delay(40);
    
    /* USER CODE END 3 */


/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)

    RCC_OscInitTypeDef RCC_OscInitStruct = 0;
    RCC_ClkInitTypeDef RCC_ClkInitStruct = 0;

    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    
        Error_Handler();
    

    /** Initializes the CPU, AHB and APB buses clocks
     */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    
        Error_Handler();
    


/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void)

    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1)
    
    
    /* USER CODE END Error_Handler_Debug */


#ifdef USE_FULL_ASSERT
/**
 * @brief  Reports the name of the source file and the source line number
 *         where the assert_param error has occurred.
 * @param  file: pointer to the source file name
 * @param  line: assert_param error line source number
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line)

    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number,
       ex: printf("Wrong parameters value: file %s on line %d\\r\\n", file, line) */
    /* USER CODE END 6 */

#endif /* USE_FULL_ASSERT */

调速并测速小车

使用TIM2的通道1、通道2给左右电机输入不同占空比的PWM来达到轮子调速的目的,并使用测速模块将轮子当前速度显示在OLED屏,OLED屏使用软件模拟的I2C来驱动。

硬件组成

测速模块

  • 发射的红外线被物体遮挡时,输出高电平,发射的红外线没被物体遮挡时,输出低电平。

  • 即有物体高电平,没物体低电平。

  • 当搭配小车测速盘,会形成下降沿(有遮挡高电平,没遮挡低电平)。

模块与STM32F103板子接线

  • 测速模块DO引脚 <-> PB13

  • OLED屏SCL引脚 <-> PB6

  • OLED屏SDA引脚 <-> PB7

STM32CubeMX相关配置

配置GPIO

  • 配置PB13引脚为引脚中断。

  • 配置PB6引脚输出高电平。

  • 重置PA0、PA1引脚的状态,防止待会配置定时器2通道输出PWM时映射到其他引脚。

配置定时器2和定时器3

配置定时器2的通道1和通道2输出PWM,用来给电机调速。

配置定时器3定时时间为1s。

配置NVIC

  • 打开定时器3和PB13引脚的中断。

  • 修改定时器3响应中断优先级为4,PB13引脚中断响应优先级为3。

文件编写

修改文件car.c

利用PWM修改在一个调速周期内低电平占用的时间来达到轮子调速的效果。

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

/* B_1A、B-2A控制左边电机,A-1A、A-1B控制右边电机 */
#define B_1A_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET) /* PB0 */
#define B_1A_HIGH HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET)

#define A_1A_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET) /* PB2 */
#define A_1A_HIGH HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET)

/* 轮子速度:0~19 */
void car_goForward()

    B_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 19); // B_2A_LOW,PA0

    A_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 19); // A_1B_LOW,PA1


void car_goBack()

    B_1A_LOW;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 0); // B_2A_HIGH;

    A_1A_LOW;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 0); // A_1B_HIGH;


void car_goStop()

    B_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 0); // B_2A_HIGH;

    A_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 0); // A_1B_HIGH;


void car_goRight()

    B_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 19); // B_2A_LOW;

    A_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 0); // A_1B_HIGH;


void car_goLeft()

    B_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 0); // B_2A_HIGH;

    A_1A_HIGH;
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 19); // A_1B_LOW;

修改文件tim.c

启动TIM2的通道1、通道2的PWM,启动TIM3并使能中断。

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file    tim.c
 * @brief   This file provides code for the configuration
 *          of the TIM instances.
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "tim.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;

/* TIM2 init function */
void MX_TIM2_Init(void)


    /* USER CODE BEGIN TIM2_Init 0 */

    /* USER CODE END TIM2_Init 0 */

    TIM_ClockConfigTypeDef sClockSourceConfig = 0;
    TIM_MasterConfigTypeDef sMasterConfig = 0;
    TIM_OC_InitTypeDef sConfigOC = 0;

    /* USER CODE BEGIN TIM2_Init 1 */

    /* USER CODE END TIM2_Init 1 */
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 71;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 19;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
    
        Error_Handler();
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
    
        Error_Handler();
    
    if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
    
        Error_Handler();
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
    
        Error_Handler();
    
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
    
        Error_Handler();
    
    if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
    
        Error_Handler();
    
    /* USER CODE BEGIN TIM2_Init 2 */

    /* 启动B-2A、A-1B引脚的PWM */
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);

    /* USER CODE END TIM2_Init 2 */
    HAL_TIM_MspPostInit(&htim2);

/* TIM3 init function */
void MX_TIM3_Init(void)


    /* USER CODE BEGIN TIM3_Init 0 */

    /* USER CODE END TIM3_Init 0 */

    TIM_ClockConfigTypeDef sClockSourceConfig = 0;
    TIM_MasterConfigTypeDef sMasterConfig = 0;

    /* USER CODE BEGIN TIM3_Init 1 */

    /* USER CODE END TIM3_Init 1 */
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 7199;
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 9999;
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
    
        Error_Handler();
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
    
        Error_Handler();
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
    
        Error_Handler();
    
    /* USER CODE BEGIN TIM3_Init 2 */

    HAL_TIM_Base_Start_IT(&htim3);  /* 启动定时器3,并使能中断 */

    /* USER CODE END TIM3_Init 2 */


void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle)


    if (tim_baseHandle->Instance == TIM2)
    
        /* USER CODE BEGIN TIM2_MspInit 0 */

        /* USER CODE END TIM2_MspInit 0 */
        /* TIM2 clock enable */
        __HAL_RCC_TIM2_CLK_ENABLE();
        /* USER CODE BEGIN TIM2_MspInit 1 */

        /* USER CODE END TIM2_MspInit 1 */
    
    else if (tim_baseHandle->Instance == TIM3)
    
        /* USER CODE BEGIN TIM3_MspInit 0 */

        /* USER CODE END TIM3_MspInit 0 */
        /* TIM3 clock enable */
        __HAL_RCC_TIM3_CLK_ENABLE();

        /* TIM3 interrupt Init */
        HAL_NVIC_SetPriority(TIM3_IRQn, 4, 0);
        HAL_NVIC_EnableIRQ(TIM3_IRQn);
        /* USER CODE BEGIN TIM3_MspInit 1 */

        /* USER CODE END TIM3_MspInit 1 */
    

void HAL_TIM_MspPostInit(TIM_HandleTypeDef *timHandle)


    GPIO_InitTypeDef GPIO_InitStruct = 0;
    if (timHandle->Instance == TIM2)
    
        /* USER CODE BEGIN TIM2_MspPostInit 0 */

        /* USER CODE END TIM2_MspPostInit 0 */

        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**TIM2 GPIO Configuration
        PA0-WKUP     ------> TIM2_CH1
        PA1     ------> TIM2_CH2
        */
        GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        /* USER CODE BEGIN TIM2_MspPostInit 1 */

        /* USER CODE END TIM2_MspPostInit 1 */
    


void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle)


    if (tim_baseHandle->Instance == TIM2)
    
        /* USER CODE BEGIN TIM2_MspDeInit 0 */

        /* USER CODE END TIM2_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_TIM2_CLK_DISABLE();
        /* USER CODE BEGIN TIM2_MspDeInit 1 */

        /* USER CODE END TIM2_MspDeInit 1 */
    
    else if (tim_baseHandle->Instance == TIM3)
    
        /* USER CODE BEGIN TIM3_MspDeInit 0 */

        /* USER CODE END TIM3_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_TIM3_CLK_DISABLE();

        /* TIM3 interrupt Deinit */
        HAL_NVIC_DisableIRQ(TIM3_IRQn);
        /* USER CODE BEGIN TIM3_MspDeInit 1 */

        /* USER CODE END TIM3_MspDeInit 1 */
    


/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

添加文件my_i2c.c

软件模拟I2C来驱动OLED屏显示,也可以使用硬件I2C,就不用此文件了。

#include "gpio.h"

#define SCL_HIGH HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET)
#define SCL_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET)

#define SDA_INPUT SDA_GPIO_PB7_INIT(GPIO_MODE_INPUT)
#define SDA_OUTPUT SDA_GPIO_PB7_INIT(GPIO_MODE_OUTPUT_PP)

#define SDA_HIGH HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET)
#define SDA_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET)
#define SDA_READ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)

/* 微秒延时函数:只适用F1系列72M主频 */
void delay_us(uint32_t us)

    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
    while (delay--)
    
        ;
    


/* 配置PB7为输入引脚或者输出引脚 */
void SDA_GPIO_PB7_INIT(uint32_t mode)

    // 打开时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();

    // 配置引脚
    GPIO_InitTypeDef GPIO_InitStruct = 0;

    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = mode; // 配置为输入(GPIO_MODE_INPUT)或输出(GPIO_MODE_OUTPUT_PP)
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


/* 起始信号 */
void IIC_start()

    SDA_OUTPUT; /* 配置SDA引脚为输出引脚 */
    SCL_HIGH;
    SDA_HIGH;
    delay_us(5);
    SDA_LOW;
    delay_us(5);
    SCL_LOW;
    delay_us(5);


/* 终止信号 */
void IIC_stop()

    SDA_OUTPUT; /* 配置SDA引脚为输出引脚 */
    SDA_LOW;
    delay_us(5);
    SCL_HIGH;
    delay_us(5);
    SDA_HIGH;
    delay_us(5);


/* 检测应答信号:ACK返回0,NACK返回1 */
uint8_t IIC_wait_ack()

    SDA_OUTPUT; /* 配置SDA引脚为输出引脚 */
    SDA_HIGH;    /* 释放数据线 */
    delay_us(5);
    SCL_HIGH; /* 从机返回ACK */
    delay_us(5);

    SDA_INPUT; /* 配置SDA引脚为输入引脚 */
               /* 读取SDA的电平 */
    if (SDA_READ == GPIO_PIN_SET)
    
        /* 如果是高电平则为NACK */
        IIC_stop();
        return 1;
    

    SCL_LOW; /* 结束应答信号的检测 */
    delay_us(5);
    return 0;


/* 发送一个字节数据 */
void IIC_send_byte(uint8_t data)

    SDA_OUTPUT; /* 配置SDA引脚为输出引脚 */
    for (uint8_t i = 0; i < 8; i++)
    
        /* 从最高位开始发送 */
        if ((data & 0x80) >> 7)
        
            SDA_HIGH;
        
        else
        
            SDA_LOW;
        
        delay_us(5);
        SCL_HIGH;
        delay_us(5);
        SCL_LOW;
        data <<= 1; /* 将下一位移至最高位 */
    
    SCL_HIGH; /* 发送完成,释放数据线*/

添加文件my_i2c.h

#include "main.h"

void delay_us(uint32_t us);

void SDA_GPIO_PB7_INIT(uint32_t mode);

void IIC_start(void);

void IIC_stop(void);

uint8_t IIC_wait_ack(void);

void IIC_send_byte(uint8_t data);

添加文件oled.c

#include "my_i2c.h"
#include <string.h>

// OLED的字符构造点阵
unsigned char oledFont[] =
    
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0
        0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x30, 0x00, 0x00, 0x00, //! 1
        0x00, 0x10, 0x0C, 0x06, 0x10, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //" 2
        0x40, 0xC0, 0x78, 0x40, 0xC0, 0x78, 0x40, 0x00, 0x04, 0x3F, 0x04, 0x04, 0x3F, 0x04, 0x04, 0x00, // # 3
        0x00, 0x70, 0x88, 0xFC, 0x08, 0x30, 0x00, 0x00, 0x00, 0x18, 0x20, 0xFF, 0x21, 0x1E, 0x00, 0x00, //$ 4
        0xF0, 0x08, 0xF0, 0x00, 0xE0, 0x18, 0x00, 0x00, 0x00, 0x21, 0x1C, 0x03, 0x1E, 0x21, 0x1E, 0x00, //% 5
        0x00, 0xF0, 0x08, 0x88, 0x70, 0x00, 0x00, 0x00, 0x1E, 0x21, 0x23, 0x24, 0x19, 0x27, 0x21, 0x10, //& 6
        0x10, 0x16, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //' 7
        0x00, 0x00, 0x00, 0xE0, 0x18, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x20, 0x40, 0x00, //( 8
        0x00, 0x02, 0x04, 0x18, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x18, 0x07, 0x00, 0x00, 0x00, //) 9
        0x40, 0x40, 0x80, 0xF0, 0x80, 0x40, 0x40, 0x00, 0x02, 0x02, 0x01, 0x0F, 0x01, 0x02, 0x02, 0x00, //* 10
        0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x1F, 0x01, 0x01, 0x01, 0x00, //+ 11
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xB0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, //, 12
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, //- 13
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, //. 14
        0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x18, 0x04, 0x00, 0x60, 0x18, 0x06, 0x01, 0x00, 0x00, 0x00, /// 15
        0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x0F, 0x10, 0x20, 0x20, 0x10, 0x0F, 0x00, // 0 16
        0x00, 0x10, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, // 1 17
        0x00, 0x70, 0x08, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x30, 0x28, 0x24, 0x22, 0x21, 0x30, 0x00, // 2 18
        0x00, 0x30, 0x08, 0x88, 0x88, 0x48, 0x30, 0x00, 0x00, 0x18, 0x20, 0x20, 0x20, 0x11, 0x0E, 0x00, // 3 19
        0x00, 0x00, 0xC0, 0x20, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x07, 0x04, 0x24, 0x24, 0x3F, 0x24, 0x00, // 4 20
        0x00, 0xF8, 0x08, 0x88, 0x88, 0x08, 0x08, 0x00, 0x00, 0x19, 0x21, 0x20, 0x20, 0x11, 0x0E, 0x00, // 5 21
        0x00, 0xE0, 0x10, 0x88, 0x88, 0x18, 0x00, 0x00, 0x00, 0x0F, 0x11, 0x20, 0x20, 0x11, 0x0E, 0x00, // 6 22
        0x00, 0x38, 0x08, 0x08, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, // 7 23
        0x00, 0x70, 0x88, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x1C, 0x22, 0x21, 0x21, 0x22, 0x1C, 0x00, // 8 24
        0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x31, 0x22, 0x22, 0x11, 0x0F, 0x00, // 9 25
        0x00, 0x00, 0x00, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, //: 26
        0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x00, 0x00, 0x00, 0x00, //; 27
        0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, //< 28
        0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, //= 29
        0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, //> 30
        0x00, 0x70, 0x48, 0x08, 0x08, 0x08, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x36, 0x01, 0x00, 0x00, //? 31
        0xC0, 0x30, 0xC8, 0x28, 0xE8, 0x10, 0xE0, 0x00, 0x07, 0x18, 0x27, 0x24, 0x23, 0x14, 0x0B, 0x00, //@ 32
        0x00, 0x00, 0xC0, 0x38, 0xE0, 0x00, 0x00, 0x00, 0x20, 0x3C, 0x23, 0x02, 0x02, 0x27, 0x38, 0x20, // A 33
        0x08, 0xF8, 0x88, 0x88, 0x88, 0x70, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x11, 0x0E, 0x00, // B 34
        0xC0, 0x30, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, 0x07, 0x18, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, // C 35
        0x08, 0xF8, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x10, 0x0F, 0x00, // D 36
        0x08, 0xF8, 0x88, 0x88, 0xE8, 0x08, 0x10, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x23, 0x20, 0x18, 0x00, // E 37
        0x08, 0xF8, 0x88, 0x88, 0xE8, 0x08, 0x10, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, // F 38
        0xC0, 0x30, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x22, 0x1E, 0x02, 0x00, // G 39
        0x08, 0xF8, 0x08, 0x00, 0x00, 0x08, 0xF8, 0x08, 0x20, 0x3F, 0x21, 0x01, 0x01, 0x21, 0x3F, 0x20, // H 40
        0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, // I 41
        0x00, 0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0xC0, 0x80, 0x80, 0x80, 0x7F, 0x00, 0x00, 0x00, // J 42
        0x08, 0xF8, 0x88, 0xC0, 0x28, 0x18, 0x08, 0x00, 0x20, 0x3F, 0x20, 0x01, 0x26, 0x38, 0x20, 0x00, // K 43
        0x08, 0xF8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x30, 0x00, // L 44
        0x08, 0xF8, 0xF8, 0x00, 0xF8, 0xF8, 0x08, 0x00, 0x20, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x20, 0x00, // M 45
        0x08, 0xF8, 0x30, 0xC0, 0x00, 0x08, 0xF8, 0x08, 0x20, 0x3F, 0x20, 0x00, 0x07, 0x18, 0x3F, 0x00, // N 46
        0xE0, 0x10, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x0F, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0F, 0x00, // O 47
        0x08, 0xF8, 0x08, 0x08, 0x08, 0x08, 0xF0, 0x00, 0x20, 0x3F, 0x21, 0x01, 0x01, 0x01, 0x00, 0x00, // P 48
        0xE0, 0x10, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x0F, 0x18, 0x24, 0x24, 0x38, 0x50, 0x4F, 0x00, // Q 49
        0x08, 0xF8, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x03, 0x0C, 0x30, 0x20, // R 50
        0x00, 0x70, 0x88, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x38, 0x20, 0x21, 0x21, 0x22, 0x1C, 0x00, // S 51
        0x18, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x18, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x00, 0x00, // T 52
        0x08, 0xF8, 0x08, 0x00, 0x00, 0x08, 0xF8, 0x08, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x20, 0x1F, 0x00, // U 53
        0x08, 0x78, 0x88, 0x00, 0x00, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x07, 0x38, 0x0E, 0x01, 0x00, 0x00, // V 54
        0xF8, 0x08, 0x00, 0xF8, 0x00, 0x08, 0xF8, 0x00, 0x03, 0x3C, 0x07, 0x00, 0x07, 0x3C, 0x03, 0x00, // W 55
        0x08, 0x18, 0x68, 0x80, 0x80, 0x68, 0x18, 0x08, 0x20, 0x30, 0x2C, 0x03, 0x03, 0x2C, 0x30, 0x20, // X 56
        0x08, 0x38, 0xC8, 0x00, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x00, 0x00, // Y 57
        0x10, 0x08, 0x08, 0x08, 0xC8, 0x38, 0x08, 0x00, 0x20, 0x38, 0x26, 0x21, 0x20, 0x20, 0x18, 0x00, // Z 58
        0x00, 0x00, 0x00, 0xFE, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x40, 0x40, 0x00, //[ 59
        0x00, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x38, 0xC0, 0x00, //\\ 60
        0x00, 0x02, 0x02, 0x02, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x7F, 0x00, 0x00, 0x00, //] 61
        0x00, 0x00, 0x04, 0x02, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //^ 62
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, //_ 63
        0x00, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //` 64
        0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x19, 0x24, 0x22, 0x22, 0x22, 0x3F, 0x20, // a 65
        0x08, 0xF8, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x11, 0x20, 0x20, 0x11, 0x0E, 0x00, // b 66
        0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0x20, 0x11, 0x00, // c 67
        0x00, 0x00, 0x00, 0x80, 0x80, 0x88, 0xF8, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0x10, 0x3F, 0x20, // d 68
        0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x22, 0x22, 0x22, 0x22, 0x13, 0x00, // e 69
        0x00, 0x80, 0x80, 0xF0, 0x88, 0x88, 0x88, 0x18, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, // f 70
        0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x6B, 0x94, 0x94, 0x94, 0x93, 0x60, 0x00, // g 71
        0x08, 0xF8, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x3F, 0x21, 0x00, 0x00, 0x20, 0x3F, 0x20, // h 72
        0x00, 0x80, 0x98, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, // i 73
        0x00, 0x00, 0x00, 0x80, 0x98, 0x98, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x80, 0x80, 0x7F, 0x00, 0x00, // j 74
        0x08, 0xF8, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x20, 0x3F, 0x24, 0x02, 0x2D, 0x30, 0x20, 0x00, // k 75
        0x00, 0x08, 0x08, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, // l 76
        0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x3F, 0x20, 0x00, 0x3F, // m 77
        0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x3F, 0x21, 0x00, 0x00, 0x20, 0x3F, 0x20, // n 78
        0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x20, 0x1F, 0x00, // o 79
        0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xA1, 0x20, 0x20, 0x11, 0x0E, 0x00, // p 80
        0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0xA0, 0xFF, 0x80, // q 81
        0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x20, 0x20, 0x3F, 0x21, 0x20, 0x00, 0x01, 0x00, // r 82
        0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x33, 0x24, 0x24, 0x24, 0x24, 0x19, 0x00, // s 83
        0x00, 0x80, 0x80, 0xE0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x00, 0x00, // t 84
        0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x10, 0x3F, 0x20, // u 85
        0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0E, 0x30, 0x08, 0x06, 0x01, 0x00, // v 86
        0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x0F, 0x30, 0x0C, 0x03, 0x0C, 0x30, 0x0F, 0x00, // w 87
        0x00, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x31, 0x2E, 0x0E, 0x31, 0x20, 0x00, // x 88
        0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x81, 0x8E, 0x70, 0x18, 0x06, 0x01, 0x00, // y 89
        0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x21, 0x30, 0x2C, 0x22, 0x21, 0x30, 0x00, // z 90
        0x00, 0x00, 0x00, 0x00, 0x80, 0x7C, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x40, 0x40, // 91
        0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, //| 92
        0x00, 0x02, 0x02, 0x7C, 0x80, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x3F, 0x00, 0x00, 0x00, 0x00, // 93
        0x00, 0x06, 0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //~ 94
;

/* OLED写入一条指令 */
void oledWriteCmd(char writeCmd)

    IIC_start();
    IIC_send_byte(0x78); // 选择一个OLED屏,写模式
    IIC_wait_ack();
    IIC_send_byte(0x00); // 写入命令,D/C位为0
    IIC_wait_ack();
    IIC_send_byte(writeCmd);
    IIC_wait_ack();
    IIC_stop();


/* OLED写入一个数据 */
void oledWriteData(char writeData)

    IIC_start();
    IIC_send_byte(0x78); // 选择一个OLED屏,写模式
    IIC_wait_ack();
    IIC_send_byte(0x40); // 写入命令,D/C位为1
    IIC_wait_ack();
    IIC_send_byte(writeData);
    IIC_wait_ack();
    IIC_stop();


// OLCD初始化
void oledInit()

    oledWriteCmd(0xAE);
    oledWriteCmd(0x00);
    oledWriteCmd(0x10);
    oledWriteCmd(0x40);
    oledWriteCmd(0xB0);
    oledWriteCmd(0x81);
    oledWriteCmd(0xFF);
    oledWriteCmd(0xA1);
    oledWriteCmd(0xA6);
    oledWriteCmd(0xA8);
    oledWriteCmd(0x3F);
    oledWriteCmd(0xC8);
    oledWriteCmd(0xD3);
    oledWriteCmd(0x00);
    oledWriteCmd(0xD5);
    oledWriteCmd(0x80);
    oledWriteCmd(0xD8);
    oledWriteCmd(0x05);
    oledWriteCmd(0xD9);
    oledWriteCmd(0xF1);
    oledWriteCmd(0xDA);
    oledWriteCmd(0x12);
    oledWriteCmd(0xDB);
    oledWriteCmd(0x30);
    oledWriteCmd(0x8D);
    oledWriteCmd(0x14);
    oledWriteCmd(0xAF);


// OLED全屏清屏
void oledClean()

    int i, j;
    for (i = 0; i < 8; i++)
    
        oledWriteCmd(0xB0 + i); // 选择PAGE
        // 选择PAGE的第0列开始显示
        oledWriteCmd(0x00);
        oledWriteCmd(0x10);
        for (j = 0; j < 128; j++)
        
            oledWriteData(0); // 写入字符0
        
    


// OLED行清屏
void oled_rowClean(char rows)

    unsigned char i, j;
    for (i = 0; i < 2; i++)
    
        oledWriteCmd(0xB0 + (rows * 2 - (2 - i))); // 选择PAGE
        // 选择PAGE的第0列开始显示
        oledWriteCmd(0x00);
        oledWriteCmd(0x10);
        for (j = 0; j < 128; j++)
        
            oledWriteData(0); // 写入字符0
        
    


// OLED显示一个字符
void oledShowByte(char rows, char columns, char oledByte)

    unsigned int i;

    // 显示字符的上半部分
    oledWriteCmd(0xb0 + (rows * 2 - 2)); // 选择行

    // 选择列
    oledWriteCmd(0x00 + (columns & 0x0f));
    oledWriteCmd(0x10 + (columns >> 4));

    // 显示数据
    for (i = ((oledByte - 32) * 16); i < ((oledByte - 32) * 16 + 8); i++)
    
        oledWriteData(oledFont[i]);
    

    // 显示字符的上半部分
    oledWriteCmd(0xb0 + (rows * 2 - 1)); // 选择行

    // 选择列
    oledWriteCmd(0x00 + (columns & 0x0f));
    oledWriteCmd(0x10 + (columns >> 4));

    // 显示数据
    for (i = ((oledByte - 32) * 16 + 8); i < ((oledByte - 32) * 16 + 8 + 8); i++)
    
        oledWriteData(oledFont[i]);
    


// OLED显示一个字符串
void oledShowString(char rows, char columns, char *str)

    while (*str != '\\0')
    
        oledShowByte(rows, columns, *str);
        str++;
        columns += 8;
    

添加文件oled.h

#include "main.h"

// OLED写入一条指令
void oledWriteCmd(uint8_t writeCmd);

// OLED写入一个数据
void oledWriteData(uint8_t writeData);

// OLCD初始化
void oledInit(void);

// OLED清屏
void oledClean(void);

// OLED行清屏
void oled_rowClean(char rows);

// OLED显示一个字符
void oledShowByte(char rows, char columns, char oledByte);

// OLED显示一个字符串
void oledShowString(char rows, char columns, char *str);

main.c文件编写

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>
#include <string.h>

#include "car.h"
#include "oled.h"

uint8_t speed_cnt = 0;
uint8_t speedMsg[24] = 0;

/* 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 */

/* PB13引脚中断的回调函数 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

    if (GPIO_Pin == GPIO_PIN_13)
     /* 如果是PB13引脚产生的中断 */
        // HAL_Delay(50);  /* 按键防抖,这个delay函数不知道为什么引起OLED显示失败 */
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13) == GPIO_PIN_RESET)
        
            speed_cnt++;
        
    


/* 定时器3中断的回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

    // 判断是否是定时器3产生的中断
    if (htim3.Instance == TIM3)
    
        if (speed_cnt < 10)
        
            sprintf((char *)speedMsg, "speed:  %d cm/s ", speed_cnt);
        
        else
        
            sprintf((char *)speedMsg, "speed: %d cm/s ", speed_cnt);
        

        printf("%s\\r\\n", speedMsg);
        oledShowString(3, 1, (char *)speedMsg);
        speed_cnt = 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_USART1_UART_Init();
    MX_TIM2_Init();
    MX_TIM3_Init();
    /* USER CODE BEGIN 2 */

    oledInit();     // OLED初始化
    oledClean(); /* 清屏函数 */

    // 设置寻址模式
    oledWriteCmd(0x20); // 设置内存
    oledWriteCmd(0x02); // 选择页寻址模式

    oledShowString(1, 1, "diedie_car");
    oledShowString(2, 1, "move: stop");

    oledShowString(4, 1, "mode: bizhang");

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */

        usart1_receive_data_handle(); /* 对串口读取的数据进行处理 */
    
    /* USER CODE END 3 */


/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)

    RCC_OscInitTypeDef RCC_OscInitStruct = 0;
    RCC_ClkInitTypeDef RCC_ClkInitStruct = 0;

    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    
        Error_Handler();
    

    /** Initializes the CPU, AHB and APB buses clocks
     */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    
        Error_Handler();
    


/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void)

    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1)
    
    
    /* USER CODE END Error_Handler_Debug */


#ifdef USE_FULL_ASSERT
/**
 * @brief  Reports the name of the source file and the source line number
 *         where the assert_param error has occurred.
 * @param  file: pointer to the source file name
 * @param  line: assert_param error line source number
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line)

    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number,
       ex: printf("Wrong parameters value: file %s on line %d\\r\\n", file, line) */
    /* USER CODE END 6 */

#endif /* USE_FULL_ASSERT */

摇头避障小车

利用sg90舵机转动不同的角度配合超声波获取小车前方的障碍物的距离来达到避障的效果。

硬件组成

超声波模块HC-SR04

  • 发送超声波:当Trig引脚接收到一个10微秒以上的高电平后开始发送超声波,当开始发送超声波后,Echo引脚会从低电平跳转到高电平。


  • 1 简介

    Hi,大家好,这里是丹成学长,今天向大家介绍一个 单片机项目

    基于 STM32 的自动泊车系统

    大家可用于 课程设计 或 毕业设计

    技术解答、毕设帮助、开题指导
    print("Q 746876041") 
    

    1 背景

    目前我们所能见到的汽车企业大多数都控制在 L2 级半自动驾驶, 其中包括半自动泊车系统: 传感器技术的组成配合下收集精确的环境信息实现“泊车路径规划” 、 系统进行“车辆控制” 等功能, 在部分自动泊车(高级泊车辅助 APA)系统的辅助下驾驶员可以在监控下实现泊车动作。 现实中, 车辆激增带来的停车问题变得更加复杂, 大城市停车空间有限, 将汽车驶入各式各样的停车位置是每个驾驶员一项必备的技能。 与之相随而来的交通压力、 复杂多样的停车环境、 有限苛刻的停车空间, 停车难成为众多驾驶员的一大难题。 泊车过程中出现的轻微碰撞以及剐蹭事件的频繁发生, 市场需求的增长, 环境检测基础的相对成熟, 推进了自动泊车系统研究的技术发展。

    2 设计概要

    自动泊车系统(Automated Parking System,简称 APS)是一项属于无人驾驶的重要技术,随着车辆的激增, 汽车自动驾驶技术的不断创新与发展, 自动泊车系统尚未普及, 为降低当下人工停车的难度, 设计了一种基于 STM32F103ZET6 实现自动倒车入库和侧方位停车的智能化小车系统。

    该自动泊车系统利用红外光感 HJ-IR2 传感器、 超声波 HC-SR04 模块、 循迹 TCRT5000模块、 标准的 IEEE 802.11nd 的 WIFI 模块组成进行环境检测, 实现了障碍物识别, 并将采集到的数据传送到处理器, 处理器将数据转换为电信号驱动小车的控制 L298N 驱动模块, 这些模块在 Keil uVsion5 编译环境下整合到一起, 在路径规划下对小车进行速度控制和转向控制。

    系统根据停车位识别在模拟停车环境下控制小车进行入库操作, 满足了在不同的停车环境下进行智能化自动泊车, 此系统属于嵌入式系统兼容于多数汽车实现侧方位泊车和倒车入库并自行细微调整, 实现更稳定的入库停车。

    3 硬件选型

    3.1 主控 - STM32

    意法半导体公司中的 STM32 系列芯片


    3.2 电机驱动

    为了更真实模拟汽车运作状态, 本设计安装了四个电机, 如下图所示。 本设计通过 PWM 占空比来调节直流电机的转速, 并通过控制前后轮的不同速度完成小车的转向。


    电机用到了主控板的 8 个 IO 口, 通过对电机的高低电平控制使之正反转, 电机分有正负极, 前进时: 先将正极的电平置 1, 负极复位 0; 后退则正极复位 0, 负极置 1; 停止则都复位 0。

    电机与芯片管脚配置表

    小车由于左右两边各用一个驱动, 所以 A 和 C 两个电机只需要 C 电机控制, B 和 D电机只需 B 控制, 这里将 D 电机控制脚当电机使能: 将 DUP 和 DDOWN 的电平置 1。

    3.3 红外遥控设计

    学长设计的系统采用红外遥控来实现对小车的初步控制, 红外线遥控是目前使用最广泛的一种通信和遥控手段, 具有体积小、 功耗低、 功能强、 成本低的优点。 通用红外遥控系统由发射和接收两大部分组成, 应用编/解码专用集成电路芯片来进行控制操作, 发射部分包括键盘、 编码调制、 LED、 红外发送器; 接收部分包括光、 电转换放大器、 解调、 解码电路。

    红外遥控器

    红外接收及解码


    相关技术原理:

      1. 引导码: MCU 检测到正确的引导码之后确认接收后面的数据, 以此来保证数据的稳定性和正确性,
      1. 客户码: 区分不同红外遥控设备
      1. 操作码: 客户操作时产生的编码, 通过操作的不同产生不同的码值, 等待红外接收头接收。
      1. 解码: 芯片通过接收到的电平信号, 解析操作码的码值, MCU 在根据码值做出相应动作。

    根据遥控器提供的键码, 优先满足小车的基本运动, 前进、 后退、 左转、 右转、 以及停止, 设计算法, 短按则为 200MS 运作, 长按则持续运行。 预留其他按钮进入自动驾驶状态, 即: 循迹模式、 避障模式、 超声波模式、 摄像头模式、 侧方位自动泊车, 倒车入库自动泊车等。

    3.4 传感器部分

    3.4.1 循迹模块


    遵循既有的道路现状, 给定小车相关的循迹算法, 基于一个三路循迹和两个二路循迹的循迹模块, 使得小车自动行驶在路劲规划下, 称为循迹技术。

    3.4.2 红外避障模块


    红外光电传感器(HJ-IR2), 发射出探测的脉冲, 当在一定距离中探测到物体会重新输入到 MCU 中进行处理, 它相当于一个红外开关, 检测到障碍物输出低电平, 未检测到则反之。 在得知在停车过程中遇到的障碍物可以依据此对小车进行控制避免碰撞, 此为避障功能。

    接收管接收到信号之后, 经集成电路进行放大, 会点亮模块的 LED 灯管, 并同时输出给 MCU 一个低电平信号。

    3.4.3 超声波模块

    HC-SR04 超声波测距可提供 2cm-40cm 的非接触式距离感测功能测距精度可达高到3mm; 模块包括超声波发射器、 接收器与控制电路。 本设计利用超声波传感器来达到对自动泊车中精细调整。 小车在进入自动泊车模式后, 环境检测部分会通过超声波收集左右部掐障碍物的具体位置, 在侧方位泊车中可以利用检测前后车辆的停车距离,倒车入库泊车中可以检测左右车辆的精准距离, 实现更完美的泊车路径规划。 超声波模块如图

    3.4.4 WIFI 视频模块

    设计模拟倒车途中的后视摄像头, 将倒车时的情景展示在与上位机的屏幕当中,模拟出更真实的停车环境, 实时监控倒车时的情况, 并通过 WIFI 模块, 在上位机编写好指定代码传送给小车上的 WIFI 模块, WIFI 模块中的芯片进行解码并发送电信号给 MCU进行控制, MCU 根据 WIFI 模块中的信号对小车进行控制。

    PC控制软件

    软件中的界面基本满足对小车的基本控制, 附带有打开摄像头的指令, 在设置中也可以按照需求编写新指令发送给主控芯片。 操作界面如下图所示

    这里的设计需要注意, 同时开启数据的收发, 根据收到的 WIFI 信号进行判断, 串口 WIFI 解码代码如下:

    unsigned char rec_data;
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
    
    USART_ClearITPendingBit(USART3, USART_IT_RXNE);
    /* Read one byte from the receive data register */
    rec_data = USART_ReceiveData(USART3);
    if(start!=0&&rec_data!=0xff) //如果已收到包头并且当前收到的不是包尾
    
    buf[start-1]=rec_data; //缓存数据
    start++;
    
    else if(start!=0&&rec_data==0xff)//如果收到包尾
    
    mode[0]=buf[0]; //给状态存储数组赋值
    mode[1]=buf[1];
    mode[2]=buf[2];
    start=0;
    mode1=1; //指示主函数循环检测一次
    
    else if(rec_data==0xff&&start==0) //如果收到的是包头
    start++;
    
    
    

    4 小车成品展示

    摄像头则安装在车尾处, 可以观察倒车时后视的环境, 也适用于作为图像处理的后续功能推进, WIFI 模块在开发板的下方, 不影响信号的前提下也得到了合理的空间放置。

    5 泊车算法设计

    常见的泊车方式有: 侧方位泊车和倒车入库泊车, 本设计根据两种泊车方式设计了不同的算法。

    5.1 侧方位泊车

    侧方位停车位的设置主要是为了道路的宽度而设置, 机动车驾驶证考试中的科目二就有一项侧方位停车法。 本设计由科目二驾驶中的侧方停车为标准, 侧方位泊车如图所示。

    根据停车的不同场景, 本设计通过收集的环境信息进行了不同的处理, 并作出不同的控制电信号。

    如果未扫描到障碍物和相邻小车, 系统主要通过循迹模块对停车线反射的强度不同来定位停车位的具体位置。 具体算法逻辑如流程图所示:

    算法步骤:

    • 1、 按下遥控器按钮平行泊车模式, 开始检测周围环境信息, 如果超声波反射回来的距离小于 10CM 则判断为障碍物, 大于 10CM 则判断为无障碍物。
    • 2、 减速后退直到超声波判断距离大于 10CM 且红外避障无感应。
    • 3、 红外避障有感应则继续后退, 重复(1)(2)步骤。
    • 4、 左循迹模块感应到停车线, 系统收集到此时车辆左转角度, 然后继续左转。
    • 5、 右循迹模块感应到停车线, 系统收集到此时车辆位置信息, 由于小车左转角度还不足, 系统此时再给予一个 200MS 的左转信号。
    • 6、 车辆左转角度已经足够, 停车时的车辆速度不能过快, 修改高电平的占比, 降低速度, 给出一个持续前进的电信号
    • 7、 中循迹模块感应到边缘停车线, 小车停止。
    • 8、 开始摆正车身, 根据左转的角度, 给出相同延时右转信号摆正车身
    • 9、 右转进行时, 对中循迹模块进行判定, 如果收集到电信号, 对小车进行后退调整,然后完成整个系统泊车操作。

    关键代码

    if(LL_DATA == BLACK_AREA)
    
    CarStop();
    delay_ms(18000);
    auto_flag = 1;
    
    CarRight();
    
    else if (auto_flag == 1)
    
    if(RR_DATA == BLACK_AREA)
    
    CarStop();
    delay_ms(18000);
    CarRight();
    delay_ms(6000);
    CarStop();
    delay_ms(18000);
    auto_flag = 2;
    
    CarRight();
    
    else if(auto_flag == 2)
    
    SPEED_DUTY = 10;
    if(SEARCH_M_DATA == BLACK_AREA)
    
    CarStop();
    delay_ms(18000);
    CarBack();
    delay_ms(6000);
    back_time = 0;
    back_flag = 0;
    auto_flag = 3;
    
    CarGo();
    
    else if(auto_flag == 3)
    
    SPEED_DUTY = 30;
    if(back_time <= 600)
    
    CarLeft();
    if(SEARCH_M_DATA == BLACK_AREA)
    
    back_flag = 1;
    
    if(back_time == 599)auto_flag = 4;
    
    
    else if (auto_flag == 4)
    
    if(back_flag == 0)sensor_flag = 0;
    else if(back_flag == 1)
    
    SPEED_DUTY = 20;
    CarBack();
    delay_ms(4000);
    CarStop();
    sensor_flag = 0;
    
    
    

    5.2 倒车入库

    倒车入库是多数停车场中的停车建设, 停停车场都有较为规范的停车线规划和停车建设, 对于线条的感应也更容易, 倒车入库示意图如图所示

    算法设计

    算法流程:

    • 1、 按下遥控器按钮垂直泊车模式, 开始检测周围环境信息, 如果超声波反射回来的距离小于 10CM 则判断为障碍物, 大于 10CM 则判断为无障碍物。
    • 2、 减速后退直到超声波判断距离大于 10CM, 且红外避障未感应到障碍物, 开始左转
    • 3、 左循迹模块感应到停车线, 系统收集到此时车辆左转角度, 然后继续左转
    • 4、 右循迹模块感应到停车线, 系统收集到此时车辆位置信息,
    • 5、 车辆左转角度已经足够,调整车身, 左转至车身垂直。
    • 6、 根据车身调整位置, 三路循迹在同一条直线上, 给出前进信号。
    • 7、 倒车入库完成。
    void AUTO_Vertical(void)
    
    if (auto_flag == 0 )
    
    if(obstacle_flag == 0)
    
    if(distance_cm < 15 )
    
    SPEED_DUTY = 10;
    CarGo();
    return;
    
    else if(distance_cm >= 15 )
    
    CarStop();
    obstacle_flag = 1;
    
    
    if( obstacle_flag == 1)
    
    SPEED_DUTY = 30;
    CarRight();
    if(LL_DATA == BLACK_AREA)
    
    CarStop();
    delay_ms(18000);
    auto_flag = 1;
    
    
    
    else if (auto_flag == 1)
    
    if(SEARCH_L_DATA == BLACK_AREA)
    
    CarStop();
    delay_ms(18000);
    auto_flag = 2;
    
    CarRight();
    
    else if(auto_flag == 2)
    
    SPEED_DUTY = 10;
    CarGo();
    if(SEARCH_M_DATA == BLACK_AREA)
    
    CarStop();
    delay_ms(18000);
    CarBack();
    delay_ms(8000);
    auto_flag = 3;
    back_time = 0;
    
    
    else if(auto_flag == 3)
    
    SPEED_DUTY = 30;
    if(back_time >= 12)auto_flag = 4;
    if(back_time <= 14)
    
    CarRight();
    
    
    else if(auto_flag == 4)
    
    if(SEARCH_M_DATA == BLACK_AREA)
    
    CarStop();
    delay_ms(18000);
    CarBack();
    delay_ms(6000);
    sensor_flag = 0;
    
    SPEED_DUTY = 10;
    CarGo();
    
    
    
    

    6 测试效果

    优先模拟的周围无障碍物的情形, 会通过判断停车线然后根据算法步骤将车停进去车位。 经过多次测试, 小车多次稳定的停入车位, 但若控制者将车驶在与停车位平行的位置, 会导致停车缓慢且容易出错, 车位可供停车的范围需对小车的位置有限制。 运行完侧方位泊车之后小车的位置如图



    7 最后

    技术解答、毕设帮助、开题指导
    print("Q 746876041") 
    

以上是关于STM32之智能小车,手把手从0到1,模块化编程的主要内容,如果未能解决你的问题,请参考以下文章

毕业设计之 - 题目:基于stm32的WiFi监控小车

基于STM32的智能小车--电机驱动设计

第二天:SLAM智能小车DIY乐趣-小车控制stm32软件基础

毕业设计:基于单片机的超声波智能跟随小车 - 物联网 智能小车 嵌入式单片机 stm32 跟随小车

基于STM32C8T6的智能蓝牙小车控制设计-毕设课设资料

黑白线循迹小车利用STM32F407与三个红外对管实现