测速显示小车---5

测速模块

 测速模块的原理很简单,概况一下就是:有遮挡的时候会输出高电平;没遮挡的时候会输出低电平根据之前小车安装的测速片,结合这个测速模块,通过计算高低电平变化的频率,就可以计算出速度!

测速的具体计算

根据上面所说,目的就是根据高低电平来计算出速度:

已知轮子走一圈,经过一个周长 C = 2 X 3.14 X 半径 = 3.14 X 直径(6.5cm),对应固定在电机轴上的码盘也转了一圈,而码盘有20个格子,每经过一个格子,就会经历一次不遮挡(格子的空袭)和一次遮挡(格子空隙之后到下一个格子之前的实心材料);即每经过一个格子就会有一次低电平和高电平,这就是一个脉冲,而走完20个格子就轮子就会转一圈,因此走过一个格子经过的距离就是C/20 = 3.14*6.5/20 = 1.0205 CM 约等于 1CM。

此时如果将定时器设计成1S,然后统计1S的脉冲数,那检测到了Y个脉冲就是YCM,对应的速度就是Y cm/s

模块组装

将模块粘在测速轮的两边,将VCC和GND接到单片机引出的面包板的正负极,并将OUT口接到P2.1 (后改为P3.2,见之后的说明)

 

代码实现

UART.c:

#include "reg52.h"
#include "delay.h"
#include "motor.h"
#include <string.h>

sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册

void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

void printSTR(char *msg)
{
	while(*msg != '\0'){
		SBUF = *msg; //往发送缓冲器里写入数据,就完成了数据的发送
		while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
		TI = 0;
		msg++;
	}
	
}

void printChar(char msg)
{
		SBUF = msg; //往发送缓冲器里写入数据,就完成了数据的发送	
		while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
		TI = 0;
}
	

speed.c:

#include "reg52.h"
#include "UART.h"

sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册

sbit OUT = P2^1;
int cnt_timer = 0;
int cnt_pulse = 0;
int speed = 0;

void Timer0Init(void)		
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x4C;		//设置定时初值 //50ms
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	ET0 = 1;
	EA = 1;   //打开中断!
}

//timer0的中断处理程序 //中断程序一般写在main函数的后面 //定时器0溢出时将触发这个中断函数
void speed_inter() interrupt 1 
{
	cnt_timer++;
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x4C;		//设置定时初值 //50ms
	
 
	if(cnt_timer == 20){//经过(20*50毫秒 =)1秒
		cnt_timer = 0;
		speed = cnt_pulse;
		printSTR("speed = ");
		
		if((speed/10) == 0){//说明speed是个位数
			printChar(speed + 0x30);
		}else{//说明speed是两位数
			printChar(speed/10%10 + 0x30);//“速度的十位”显示字符型数字
			printChar(speed/1%10 + 0x30);//“速度的个位”显示字符型数字
		}
		
		printSTR("cm/s\r\n");
		cnt_pulse = 0;
	}
	
}

void pulse_detect()
{
	while(OUT == 1);//等待OUT变成低电平,即等待一次不遮挡
	while(OUT == 0);//等待OUT变成高电平,即等待一次遮挡
	cnt_pulse++; //此时经过一个脉冲
	
}

main.c

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include "motor.h"
#include "delay.h"
#include "speed.h"
#include "UART.h"


void main()
{
	Timer0Init();
	UartInit();
	
	while(1){
		move_forward();
		pulse_detect();
	}
	
}

实现效果

由于使用了串口来不断发送速度信息,因此可以使用蓝牙模块在手机上接收:

一些问题

以上代码虽然可以实现期望的效果,但是也有一些问题,就是由于“paulse_detect"函数使用了两个while(),会导致一旦调用了这个函数,那么除了中断之外的方法都无法让程序执行其他功能了,在这个单纯实现测速的代码中这样做没有问题,但是如果想要在测速的基础上再实现一些其他的功能,那“paulse_detect"函数的写法就不利于功能的扩展了....

解决办法就是将测速传感器的脉冲检测的OUT口也配置成一个中断,即使用P3.2口的外部中断(但是P3.2之前用于给电机供电,所以别忘记还需要修改一下电机部分的sbit定义!!),这样既可以保留测速的功能,也不会影响main函数之后可能进行的其他操作。

同时需要注意,配置外部中断时,应该配置成下降沿触发而不是低电平触发:

也就是IT0 = 1 

同时,对于speed的显示,刚刚对于每一位的提取再转化为字符的行为很麻烦,可以使用sprintf(speed_real,"speed: %d cm/s\r\n",speed); 来直接构建字符串,但是记得提前定义一个char speed_real[24],并添加stdio.h的库!

接下来看看修改后的代码:

motor.c中的新定义:

sbit B_1A_le = P3^6;//LEFT WHEEL

speed.c:

#include "reg52.h"
#include "UART.h"
#include "stdio.h"

sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册

sbit OUT = P3^2;
int cnt_timer = 0;
int cnt_pulse = 0;
int speed = 0;
char speed_real[24];

void Timer0Init(void)		
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x4C;		//设置定时初值 //50ms
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	ET0 = 1;
	EA = 1;   //打开中断!
	
}

void EX0_Init() 
{
	EX0 = 1; //打开外部中断
	IT0 = 1;  //不是低电平触发,而是下降沿触发!!!
}


//timer0的中断处理程序 //中断程序一般写在main函数的后面 //定时器0溢出时将触发这个中断函数
void speed_inter() interrupt 1 
{
	cnt_timer++;
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x4C;		//设置定时初值 //50ms
	
 
	if(cnt_timer == 20){//经过(20*50毫秒 =)1秒
		cnt_timer = 0;
		speed = cnt_pulse;
				
		sprintf(speed_real,"speed: %d cm/s\r\n",speed); //构建字符串
		printSTR(speed_real);

		cnt_pulse = 0;
	}
	
}



void pulse_Inter() interrupt 0 //外部中断0,即P3.2口变低电平时会自动触发这个中断处理程序,即触发一次不遮挡就会进入中断
{
	cnt_pulse++; //此时经过一个脉冲
}

UART.c:

#include "reg52.h"
#include "delay.h"
#include "motor.h"
#include <string.h>

sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
static int i = 0; //此时这句命令只会被执行一次。避免每次发生中断i都会清0
char cmd[12];

void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

void printSTR(char *msg)
{
	while(*msg != '\0'){
		SBUF = *msg; //往发送缓冲器里写入数据,就完成了数据的发送
		while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
		TI = 0;
		msg++;
	}
	
}

main.c:

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include "motor.h"
#include "delay.h"
#include "speed.h"
#include "UART.h"



void main()
{
	Timer0Init();
	EX0_Init();
	UartInit();
	
	while(1){
		move_forward();
	}
	
}

又一些问题

我发现测速模块显示的速度有时候很诡异,显示200cm/s左右,但是实际必然没有那么快,似乎只有拍摄上面GIF动图时的速度比较正常,但是我反复查看代码感觉逻辑也没有什么问题,所以我猜测也是应该硬件的原因,我的测速模块粘的位置可能欠佳,没法准确的捕捉电机轴上速度轮的间隙导致的 = =.....

测速OLED显示

在速度可以得出并通过串口发送之后,就可以尝试将速度显示在OLED屏幕上,OLED的显示之前就学习过,所以无非就是代码的移植。

首先,再次使用万能的星巴克咖啡杯热熔胶浅浅固定一下OLED屏幕:

 

但是有一个问题,我希望在OLED上显示的是 speed = xxx cm/s,而根据之前学习的知识,每一个字符都需要去字模软件中去转换,十分麻烦,在购买OLED的时候,厂商一般会提供一个h文件,和利用这个文件来显示字符的函数,因此之间将这个h文件 include进来并使用预设的函数就可以方便的调用!

 

 

利用这些,可以轻松写出OLED.c部分的代码:

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include <string.h>
#include "OLEDfont.h"
 
 
sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册

sbit SCL = P1^3;
sbit SDA = P1^2;
 
 
 
void IIC_start()
{
	SDA = 0;	
	SCL = 1;
	SDA = 1;
	_nop_(); //约5微妙
	SDA = 0;
	_nop_();
	SCL = 0;
	
}
 
void IIC_stop()
{
	SCL = 0;	
	SCL = 1;
	SDA = 0;
	_nop_();
	SDA = 1;
	_nop_();
	SDA = 0;
	
}
 
char IIC_ACK()
{
	char flag;
	
	SDA = 1;//在脉冲9释放数据线
	_nop_();
	SCL = 1;
	_nop_();
	
	flag = SDA;
	_nop_();
	SCL = 0;
	//_nop_();
	
	return flag;
}
 
void IIC_sendByte(char sdata) //data直接定义为char的好处是char就是1个byte(8个bit)
{	
	int i;
	
	for(i = 0; i < 8; i++){
		SCL = 0; //拉低,让SDA准备数据
		SDA = sdata & 0x80; //与上“1000 0000”,即只有最高位保留,其他全部清0
		_nop_();//给数据建立一个时间
		SCL = 1; //拉高,形成脉冲开始传输,此时SDA不能变
		_nop_();//发送中
		SCL = 0;//重新拉低
		_nop_();
		sdata = sdata << 1; //发完1个bit左移一位
	}
 
}
 
void OLED_writecmd(char cmd)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x00);
	IIC_ACK();
	IIC_sendByte(cmd);
	IIC_ACK();
	IIC_stop();
		
}
 
void OLED_writedata(char wdata)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x40);
	IIC_ACK();
	IIC_sendByte(wdata);
	IIC_ACK();
	IIC_stop();
	
}
 
void Oled_Init()
{
	OLED_writecmd(0xAE);//--display off
	OLED_writecmd(0x00);//---set low column address
	OLED_writecmd(0x10);//---set high column address
	OLED_writecmd(0x40);//--set start line address  
	OLED_writecmd(0xB0);//--set page address
	OLED_writecmd(0x81); // contract control
	OLED_writecmd(0xFF);//--128   
	OLED_writecmd(0xA1);//set segment remap 
	OLED_writecmd(0xA6);//--normal / reverse
	OLED_writecmd(0xA8);//--set multiplex ratio(1 to 64)
	OLED_writecmd(0x3F);//--1/32 duty
	OLED_writecmd(0xC8);//Com scan direction
	OLED_writecmd(0xD3);//-set display offset
	OLED_writecmd(0x00);//
	
	OLED_writecmd(0xD5);//set osc division
	OLED_writecmd(0x80);//
	
	OLED_writecmd(0xD8);//set area color mode off
	OLED_writecmd(0x05);//
	
	OLED_writecmd(0xD9);//Set Pre-Charge Period
	OLED_writecmd(0xF1);//
	
	OLED_writecmd(0xDA);//set com pin configuartion
	OLED_writecmd(0x12);//
	
	OLED_writecmd(0xDB);//set Vcomh
	OLED_writecmd(0x30);//
	
	OLED_writecmd(0x8D);//set charge pump enable
	OLED_writecmd(0x14);//
	
	OLED_writecmd(0xAF);//--turn on oled panel		
}
 
void Oled_Clear()
{
	int i;
	int j;
	
	for(i = 0; i<8; i++){
		OLED_writecmd(0xB0 + i);//依次选择每一页
		OLED_writecmd(0x00);//选择0列
	  OLED_writecmd(0x10);//选择0列
		for(j = 0; j<128; j++){
			OLED_writedata(0x00);//由于地址会自动偏移,所以只要重复写128次全0,就可以清一个PAGE
		}
	}
}
 
void Oled_Show_Char(char row,char col,char oledChar){ //row*2-2
	unsigned int  i;
	OLED_writecmd(0xb0+(row*2-2));                           //page 0
	OLED_writecmd(0x00+(col&0x0f));                          //low
	OLED_writecmd(0x10+(col>>4));                            //high	
	for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){
		OLED_writedata(F8X16[i]);                            //写数据oledTable1
	}

	OLED_writecmd(0xb0+(row*2-1));                           //page 1
	OLED_writecmd(0x00+(col&0x0f));                          //low
	OLED_writecmd(0x10+(col>>4));                            //high
	for(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){
		OLED_writedata(F8X16[i]);                            //写数据oledTable1
	}		
}

void Oled_Show_Str(char row,char col,char *str){
	while(*str!=0){
		Oled_Show_Char(row,col,*str);
		str++;
		col += 8;	
	}		
}

 speed.c:

#include "reg52.h"
#include "UART.h"
#include "stdio.h"

sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册

sbit OUT = P3^2;
int cnt_timer = 0;
int cnt_pulse = 0;
int speed = 0;
char speed_real[24];


void Timer0Init(void)		
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x4C;		//设置定时初值 //50ms
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	ET0 = 1;
	EA = 1;   //打开中断!
	
}

void EX0_Init() 
{
	EX0 = 1; //打开外部中断
	IT0 = 1;  //不是低电平触发,而是下降沿触发!!!
}


//timer0的中断处理程序 //中断程序一般写在main函数的后面 //定时器0溢出时将触发这个中断函数
void speed_inter() interrupt 1 
{
	cnt_timer++;
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x4C;		//设置定时初值 //50ms
	
 
	if(cnt_timer == 20){//经过(20*50毫秒 =)1秒
		cnt_timer = 0;
		speed = cnt_pulse;
		
		sprintf(speed_real,"speed: %d cm/s",speed); //构建字符串
		printSTR(speed_real);
		printSTR("\r\n");

		cnt_pulse = 0;
	}
	
}



void pulse_Inter() interrupt 0 //外部中断0,即P3.2口变低电平时会自动触发这个中断处理程序,即触发一次不遮挡就会进入中断
{
	cnt_pulse++; //此时经过一个脉冲
}

main.c:

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include "motor.h"
#include "delay.h"
#include "speed.h"
#include "UART.h"
#include "OLED.h"

extern char speed_real[24];

void main()
{
	Timer0Init();
	EX0_Init();
	UartInit();
	ES = 1;
	EA = 1; //打开中断!
	
	Oled_Init();
	Oled_Clear();
	OLED_writecmd(0x20);//页寻址模式
	OLED_writecmd(0x02);//页寻址模式
		
	while(1){
		Oled_Show_Str(2,2,speed_real);
		Delay1000ms();	
	}
	
}

实现效果:

在手机蓝牙中发送“M1”指令,小车轮子开始转动,此时速度信息同时通过手机蓝牙OLED显示:

(虽然速度的值不太对就是了...)

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值