链接:基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----上篇(第123点)
其实上篇就是一些基础的东西,本篇讲一下如何让小车循迹
我的理解是,对于直流电机来讲,一上高平电机就会转动,如果改变了高电平占有的比例,就是让电机在单位时间内接到的高电平时间减少,它的速度就会减小。
注意这里的pwm是由单片机输出到电机驱动的IN1-IN4端的,所以我们要进行软件设置,以输出想要的高电平,这里主要就是软件方面的事啦。
stm32没有单独的pwm模块,它是利用定时器来输出pwm的,每个定时器有4个通道,我们需要找到定时器这4个通道所接的引脚,再将这4个引脚接到电机驱动的输入端上就好了。先简单回顾一下stm32定时器的相关知识。首先我查看了stm3210x系列的技术手册
可以看到的是,我们手上的这块STM32F103C8T6有3个通用定时器(我的工程中把这三个定时器都用完了),再往下,每个定时器有4个通道,我们需要找到这4个通道分别对应的引脚。比如我给电机所使用的定时器为TIM3,查看数据手册对引脚的定义后发现如下:
所以定时器3的通道1-4分别对应的是PA6,PA7,PB0,PB1,我将这4个引脚分别接到电机驱动的IN1-IN4,如此一来,关于电机调速的硬件电路我们已经搭好了。
这是最关键的地方了,先贴一下我的电机设置的代码吧,下面是moter.c程序
#include "moter.h"
void CarGo(void)
{
TIM_SetCompare1(TIM3 , 400); //数值越大速度越慢
TIM_SetCompare2(TIM3 , 900);
TIM_SetCompare3(TIM3 , 400);
TIM_SetCompare4(TIM3 , 900);
}
void CarStop(void)
{
TIM_SetCompare1(TIM3 , 900);
TIM_SetCompare2(TIM3 , 900);
TIM_SetCompare3(TIM3 , 900);
TIM_SetCompare4(TIM3 , 900);
}
void CarBack(void)
{
TIM_SetCompare1(TIM3 , 900);
TIM_SetCompare2(TIM3 , 300);
TIM_SetCompare3(TIM3 , 900);
TIM_SetCompare4(TIM3 , 300);
}
void CarLeft(void)
{
TIM_SetCompare1(TIM3 , 900);
TIM_SetCompare2(TIM3 , 300);
TIM_SetCompare3(TIM3 , 300);
TIM_SetCompare4(TIM3 , 900);
}
void CarBigLeft(void)
{
TIM_SetCompare1(TIM3 , 900);
TIM_SetCompare2(TIM3 , 100);
TIM_SetCompare3(TIM3 , 100);
TIM_SetCompare4(TIM3 , 900);
}
void CarRight(void)
{
TIM_SetCompare1(TIM3 , 300);
TIM_SetCompare2(TIM3 , 900);
TIM_SetCompare3(TIM3 , 900);
TIM_SetCompare4(TIM3 , 300);
}
void CarBigRight(void)
{
TIM_SetCompare1(TIM3 , 100);
TIM_SetCompare2(TIM3 , 900);
TIM_SetCompare3(TIM3 , 900);
TIM_SetCompare4(TIM3 , 100);
}
void TIM3_PWM_Init(void) //TIM3的pwm设置和相应的引脚设置
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 899;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM3 , &TIM_TimeBaseStructure);
//端口复用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //初始化要用的A6/A7口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //初始化要用的B0/B1口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//PWM通道1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 900;
TIM_OC1Init(TIM3 , &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3 , TIM_OCPreload_Enable);
//PWM通道2
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 900;
TIM_OC2Init(TIM3 , &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3 , TIM_OCPreload_Enable);
//PWM通道3
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 900;
TIM_OC3Init(TIM3 , &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3 , TIM_OCPreload_Enable);
//PWM通道4
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 900;
TIM_OC4Init(TIM3 , &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3 , TIM_OCPreload_Enable);
TIM_Cmd(TIM3 , ENABLE);
}
下面是moter.h文件内容
#ifndef __MOTER_H
#define __MOTER_H
#include "stm32f10x.h"
void TIM3_PWM_Init(void);
void CarGo(void);
void CarStop(void);
void CarBack(void);
void CarLeft(void);
void CarBigLeft(void); //大右转
void CarRight(void);
void CarBigRight(void); //大左转
#endif
这些代码也很好用,在main函数前调用TIM3_PWM_Init()这一个函数就可以完成引脚和定时器的初始化了,在后面直接用前进后退转弯这些函数就可以了。
下面介绍一下这个函数的一些关键参数:
TIM_TimeBaseStructure.TIM_Period = 899;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
根据定时器的周期计算公式:周期=(arr+1)*(psc+1)/CLK=900/72000000,
所以就是设置定时器基准频率为80KHZ
再看每个pwm口的设置基本是一样的,主要参数是这几个:
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 900;//脉宽设置
1,TIM_OCMode_PWM2的意思是什么?
当计时器值小于比较器设定值时则TIMX输出脚此时输出有效低电位。
当计时器值大于或等于比较器设定值时则TIMX输出脚此时输出高电位。
2,TIM_OCPolarity_High表示起始波形为高电位
3,TIM_Pulse = 900表示总的脉宽值为900
由于计数器的值为899+1,则此时的占空比是1,我们通过下面这个SetCompare函数再来设置比较值从而设置占空比。
4,最后我们用TIM_SetCompare1(TIM3 , 400)这个函数来设置比较值
比如说,我们这里设置比较值为400,总脉宽为900,初始电位为高电平,且设置的是PWM2模式,那么在0-400里为低电平,在400-900里为高电平。也就是说,设置的比较值越大,高电平占有的时间越短。
比如我的前进函数:
void CarGo(void)
{
TIM_SetCompare1(TIM3 , 400); //数值越大速度越慢
TIM_SetCompare2(TIM3 , 900);
TIM_SetCompare3(TIM3 , 400);
TIM_SetCompare4(TIM3 , 900);
}
这样的设置就相当于是将CH1和CH3通道的pwm高电平设置在400-900这个时间里,将CH2和CH4这两个通道拉低,这样一来,改变通道1和通道3的比较值就可以改变电机的转速了。
好了 电机调速我们已经设置完成了。
这个比较简单,就是读取io口的高低电平。
我们先看一下TCRT5000循迹模块怎么用,
它有4个IO接口,我们只需要用3个即可,就是VCC,GND,D0其中D0就是用来返回信号的,它有两个状态,就是高电平和低电平。
就我用的模块来讲,正常情况下,D0返回低电平,当模块检测到黑线的时候,返回高电平,以此,我们就可以进行相关的程序编写了。
下面是我的xunji.c文件内容:
#include "xunji.h"
void xunji_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); // 使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; //选择对应的引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//配置GPIO模式,输入上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PC端口
}
void Read_xunji_Date(void)
{
xunji_1;
xunji_2;
xunji_3;
xunji_4;
}
下面是xunji.h文件内容:
#ifndef __XUNJI_H
#define __XUNJI_H
#include "stm32f10x.h"
#define xunji_1 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_4)
#define xunji_2 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)
#define xunji_3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)
#define xunji_4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)
void xunji_config(void);
void Read_xunji_Date(void); //读循迹模块返回的值
#endif
可以看到,我所用的是引脚B4,B5,B6,B7,这4个引脚分别接到4个循迹模块的D0口上,这里将它们初始化,再调用GPIO_ReadInputDataBit读取引脚电平即可。
用法:在主函数中先调用xunji_config()进行引脚初始化,在循环的最前面调用Read_xunji_Date(void)函数读取4个循迹模块的电平值后就可以进行相关的循迹程序编写了。
前面已经完成了调速和循迹模块函数的编写,这里可以直接编写主函数了:
int main(void)
{
float length_res[5]; //用来存放测距结果
SystemInit(); // 配置系统时钟为72M
delay_init(); //延时初始化
xunji_config(); //循迹初始化
TIM3_PWM_Init(); //电机pwm TIM3
SG90_pwm_init(); //舵机pwm TIM2
CH_SR04_Init(); //超声波定时器 TIM4
OLED_Init(); //oled显示初始化
while(1)
{
Read_xunji_Date(); //读循迹线值
//车前4个循迹模块从左到右分别是xunji_1,xunji_2,xunji_3,xunji_4
if(xunji_1==0&&xunji_2==0&&xunji_3==0&&xunji_4==0)//0000
{
CarGo(); //如果都没有读取到黑线,直走
delay_ms(10);
}
if(xunji_1==0&&xunji_2==1&&xunji_3==1&&xunji_4==0)//0110
{
CarGo(); //如果中间两个读取到黑线,直走
delay_ms(10);
}
if(xunji_1==0&&xunji_2==1&&xunji_3==0&&xunji_4==0)//0100
{
CarBigLeft(); //如果第二个读取到黑线,左转
delay_ms(10);
}
if(xunji_1==0&&xunji_2==0&&xunji_3==1&&xunji_4==0)//0010
{
CarBigRight(); //如果第三个读取到黑线,右转
delay_ms(10);
}
if(xunji_1==1&&xunji_2==0&&xunji_3==0&&xunji_4==0)//1000
{
CarBigLeft(); //如果第一个读取到黑线,左转
delay_ms(10);
}
if(xunji_1==0&&xunji_2==0&&xunji_3==0&&xunji_4==1)//0001
{
CarBigRight(); //如果第四个读取到黑线,左转
delay_ms(10);
}
}
}
主函数前面是一些初始化的配置,while(1)里就是循迹的循环了,代码里我已经写了很多注释了,这里就不再多解释了,就是一堆if函数,也很容易看懂。
本篇就到此为止了。
下面是我写的已经测试完成了的工程,主函数里有两个循环,分别是循迹和避障循环,他们是单独工作的,引用掉一个再打开另一个就行。
https://download.csdn.net/download/weixin_43924857/11650617
当然循迹和避障的策略都是我自己为了完成任务写的,比较简单,能够实现循迹避障功能,到后面你已经会操作各个模块后,自己写个更好的循迹避障策略是完全没有问题的,或者是用我的工程,里面的各模块函数也都写好了,直接调用就行。
整个完整过程会分为上中下三篇,还剩一篇(下篇)过两天慢慢码完再发。
下面是下篇的地址:
基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----下篇(第789点)
工程文件已经放到百度网盘:
链接:https://pan.baidu.com/s/1VSRC418Tz8uLCF8cjrIY1g
提取码:7m9y