智能小车
1. 描述
1.1 项目描述
智能小车项目是一个集成了机械、机电、计算机、传感器、等多种技术的综合性项目。本项目旨在通过设计制作智能小车,探索机器人控制、机械设计及电路控制等多方面知识,并将其汇总应用到实际中,以提高工程设计能力和动手能力。
1.2 物料清单
- 物料在淘宝搜索即可
- 充电宝供电的好处:调试时可以移植开着,不用担心电量不足导致的驱动力不够
- 除了小车底盘,价格都是几块钱左右
1.3 开发方案
-
蓝牙接收控制信号
-
PWM 驱动电机4个电机、1个舵机,
- TIM2,ch3驱动4个电机;
- TIM3,ch3驱动1个舵机;
-
超声波测距,实现避障,
- TIM4作为定时器计时;
- EXTI外部中断检测指定GPIO电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请;
- 选择配置NVIC的EXTI15_10中的13号线为外部中断
- 根据声音传递的速度、距离、时间 测量距离
-
寻迹,根据红外反射传感器接收引脚高低电平判断小车转弯或直行
2. Bug记录
2.1 左转、右转问题
- 问题:左转、右转控制不了,要么同时不转动,要么同时不转
- 方式一、同一个PWM 控制不同电机,制动其中一个,直接给低电平就行
- 方式二、用不同PWM控制不同电机,就不会存在上述问题
2.2 舵机在某一位置抖动
- 现象 :舵机在某一位置附近不停地
抖动
,有时会突然安装单片机指令转动一定角度,之后继续抖动,感觉不受单片机控制。 - 原因:STM32开发板 ---> 舵机 供电
- 解决 :
5V 直连 舵机
即可解决。
2.3 超声波测距 影响电机测速
-
问题描述: 只要打开超声波测距模块,电机转速就会变成一个固定值
-
原因: 超声波用的是TIM4计时,电机用的时TIM2 生成PWM波形控制转速,这里手误写成了TIM2,导致电机使用的TIM2失能,不起作用,只能是一个惯性的固定值
- 解决: 将TIM2 改成TIM4即可。
3. 超声波模块
型号:HC_SR04
3.1 超声波测距原理
- 超声波
发射器
向外发射超声波信号同时定时器开始计时,超声波在空气中传播时若遇到障碍物就立即被反射回来超声波接收器
在收到反射波后立即停止定时器; - 超声波在空气中的传播速度约为
340m/s
, - 若定时器记录的时间为t(s),那么超声波传感器距离障碍物的距离为:
s = 340 * t / 2
3.2 超声波传感器
-
超声波传感器共有四个引脚Vcc、Gnd、Trig、Echo,
- 其中
Vcc
和Gnd
为供电引脚; - 模块工作时 向Trig引脚发送一个持续
10us以上
高电平信号,模块就 对外发送40kHz
的超声波信号, - 发送完成后 Echo 引脚 自动变为 高电平,若模块
接收到
被反射回来
的回波信号
Echo引脚又会自动变为 低电平,即Echo引脚上高电平
持续的时间 即为 超声波信号在空气中传播的时间。 - 测试距离 = (高电平时间 * 音速)/ 2
- 其中
-
实物:
-
特点:
- 宽电压工作:3V-5.5V
- 探测距离:
- 5V: 2cm -- 450cm
- 3.3V: 2cm -- 400cm
- 探测角度:<15°
- 工作温度:-20℃ -- 80℃
-
时序图:
-
距离计算方式:
-
以上时序图表明只需要提供一个
10us以上
脉冲触发信号,该模块内部
将发出8个40kHz周期电平
并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。 -
公式:
uS/58 = 厘米
或者uS/148=英寸
;- 或是:
距离=高电平时间*声速(340M/S) /2
; - 建议测量周期为60ms以上,以防止发射信号对回响信号的影响。
-
所以:
-
我们仅需提供10us的高电平给Trig口即可, 然后HC-SR04在测量完毕之后会将结果通过Echo回响回来;
-
只需要将Trig口拉高,等待10us(最好再延长一些,15us~20us)后再拉低即可;
-
接着就只需要等待Echo将数据传输回来,通过时序图我们可以得知回响信号是拉高Echo口,再拉低,中间持续的时间就是测距的结果。
-
所以我们给Echo口配置一个中断事件,设置为上跳变下跳变都触发,另外再用一个变量记录Echo口到底是拉高还是拉低即可。
- 如果是拉高,那么我们需要记录下持续的时间,这时候我们需要用定时器计时,所以需要在一开始的时候就配置好定时器的初始化。唯一的问题就是该如何配置定时器的预分频器和自动重装器了。
- 根据说明书我们可以知道HC-SR04的精度为3mm,而测距的公式为 us/58-cm,稍加计算可知,如果我们需要测量3mm,那么得到的时间为17.4us,以此为一个刻度,那么定时器的频率应该为57471Hz。然而这样太麻烦了,而且也不好用;
- 因此我们可以随意一些,在代码中使用的是
预分频器为72
,自动重装器为100
,那么得到的频率为72MHz/72/100=10000Hz
,也就是 一次定时器中断的时间为100us,而 自动重装器里的每一个值就是1us, - 所以每次外部中断的下降沿触发之后只需要将
定时器触发的次数*100+自动重装器里的值
就可以得到回响信号的持续时间了,单位是us。
-
-
3.3 测试代码
MyTimer.c
c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //开启TIM4的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM4); //选择TIM4为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
/*中断输出配置*/
TIM_ClearFlag(TIM4, TIM_FLAG_Update); //清除定时器更新标志位,TIM_TimeBaseInit函数末尾,手动产生了更新事件
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //开启TIM4的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2,
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);
/*TIM使能*/
TIM_Cmd(TIM4, ENABLE); //使能TIM4,定时器开始运行
}
Ultrasound.c
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "MyTimer.h"
uint8_t flage=0; //用于记录中断信号是上升沿还是下降沿
uint16_t Timer_cnt; //记录定时器中断的次数
uint16_t times; //记录回想信号持续时间
void Ultrasound_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // Trig
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入,
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // Echo
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);//将外部中断的13号线映射到GPIOB,即选择PB13为外部中断引脚
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line13; //选择配置外部中断的13号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //指定外部中断线为上升、下降沿触发
EXTI_Init(&EXTI_InitStructure);
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);
Timer_Init();
}
/*TIM4定时器中断*/
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET) //判断是否是TIM4的更新事件触发的中断
{
Timer_cnt++; //Num变量自增,用于测试定时中断
TIM_ClearITPendingBit(TIM4, TIM_IT_Update); //清除TIM4更新事件的中断标志位
}
}
/*EXTI15_10外部中断函数*/
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line13) == SET) //判断是否是外部中断13号线触发的中断
{
if (flage == 0) //上升沿即回响电平开始,打开计数器
{
Timer_cnt = 0; //定时器中断次数置0
flage = 1;
TIM_SetCounter(TIM4, 0); //设置TIM4值为0
TIM_Cmd(TIM4, ENABLE); //使能TIM4
}
else //下降沿即回响电平结束,统计高电平持续时长
{
TIM_Cmd(TIM4, DISABLE);
flage = 0;
times=Timer_cnt * 100 + TIM_GetCounter(TIM4); //得到回响的高电平持续的us
}
EXTI_ClearITPendingBit(EXTI_Line13); //清除外部中断13号线的中断标志位
}
}
// 测量距离
float Get_Distance(void)
{
uint32_t distance = 0;
for(int i=0; i<5; i++)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12); // Trig引脚拉高保持大于10us以上
Delay_us(20);
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
Delay_ms(60); //根据说明书,每个周期至少需要等待60ms
distance += (times/58); //根据说明书提供的公式,获取单位为cm的距离
}
return distance/5;
}
main.c
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Ultrasound.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Ultrasound_Init();
OLED_ShowString(1, 1, "Dis:", OLED_8X16);
while (1)
{
OLED_ShowNum(32, 1, Get_Distance(), 5, OLED_8X16);
OLED_Update();
}
}
4. 红外寻迹模块
型号:TCRT5000,反射是红外传感器
4.1 原理
TCRT5000传感器
的红外发射二极管不断 发射红外线
,
-
当发射出的红外线没有被反射回来或被反射回来但强度不够大时,红外接收管一直处于关断状态,此时 模块的输出端为高电平,
指示二极管
一直处于熄灭状态
; -
被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和,此时 模块的输出端为低电平,
指示二极管被点亮
。
没反射------DO输出高电平------灭灯
反射------DO输出低电平------点亮
4.1.1 寻迹原理
普通的循迹跑道都是黑色的原因:
- 黑色具有较强的吸收能力,当循迹模块发射的红外线照射到黑线时,红外线将会被黑线吸收,导致循迹模块上光敏三极管处于关闭状态,此时模块上一个LED熄灭。在没有检测到黑线时,模块上两个LED 常亮
- 即 感应到黑线,DO输出高电平,灭灯
4.2 模块测试代码
main.c
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
int main(void)
{
OLED_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO初始化
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_Initstructure);
OLED_ShowString(0, 0, "Trace:", OLED_8X16);
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1))
{OLED_ShowNum(48, 0, 1, 1, OLED_8X16);}
else
{OLED_ShowNum(48, 0, 0, 1, OLED_8X16);}
OLED_Update();
}
}
5. 代码
5.1 main.c
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Car.h"
#include "Servo.h"
#include "Ultrasound.h"
#include "Serial.h"
#include "Motor.h"
#include "Trace.h"
#include <stdio.h>
uint8_t Mode; // 小车模式
uint8_t Car_State; // 小车运行状态
uint8_t Serial_RxData; // 串口接收的数据变量
uint8_t Speed = 60; // 定义速度变量
// 中断函数 接收蓝牙控制的数据(小车状态、模式)
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
if(Serial_RxData == 0x01) Car_State = 1; // 前进
if(Serial_RxData == 0x02) Car_State = 2; // 后退
if(Serial_RxData == 0x03) Car_State = 3; // 向左
if(Serial_RxData == 0x04) Car_State = 4; // 向右
if(Serial_RxData == 0x05) Car_State = 5; // 加速
if(Serial_RxData == 0x06) Car_State = 6; // 自转
if(Serial_RxData == 0x11) Mode = 1; // 跟随
if(Serial_RxData == 0x12) Mode = 2; // 蓝牙
if(Serial_RxData == 0x13) Mode = 3; // 避障
if(Serial_RxData == 0x14) Mode = 4; // 寻迹
if(Serial_RxData == 0x00) Mode = 0; // 停止
}
}
int main(void)
{
/*模块初始化*/
OLED_Init(); // OLED初始化
Car_Init(); // 小车初始化
Serial_Init(); // 串口初始化
Servo_Init(); // 舵机
Ultrasound_Init(); // 超声波
Trace_Init(); // 寻迹
while (1)
{
OLED_ShowString(0, 0, "Mode:", OLED_8X16);
OLED_ShowString(0, 17, "State:", OLED_8X16);
if(Mode == 0)
{
Car_Stop();
}
if(Mode == 1)
{
// 定距离跟随
OLED_ShowChinese(40, 0, "跟随");
OLED_ShowString(0, 33, "Distance:", OLED_8X16);
Servo_setAngle(110); // 舵机向前
if(Get_Distance() > 25)
{
Car_Ahead();
OLED_ShowString(48, 17, "go_ahead ", OLED_8X16);
OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
Delay_ms(100);
}
if(Get_Distance() < 20)
{
Car_Back();
OLED_ShowString(48, 17, "go_back ", OLED_8X16);
OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
Delay_ms(100);
}
OLED_Update();
}
if(Mode == 2)
{
// 蓝牙控制小车
OLED_ShowChinese(40, 0, "蓝牙");
OLED_ShowString(0, 33, "Speed:", OLED_8X16);
if(Car_State == 1) // 前进
{
Car_Ahead();
OLED_ShowString(48, 17, "go_ahead ", OLED_8X16);
OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
Car_State = 0;
}
if(Car_State == 2) // 后退
{
Car_Back();
OLED_ShowString(48, 17, "go_back ", OLED_8X16);
OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
Car_State = 0;
}
if(Car_State == 3) // 向左
{
Turn_Left();
OLED_ShowString(48, 17, "turn_left", OLED_8X16);
OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
Delay_ms(500);
Car_State = 0;
}
if(Car_State == 4) // 向右
{
Turn_Right();
OLED_ShowString(48, 17, "turn_righ", OLED_8X16);
OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
Delay_ms(500);
Car_State = 0;
}
if(Car_State == 5) // 加速
{
OLED_ShowString(48, 17, "speed_up ", OLED_8X16);
Speed += 10;
if (Speed > 100)
{
Speed = 60;
}
OLED_ShowSignedNum(48, 33, Speed, 3, OLED_8X16);
Change_Speed(Speed);
Car_State = 0;
}
if(Car_State == 6) // 自转
{
Self_Left();
OLED_ShowString(48, 17, "self_left", OLED_8X16);
OLED_ShowNum(48, 33, Speed, 3, OLED_8X16);
Car_State = 0;
}
OLED_Update();
}
if(Mode == 3)
{
// 超声波避障
OLED_ShowChinese(40, 0, "避障");
OLED_ShowString(0, 33, "Distance:", OLED_8X16);
Servo_setAngle(110); // 舵机向前
if(Get_Distance() > 30) // 前方无障碍物
{
Car_Ahead();
Delay_ms(100);
OLED_ShowString(48, 17, "go_ahead ", OLED_8X16);
OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
}
else // 向前有障碍物
{
Car_Stop();
Servo_setAngle(185); // 舵机左转
Delay_ms(200);
if(Get_Distance() > 30) // 左侧无障碍物判断
{
Turn_Left();
Delay_ms(1000);
Delay_ms(700);
Car_Ahead();
OLED_ShowString(48, 17, "turn_left", OLED_8X16);
OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
}
else
{
Car_Stop();
Servo_setAngle(25); // 舵机向右
Delay_ms(200);
if(Get_Distance() > 30) // 右侧无障碍物
{
Turn_Right();
Delay_ms(1000);
Delay_ms(700);
OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
OLED_ShowString(48, 17, "turn_righ", OLED_8X16);
Car_Ahead();
}
else
{
Car_Back(); // 右侧有障碍物,后退
Delay_ms(1200);
OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
OLED_ShowString(48, 17, "go_back ", OLED_8X16);
Turn_Left();
Delay_ms(1000);
Delay_ms(700);
OLED_ShowNum(72, 33, Get_Distance(), 3, OLED_8X16);
OLED_ShowString(48, 17, "turn_left", OLED_8X16);
Car_Ahead();
}
}
}
OLED_Update();
}
if(Mode == 4)
{
// 红外寻迹
OLED_ShowChinese(40, 0, "寻迹");
OLED_ShowString(0, 17, " ", OLED_8X16);
OLED_ShowString(0, 33, " ", OLED_8X16);
Servo_setAngle(110); // 舵机向前
Trace();
OLED_Update();
}
OLED_Update();
}
}
5.2 小车初始化
5.2.1 Car
Car.h
c
#ifndef __CAR_H
#define __CAR_H
void Car_Init(void);
void Car_Ahead(void);
void Car_Back(void);
void Car_Stop(void);
void Turn_Left(void);
void Turn_Right(void);
void Self_Left(void);
void Self_Right(void);
void Change_Speed(int8_t speed);
#endif
Car.c
c
#include "stm32f10x.h" // Device header
#include "Motor.h"
#include "Delay.h"
void Car_Init(void)
{
Motor_Init();
}
void Car_Ahead(void)
{
Motor_SetLeftSpeed(60);
Motor_SetRightSpeed(60);
}
void Car_Back(void)
{
Motor_SetLeftSpeed(-60);
Motor_SetRightSpeed(-60);
}
void Car_Stop(void)
{
Motor_SetLeftSpeed(0);
Motor_SetRightSpeed(0);
}
void Turn_Left(void)
{
Motor_SetLeftSpeed(60);
Motor_SetRightSpeed(0);
}
void Turn_Right(void)
{
Motor_SetLeftSpeed(0);
Motor_SetRightSpeed(60);
}
void Self_Left(void)
{
Motor_SetLeftSpeed(-60);
Motor_SetRightSpeed(60);
}
void Self_Right(void)
{
Motor_SetLeftSpeed(60);
Motor_SetRightSpeed(-60);
}
void Change_Speed(int8_t speed)
{
Motor_SetLeftSpeed(speed);
Motor_SetRightSpeed(speed);
}
5.2.2 Motor
Motor.h
c
#ifndef __MOTOR_H
#define __MOTOR_H
void Motor_Init(void);
void Motor_SetLeftSpeed(int8_t speed);
void Motor_SetRightSpeed(int8_t speed);
#endif
Motor.c
c
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Motor_Init(void)
{
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 初始化GPIO(控制电机引脚)
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_Initstructure);
PWM_Init();
}
// 左轮速度
void Motor_SetLeftSpeed(int8_t speed)
{
if(speed > 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare3(speed);
}
else if(speed == 0)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
}
else{
GPIO_SetBits(GPIOA, GPIO_Pin_5);
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
PWM_SetCompare3(-speed);
}
}
// 右轮速度
void Motor_SetRightSpeed(int8_t speed)
{
if(speed > 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
PWM_SetCompare3(speed);
}
else if(speed == 0)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
}
else{
GPIO_SetBits(GPIOA, GPIO_Pin_7);
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
PWM_SetCompare3(-speed);
}
}
5.2.3 PWM
PWM.h
c
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare);
#endif
PWM.c
c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
// 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO初始化
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_Initstructure);
// 配置时钟源
TIM_InternalClockConfig(TIM2); // 选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
// 初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100-1; // ARR,自动重装器,计数周期
TIM_TimeBaseInitStructure.TIM_Prescaler = 36-1; // PSC, 预分频器
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 初始化 输出比较 单元
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); // 给整个结构体默认值,防止结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性,选择为高
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
TIM_OCInitStructure.TIM_Pulse = 0; // 初始的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure); // TIM2的通道3
// 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR1的值
}
5.3 串口初始化
Serial.h
c
#ifndef __SERIAL_H
#define __SERIAL_H
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
#endif
Serial.c
c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // Tx
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // Rx
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART初始化
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
// 中断输出配置
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
// NVIC中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
// NVIC配置
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
// USART使能
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址
* 返 回 值:无
*/
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数
return ch;
}
5.4 舵机初始化
Servo.h
c
#ifndef __SERVO_H
#define __SERVO_H
void Servo_Init(void);
void Servo_setAngle(float angle);
#endif
Servo.c
c
#include "stm32f10x.h" // Device header
#include "PWMServo.h"
void Servo_Init(void)
{
PWM_Servo_Init();
}
void Servo_setAngle(float angle)
{
PWM_Servo_SetCompare3(angle / 180 * 2000 + 500); //设置占空比
}
PWMServo.h
c
#ifndef __PWMSERVO_H
#define __PWMSERVO_H
void PWM_Servo_Init(void);
void PWM_Servo_SetCompare3(uint16_t Compare);
#endif
PWMServo.c
c
#include "stm32f10x.h" // Device header
void PWM_Servo_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM3); //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //给结构体所有成员都赋一个默认值,避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC3Init(TIM3, &TIM_OCInitStructure); //将结构体变量交给TIM_OC3Init,配置TIM3的输出比较通道1
/*TIM使能*/
TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_Servo_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM3, Compare); //设置CCR1的值
}
5.5 超声波初始化
Ultrasound.h
c
#ifndef __ULTRASOUND_H
#define __ULTRASOUND_H
void Ultrasound_Init(void);
float Get_Distance(void);
#endif
Ultrasound.c
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "MyTimer.h"
uint8_t flage=0; //用于记录中断信号是上升沿还是下降沿
uint16_t Timer_cnt; //记录定时器中断的次数
uint16_t times; //记录回想信号持续时间
void Ultrasound_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // Trig
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入,
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // Echo
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);//将外部中断的13号线映射到GPIOB,即选择PB13为外部中断引脚
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line13; //选择配置外部中断的13号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //指定外部中断线为上升、下降沿触发
EXTI_Init(&EXTI_InitStructure);
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
Timer_Init();
}
/*TIM4定时器中断*/
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET) //判断是否是TIM4的更新事件触发的中断
{
Timer_cnt++; //Num变量自增,用于测试定时中断
TIM_ClearITPendingBit(TIM4, TIM_IT_Update); //清除TIM4更新事件的中断标志位
}
}
/*EXTI15_10外部中断函数*/
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line13) == SET) //判断是否是外部中断13号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (flage == 0) //上升沿即回响电平开始,打开计数器
{
Timer_cnt = 0; //定时器中断次数置0
flage = 1;
TIM_SetCounter(TIM4, 0); //设置TIM4值为0
TIM_Cmd(TIM4, ENABLE); //使能TIM4
}
else //下降沿即回响电平结束,统计高电平持续时长
{
TIM_Cmd(TIM4, DISABLE);
flage = 0;
times=Timer_cnt * 100 + TIM_GetCounter(TIM4); //得到回响的高电平持续的us
}
EXTI_ClearITPendingBit(EXTI_Line13); //清除外部中断13号线的中断标志位
}
}
// 测量距离
float Get_Distance(void)
{
uint32_t distance = 0;
for(int i=0; i<5; i++)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12); // Trig引脚拉高保持大于10us以上
Delay_us(20);
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
Delay_ms(60); //根据说明书,每个周期至少需要等待60ms
distance += (times/58); //根据说明书提供的公式,获取单位为cm的距离
}
return distance/5;
}
MyTimer.h
c
#ifndef __MYTIMER_H
#define __MYTIMER_H
void Timer_Init(void);
#endif
MyTimer.c
c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //开启TIM4的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM4); //选择TIM4为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM4的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM4, TIM_FLAG_Update); //清除定时器更新标志位,TIM_TimeBaseInit函数末尾,手动产生了更新事件
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //开启TIM4的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2,即抢占优先级和响应优先级范围:0~3
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM4, ENABLE); //使能TIM4,定时器开始运行
}
5.6 寻迹初始化
Trace.h
c
#ifndef __TRACE_H
#define __TRACE_H
#define DO1 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)
#define DO2 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)
#define DO3 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)
#define DO4 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15)
void Trace_Init(void);
void Trace(void);
#endif
Trace.c
c
#include "stm32f10x.h" // Device header
#include "Trace.h"
#include "Car.h"
#include "Delay.h"
void Trace_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_Initstructure);
}
void Trace(void)
{
if(DO1==0 && DO2==0 && DO3==0 && DO4==0) //0000
{
Car_Ahead();
}
if(DO1==1 && DO2==1 && DO3==1 && DO4==1) //1111
{
Car_Stop();
}
if(DO1==1 && DO2==0 && DO3==0 && DO4==0) //1000
{
Turn_Left();
Delay_ms(300);
}
if(DO1==0 && DO2==1 && DO3==0 && DO4==0) //0100
{
Turn_Left();
Delay_ms(200);
}
if(DO1==1 && DO2==1 && DO3==0 && DO4==0) //1100
{
Turn_Left();
Delay_ms(370);
}
if(DO1==0 && DO2==0 && DO3==1 && DO4==0) //0010
{
Turn_Right();
Delay_ms(150);
}
if(DO1==0 && DO2==0 && DO3==0 && DO4==1) //0001
{
Turn_Right();
Delay_ms(300);
}
if(DO1==0 && DO2==0 && DO3==1 && DO4==1) //0011
{
Turn_Right();
Delay_ms(370);
}
}
6. 说明
备注:江协科技STM32入门教程学完后,基本上可以完成这个小项目
部分代码参考江协科技教程