之前本人制作了一个智能小车,当时是用手机蓝牙APP对小车进行遥控,后来就想着用自己绘制一个遥控手柄,显得高端一点哈哈哈,所以参考了手柄制作要点,主要还是好看并且拿在手里舒适,就用CAD绘制了手柄的板框层,导入AD进行PCB绘制,PCB板框层和最后布局如下:
实物图
void USART2_IRQHandler(void) //串口2中断服务程序
{
u8 res;
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
{
res= USART_ReceiveData(USART2);
//LED1=!LED1;
if(res=='a') flag='a';//前进
else if(res=='b') flag='b';//后退
else if(res=='c') flag='c';//左拐
else if(res=='d') flag='d';//右拐
else if(res=='e') flag='e';//停止
else if(res=='f') flag='f';//速度低档
else if(res=='g') flag='g';//速度快档
else if(res=='h') flag='h'; //速度中档
else if(res=='i') mode=1;//蓝牙遥控模式
else if(res=='j') mode=2;//红外循迹模式
else if(res=='l') mode=4;//红外跟随模式
else if(res=='k')//避障跟随模式
{
mode=3;
SR04_FLAG++;
if(SR04_FLAG==3)
{
SR04_FLAG=1;
}
}
}
遥控端串口发送指令代码
说明:发送那个字符通过操作按键和PS4摇杆决定,比如发送字符’a’就是前进指令,其他依此类推
if((x==1&&y==3)||(x==1&&y==2))//前进
{
USART_SendData(USART1,'a');
OLED_ShowCHinese(80,4,27);
OLED_ShowCHinese(96,4,28);
}
if(x==1&&y==0)//后退
{
USART_SendData(USART1,'b');
OLED_ShowCHinese(80,4,29);
OLED_ShowCHinese(96,4,30);
}
if((x==3&&y==1)||(x==3&&y==0)||(x==3&&y==2))//左拐
{
USART_SendData(USART1,'c');
OLED_ShowCHinese(80,4,33);
OLED_ShowCHinese(96,4,34);
}
if((x==0&&y==1)||(x==0&&y==2)||(x==0&&y==3))//右拐
{
USART_SendData(USART1,'d');
OLED_ShowCHinese(80,4,35);
OLED_ShowCHinese(96,4,36);
}
if(x==1&&y==1)//停止
{
USART_SendData(USART1,'e');
OLED_ShowCHinese(80,4,31);
OLED_ShowCHinese(96,4,32);
}
//按键选择模式
key=KEY_Scan(0); //得到键值
if(key)
{
switch(key)
{
case KEY4_PRES: //速度档位模式
flag0++;
if(flag0==1)//低档
{
OLED_ShowCHinese(40,2,14);
USART_SendData(USART1,'f');//发送字符f给小车,小车接收到指令就可以进行相关操作,以下类推
Buzzer_ON();
delay_ms(100);
Buzzer_OFF();
}
else if(flag0==2)//中档
{
OLED_ShowCHinese(40,2,11);
USART_SendData(USART1,'h');
Buzzer_ON();
delay_ms(100);
Buzzer_OFF();
}
else if(flag0==3)//快裆
{
OLED_ShowCHinese(40,2,13);
USART_SendData(USART1,'g');
flag0=0;
Buzzer_ON();
delay_ms(100);
Buzzer_OFF();
}
break;
case KEY3_PRES: //避障跟随模式
{
flag++;//flag原理都是一个按键切换多种模式
if(flag==3)
{
flag=1;
}
if(flag==1)
{
OLED_ShowCHinese(40,0,21);
OLED_ShowCHinese(56,0,22);
OLED_ShowCHinese(72,0,9);
OLED_ShowCHinese(88,0,10);
USART_SendData(USART1,'k');
}
else if(flag==2)
{
OLED_ShowCHinese(40,0,17);
OLED_ShowCHinese(56,0,18);
OLED_ShowCHinese(72,0,9);
OLED_ShowCHinese(88,0,10);
}
USART_SendData(USART1,'k');
Buzzer_ON();
delay_ms(100);
Buzzer_OFF();
}
break;
case KEY2_PRES: //红外循迹模式
{
OLED_ShowCHinese(40,0,2);
OLED_ShowCHinese(56,0,3);
OLED_ShowCHinese(72,0,4);
OLED_ShowCHinese(88,0,5);
USART_SendData(USART1,'j');//
Buzzer_ON();
delay_ms(100);
Buzzer_OFF();
}
break;
case KEY1_PRES: //蓝牙遥控模式
{
OLED_ShowCHinese(40,0,0);
OLED_ShowCHinese(56,0,1);
OLED_ShowCHinese(72,0,19);
OLED_ShowCHinese(88,0,20);
USART_SendData(USART1,'i');//
Buzzer_ON();
delay_ms(100);
Buzzer_OFF();
}
break;
}
}
ds18b.c:
#include "ds18b20.h"
#include "delay.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK MiniSTM32开发板
//DS18B20驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2014/4/12
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//
//复位DS18B20
void DS18B20_Rst(void)
{
DS18B20_IO_OUT(); //SET PA0 OUTPUT
DS18B20_DQ_OUT=0; //拉低DQ
delay_us(750); //拉低750us
DS18B20_DQ_OUT=1; //DQ=1
delay_us(15); //15US
}
//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check(void)
{
u8 retry=0;
DS18B20_IO_IN();//SET PA0 INPUT
while (DS18B20_DQ_IN&&retry<200)
{
retry++;
delay_us(1);
};
if(retry>=200)return 1;
else retry=0;
while (!DS18B20_DQ_IN&&retry<240)
{
retry++;
delay_us(1);
};
if(retry>=240)return 1;
return 0;
}
//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit(void) // read one bit
{
u8 data;
DS18B20_IO_OUT();//SET PA0 OUTPUT
DS18B20_DQ_OUT=0;
delay_us(2);
DS18B20_DQ_OUT=1;
DS18B20_IO_IN();//SET PA0 INPUT
delay_us(12);
if(DS18B20_DQ_IN)data=1;
else data=0;
delay_us(50);
return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void) // read one byte
{
u8 i,j,dat;
dat=0;
for (i=1;i<=8;i++)
{
j=DS18B20_Read_Bit();
dat=(j<<7)|(dat>>1);
}
return dat;
}
//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(u8 dat)
{
u8 j;
u8 testb;
DS18B20_IO_OUT();//SET PA0 OUTPUT;
for (j=1;j<=8;j++)
{
testb=dat&0x01;
dat=dat>>1;
if (testb)
{
DS18B20_DQ_OUT=0;// Write 1
delay_us(2);
DS18B20_DQ_OUT=1;
delay_us(60);
}
else
{
DS18B20_DQ_OUT=0;// Write 0
delay_us(60);
DS18B20_DQ_OUT=1;
delay_us(2);
}
}
}
//开始温度转换
void DS18B20_Start(void)// ds1820 start convert
{
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0xcc);// skip rom
DS18B20_Write_Byte(0x44);// convert
}
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在
u8 DS18B20_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PORTA口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PORTB13 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_13); //输出1
DS18B20_Rst();
return DS18B20_Check();
}
void DS18B20_IO_IN()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PORTA口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PORTB13
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void DS18B20_IO_OUT()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PORTA口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PORTB13 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250)
short DS18B20_Get_Temp(void)
{
u8 temp;
u8 TL,TH;
short tem;
DS18B20_Start (); // ds1820 start convert
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0xcc);// skip rom
DS18B20_Write_Byte(0xbe);// convert
TL=DS18B20_Read_Byte(); // LSB
TH=DS18B20_Read_Byte(); // MSB
if(TH>7)
{
TH=~TH;
TL=~TL;
temp=0;//温度为负
}else temp=1;//温度为正
tem=TH; //获得高八位
tem<<=8;
tem+=TL;//获得底八位
tem=(float)tem*0.625;//转换
if(temp)return tem; //返回温度值
else return -tem;
}
ds18b20.h:
#ifndef __DS18B20_H
#define __DS18B20_H
#include "sys.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK MiniSTM32开发板
//DS18B20驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2014/3/12
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//
//IO方向设置
//#define DS18B20_IO_IN() {GPIOB->CRH&=0XFF0FFFFF;GPIOB->CRH|=8<<5;}
//#define DS18B20_IO_OUT() {GPIOB->CRH&=0XFF0FFFFF;GPIOB->CRH|=3<<5;}
void DS18B20_IO_OUT(void);
void DS18B20_IO_IN(void);
IO操作函数
#define DS18B20_DQ_OUT PBout(13) //数据端口 PB13
#define DS18B20_DQ_IN PBin(13) //数据端口 PB13
u8 DS18B20_Init(void); //初始化DS18B20
short DS18B20_Get_Temp(void); //获取温度
void DS18B20_Start(void); //开始温度转换
void DS18B20_Write_Byte(u8 dat);//写入一个字节
u8 DS18B20_Read_Byte(void); //读出一个字节
u8 DS18B20_Read_Bit(void); //读出一个位
u8 DS18B20_Check(void); //检测是否存在DS18B20
void DS18B20_Rst(void); //复位DS18B20
#endif
主函数调用显示温度函数:
T=DS18B20_Get_Temp();//获取温度
if(T<0)//判断温度正负
{
OLED_ShowChar(72,0,'-',16);
}
else
{
OLED_ShowChar(72,0,'+',16);
}
OLED_ShowCHinese(0,0,37);
OLED_ShowCHinese(16,0,38);
OLED_ShowCHinese(32,0,39);
OLED_ShowCHinese(48,0,40);
OLED_ShowString(64,0,":",16);
OLED_ShowCHinese(110,0,41);
OLED_ShowString(96,0,".",16);
OLED_ShowNum(101,0,T%10,1,16);
OLED_ShowNum(80,0,T/10,2,16);
adc.c:
#include "adc.h"
#include "delay.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK miniSTM32开发板
//ADC 代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/7
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//
extern volatile uint16_t ADC_ConveredValue[3];
//初始化ADC
//这里我们仅以规则通道为例
//我们默认将开启通道0~3
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//PA1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 3; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_RegularChannelConfig(ADC1,ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_2, 2, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_5, 3, ADC_SampleTime_239Cycles5);
// ADC_DMACmd(ADC1, ENABLE); //使能ADC1的DMA功能
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
}
//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间,1是转换顺序,多通道使用,单通道就没有顺序而言了,设置1即可
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
u16 Get_Adc_Average(u8 ch,u8 times)//正点原子求平均值测试,防止ADC数值波动
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
u16 Get_Adc_Average2(u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=ADC_ConveredValue[2];//DMA电池电压
delay_ms(5);
}
return temp_val/times;
}
u16 Get_Adc_Average1(u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=ADC_ConveredValue[1];//DMA y轴电压
delay_ms(5);
}
return temp_val/times;
}
u16 Get_Adc_Average0(u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=ADC_ConveredValue[0];//DMA x轴电压
delay_ms(5);
}
return temp_val/times;
}
adc.h+DMA配置
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK战舰STM32开发板
//ADC 代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/7
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//
void Adc_Init(void);
u16 Get_Adc(u8 ch);
u16 Get_Adc_Average(u8 ch,u8 times);
u16 Get_Adc_Average0(u8 times);
u16 Get_Adc_Average1(u8 times);
u16 Get_Adc_Average2(u8 times);
#endif
//ADCDMA配置
void ADC_DMA_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); //ADC外设数据寄存器地址作为基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConveredValue;//存储数据内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向为外设到内存
DMA_InitStructure.DMA_BufferSize = 3; //单位是下面的HalfWord 16bit
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //用到多通道ADC, 所以使能自动增加,一个通道转换完的数据放在g_stTempInfo.ADC_ValTab[0],
//下一个通道数据就自动存放在g_stTempInfo.ADC_ValTab[1]
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据大小为半字
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存数据大小也为半字
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //不使用内存到内存的传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
/* Enable DMA1 Channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA通道1
ADC_DMACmd(ADC1, ENABLE); //使能ADC1的DMA功能
}
主函数调用
OLED_ShowCHinese(0,4,42);
OLED_ShowCHinese(16,4,43);
OLED_ShowCHinese(32,4,44);
OLED_ShowCHinese(48,4,45);
OLED_ShowString(64,4,":",16);
adc=Get_Adc_Average2(20)*(3.3/4096)*1000;//获取电池电压
OLED_ShowNum(96,4,adc%10,1,16);
OLED_ShowString(80,4,".",16);
OLED_ShowNum(88,4,adc/10%10,1,16);
OLED_ShowNum(72,4,adc/100%10,1,16);
// OLED_ShowNum(72,2,flag1,1,16);
OLED_ShowString(104,4,"V",16);
x=(int)Get_Adc_Average0(10)*(3.3/4096);//PS4遥杆x轴ADC电压,取int型
y=(int)Get_Adc_Average1(10)*(3.3/4096);//PS4遥杆y轴ADC电压
// OLED_ShowNum(120,4,x,1,16);
// OLED_ShowNum(128,4,y,1,16);
//以下根据PS4遥感输出ADC即可判断
if((x==1&&y==3)||(x==1&&y==2))//前进
{
USART_SendData(USART1,'a');
OLED_ShowCHinese(80,4,27);
OLED_ShowCHinese(96,4,28);
}
if(x==1&&y==0)//后退
{
USART_SendData(USART1,'b');
OLED_ShowCHinese(80,4,29);
OLED_ShowCHinese(96,4,30);
}
if((x==3&&y==1)||(x==3&&y==0)||(x==3&&y==2))//左拐
{
USART_SendData(USART1,'c');
OLED_ShowCHinese(80,4,33);
OLED_ShowCHinese(96,4,34);
}
if((x==0&&y==1)||(x==0&&y==2)||(x==0&&y==3))//右拐
{
USART_SendData(USART1,'d');
OLED_ShowCHinese(80,4,35);
OLED_ShowCHinese(96,4,36);
}
if(x==1&&y==1)//停止
{
USART_SendData(USART1,'e');
OLED_ShowCHinese(80,4,31);
OLED_ShowCHinese(96,4,32);
}