通过 STM32与红外循迹模块对小车自动控制,将小车与联网模块、定位模块连接,小车提供联网模块与云端连接,实现信息的上传,同时若有需要,也可以通过云端下发命令控制小车行进。
主控:STM32F103C8T6最小系统版传感器:红外传感器电机驱动:TB6612联网模块:ESP8266GPS:SR2828Z1编程:KEIL5(库函数)云平台:腾讯云
目录
一、STM32F103C8T6最小系统版
实物图如下

STM32F103片上外设有下表

而本设计需要用到的外设主要有NVIC、RCC、GPIO、EXTI、TIM、ADC、USART,这些外设都是STM32最基本的,也是必须掌握的。
本设计主要使用得STM32功能主要为要想外设与STM32能正常地联系工作,就要清楚STM32引脚的主要功能。结合上表与实物图选择相应的引脚连接。

二、红外传感器
主要使用淘宝就能买到的红外传感器,用来实现避障的简单功能。实物图如下。

这个传感器工作原理简单啊概括就是收到反射的信号就输出低电平。因此在程序中检测STM32连接传感器的引脚是否收到低电平就可以判断小车是否遇到障碍物。传感器上的十字旋钮是调整敏感度的。
要实现传感器与stm32的配合就要使用GPIO外设功能。当然,对于判断引脚电平不停使用库函数既麻烦代码也不简洁。因此可以使用位带操作的方法来直接使用if(PNin(n))来进行逻辑判断。
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#ifndef __CH_LIB_GPIO_H__
#define __CH_LIB_GPIO_H__
#ifdef __cplusplus
extern "C" {
#endif
//位带操作,实现51类似的GPIO控制功能
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
/* GPIO端口定义 */
#define HW_GPIOA (0x00U)
#define HW_GPIOB (0x01U)
#define HW_GPIOC (0x02U)
#define HW_GPIOD (0x03U)
#define HW_GPIOE (0x04U)
#endif
这样就可以直接使用PNin(n)来指代引脚的输入状态。
下面进行GPIO配置。下面是直接包装好的配置函数,对于红外传感器一般使用上拉输入。
u8 GPIO_QuickInit(int instance, int GPIO_Pin_x, GPIOMode_TypeDef Mode)
{
/* config state */
GPIO_InitTypeDef GPIO_InitStructure;
switch(instance)
{
case HW_GPIOA:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
GPIO_InitStructure.GPIO_Mode = Mode;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始
break;
case HW_GPIOB:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
if(GPIO_Pin_x == GPIO_Pin_4 || GPIO_Pin_x == GPIO_Pin_3 ){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
}
GPIO_InitStructure.GPIO_Mode = Mode;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始
break;
case HW_GPIOC:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
GPIO_InitStructure.GPIO_Mode = Mode;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始
break;
case HW_GPIOD:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
GPIO_InitStructure.GPIO_Mode = Mode;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOD, &GPIO_InitStructure); //根据设定参数初始
break;
case HW_GPIOE:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
GPIO_InitStructure.GPIO_Mode = Mode;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOE, &GPIO_InitStructure); //根据设定参数初始
break;
default:
return 0;
}
return 1;
}
这样GPIO就基本配置完成,我们只要在主函数中检测引脚状态就可以自动判断是否存在障碍物。例如我把传感器接在PA4,当出现障碍物让小车急停。
while(PAin(4))
{
carstop();
}
三、电机驱动
既然是小车就要有轮子,有轮子就要用舵机让车动起来。如何顺利驱动四个轮子呢?就是用电机驱动的芯片实现。下面以TB6612为例。实物图、引脚电路图如下。

驱动表如下。

有了传感器工作为基础,这就简单多了。无非又是用GPIO外设控制罢了,不过这次是输出而不是输入哦。通过GPIO函数配置推挽输出,再排列组合出小车的各种运动姿态。
/*宏定义右轮*/
#define WHEEL_A_PWM PBout(7)
#define WHEEL_A_1 PBout(8)//前进轮
#define WHEEL_A_2 PBout(9)
/*宏定义左轮*/
#define WHEEL_B_PWM PBout(6)
#define WHEEL_B_1 PBout(5)//前进轮
#define WHEEL_B_2 PBout(4)
void carforward(void)
{
WHEEL_A_PWM = 1;
WHEEL_A_1 = 0;
WHEEL_A_2 = 1;
WHEEL_B_PWM = 1;
WHEEL_B_1 = 0;
WHEEL_B_2 = 1;
}
void carstop(void)
{
WHEEL_A_PWM = 0;
WHEEL_A_1 = 0;
WHEEL_A_2 = 0;
WHEEL_B_1 = 0;
WHEEL_B_2 = 0;
WHEEL_B_PWM = 0;
}
void carback(void)
{
WHEEL_A_PWM = 1;
WHEEL_A_1 = 1;
WHEEL_A_2 = 0;
WHEEL_B_1 = 1;
WHEEL_B_2 = 0;
WHEEL_B_PWM = 1;
}
void turn_right(void)
{
WHEEL_A_PWM = 0;
WHEEL_A_1 = 0;
WHEEL_A_2 = 0;
WHEEL_B_1 = 0;
WHEEL_B_2 = 1;
WHEEL_B_PWM = 1;
}
void turn_left(void)
{
WHEEL_A_PWM = 1;
WHEEL_A_1 = 0;
WHEEL_A_2 = 1;
WHEEL_B_PWM = 0;
WHEEL_B_1 = 0;
WHEEL_B_2 = 0;
}
void turn_right_in_place(void)
{
WHEEL_A_PWM = 1;
WHEEL_A_1 = 1;
WHEEL_A_2 = 0;
WHEEL_B_PWM = 1;
WHEEL_B_1 = 0;
WHEEL_B_2 = 1;
}
void turn_left_in_place(void)
{
WHEEL_A_PWM = 1;
WHEEL_A_1 = 0;
WHEEL_A_2 = 1;
WHEEL_B_PWM = 1;
WHEEL_B_1 = 1;
WHEEL_B_2 = 0;
}
当然,如果觉得车速过高or低,可以通过PWM波的方法来控制车速,这里就不赘述了。
四、联网模块
既然标题有物联网,就少不了使用ESP8266。实物图、引脚电路图如下。

在使用ESP8266上云方面我选择讨巧使用AT固件的方法,使用AT固件固然简单,但对代码移植和个人学习都不利,但是我就不是物联网专业的,所以选择所以讨巧的方法。
具体方式我写过文章。
五、电量显示
小车电量是很重要的参数,通过对小车电量的多少可以知道小车是否能够运行。本设计通过使用STM32的模数转换器来计算小车的电量多少。通过串联电阻对电源进行分压处理,电容进行滤波,使得模数转换器测量的电压在3.3V以下。图AD采样电路图。

由于本设计只有电量需要使用模数转换器,在程序配置中采用单通道单次扫描的方式进行转换。通过判断转换标志位(ADC_FLAG_EOC)来判断转换是否完成,当转换完成后使用函数读取数据寄存器的到转换后的值。考虑到数据的波动影响,对数据进行均值滤波。
#include "stm32f10x.h" // Device header
u16 ADValue;
float Power;
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitSturcture;
ADC_InitSturcture.ADC_Mode = ADC_Mode_Independent;
ADC_InitSturcture.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitSturcture.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitSturcture.ADC_ContinuousConvMode = DISABLE;
ADC_InitSturcture.ADC_ScanConvMode = DISABLE;
ADC_InitSturcture.ADC_NbrOfChannel = 1;
ADC_Init(ADC1,&ADC_InitSturcture);
ADC_Cmd(ADC1,ENABLE);
//开始校准
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
}
u16 AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
u16 CarPower(void)
{
u32 temp = 0;
u8 t;
u32 AD_Value;
double AD_Final_Value;
double Power;
for(t=0;t<10;t++)
{
temp += AD_GetValue();
}
AD_Value = temp / 10;
AD_Final_Value = 3 * (float)AD_Value / 4095 * 3.3;
if(AD_Final_Value > 9.9) {AD_Final_Value = 9.9 ;}
Power = AD_Final_Value * 10;
return(Power);
}
AD转换器功能当然不止转换电量那么简单。可以考虑在小车放置光敏传感器、热敏传感器、湿度传感器等,通过AD转换器得到数值信息。转换多个模拟信号就要采用多通道的方法,同时考虑到数据的实时性,要采用DMA的方法转运数据。这里也不过多赘述。
六、信息传递
联网模块和GPS模块都是需要与stm32实现串口的信息传递的,所以,这就需要使用stm32的USART外设。同时,因为信息传递要有实时性,我们一旦接受到信息就要立刻解包,使用这里要使用到接受中断NVIC。将两个模块连接到串口1,2,通过串口接收中断,在中断程序中解包,把信息传递给stm32。下面是包装好的串口配置函数。
/**
* @brief 串口快速初始化程序
* @code
* 初始化UART2: 9600-0-8-1,主优先级preepri=2,响应优先级subpri=2,使能中断ITsta=ENABLE
* UART_QuickInit(HW_UART2, 9600, 2, 2, ENABLE);
* @endcode
* @param[in] instance 模块号
* @arg HW_UART1 1端口
* @arg HW_UART2 2端口
* @arg HW_UART3 3端口
* @param[in] bound 波特率: 9600,115200....
* @param[in] preepri 抢占优先级,默认group2,可选值0,1,2,3
* @param[in] subpri 响应优先级,默认group2,可选值0,1,2,3
* @param[in] ITsta 使能/失能中断,可选值ENABLE,DISABLE
* @retval UART初始化结果,1初始化正确,0初始化失败
*/
uint8_t UART_Config(int instance, int bound, int preepri, int subpri, FunctionalState ITsta)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
switch(instance)
{
case HW_UART1:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
USART_DeInit(USART1); //复位串口1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //USART1_TX PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_RX PA.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=preepri ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = subpri; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ITsta; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
USART_InitStructure.USART_BaudRate = bound; //一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ITsta); //开启中断
USART_Cmd(USART1, ENABLE); //使能串口
case HW_UART2:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); //使能USART1,GPIOA时钟
USART_DeInit(USART2); //复位串口1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //USART1_TX PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //USART1_RX PA.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=preepri ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = subpri; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ITsta; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
USART_InitStructure.USART_BaudRate = bound; //一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART2, &USART_InitStructure); //初始化串口
USART_ITConfig(USART2, USART_IT_RXNE, ITsta); //开启中断
USART_Cmd(USART2, ENABLE); //使能串口 //根据设定参数初始
break;
case HW_UART3:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB, ENABLE); //使能USART1,GPIOA时钟
USART_DeInit(USART3); //复位串口1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_TX PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PA9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //USART1_RX PA.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PA10
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=preepri ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = subpri; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ITsta; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
USART_InitStructure.USART_BaudRate = bound; //一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART3, &USART_InitStructure); //初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ITsta); //开启中断
USART_Cmd(USART3, ENABLE); //使能串口 //根据设定参数初始
break;
default:
return 0;
}
return 1;
}
七、结束
这篇文章其实都是相当基础的东西,想要驱动小车,就要实现stm32上很基础的外设功能。这也是我第一次写这么长的文章,如有纰漏,敬请指正。
本文介绍了一款基于STM32F103C8T6的物联网监控小车设计,包括STM32最小系统、红外传感器避障、电机驱动、联网模块(ESP8266)以及电量显示。通过红外传感器实现避障,电机驱动控制小车运动,ESP8266实现与云端的通信,电量显示通过ADC监测电池状态。整个设计利用STM32的基本外设功能,如GPIO、EXTI、TIM、ADC、USART。
2786






