提示:本文章是基于江协科技的stm32标准库教程 编码接口测速之前的实战项目。主要运用了GPIO的输入输出、定时中断、PWM驱动知识点做出的寻迹小车项目,开源分享。
文章目录
声明
本项目用于巩固关于PWM驱动知识点,用于刚接触STM32的项目分享。
!!!最重要的是小车的寻迹速度是根据 具体所铺设的黑线 所决定的,并不一定都能适用!!
当铺设的黑线转弯角度改变,左右轮电机速度也需要改变,不能一味的照搬
总的工程文件已经开源分享,压缩密码为stm32,免费开源!
欢迎大家提出问题,作者也会虚心请教,如果没有显示资源,可以直接找我询问
一、硬件准备
1.STM32开发板
(备注:本人使用的是STM32F103C8T6)
2.L298N模块
3.红外寻迹模块(4个)
也可以选择一个4路巡线传感器
4.12V锂电池
5.OLED屏幕
(用于调试)
6.面包板
7.杜邦线
8.智能小车框架
二、实现思路
思维导图
三、Keil5代码实现
1.配置标准库
2.main.c文件
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Adjust.h"
int main(void)
{
OLED_Init();
Adjust_Init();
OLED_ShowString(3,1," Track Car");
while(1)
{
Straight();
Straight_Adjust();
Turn_Left();
Turn_Right();
}
}
3.PWMA.c
#include "stm32f10x.h" // Device header
void PWMA_Init(void)
{
//开启时钟(GPIOA 和 TIM2)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); //将PA7引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
TIM_InternalClockConfig(TIM3); //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
//时基单元初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //定义结构体变量
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStruct.TIM_Period = 100 - 1; //计数周期,即ARR的值。到ARR后重新计数(100这个数字也直接读取占空比)
TIM_TimeBaseInitStruct.TIM_Prescaler = 36 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
//输出比较初始化
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_OC2Init(TIM3, &TIM_OCInitStructure); //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
//使能TIM
TIM_Cmd(TIM3, ENABLE); //使能TIM2,定时器开始运行
}
//PWM设置CCR
void PWMA_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM3, Compare); //设置CCR3的值
}
4.PWMA.h
#ifndef __PWMA_H
#define __PWMA_H
void PWMA_Init(void);
void PWMA_SetCompare2(uint16_t Compare);
#endif
5.PWMB.c
#include "stm32f10x.h" // Device header
void PWMB_Init(void)
{
//开启时钟(GPIOA 和 TIM2)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,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); //将PA7引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
//时基单元初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //定义结构体变量
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStruct.TIM_Period = 100 - 1; //计数周期,即ARR的值。到ARR后重新计数
TIM_TimeBaseInitStruct.TIM_Prescaler = 36 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
//输出比较初始化
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); //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
//使能TIM
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
//PWM设置CCR
void PWMB_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR3的值
}
6.PWMB.h
#ifndef __PWMB_H
#define __PWMB_H
void PWMB_Init(void);
void PWMB_SetCompare3(uint16_t Compare);
#endif
7.Motor.c
#include "stm32f10x.h" // Device header
#include "PWMA.h"
#include "PWMB.h"
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_5|GPIO_Pin_4|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6~3引脚初始化为推挽输出
PWMA_Init();
//初始化直流电机的底层PWMA
PWMB_Init(); //初始化直流电机的底层PWMB
}
//直流电机设置速度
void Motor_SetSpeed_Left(int8_t Speed)
{
if (Speed >= 0) //如果设置正转的速度值
{
GPIO_SetBits(GPIOA, GPIO_Pin_6); //PA6置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //PA5置低电平,设置方向为正转
PWMA_SetCompare2(Speed); //PWMA设置为速度值
}
else //否则,即设置反转的速度值
{
GPIO_ResetBits(GPIOA, GPIO_Pin_6); //PA5置低电平
GPIO_SetBits(GPIOA, GPIO_Pin_5); //PA6置高电平,设置方向为反转
PWMA_SetCompare2(-Speed); //PWMA设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
}
}
void Motor_SetSpeed_Right(int8_t Speed)
{
if(Speed >= 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_3); //PA3置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //PA4置低电平,设置方向为正转
PWMB_SetCompare3(Speed); //PWMB设置为速度值
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_3); //PA3置低电平
GPIO_SetBits(GPIOA, GPIO_Pin_4); //PA4置高电平,设置方向为反转
PWMB_SetCompare3(-Speed); //PWMB设置为速度值
}
}
8.Motor.h
#ifndef __MOTOR_H
#define __MOTOR_H
void Motor_Init(void);
void Motor_SetSpeed_Left(int8_t Speed);
void Motor_SetSpeed_Right(int8_t Speed);
#endif
9.Track.c
#include "stm32f10x.h" // Device header
void Track_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15|GPIO_Pin_12;
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_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
uint8_t Track_Mid_1(void)
{
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14); //如果被遮住,就为低电平,输出0
//即有反射,没有黑线区域,输出0
//黑线即没有反射,遇到黑线为输出1
}
uint8_t Track_Mid_2(void)
{
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15); //如果被遮住,就为低电平,输出0
}
uint8_t Track_Left(void)
{
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12);
}
uint8_t Track_Right(void)
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9);
}
10.Track.h
#ifndef __TRACK_H
#define __TRACK_H
void Track_Init(void);
uint8_t Track_Mid_1(void);
uint8_t Track_Mid_2(void);
uint8_t Track_Left(void);
uint8_t Track_Right(void);
#endif
11.Adjust.c
#include "stm32f10x.h" // Device header
#include "Adjust.h"
//寻迹模块默认高电压,设置没有遮挡为默认0,黑线识别为不被遮挡(不反射)
uint8_t Track_1 = 0; //即有反射,没有黑线区域,输出0
uint8_t Track_2 = 0; //黑线即没有反射,遇到黑线为输出1
uint8_t Track_3 = 0;
uint8_t Track_4 = 0;
uint8_t flag = 0;
void Adjust_Init(void)
{
Track_Init();
Motor_Init();
Motor_SetSpeed_Left(Speed_Stop);
Motor_SetSpeed_Right(Speed_Stop);
Track_1 = Track_Mid_1();
Track_2 = Track_Mid_2();
}
/*判断标志位*/
uint8_t IS_Flag(void)
{
Track_1 = Track_Mid_1();
Track_2 = Track_Mid_2();
Track_3 = Track_Left();
Track_4 = Track_Right();
//没有反射,即寻迹模块OUT端口为高电平
if(Track_1 == 1 && Track_2 == 1 && Track_3 == 0 && Track_4 == 0 ) //&&Track_3 == 0 && Track_4 == 0
{
flag = 1;
}else
{
flag = 0;
}
return flag;
}
/*直线行走*/
void Straight(void)
{
if(IS_Flag() == 1)
{
Motor_SetSpeed_Left(Speed_Nor);
Motor_SetSpeed_Right(Speed_Nor);
}else
{
Motor_SetSpeed_Left(Speed_Stop);
Motor_SetSpeed_Right(Speed_Stop);
}
}
/*直线的微调*/
void Straight_Adjust(void)
{
//如果中间左边1号没有黑线,中间右边2号有黑线
if(Track_1 == 0 && Track_2 == 1 )
{
Motor_SetSpeed_Left(Speed_Hig);
Motor_SetSpeed_Right(Speed_Low);
}else if(Track_1 == 1 && Track_2 == 0)
{
Motor_SetSpeed_Right(Speed_Hig);
Motor_SetSpeed_Left(Speed_Low);
}
}
/*向左转弯 (面对着小车来说)*/
void Turn_Left(void)
{
//当最左边上的没有反射(Track_Left == 1),其余都有反射( == 0)
if(Track_4 == 0 && Track_3 == 1)
{
Motor_SetSpeed_Right(Speed_Turn);
Motor_SetSpeed_Left(Speed_Low);
}
}
void Turn_Right(void)
{
if(Track_3 == 0 && Track_4 == 1)
{
Motor_SetSpeed_Left(Speed_Turn + 5); //根据实际布线填写的CCR占空比的大小
Motor_SetSpeed_Right(Speed_Stop);
}
}
12.Adjust.h
#ifndef __ADJUST_H
#define __ADJUST_H
#define Speed_Stop 0
#define Speed_Nor 60
#define Speed_Turn 85
#define Speed_Hig Speed_Nor + 5
#define Speed_Low Speed_Nor - 5
#include "Motor.h"
#include "Track.h"
void Adjust_Init(void);
void Straight(void);
void Straight_Adjust(void);
uint8_t IS_Flag(void);
void Turn_Left(void);
void Turn_Right(void);
#endif
四、结果展示
1.铺设路线
2.顺时针寻迹
3.逆时针寻迹
4.小车模型
总结
寻迹小车涉及到的PWM、定时器、GPIO输入输出等的复习
自检缺点:改变铺设的黑线,左右轮电机的占空比就要改变,没有拓展性。