一、设计要求:
1.1 功能要求:
设计并制作一个基于STM32C6T6核心板的智能小车,具备自动寻迹、避障和无线控制功能。小车应能够沿着不规则的黑色轨迹行驶,遇到障碍物时能够自动绕行,并可通过蓝牙模块进行无线控制。
自动寻迹:小车应能够沿着不规则的黑色轨迹行驶,根据五路灰度循迹模块的反馈调整方向。
自动避障:小车在检测到前方障碍物时,应能够自动绕行,并重新回到轨迹上。
无线控制:小车应能够通过蓝牙模块接收无线控制信号,实现启动、停止、转向等操作。
状态显示:通过OLED屏幕显示小车的实时状态,包括速度、电池电量、障碍物距离等。
声光报警:小车在遇到障碍物或电池电量低时,应能够通过蜂鸣器和LED灯发出声光报警。
1.2 赛道设计:
避障赛道:
图1-1 避障设计图
循迹设计:
图1-2 循迹设计图
二、设计思路:
2.1 设计思路:
首先,明确智能小车的功能需求,包括自动寻迹、避障、无线控制、状态显示和声光报警。选用STM32C6T6核心板作为主控单元,利用其强大的处理能力和丰富的外设接口来管理整个系统。选择L298N电机驱动模块来控制小车的两个TT电机,实现前进、后退和转向功能。五路灰度循迹模块用于检测黑色轨迹,提供实时路径信息给主控板。超声波模块用于测量前方障碍物的距离,实现自动避障功能。
蓝牙模块用于实现无线控制,用户可以通过手机或遥控器发送指令来控制小车的启动、停止和转向等操作。电源转换模块将12V电池电压转换为适合STM32C6T6核心板和其他低压组件的电压,确保系统供电稳定。OLED屏幕用于实时显示小车的状态信息,如速度、电池电量和障碍物距离等。LED灯用于指示小车的工作状态,例如启动、停止和避障。蜂鸣器用于在遇到障碍物或电池电量低时发出声音报警。
具体实现时,首先编写路径跟踪算法,利用五路灰度循迹模块的数据实现精准的路径跟踪;其次编写避障算法,根据超声波模块的数据进行障碍物检测和绕行路径规划;然后实现无线控制接口,接收和解析蓝牙模块的数据,响应用户的控制命令;同时,编写状态监测和显示功能,通过OLED屏幕和LED灯实时显示小车的状态;最后实现电源管理,监测电池电量并在低电量时发出报警或自动关机。
通过多次测试和调试,确保小车在不同环境下能够稳定、准确地执行各项功能。整体设计思路以模块化、功能分离为原则,各个模块之间通过STM32C6T6核心板进行协调,实现智能小车的多功能、高可靠性运行。
图2-1核心系统设计
2.2 硬件选择与理由:
1. STM32C6T6核心板:STM32C6T6核心板具有强大的处理能力和丰富的外设接口,适合用作主控单元。它具备高性能的ARM Cortex-M3处理器,可以处理复杂的路径跟踪、避障和无线通信算法。此外,其低功耗特性有助于延长电池续航时间。
图2-2 STM32C6T6核心板实物图
引脚定义:
2. L298N电机驱动:L298N电机驱动模块是一款高效、可靠的双H桥电机驱动模块,能够控制两台TT电机的前进、后退和转向。其设计简单、易于集成,非常适合用于小型智能小车的电机控制。
图2-3 L298N电机驱动实物图
3. 灰度循迹模块:五路灰度循迹模块能够同时检测五个点的灰度值,从而准确识别黑色轨迹。这为小车的路径跟踪提供了精确的输入数据,有助于实现平稳和准确的自动寻迹。
图2-4 灰度循迹模块实物图
4. 带电机轮子的小车底盘(自带TT电机): 带电机轮子的小车底盘提供了小车的机械结构,包含两个TT电机和轮子,构造坚固,能够承载所有电子元件。其设计紧凑,易于安装电机和传感器,适合实现小车的移动控制。
图2-5 带电机轮子的小车底盘
5. 12V供电电池:12V供电电池提供了稳定且充足的电源,支持小车的长时间运行。其高电压可以通过电源转换模块转换为适合各种电子元件的工作电压。
图2-5 12V电源
6. 蓝牙模块:蓝牙模块用于实现无线控制,允许用户通过手机或遥控器发送指令。蓝牙通信方式使用方便、成本低、通信距离适中,非常适合用于小型智能设备的无线控制。
图2-6 HC-05蓝牙模块,USB转TTL(用于蓝牙AT指令操作)
7. 超声波模块:超声波模块用于检测前方障碍物的距离,提供实时的测距信息。其测量精度高、反应速度快,对实现自动避障功能非常重要。
图2-7 超声波模块
测距原理:
(1)采用IO触发测距,给至少10us的高电平信号,实际40-50uS效果好。
(2)模块自动发送8个40khz的方波。
(3)有信号返回,通过IO输出一高电平,高电平持续的时间就是超声波从发射到返回的时间。
(4)测试距离=(高电平时间*声速(340M/S))/2。
(5)有2厘米测距盲区。
8. 电源转换模块:电源转换模块将12V电池电压转换为STM32C6T6核心板和其他低压组件所需的电压,确保系统供电稳定。其高效的电压转换能力有助于延长电池的使用时间。
图2-8 电源转接模块
9. OLED屏幕: OLED屏幕用于实时显示小车的状态信息,如速度、电池电量和障碍物距离。OLED屏幕具有高对比度、低功耗和广视角的特点,适合在小型智能设备上进行信息显示。
图2-9 OLED屏幕实物图
10. LED灯: LED灯用于指示小车的工作状态,如启动、停止和避障。LED灯响应迅速、功耗低、亮度高,适合作为状态指示器。
11. 蜂鸣器: 蜂鸣器用于在遇到障碍物或电池电量低时发出声音报警。其体积小、声音大、反应迅速,适合作为报警装置。
图2-10 蜂鸣器实物图与电路图
STM32C6T6核心板 | 1块 |
L298N电机驱动 | 1块 |
五路灰度循迹模块 | 1个 |
带电机轮子的小车底盘(自带tt电机) | 1个 |
12V供电电池 | 1个 |
蓝牙模块 | 1个 |
超声波 | 1个 |
电源转换模块 | 1个 |
OLED屏幕 | 1个 |
LED灯 | 1个 |
蜂鸣器 | 1个 |
2.3 软件算法选择:
1. 路径跟踪算法: PID控制算法在工业控制领域被广泛应用,具有成熟性和稳定性。对于智能小车的路径跟踪,PID算法能够根据五路灰度循迹模块的反馈实时调整小车的行驶方向,确保小车始终沿着黑色轨迹前进。PID算法的调节参数(比例、积分、微分)可以通过实验进行优化,以达到最佳的路径跟踪效果。
2. 避障算法:超声波避障算法利用超声波传感器实时测量前方障碍物的距离,并根据测距结果进行避障决策。这种算法实现简单、反应迅速,适合在小车前行过程中进行实时避障。具体实现时,当超声波传感器测到障碍物距离小于设定阈值时,小车将减速或转向,直至绕过障碍物后继续沿轨迹前行。
3. 无线控制接口: 蓝牙通信协议是无线控制的主流方式,其稳定性和兼容性较好。通过实现蓝牙通信协议的命令解析算法,可以接收、解析来自手机或遥控器的控制命令,并根据命令执行相应操作,如启动、停止和转向等。该算法的实现相对简单,能够有效提升用户体验。
4. 状态监测与显示:状态监测与显示需要实时更新小车的状态信息,如速度、电池电量和障碍物距离等。基于定时器中断的状态更新算法能够保证状态信息的实时性和准确性。通过定时器中断定期读取传感器数据,并将这些数据更新到OLED屏幕和LED灯上,用户可以随时了解小车的工作状态。
5. 电源管理: 电源管理是保证智能小车长时间稳定运行的重要环节。基于电压检测的低电量报警和自动关机算法能够实时监测电池电量,当电池电量低于设定阈值时,触发蜂鸣器报警并在必要时自动关机。该算法实现简单,但对延长电池寿命和保护电池有重要作用。
三、PCB设计与小车组装
3.1 电路原理图
采用b站up:好家伙VCC 的智能循迹小车V1.5版本PCB,光电管和LED通过拓展排针和面包板外接,具体引脚可参考代码部分,注意,stm32有的引脚不能承受5V电压,所以5V供电的外设尽量连接到可以承受5V的引脚上(引脚定义带FT标识)。
图3-1 电路图设计
3.2 pcb布线:
选择一个PCB设计软件,如Altium Designer、Eagle、KiCad等,开始绘制原理图。添加元件STM32C6T6核心板连接电源、地和所有需要的外设引脚。L298N电机驱动连接到TT电机和STM32C6T6的PWM控制引脚。五路灰度循迹模块连接到STM32C6T6的ADC输入或GPIO。12V供电电池:连接到电源转换模块。电源转换模块 输出为STM32C6T6核心板和其他低压组件供电。
蓝牙模块:连接到STM32C6T6的UART引脚。超声波模块连接到STM32C6T6的GPIO。OLED屏幕连接到STM32C6T6的I2C或SPI引脚。LED灯连接到STM32C6T6的GPIO。蜂鸣器连接到STM32C6T6的GPIO。
将所有元件按照设计要求连接,确保电源、地和信号线都连接正确。添加去耦电容(如0.1μF和10μF),尽量靠近STM32C6T6核心板的电源引脚,以减少电源噪声。
元件布局STM32C6T6核心放在PCB的中心位置,便于连接外设。L298N电机驱动接近电机,减少电机电源线路的长度。五路灰度循迹模块放在小车前部,确保能够检测路径。
电源转换放在靠近电池的位置,减少高电压线路长度。蓝牙模块放在远离电源和电机的位置,减少电磁干扰。超声波模块安装在小车前方。OLED屏幕安装在方便查看的位置。LED灯和蜂鸣器放置在合理的可见和听到的位置。
使用较宽的走线(如50 mil或更宽)进行电源和地线的布线,确保充足的电流容量。创建一个地平面(Ground Plane),减少电源噪声。尽量使用较短的信号线,减少信号延迟和干扰。对高速信号(如I2C、SPI和PWM)的布线要特别注意,避免交叉走线和长距离的平行走线,减少干扰。添加过孔(Via)将电源和信号线引导到适当的层。
使用设计软件的DRC功能检查所有的布线规则,包括最小走线宽度、走线间距、过孔尺寸等,确保设计没有违反规则。生成Gerber文件:包括顶层铜箔、底层铜箔、丝印层、阻焊层、钻孔文件等。生成BOM(物料清单):列出所有元件的规格和数量。
图3-2 PCB设计
3.3 实物焊接:
将Gerber文件发送到PCB制造商制作PCB。收到PCB后,将元件焊接到PCB上,进行组装和测试。
四、软件底层设计
4.1 避障思路:
当前方障碍物距离大于安全距离阈值时,小车继续按照路径跟踪算法正常行驶。当前方障碍物距离接近安全距离阈值时,小车减速,准备避障。当前方障碍物距离小于安全距离阈值时,小车停止,进入避障模式。转向判断: 通过综合考虑左右红外避障传感器的数据,决定转向方向。如左侧障碍物较少,则向左转,反之向右转。根据转向判断结果,调整小车的行驶路径,绕过障碍物后,重新进入正常行驶状态。
图4-1 避障流程图
4.2 循迹程序设计
循迹主要有两个难点,1、2两个急转弯和3入库,利用红外对管3、4同时检测到时进行快速左转,速度要比普通转弯快,实际使用时可能要把4扳到离3近一些,检测到的可能性会有所提高。由于四个光电管同时检测到的概率较小,所以利用2、3同时检测到时进行入库停车操作。
图4-2 循迹设计图
4.3跟随程序设计
主要实现一个定距离跟随功能,离得太近小车后退,离得太远小车前进,这里只写了直线跟随,可以利用光电管实现非直线跟随。
4.4蓝牙遥控程序设计
实现蓝牙发送数据,小车实现对应功能。
五、底层代码实现:
用于转向灯与蜂鸣器,二者总是同时进行的,所以封装到一个函数里。
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3);
}
void BUZZER_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_14);
}
void LED_LEFT_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
GPIO_ResetBits(GPIOB, GPIO_Pin_14);
}
void LED_LEFT_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
GPIO_SetBits(GPIOB, GPIO_Pin_14);
}
void LED_RIGHT_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_3);
GPIO_ResetBits(GPIOB, GPIO_Pin_14);
}
void LED_RIGHT_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_3);
GPIO_SetBits(GPIOB, GPIO_Pin_14);
}
void LED_ALL_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
GPIO_SetBits(GPIOA, GPIO_Pin_3);
GPIO_SetBits(GPIOB, GPIO_Pin_14);
}
采用外部中断法,能够及时响应,切换模式。
uint16_t mode;
void NVIC_Group_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC分组
}
void key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA,PORTE时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA7
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟
//GPIOA.12 中断线以及中断初始化配置 下降沿触发 KEY2
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource12);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line12;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOA.7 中断线以及中断初始化配置 上升沿触发 KEY1
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7);
EXTI_InitStructure.EXTI_Line=EXTI_Line7;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //使能按键KEY1所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //使能按键KEY2所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
/**
* @brief 按键KEY1 PA7 中断处理函数
* @param
* @return
*/
void EXTI9_5_IRQHandler(void)
{
Delay_ms(10);//消抖
if( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==1) //按键KEY1 PA7
{
mode = (mode+1)%7;
}
EXTI_ClearITPendingBit(EXTI_Line7); //清除LINE4上的中断标志位
}
/**
* @brief 按键KEY2 PA12 中断处理函数
* @param
* @return
*/
void EXTI15_10_IRQHandler(void)
{
Delay_ms(10);//消抖
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12)==0) //按键KEY2
{
mode = 0;
}
EXTI_ClearITPendingBit(EXTI_Line12); //清除LINE4上的中断标志位
}
用于显示内容,采用软件IIC。
/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOC, GPIO_Pin_14, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOC, GPIO_Pin_15, (BitAction)(x))
/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOC, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
超声波有定时器中断法、外部中断法、输入捕获法等多种方法,这里使用输入捕获法。
int time = 0;
void SR04_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
//trig
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0端口配置, 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOA,GPIO_Pin_0);
//echo
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1 清除之前设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA1 输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA1 下拉
//初始化定时器2
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 65536-1; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =72-1; //预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM5输入捕获参数
TIM_ICInitTypeDef TIM2_ICInitStructure;
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_2; // 选择输入端 IC1映射到TI2上
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI2上
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM2_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);//允许更新中断 ,允许CC2IE捕获中断
TIM_Cmd(TIM2,ENABLE ); //使能定时器2
}
u8 TIM2CH1_CAPTURE_STA=0; // 输入捕获状态
u16 TIM2CH1_CAPTURE_VAL; // 输入捕获值
void TIM2_IRQHandler(void)
{
if((TIM2CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
if(TIM2CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM2CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM2CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM2CH1_CAPTURE_VAL=0XFFFF;
}else TIM2CH1_CAPTURE_STA++;
}
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)//捕获1发生捕获事件
{
if(TIM2CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM2CH1_CAPTURE_STA|=0X80; //标记成功捕获
TIM2CH1_CAPTURE_VAL=TIM_GetCapture2(TIM2);
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM2CH1_CAPTURE_STA=0; //清空
TIM2CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM2,0);
TIM2CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
}
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
}
int Get_Distance(void)
{
int distance ;
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_us(13);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);;
if(TIM2CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
{
time=TIM2CH1_CAPTURE_STA&0X3F;
time*=65536;//溢出时间总和
time+=TIM2CH1_CAPTURE_VAL;//得到总的高电平时间
distance = time*0.033/2;
TIM2CH1_CAPTURE_STA=0;//开启下一次捕获
}
return distance;
}
用于读取红外对管与光电管电平,光电管是5V供电,要把他连接到32能承受5V的引脚(引脚定义中带FT标识)。
void E18_D80NK_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void TCRT5000_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);//使能PORTA,PORTE时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3| GPIO_Pin_4| GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //默认下拉
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
用于封装小车各种动作函数,方便调用,其他动作读者可自行设置,修改speed里的数值可改变速度。
//前进
void Car_Ahead(void)
{
LED_ALL_OFF();
Motor_RightSpeed(18);
Motor_LeftSpeed(-18);
}
//直行
void Car_Line(void)
{
LED_ALL_OFF();
Motor_RightSpeed(22);
Motor_LeftSpeed(-20);
}
//后退
void Car_Back(void)
{
LED_ALL_OFF();
Motor_RightSpeed(-18);
Motor_LeftSpeed(18);
}
//原地右转
void Self_Right(void)
{
LED_RIGHT_ON();
Motor_RightSpeed(-30);
Motor_LeftSpeed(-30);
}
//原地左转
void Self_Left(void)
{
LED_LEFT_ON();
Motor_RightSpeed(30);
Motor_LeftSpeed(30);
}
//右转,右轮不转
void Turn_Right(void)
{
LED_RIGHT_ON();
Motor_RightSpeed(0);
Motor_LeftSpeed(-20);
}
//左转,左轮不转
void Turn_Left(void)
{
LED_LEFT_ON();
Motor_RightSpeed(20);
Motor_LeftSpeed(0);
}
//停止
void Car_Stop(void)
{
LED_ALL_OFF();
Motor_RightSpeed(0);
Motor_LeftSpeed(0);
}
//前进并右转
void Forwar_Right(void)
{
LED_RIGHT_ON();
Motor_RightSpeed(10);
Motor_LeftSpeed(-20);
}
//前进并左转
void Forward_Left(void)
{
LED_LEFT_ON();
Motor_RightSpeed(20);
Motor_LeftSpeed(-10);
}
//加速
void Accelerate(uint8_t acc)
{
LED_ALL_OFF();
Motor_RightSpeed(acc);
Motor_LeftSpeed(-acc);
}
//快速原地左转
void Self_LeftFast(void)
{
LED_LEFT_ON();
Motor_RightSpeed(40);
Motor_LeftSpeed(40);
}
//快速原地右转
void Self_RightFast(void)
{
LED_RIGHT_ON();
Motor_RightSpeed(-40);
Motor_LeftSpeed(-40);
}
主函数:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Key.h"
#include "ultrasound.h"
#include "hw.h"
#include "pwm.h"
#include "steer.h"
#include "uart.h"
#include "timer.h"
#include "MyRTC.h"
uint8_t RxData;
int distance = 0;
int main(void)
{
OLED_Init();
LED_Init();
BUZZER_Init();
NVIC_Group_Init();
key_Init();
SR04_Init();
E18_D80NK_Init();
TCRT5000_Init();
TB6612_GPIO_Init();
pwm_Init();
Uart3_Init();
MyRTC_Init();
// Timer_Init();
OLED_ShowString(1,1,"mode:");//模式
OLED_ShowString(2,1,"Time:XX:XX:XX");//时间
OLED_ShowString(3,1,"dis:");//距离
while (1)
{
distance = Get_Distance();
OLED_ShowNum(1,6,mode,1);
MyRTC_ReadTime(); //RTC读取时间,最新的时间存储到MyRTC_Time数组中
OLED_ShowNum(2, 6, MyRTC_Time[3], 2); //时
OLED_ShowNum(2, 9, MyRTC_Time[4], 2); //分
OLED_ShowNum(2, 12, MyRTC_Time[5], 2); //秒
OLED_ShowNum(3, 5,distance , 3);
if(mode == 0)//停止
{
Car_Stop();
}
if(mode == 1)
{
}
if(mode == 2)//蓝牙
{
Car_Stop();
switch(UART3_FLAG)
{
case 1:Car_Ahead();
break;
case 2:Car_Back();
break;
case 3:Self_Left();
break;
case 4:Self_Right();
break;
case 5:Car_Stop();
break;
case 6:
break;
case 7:
break;
case 8:Accelerate(acc);
break;
case 9:
break;
case 10:
break;
default:Car_Stop();
break;
}
}
if(mode == 3)//直行
{
Car_Line();
}
if(mode == 4)//红外对管循迹
{
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
{
Car_Ahead();
Delay_ms(20);
}
if(HW_1 == 0 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
{
Self_Right();
Delay_ms(20);
}
if(HW_1 == 1 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
{
Self_Right();
Delay_ms(50);
}
if(HW_1 == 1 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
{
Self_Right();
Delay_ms(300);
}
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 0)
{
Self_Left();
Delay_ms(20);
}
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 1)
{
Self_Left();
Delay_ms(50);
}
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 1)
{
Self_LeftFast();
Delay_ms(350);
}
if(HW_2 == 1 && HW_3 == 1)
{
Car_Ahead();
Delay_ms(1500);
Car_Stop();
mode = 0;
}
}
if(mode == 5)//跟随
{
distance = Get_Distance();
OLED_ShowNum(3,5,distance,3);
if(distance > 20)
{
Car_Ahead();
Delay_ms(50);
}
if(distance < 15)
{
Car_Back();
Delay_ms(50);
}
Car_Stop();
}
}
}
六、测试:
5.1 避障测试:
5.2 循迹测试
标签:避障,Pin,寻迹,NVIC,TIM,源码,InitStructure,GPIO,void From: https://blog.csdn.net/qq_62634009/article/details/145074327