测量车辆或电机的速度/转速一直是我们尝试的迷人项目。因此,在本项目中,我们将使用工业就绪的PIC微控制器构建一个。我们将使用一块磁铁和一个霍尔传感器来测量速度。还有其他方法/传感器可以测量速度,但是,使用霍尔传感器很便宜,也可用于任何类型的电机/车辆。通过做这个项目,我们还将提高我们学习PIC16F877A的技能,因为该项目涉及中断和定时器的使用。在该项目结束时,您将能够计算任何旋转物体所覆盖的速度和距离,并将其显示在 16x2 LCD 屏幕上。让我们从这个带有PIC的数字车速表和里程表电路开始。
所需材料:
- PIC16F877A
- 7805 稳压器
- 霍尔效应传感器 (US1881/04E)
- 16*2液晶显示屏
- 一小块磁铁
- 连接线
- 电容器
- 面包板。
- 电源
计算速度和覆盖距离:
在我们实际开始构建电路之前,让我们了解如何使用霍尔传感器和磁铁来计算车轮的速度。
霍尔传感器是一种可以根据磁铁的极性检测磁铁存在的设备。我们将一小块磁铁贴在车轮上,并将霍尔传感器放置在其附近,以便每次车轮旋转时,霍尔传感器都会检测到它。然后,我们使用PIC微控制器上的定时器和中断来计算车轮完全旋转所需的时间。
知道所花费的时间后,我们可以使用以下公式计算 RPM,其中 1000/时间将给我们 RPS,进一步将其乘以 60 将得到 RPM
rpm = (1000/timetaken) * 60
其中(1000/时间)给出rps(每秒转数),乘以60将rps转换为rpm(每分钟转数)。
现在要计算车辆的速度,我们必须知道车轮的半径。在我们的项目中,我们使用了半径仅为3厘米的小玩具轮。但是, 我们假设车轮的半径为30厘米 (0.3米),以便我们可以可视化读数。
该值也乘以 0.37699,因为我们知道 速度 = (RPM(直径 * Pi)/ 60)。 公式简化为
v= radius_of_wheel * rpm * 0.37699
一旦我们计算出速度,我们还可以使用类似的方法计算覆盖的距离。通过我们的霍尔和磁铁布置,我们知道轮子旋转了多少次。我们还知道车轮的半径,使用它我们可以找到车轮的周长,假设车轮的半径为 0.3m(R),周长 PiRR 的值将为 0.2827。这意味着每次霍尔传感器与磁体相遇时,车轮都会覆盖 0.2827 米的距离。
Distance_covered = distance_covered + circumference_of_the_circle
因为,现在我们知道这个项目将如何工作,让我们继续我们的电路图并开始构建它。
电路图和硬件设置:
这个车速表和里程表项目的电路图非常简单,可以构建在面包板上。如果您一直遵循PIC教程,那么您还可以重用我们用于学习PIC微控制器的硬件。在这里,我们使用了与PIC微控制器一起为LED闪烁构建的相同 性能板 ,如下所示:
PIC16F877A MCU 的引脚连接如下表所示。
S.No: |
引脚编号 |
引脚名称 |
已连接到 |
---|
1 |
21 |
RD2 |
液晶显示器的 RS |
2 |
22 |
RD3 |
液晶显示器的E |
3 |
27 |
RD4 |
液晶屏D4 |
4 |
28 |
RD5 |
液晶屏D5 |
5 |
29 |
RD6 |
液晶屏D6 |
6 |
30 |
RD7 |
液晶屏D7 |
7 |
33 |
RB0/INT |
3^RD^霍尔传感器引脚 |
构建项目后,如下图所示
如您所见,我用两个盒子将电机和一个霍尔传感器放置在附近的位置。您可以将磁铁固定在旋转物体上,并完好无损地靠近它的霍尔传感器,使其能够检测到磁铁。
注意: 霍尔传感器具有极性,因此请确保它正在检测哪个极并相应地放置它。
还要确保将上拉电阻与霍尔传感器的输出引脚一起使用。
模拟:
该项目的模拟是使用 Proteus 完成的。由于该项目涉及移动物体,因此无法使用仿真来演示整个项目,但可以验证LCD的工作。只需将十六进制文件加载到模拟中并对其进行模拟即可。您将能够注意到液晶屏工作,如下所示。
为了检查车速表和里程表是否正常工作,我已将霍尔传感器更换为逻辑状态设备。在仿真过程中,您可以单击逻辑状态按钮来触发中断,并检查所覆盖的速度和距离是否如上所示得到更新。
对 PIC16F877A 进行编程:
如前所述,我们将借助PIC16F877A微控制器中的定时器和中断来计算车轮完全旋转所需的时间。我们已经在之前的教程中学习了如何使用计时器。我在本文末尾给出了该项目的完整代码。此外,我在下面解释了几行重要内容。
以下代码行将端口 D 初始化为用于 LCD 接口的输出引脚,将 RB0 初始化为输入引脚,以将其用作外部引脚。此外,我们还使用OPTION_REG启用了内部上拉电阻,并将64设置为预售。然后,我们启用全局和外设中断以启用定时器和外部中断。要将RB0定义为外部中断位,应将INTE设为高电平。溢出值设置为 100,以便每 1 毫秒触发计时器中断标志 TMR0IF。这将有助于运行毫秒计时器以确定以毫秒为单位所花费的时间:
TRISD = 0x00
TRISB0 = 1
OPTION_REG = 0b00000101
TMR0=100
TMR0IE=1
GIE=1
PEIE=1
INTE = 1
每次检测到中断时,都会执行以下函数。我们可以根据自己的意愿命名函数,所以我将其命名为 speed_isr()。 该程序处理两个中断,一个是定时器中断,另一个是外部中断。每当发生定时器中断时,标志 TMR0IF 都会变高,为了清除和重置中断,我们必须通过定义 TMR0IF=0 使其变低,如下面的代码所示。
void interrupt speed_isr()
{
if(TMR0IF==1)
{
TMR0IF=0;
milli_sec++;
}
if (INTF==1)
{
rpm = (1000/milli_sec) * 60;
speed = 0.3 * rpm * 0.37699;
INTF = 0;
milli_sec=0;
distance= distance+028.2;
}
}
同样,当发生外部中断时,标志 INTF 将变为高电平,这也应通过定义 INTF=0 来清除。计时器中断跟踪所花费的时间,外部中断确定车轮何时完成一次完整旋转。有了这些数据,在每次外部中断期间计算车轮覆盖的速度和距离。
计算出速度和距离后,可以使用我们的LCD功能简单地显示在LCD屏幕上。如果您不熟悉 LCD,请参阅我们的 LCD 与 PIC16F877A MCU 接口教程。
工作说明:
准备好硬件和软件后,只需将代码上传到PIC16F877A即可。如果您是PIC的新手,那么您应该阅读一些有关如何将程序上传到PIC16F877A微控制器的教程。
我使用可变锅来调整电机的速度以进行演示。您也可以使用相同的查找实时应用程序。如果一切按预期工作,那么您应该能够以公里/小时为单位获得速度和以米为单位的距离,如下面的视频所示。
/*
Speedometer and Odometer for PIC16F877A
* Code by: B.Aswinth Raj
* Dated: 27-07-2017
*/
#define _XTAL_FREQ 20000000
#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7
#include
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
int speed =0;
int milli_sec=0;
int rpm=0;
int c1,c2,c3;
int d1,d2,d3;
int distance;
//LCD Functions Developed by Circuit Digest.
void Lcd_SetBit(char data_bit) //Based on the Hex value Set the Bits of the Data Lines
{
if(data_bit& 1)
D4 = 1;
else
D4 = 0;
if(data_bit& 2)
D5 = 1;
else
D5 = 0;
if(data_bit& 4)
D6 = 1;
else
D6 = 0;
if(data_bit& 8)
D7 = 1;
else
D7 = 0;
}
void Lcd_Cmd(char a)
{
RS = 0;
Lcd_SetBit(a); //Incoming Hex value
EN = 1;
__delay_ms(4);
EN = 0;
}
void Lcd_Clear()
{
Lcd_Cmd(0); //Clear the LCD
Lcd_Cmd(1); //Move the curser to first position
}
void Lcd_Set_Cursor(char a, char b)
{
char temp,z,y;
if(a== 1)
{
temp = 0x80 + b - 1; //80H is used to move the curser
z = temp>>4; //Lower 8-bits
y = temp & 0x0F; //Upper 8-bits
Lcd_Cmd(z); //Set Row
Lcd_Cmd(y); //Set Column
}
else if(a== 2)
{
temp = 0xC0 + b - 1;
z = temp>>4; //Lower 8-bits
y = temp & 0x0F; //Upper 8-bits
Lcd_Cmd(z); //Set Row
Lcd_Cmd(y); //Set Column
}
}
void Lcd_Start()
{
Lcd_SetBit(0x00);
for(int i=1065244; i<=0; i--) NOP();
Lcd_Cmd(0x03);
__delay_ms(5);
Lcd_Cmd(0x03);
__delay_ms(11);
Lcd_Cmd(0x03);
Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD
Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD
Lcd_Cmd(0x08); //Select Row 1
Lcd_Cmd(0x00); //Clear Row 1 Display
Lcd_Cmd(0x0C); //Select Row 2
Lcd_Cmd(0x00); //Clear Row 2 Display
Lcd_Cmd(0x06);
}
void Lcd_Print_Char(char data) //Send 8-bits through 4-bit mode
{
char Lower_Nibble,Upper_Nibble;
Lower_Nibble = data&0x0F;
Upper_Nibble = data&0xF0;
RS = 1; // => RS = 1
Lcd_SetBit(Upper_Nibble>>4); //Send upper half by shifting by 4
EN = 1;
for(int i=2130483; i<=0; i--) NOP();
EN = 0;
Lcd_SetBit(Lower_Nibble); //Send Lower half
EN = 1;
for(int i=2130483; i<=0; i--) NOP();
EN = 0;
}
void Lcd_Print_String(char *a)
{
int i;
for(i=0;a[i]!='\\0';i++)
Lcd_Print_Char(a[i]); //Split the string using pointers and call the Char function
}
/*****End of LCD Functions*****/
/****Interrupt function ****/
void interrupt speed_isr()
{
if(TMR0IF==1) // Timer has overflown
{
TMR0IF=0; // Clear timer interrupt flag
milli_sec++;
}
if (INTF==1)
{
rpm = (1000/milli_sec) * 60;
speed = 0.3 * rpm * 0.37699; // (Assuming the wheel radius to be 30cm)
INTF = 0; // clear the interrupt flag
milli_sec=0;
distance= distance+028.2;
}
}
/****End of Interrupt Function****/
int main()
{
TRISD = 0x00; //PORTD declared as output for interfacing LCD
TRISB0 = 1; //DEfine the RB0 pin as input to use as interrupt pin
OPTION_REG = 0b00000101; // Timer0 with external freq and 64 as prescalar // Also Enables PULL UPs
TMR0=100; // Load the time value for 1ms; delayValue can be between 0-256 only
TMR0IE=1; //Enable timer interrupt bit in PIE1 register
GIE=1; //Enable Global Interrupt
PEIE=1; //Enable the Peripheral Interrupt
INTE = 1; //Enable RB0 as external Interrupt pin
Lcd_Start();
while(1)
{
c1 = (speed/100)%10;
c2 = (speed/10)%10;
c3 = (speed/1)%10;
d1 = (distance/100)%10;
d2 = (distance/10)%10;
d3 = (distance/1)%10;
if (milli_sec>1000)
{
speed=0;
}
Lcd_Set_Cursor(1,1);
Lcd_Print_String("Speed(km/hr): ");
Lcd_Print_Char(c1+'0');
Lcd_Print_Char(c2+'0');
Lcd_Print_Char(c3+'0');
Lcd_Set_Cursor(2,1);
Lcd_Print_String("Dist_Cov(m): ");
Lcd_Print_Char(d1+'0');
Lcd_Print_Char(d2+'0');
Lcd_Print_Char(d3+'0');
}
return 0;
}