有幸赶上了今年的电赛,赛前用了一周多的时间准备迷宫小车赛题,苦于摄像头刚刚入门并不能实际应用,只能用红外传感器做出个半成品。
制作小车的硬件清单:
(1)小车框架
小车底层版*1、顶层版*1、面包板*2、、车轮*2、万向轮*1
(2)小车设备
STM32C8t6核心板*1、TB6612电机驱动*1、0.96OLED、JDY-31蓝牙模块、TCRT5000红外摄像头*6、电池、电机(带编码器)*2
一、小车框架制作
底层:底层版淘宝上比较多 我用的是学校提供的两轮驱动板子 大致如下
根据板子可以直接完成电机+轮子的固定和万向轮的安装
顶层:我们只在顶层粘贴了面包板并安装了摄像头待用,安装并不是很严格,只需和底层能对应上并保证稳定即可
设备安装:主要集中在顶层表面的面包板上,主要包括OLED、TB6612、核心板,顶层底面也粘贴面包板,出于电池线长度问题,因此将电源模块插在底层
二、整体思路
(1)驱动电机
我们采用TB6612作为电机驱动模块,由单片机产生pwm波和I/O口正负电压来控制电机的转速与方向。
//PWM.C
void PWM_Init(int16_t arr ,int16_t psc )
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; // 初始化GPIO--PA8、PA11为复用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8 | GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct); // 初始化定时器。
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上计数 0记到重装值
TIM_TimeBaseInitStruct.TIM_Period=arr;
TIM_TimeBaseInitStruct.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStruct); // TIM1
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1; // 初始化输出比较
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High; //有效电平 高
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse=0; //ccr
TIM_OC1Init(TIM1,&TIM_OCInitStruct);
TIM_OC4Init(TIM1,&TIM_OCInitStruct);
TIM_CtrlPWMOutputs(TIM1,ENABLE);// 高级定时器专属!!!--MOE主输出使能
TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);// OC1预装载寄存器使能 //控制波形是立即生效还是定时器发生下一更新事件生效
TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable);// OC4预装载寄存器使能 //此处为下一次更新
TIM_ARRPreloadConfig(TIM1,ENABLE);// TIM1在ARR上预装载寄存器使能
TIM_Cmd(TIM1,ENABLE); // 开定时器
}
void PWM_SetCompare(uint16_t Compare) //更改ccr值
{
TIM_SetCompare1(TIM1, Compare);
TIM_SetCompare4(TIM1, Compare);
}
//Motor.c
void Motor_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_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
}
void Motor_SetSpeed(int16_t moto1,int16_t moto2)
{
if (moto1 >= 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12 );
GPIO_ResetBits(GPIOB, GPIO_Pin_13 );
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12 );
GPIO_SetBits(GPIOB, GPIO_Pin_13);
}
if (moto2 >= 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_14);
GPIO_ResetBits(GPIOB, GPIO_Pin_15);
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_14);
GPIO_SetBits(GPIOB, GPIO_Pin_15);
}
TIM_SetCompare1(TIM1, 7200-myabs(moto1)); //7200-0 由小到大 计算结果为到7200的差距 上下相等即可
TIM_SetCompare4(TIM1, myabs(moto2)); //0-7200 有小到大
}
//直行
void Motor_Straight(void)
{
Motor_SetSpeed(3580,3600);
}
//右转
void Motor_RIGHT(void)
{
Motor_SetSpeed(900,3600);
}
//大右转
void Motor_BIGRIGHT(void)
{
Motor_SetSpeed(900,4800);
}
//左转
void Motor_LEFT(void)
{
Motor_SetSpeed(3600,900);
}
//大左转
void Motor_BIGLEFT(void)
{
Motor_SetSpeed(4800,900);
}
//刹车
void Motor_STOP(void)
{
Motor_SetSpeed(0,0);
Delay_ms(5);
}
//转圈
void Motor_Roll(void)
{
Motor_SetSpeed(-3600,3600);
}
//限幅函数
void Limit(int *motoA,int *motoB)
{
if(*motoA>PWM_MAX)*motoA=PWM_MAX;
if(*motoA<PWM_MIN)*motoA=PWM_MIN;
if(*motoB>PWM_MAX)*motoB=PWM_MAX;
if(*motoB<PWM_MIN)*motoB=PWM_MIN;
}
/**************************************************************************
函数功能:绝对值函数
入口参数:int
返回 值:unsigned int
**************************************************************************/
int myabs(int a)
{
int temp;
if(a<0)
temp=-a;
else temp=a;
return temp;
}
最终实现效果:左右电机都正转(直行)、左右电机偏差(左转或右转)、左右电机反转(后退)
(2)红外传感器应用
我们采用六个红外传感器作为循迹判断,其中前面两个传感器用于判断终点,如果都识别到了黑线就停止,左前左后右前右后分别进行左转右转控制,只是转弯的速度角度不一样罢了。
//CountSensor.c
void CountSensor1_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line5 ;
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_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
此处只写出了初始化,我们对于黑线的识别没有采用中断函数的形式,而是直接写在了main函数中。
(3)蓝牙模块应用
蓝牙其实相当于另一种形式的串口,通过和手机相连,接受手机上发送的数据,然后传达给单片机,本质上和串口发送和接受的流程类似
//bluetooth
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
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;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
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;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void Serial_SendByte(uint8_t Byte) //发送字符
{
USART_SendData(USART1, Byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void Serial_SendArray(uint8_t *Array, uint16_t Length) //发送数组
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String) //发送字符串
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y) //次方函数
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
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');
}
}
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format, ...) //打印函数
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
void BLUE_TOOTH_CONTROL(void) //蓝牙控制函数
{
if (Serial_GetRxFlag() == 1)
{
RxData = Serial_GetRxData();
if(RxData == 0x41) //前进
{
Motor_Straight();
}
else if(RxData == 0x42) //后退
{
Motor_Roll();
}
else if(RxData == 0x43) //左转
{
Motor_LEFT();
}
else if(RxData == 0x44) //右转
{
Motor_RIGHT();
}
else if(RxData == 0x40) //停车
{
Motor_STOP();
}
}
}
蓝牙使用界面
三、总结反思
1.车模选取
一个好的车模在前期搭建车模过程中可以省下不小功夫,不至于因为如何安装一个模块而大眼瞪小眼,建议选取一些配套的,孔比较多的模板来搭建(个人感觉这种比较方便)
2.模块提前测试好
各个外设在组合使用前我认为都应该进行单独的测试,我们在搭建过程中比较匆忙,没有系统的进行配合,导致后面倒车的时候发现只有一个轮子能反方向转,另一个不知道为什么转不了
3.提前对实物有一定的思路和概念
我认为对于设计的作品脑海中一定要有清晰的概念,其次就是有理论基础的支撑,我们的红外传感器就是因为开始设计不够精细,导致后面生掰的时候反而不好用了,必须要贴到地面才能识别出来4.一些外设
红外传感器尽量选择识别距离远的,然后就是一旦固定了就不要再动(真的哭死)
电池尽量选择能充电的
四、程序代码
链接:https://pan.baidu.com/s/1c8Jio4UbYpT1RzrwyBwBJQ
提取码:tap2
本次智能小车的制作为我们团队的第一次实践,感觉还有非常大的提升空间,欢迎各路大神多多指教