文章目录
1 背景
近几年,人们的生活正在逐渐向智能化转变, 嵌入式技术及一些新技术的快速发展, 使人们生活和工作变得越来越智能化 。智能小车可以在所处的环境中通过传感器自 行进行判断和分析,在无人操作的情况下自 主完成任务。
设计的智能小车通过WIFI实现远程无线控制,同时具有避障及温度采集功能, 实现了小车的智能化, 可以作为研究智能汽车或者其他移动机器人的基础模型,具有较大的研究空间。
2 系统设计方案
2.1 实现功能
学长设计的小车能够实现无线控制, 避障, 循迹等功能, 由硬件, 软件,无线传输三大部分组成,现对以上三部分功能进行具体的叙述 。
2.1.1 硬件部分:
输出 PWM 控制电机; 检测障碍物, 检测距离为 10cm;串口通信, 需要通过串口对智能小车进行调试, 串口波特率设置为 115200;对接收到的命令进行处理和判断;通过温度传感器检测环境温度; 连接WIFI 模块及控制 WIFI模块;通过uCOS-II实现多个任务同时执行。
2.1.2 软件部分:
操作界面功能;通过SOCKET编程实现联网,可以连接到 WIFI模块;接收STM32开发板传输的数据和发送指令数据;利用摄像头进行拍照, 在小车行驶过程中接收小车传输的图片信息并显示;实现小车的模式切换, 模式 1 为无线控制行驶模式,模式2为自动行驶模式。
2.1.3 WIFI通信功能:
与 PC 端进行联网连接;实现PC 与单片机之间的数据交换功能。
2.2 系统架构
小车采用 STM32F103 开发板,与 51 单片机相比, STM32可以搭载小型系统且速度更快,设计方案如下图所示。
STM32开发板作为本设计的控制中心, 使用 PWM 输出波形驱动电机转动, 通过内部定时器达到控制方向的效果, 同时将接收到的数据及命令经过处理器判断和计算从IO口输出。
- 利用温度传感器采集温度信息通过串口传输到 WIFI模块,在PC 端显示;
- 利用红外传感器实现探测障碍和循迹功能。
- WIFI模块是 PC 端和开发板进行数据交互的媒介, PC 端的操作指令由WIFI模块进行发送,开发板和 PC 端之间设置了相应的数据协议,由此判断接收到的是哪种类型的命令,小车根据命令执行相应的操作。
2.2.1 WiFi 通信
WIFI通信模块作为 STM32和 PC 端通信的中介, 两端都通过 WIFI模块进行数据交互, 该模块选用 ESP8266芯片 [5] , 其特点就是如果断开连接, 再次连接, 模块会连接到最近一次连接过的热点。
ESP8266支持三种模式, 分别为 STA 模式, AP模式, STA+AP 模式。
这里学长使用 AP 模式, 使其他网 络能 够连接到ESP8266。 本设计使用 ESP8266的 AP模式,使其他网络能够连接到 ESP8266,与STM32的引脚连接如表所示。
2.2.2 电机驱动
为了实现PC 端能够控制小车的速度以及满足直流电机的驱动 电 压, 本设计选用 L298N 电 机驱动 模块给直流 电 机供电 。
电机状态与STM32输入如表2。 电机驱动共有两个电源输入接口,一个是5V,另一个是12V,在使用时12V的接口输入电压要大于7V,5V的接口就可以向单片机供电。
2.2.3 摄像头
ov7670是OmniVision公司生产的一款感光整列,30W像素。
为什么要用带FIFO的模块呢?因为stm32速度比较慢,带FIFO可已将摄像头拍摄的数据暂时存在FIFO里,然后我们的stm32再慢慢的将拍摄的数据读出来,通过串口发送到串口上位机显示。
FIFO,即first in first out的缩写。在这里,FIFO的速度很快,可以将摄像头的数据暂时存起了。以便我们慢速的CPU将获得的数据慢慢取出来并处理,达到一个类似水库的作用。
2.2.4 舵机
舵机的输入线共有三条, 红色中间, 是电源线, 一边黑色的是地线, 这辆根线给舵机提供最基本的能源保证, 主要是电机的转动消耗。 电源有两种规格, 一是 4.8V, 一是 6.0V, 分别对应不同的转矩标准, 即输出力矩不同, 6.0V 对应的要大一些, 具体看应用条件; 另外一根线是控制信号线, Futaba 的一般为白色, JR 的一般为桔黄色。
另外要注意一点, SANWA 的某些型号的舵机引线电源线在边上而不是中间, 需要辨认。但记住红色为电源, 黑色为地线, 一般不会有错。
2.2.5 PWM舵机控制
舵机的控制信号为周期是 20ms 的脉宽调制( PWM) 信号, 其中脉冲宽度从0.5ms-2.5ms, 相对应舵盘的位置为 0-180 度, 呈线性变化。 也就是说, 给它提供一定的脉宽, 它的输出轴就会保持在一个相对应的角度上, 无论外界转矩怎样改变, 直到给它提供一个另外宽度的脉冲信号, 它才会改变输出角度到新的对应的位置上。
2.2.6 红外循迹模块
采用TCRT5000红外反射传感器,一种集发射与接收于一体的光电传感器,它由一个红外发光二极管和一个NPN红外光电三极管组成。检测反射距离1mm-25mm适用,传感器特设M3固定安装孔,调节方向与固定方便易用,使用宽电压LM393比较器,信号干净,波形好,驱动能力强,超过15mA。可以应用于机器人避障、机器人进行白线或者黑线的跟踪,可以检测白底中的黑线,也可以检测黑底中的白线,是寻线机器人的必备传感器。流水线计件、电度表脉冲数据采样、传真机碎纸机纸张检测等众多场合。
3 软件设计
小车控制是通过 stm32单片机控制驱动电路和给舵机送控制信号, 然而这些控制信号的命令又是电脑等终端设备通过无线路由器串口传送给单片机的, 所以在程序中我们需要设计到串口的使用、 定时器使用、 I/O 口的使用。
系统主程序模块主要完成对系统中各模块电路的初始化等工作, 主要包括对定时器、 串口中断、 外部中断的初始化, 同时执行电脑等终端设备所发送的命令, 等待外部中断以及根据所需要的功能进行相应操作。 软件总体设计及程序流程如下图
4 测试效果
5 部分关键代码
主程序
#include "stm32f10x.h"
#include "bsp_SysTick.h"
#include "bsp_led.h"
#include <string.h>
#include <stdlib.h>
#include "bsp_pwm_output.h"
#include "infrared.h"
#include "delay.h"
//全局变量
unsigned int Task_Delay[NumOfTask];
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
//初始化systick
SysTick_Init(); //用于延时等操作
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;
TIMx_PWM_Init(); //PWM波初始化
infrared_Initial(); //红外初始化
while(1)
{
if((infrared_Scan(infrared_5_GPIO_PORT,infrared_5_GPIO_PIN)==INFRARED_ON)&&\
(infrared_Scan(infrared_6_GPIO_PORT,infrared_6_GPIO_PIN)==INFRARED_ON))//前方有障碍,必须两边同时检测到才能触发,否则均认为是误触
{
DelayS(3); //延迟3秒
if((infrared_Scan(infrared_5_GPIO_PORT,infrared_5_GPIO_PIN)==INFRARED_ON)&&\
(infrared_Scan(infrared_6_GPIO_PORT,infrared_6_GPIO_PIN)==INFRARED_ON))//仍旧有障碍
{
TIMx_Mode_Config(500,0,0,500);//右转500ms
DelayMs(500);
}
else
{
TIMx_Mode_Config(500,0,0,500);//直行500ms
DelayMs(500);
}
}
else
{
if((infrared_Scan(infrared_3_GPIO_PORT,infrared_3_GPIO_PIN)==INFRARED_ON))//循迹,右侧内部传感器见到黑线,说明车身方向偏右
TIMx_Mode_Config(0,0,600,0);//轻微左转
else if((infrared_Scan(infrared_2_GPIO_PORT,infrared_2_GPIO_PIN)==INFRARED_ON))//循迹,左侧内部传感器见到黑线,说明车身方向偏左
TIMx_Mode_Config(600,0,0,0);//轻微右转
else if((infrared_Scan(infrared_4_GPIO_PORT,infrared_4_GPIO_PIN)==INFRARED_ON))//循迹,右侧外部传感器见到黑线,说明车身方向偏左
TIMx_Mode_Config(0,0,900,0);//剧烈左转
else if((infrared_Scan(infrared_1_GPIO_PORT,infrared_1_GPIO_PIN)==INFRARED_ON))//循迹,左侧外部传感器见到黑线,说明车身方向偏左
TIMx_Mode_Config(900,0,0,0);//剧烈右转
else
TIMx_Mode_Config(500,0,500,0);//直行
}
}
}
PWM程序
#include "bsp_pwm_output.h"
/**
* @brief 配置TIM3复用输出PWM时用到的I/O
* @param 无
* @retval 无
*/
static void TIMx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 设置TIM3CLK 为 72MHZ */
// RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
macTIM_APBxClock_FUN (macTIM_CLK, ENABLE);
/* GPIOA and GPIOB clock enable */
macTIM_GPIO_APBxClock_FUN (macTIM_GPIO_CLK, ENABLE);
/*GPIOA Configuration: TIM3 channel 1 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = macTIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(macTIM_CH1_PORT, &GPIO_InitStructure);
/*GPIOB Configuration: TIM3 channel 2 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = macTIM_CH2_PIN;
GPIO_Init(macTIM_CH2_PORT, &GPIO_InitStructure);
/*GPIOB Configuration: TIM3 channel 3 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = macTIM_CH3_PIN;
GPIO_Init(macTIM_CH3_PORT, &GPIO_InitStructure);
/*GPIOB Configuration: TIM3 channel 4 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = macTIM_CH4_PIN;
GPIO_Init(macTIM_CH4_PORT, &GPIO_InitStructure);
}
/**
* @brief 配置TIM3输出的PWM信号的模式,如周期、极性、占空比
* @param 无
* @retval 无
*/
/*
* TIMxCLK/CK_PSC --> TIMxCNT --> TIMx_ARR --> TIMxCNT 重新计数
* TIMx_CCR(电平发生变化)
* 信号周期=(TIMx_ARR +1 ) * 时钟周期
* 占空比=TIMx_CCR/(TIMx_ARR +1)
*/
void TIMx_Mode_Config(u16 CCR1_Val,u16 CCR2_Val,u16 CCR3_Val, u16 CCR4_Val)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* -----------------------------------------------------------------------
macTIMx Channel1 duty cycle = (macTIMx_CCR1/ macTIMx_ARR+1)* 100% = 50%
macTIMx Channel2 duty cycle = (macTIMx_CCR2/ macTIMx_ARR+1)* 100% = 37.5%
macTIMx Channel3 duty cycle = (macTIMx_CCR3/ macTIMx_ARR+1)* 100% = 25%
macTIMx Channel4 duty cycle = (macTIMx_CCR4/ macTIMx_ARR+1)* 100% = 12.5%
----------------------------------------------------------------------- */
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 999; //当定时器从0计数到999,即为1000次,为一个定时周期
TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置预分频:不预分频,即为72MHz
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ; //设置时钟分频系数:不分频(这里用不到)
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(macTIMx, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val; //设置跳变值,当计数器计数到这个值时,电平发生跳变
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //当定时器计数值小于CCR1_Val时为高电平
TIM_OC1Init(macTIMx, &TIM_OCInitStructure); //使能通道1
TIM_OC1PreloadConfig(macTIMx, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val; //设置通道2的电平跳变值,输出另外一个占空比的PWM
TIM_OC2Init(macTIMx, &TIM_OCInitStructure); //使能通道2
TIM_OC2PreloadConfig(macTIMx, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR3_Val; //设置通道3的电平跳变值,输出另外一个占空比的PWM
TIM_OC3Init(macTIMx, &TIM_OCInitStructure); //使能通道3
TIM_OC3PreloadConfig(macTIMx, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR4_Val; //设置通道4的电平跳变值,输出另外一个占空比的PWM
TIM_OC4Init(macTIMx, &TIM_OCInitStructure); //使能通道4
TIM_OC4PreloadConfig(macTIMx, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(macTIMx, ENABLE); // 使能TIM3重载寄存器ARR
/* TIM3 enable counter */
TIM_Cmd(macTIMx, ENABLE); //使能定时器3
}
/**
* @brief TIM3 输出PWM信号初始化,只要调用这个函数
* TIM3的四个通道就会有PWM信号输出
* @param 无
* @retval 无
*/
void TIMx_PWM_Init(void)
{
TIMx_GPIO_Config();
TIMx_Mode_Config(0,0,0,0);
}
延时程序
#include "stm32f10x.h"
#include "delay.h"
static u8 fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void DelayInit()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8
fac_ms=(u16)fac_us*1000;//非ucos下,代表每个ms需要的systick时钟数
}
//延时nus
//nus为要延时的us数.
void DelayUs(unsigned long nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}
while(temp&0x01&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void DelayMs(unsigned int nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}
while(temp&0x01&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
void DelayS(unsigned int ns)//延时秒
{
unsigned char i;
for(i=0;i<ns;i++)
{
DelayMs(1000);
}
}
6 最后