学完stm32,总是想做点东西“大显身手”一下,智能小车就成了首选项目,其核心只是就是PWM输出,I/O口引脚电平判断。
制作智能小车的硬件名单:
制作智能小车的硬件列表:
(1) STM32C8T6核心板 一块( 遥控 需要两块)
(2) L298N电机驱动 两个(或一块)
(3) 2.4G无线通讯模块 两个
(4) 红外壁障模块 三个
(5) 红外循迹模块 三个
(6) 电源转换模块 一个
(7) 18650供电电池 两节
(8) 带电机轮子的小车支架(自带tt电机) 一个
(9) 电子产品专用胶 一支
(10) LED灯 若干
接下来就一步步的实现。
我们在淘宝上买的那种智能小车底板都是自带tt电机的,不管我们用那种控制方式,首先要做的都是让电机先跑起来。
说到驱动电机,就不得不说一下L298N(电机驱动)了,为什么要说L298N呢?
当时我第一次用电机的时候,也很疑惑,为什么要用L298N,我电机是5v的,直接连上单片机IO口,让其输出高低电平不久能控制电机转动吗????但其实是不是的,IO口确实能输出5V的电压,也确实是和电机的电压一样,但大家不要忽略IO口输出的电流,也就是驱动能力。IO口输出的电流太小了,根本带不动电机啊。。。。举个例子:“可以想象一下让一个小伙子去耕地,他肯定拉不动,但如果给他一头牛,就让小伙拿着小皮鞭赶牛,让牛去耕地,very esay。”
而L298N的作用和刚刚说的“牛”的作用一样,,我们只需用单片机IO口控制L298N的工作,其他的脏活累活全让L298n去做,,好奸商的感觉 哈哈哈哈。
L298n电源接口的接线:电源12V正极→L298n正极 电源12V负极、单片机GND→L298n的GND
L298n的5V输出→单片机的5V(用L298n产生的5V给单片机供电)
刚刚说过了 我们使用单片机的IO口输出控制L298n的工作,怎么控制呢??看到逻辑输入那四个引脚了吗 对就是他们。
左边两个逻辑输入控制电机A(正传、反转、停止)
右边两个逻辑输入控制电机B(正传、反转、停止)
具体如何控制正反转及停止的:简单说IN1、IN2 输出(0,1)正传、输出(1,1)反转、输出(1,1)制动。
具体的解释大家可以看这篇文章 : L298N具体使用及控制
那我们只需要输出不同电平就能驱动电机正反转了,但是为了控制电机的转速,不能单纯的输出1、0,可以用PWM控制,通过调整PWM的占空比,就能控制电机的转速。(很好理解吧,我们日常开私家车,也不是一脚踩足油门,一脚刹车踩到底,而是均匀的升速,或者均匀的降速)这就是PWM的功能。
刚刚已经了解了如何驱动一个电机的转动,那控制小车的前进方向,无非就是四个轮子搭配着运行。
前行:四个轮子都顺转
后退:四个轮子都逆转
左转:左侧两个轮子不动,右边两个轮子往前走。
右转:右侧两个轮子不动,左边两个轮子往前走。
四个电机,需要两个定时器TIM1、TIM4两个定时器产生两路PWM,一路PWM又有四个通道,一共8个通道,因为两个逻辑输入控制L298n的一个电机呀(这8路pwm输出都是连接到L298N的逻辑输入端)。
PWM.C
#include "timer_pwm.h"
#include "led.h"
#include "usart.h"
// TIM1_PWM输出初始化
// TIM1_CH1 = PA8
// TIM1_CH2 = PA9
// TIM1_CH3 = PA10
// TIM1_CH4 = PA11
// TIM1_Reload_Num = TIM1自动重装值
// TIM1_Frequency_Divide = 时钟预分频数
//-----------------------------------------------------------------------------------------------------------------
void TIM1_PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 使能TIM1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOB时钟
// 配置IO模式
GPIO_InitStructure.GPIO_Pin = TIM1_CH1_GPIO_PIN | TIM1_CH2_GPIO_PIN | TIM1_CH3_GPIO_PIN | TIM1_CH4_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(TIM1_CH1_GPIO_PORT, &GPIO_InitStructure); // 初始化PA8、PA9、PA10、PA11
//初始化TIM1的计数模式、分频值、重装载值等
TIM_TimeBaseStructure.TIM_Period = TIM1_Reload_Num; // 设置下一个更新事件后,装入自动重装载寄存器的值
TIM_TimeBaseStructure.TIM_Prescaler =TIM1_Frequency_Divide; // 设置TIM3时钟预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // 根据参数初始化TIM1
//初始化TIM1_CH1-4的PWM
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 选择定时器模式:TIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 比较输出使能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputState_Disable; // 比较输出N不使能
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性:TIM输出比较极性高
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; // 互补输出极性
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; //在空闲时输出低
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset ; //在空闲时互补输出低
TIM_OC1Init(TIM1, &TIM_OCInitStructure); // 数初始化TIM1_OC1
TIM_OC2Init(TIM1, &TIM_OCInitStructure); // 数初始化TIM1_OC2
TIM_OC3Init(TIM1, &TIM_OCInitStructure); // 数初始化TIM1_OC3
TIM_OC4Init(TIM1, &TIM_OCInitStructure); // 数初始化TIM1_OC4
TIM_ARRPreloadConfig(TIM1,ENABLE); // 使能TIM1的自动重装载寄存器
TIM_CtrlPWMOutputs(TIM1,ENABLE); // 主输出使能
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能TIM1在OC1上的预装载寄存器
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能TIM1在OC2上的预装载寄存器
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能TIM1在OC3上的预装载寄存器
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能TIM1在OC4上的预装载寄存器
TIM_Cmd(TIM1, ENABLE); // 使能TIM1
}
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM4_PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
//时钟配置
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
// 配置IO模式
GPIO_InitStructure.GPIO_Pin = TIM4_CH1_GPIO_PIN | TIM4_CH2_GPIO_PIN | TIM4_CH3_GPIO_PIN | TIM4_CH4_GPIO_PIN; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(TIM4_CH1_GPIO_PORT, &GPIO_InitStructure);//初始化GPIO
//初始化TIM4的计数模式、分频值、重装载值等
TIM_TimeBaseStructure.TIM_Period = TIM4_Reload_Num; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =TIM4_Frequency_Divide; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM4_CH1-4的PWM
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2(模式二:计数时当计数器值超过设定值时输出有效电平,低于时输出无效电平)
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高(高电平是有效电平,还是低电平是有效电平)
TIM_OC1Init(TIM4, &TIM_OCInitStructure);
TIM_OC2Init(TIM4, &TIM_OCInitStructure);
TIM_OC3Init(TIM4, &TIM_OCInitStructure);
TIM_OC4Init(TIM4, &TIM_OCInitStructure);
//TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器(作用:当要改变占空比时,能否立马生效)
TIM_Cmd(TIM4, ENABLE); //使能TIM3
}
PWM.h
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
#define TIM1_Frequency_Divide 719 // TIM1时钟预分频值
#define TIM1_Reload_Num 99 // 自动重装载寄存器的值
#define TIM4_Frequency_Divide 719 // TIM1时钟预分频值
#define TIM4_Reload_Num 99 // 自动重装载寄存器的值
//左前电机
#define TIM1_CH1_GPIO_PIN GPIO_Pin_8
#define TIM1_CH1_GPIO_PORT GPIOA
#define TIM1_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define TIM1_CH2_GPIO_PIN GPIO_Pin_9
#define TIM1_CH2_GPIO_PORT GPIOA
#define TIM1_CH2_GPIO_CLK RCC_APB2Periph_GPIOA
//左后电机
#define TIM1_CH3_GPIO_PIN GPIO_Pin_10
#define TIM1_CH3_GPIO_PORT GPIOA
#define TIM1_CH3_GPIO_CLK RCC_APB2Periph_GPIOA
#define TIM1_CH4_GPIO_PIN GPIO_Pin_11
#define TIM1_CH4_GPIO_PORT GPIOA
#define TIM1_CH4_GPIO_CLK RCC_APB2Periph_GPIOA
//右前电机
#define TIM4_CH1_GPIO_PIN GPIO_Pin_6
#define TIM4_CH1_GPIO_PORT GPIOB
#define TIM4_CH1_GPIO_CLK RCC_APB2Periph_GPIOB
#define TIM4_CH2_GPIO_PIN GPIO_Pin_7
#define TIM4_CH2_GPIO_PORT GPIOB
#define TIM4_CH2_GPIO_CLK RCC_APB2Periph_GPIOB
//右后电机
#define TIM4_CH3_GPIO_PIN GPIO_Pin_8
#define TIM4_CH3_GPIO_PORT GPIOB
#define TIM4_CH3_GPIO_CLK RCC_APB2Periph_GPIOB
#define TIM4_CH4_GPIO_PIN GPIO_Pin_9
#define TIM4_CH4_GPIO_PORT GPIOB
#define TIM4_CH4_GPIO_CLK RCC_APB2Periph_GPIOB
void TIM1_PWM_Init(void);
void TIM4_PWM_Init(void);
#endif
本项目采用三个红外循迹模块。
其原理很简单,// 未碰到黑线(接收到红外光):对应状态 = 0 // 碰到黑线(未接收到红外光):对应状态 = 1
也就是说,正常沿着黑线行驶,循迹模块输出高电平。当偏离黑线时,循迹模块输出低电平。我们只需采集引脚电平,当左侧出现低电平时,让小车向右转一点调整一定的角度。当右侧出现低电平时,让小车向左转一点调整一定的角度。
trail.c(循迹)
#include "trail.h"
u8 S_Trail_Input = 0 ; // 三个寻迹模块的返回值
// 红外寻迹初始化(将PB3、PB4、PB5初始化为上拉输入)
// Trail -- PB3
// Trai2 -- PB4
// Trai3 -- PB5
//----------------------------------------------------------------------------------------------
void Trail_Input_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOB端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIOB时钟使能
//PB3、PB4默认设置JTCK引脚,释放为通用GPIO口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 复用时钟使能
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); // 将PB3、PB4释放为通用GPIO口
// 寻迹:Trail--PB3、PB4、PB5端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; // Trail--PB3、PB4、PB5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式不需要设端口速度
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化PB3、PB4、PB5
}
//----------------------------------------------------------------------------------------------
// 黑线寻迹函数
// S_Trail_Input的低三位分别对应[PB5、PB4、PB3]的状态值
// 未碰到黑线(接收到红外光):对应状态 = 0
// 碰到黑线(未接收到红外光):对应状态 = 1
//----------------------------------------------------
void Trail_black_line(void)
{
S_Trail_Input = 0 ;
S_Trail_Input = (((u8)GPIOB->IDR) & 0x38)>>3;
}
//----------------------------------------------------
trail.h
#ifndef __TRAIL_H
#define __TRAIL_H
#include "stm32f10x.h"
// S_Trail_Input的低三位分别对应[PB5、PB4、PB3]的状态值
// 未碰到黑线(接收到红外光):对应状态 = 0
// 碰到黑线(未接收到红外光):对应状态 = 1
//-----------------------------------------------------
extern u8 S_Trail_Input ; // 三个寻迹模块的返回值
//-----------------------------------------------------
// 黑线寻迹情况
//---------------------------------------------------------------------
#define Not_Find_Black_Line 0x00 // 未发现黑线
#define Middle_Find_Black_Line 0x02 // 中间发现黑线
#define Left_Find_Black_Line 0x01 // 左侧发现黑线
#define Left_Middle_Find_Black_Line 0x03 // 左中侧发现黑线
#define Right_Find_Black_Line 0x04 // 右侧发现黑线
#define Right_Middle_Find_Black_Line 0x06 // 右中侧发现黑线
#define Left_Right_Find_Black_Line 0x05 // 左右侧发现黑线
#define All_Find_Black_Line 0x07 // 全部发现黑线
//---------------------------------------------------------------------
void Trail_Input_Init(void); // 红外寻迹初始化
void Trail_black_line(void); // 黑线寻迹函数
#endif /* __TRAIL_H*/
原理与红外循迹差不多,用了三个避障模块。
网上还有很多其他楼主,是用的一个舵机带动一个超声波避障模块做的,但是那种遇到障碍时,必须停下来,然后转动舵机 从而让超声波避障模块转动,测那边没有障碍,从而往那边走。
而我们这种设计不需要停下小车再去判断,在行使的过程中直接判断。
elude.c (避障)
#include "elude.h"
u8 S_Elude_Input = 0 ; // 三个红外避障模块的返回值
// 红外避障初始化(将PA1、PA2、PA3初始化为上拉输入)
// Elude_左 -- PA1
// Elude_中 -- PA2
// Elude_右 -- PA3
//----------------------------------------------------------------------------------------------
void Elude_Input_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOB时钟使能
// 避障:Elude--PA1、PA2、PA3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; // Elude--PA1、PA2、PA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式不需要设端口速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化PA1、PA2、PA3
}
//----------------------------------------------------------------------------------------------
// 红外避障检测函数
// S_Elude_Input的低三位分别对应[PA3、PA2、PA1]的状态值
// 未遇到障碍(未接收到红外光):对应状态 = 1
// 遇到障碍(接收到红外光):对应状态 = 0
//-------------------------------------------------------------
void Elude_detect_barrier(void)
{
S_Elude_Input = 0 ;
S_Elude_Input = (((u8)GPIOA->IDR) & 0x0E)>>1;
}
//-------------------------------------------------------------
elude.h
#ifndef __ELUDE_H
#define __ELUDE_H
#include "stm32f10x.h"
// S_Elude_Infrared_Input的低三位分别对应[PA3、PA2、PA1]的状态值
// 未遇到障碍(未接收到红外光):对应状态 = 1
// 遇到障碍(接收到红外光):对应状态 = 0
//--------------------------------------------------------------
extern u8 S_Elude_Input ; // 三个避障模块的返回值
//--------------------------------------------------------------
// 红外避障情况
//-----------------------------------------------------------------
#define Not_Find_Barrier 0x07 // 未发现障碍
#define Middle_Find_Barrier 0x05 // 中间发现障碍
#define Left_Find_Barrier 0x06 // 左边发现障碍
#define Left_Middle_Find_Barrier 0x04 // 左中侧发现障碍
#define Right_Find_Barrier 0x03 // 右边发现障碍
#define Right_Middle_Find_Barrier 0x01 // 右中侧发现障碍
#define Left_Right_Find_Barrier 0x05 // 左右测发现障碍
#define All_Find_Barrier 0x00 // 全部发现障碍
//-----------------------------------------------------------------
void Elude_Input_Init(void); // 红外避障初始化
void Elude_detect_barrier(void); // 红外避障检测函数
#endif /* __ELUDE_H*/
智能小车完整工程:https://item.taobao.com/item.htm?spm=a21dvs.23580594.0.0.3f063d0dMDNK4b&ft=t&id=679724523188
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/180919.html原文链接:https://javaforall.cn