В данной статье мы рассмотрим изготовление цифрового спидометра и одометра на микроконтроллере PIC, который может быть использован не только для домашнего применения, но и для промышленного. Для измерения скорости мы будем использовать магнит и датчик Холла. Про принцип действия датчика Холла можно прочитать в этой статье.
Для измерения скорости можно использовать и другие разнообразные датчики, но использование датчика Холла является одним из самых дешевых способов, к тому же его модно применить практически в любом транспортном средстве. В проекте мы будем использовать таймеры и прерывания в микроконтроллере PIC16F877A. Про таймеры в этом микроконтроллере у нас уже есть статья, а статья про прерывания появится на нашем сайте немного позже.
Итак, в нашем проекте мы можем определять скорость и пройденную дистанцию с помощью микроконтроллера PIC и датчика Холла и выводить их на экран ЖК дисплея 16x2. Также на нашем сайте вы можете посмотреть другие проекты спидометров и одометров:
- GPS спидометр на Arduino и OLED дисплее;
- аналоговый спидометр на основе Arduino и инфракрасного датчика;
- спидометр с использованием Arduino и приложения на Android;
- измерение скорости, пройденного пути и угла поворота с помощью Arduino и датчика LM393.
Необходимые компоненты
- Микроконтроллер PIC16F877A (купить на AliExpress).
- Держатель микросхем на 40 контактов (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- Датчик Холла US1881/04E (купить на AliExpress).
- Небольшой кусок магнита.
- Кварцевый генератор 20 МГц (купить на AliExpress).
- Конденсатор 0,1 мкФ и 22 пФ (2 шт.) (купить на AliExpress).
- Конденсатор 10 мкФ (купить на AliExpress).
- Регулятор напряжения 7805 (купить на AliExpress).
- Резистор 10 кОм (купить на AliExpress).
- Потенциометр 10 кОм (купить на AliExpress).
- ЖК дисплей 16х2 (купить на AliExpress).
- Перфорированная плата.
- Соединительные провода.
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Расчет скорости и пройденного расстояния
Датчик Холла представляет собой устройство, способное обнаруживать присутствие магнита. В нашем проекте мы закрепим небольшой кусок магнита на вращающемся колесе, а датчик Холла разместим рядом с ним. При этом при вращении колеса мы сможем обнаруживать магнит каждый раз когда он будет находиться вблизи датчика Холла. С помощью таймера и прерываний в микроконтроллере PIC мы сможем определять время, затрачиваемое на один полный оборот колеса.
Если нам будет известно время одного оборота, то мы сможем рассчитать количество оборотов в минуту (revolutions per minute, RPM) по следующей формуле:
1 |
rpm = (1000/timetaken) * 60; |
В этой формуле (1000/timetaken) позволяет нам рассчитать число оборотов в секунду (Revolutions per second, rps), а при помощи умножения этой величины на 60 мы как раз и получаем число оборотов в минуту (rpm).
Теперь, чтобы рассчитать скорость транспортного средства нам необходимо знать радиус колеса. В нашем проекте мы использовали небольшое колесо от детской игрушки радиусом 3 см. Но мы будем считать что у нас колесо радиусом 30 см чтобы смоделировать измерение скорости для реального транспортного средства.
Теперь нам необходимо умножить полученное значение числа оборотов в минуту на значение 0.37699 поскольку скорость=(RPM (diameter * Pi) / 60). В результате получаем следующую упрощенную формулу для расчета скорости:
1 |
v= radius_of_wheel * rpm * 0.37699; |
Затем мы можем определить и пройденное расстояние аналогичным образом. С помощью наших магнита и датчика Холла мы будем знать какое число оборотов совершило колесо. Зная радиус колеса мы сможем определить длину его окружности, в нашем случае оно составит величину 0,2827 метра. Это будет означать, что каждый раз когда датчик Холла будет обнаруживать вблизи себя магнит наше транспортное средство будет проезжать расстояние равное 0,2827 метра. В результате получим следующую формулу для расчета пройденного расстояния:
1 |
Distance_covered = distance_covered + circumference_of_the_circle |
где circumference_of_the_circle – длина окружности нашего колеса.
Схема проекта
Схема спидометра и одометра на основе микроконтроллера PIC представлена на следующем рисунке.
Для ее сборки мы использовали ту же самую перфорированную плату, которая использовалась в нашем проекте мигания светодиодом на микроконтроллере PIC.
Схема соединений микроконтроллера PIC в нашем проекте приведена в следующей таблице.
№ п/п | Номер контакта | Наименование контакта | Куда подключен |
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-й контакт датчика Холла |
Внешний вид собранной конструкции проекта показан на следующем рисунке.
Как видите, мы использовали две небольшие коробочки для закрепления нашей конструкции.
Примечание: датчик Холла имеет полярность, поэтому убедитесь в том, что вы разместили его правильной полярностью. Также убедитесь в том, что к выходному контакту датчика подключен подтягивающий резистор.
Моделирование работы проекта
Моделирование работы проекта производилось с помощью симулятора Proteus. Поскольку проект содержит движущиеся части, то полностью, конечно, смоделировать его в Proteus невозможно. Но проверить работу ЖК дисплея можно. Поэтому загрузите hex файл программы в симулятор и проверьте работу проекта. Вы должны заметить что ЖК дисплей успешно функционирует.
Для проверки работы проекта в Proteus мы заменили датчик Холла на логическое устройство. Для проверки работы проекта вы можете нажимать на эту логическую кнопку что будет приводить к срабатыванию прерывания в микроконтроллере и, таким образом, будет производиться расчет скорости и пройденного расстояния.
Объяснение программы для микроконтроллера PIC16F877A
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты. Для расчета скорости и пройденного пути мы будем использовать, как уже отмечали, таймеры и прерывания в микроконтроллере PIC16F877A.
В следующем фрагменте кода мы инициализируем Port D для работы на вывод данных, а контакт RB0 – на ввод данных, на нем мы будем производить обнаружение сигнала внешнего прерывания. Далее мы задействуем внутренний подтягивающий резистор используя OPTION_REG и установим коэффициент деления предделителя равный 64. Затем мы разрешим прерывание таймера, установим глобальное разрешение прерываний и разрешение прерываний от периферийных устройств. В качестве значения начала счета для таймера укажем 100 – в этом случае флаг прерывания от таймера TMR0IF будет срабатывать каждую миллисекунду.
1 2 3 4 5 6 7 8 |
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 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 |
Далее запрограммируем функцию обработки прерывания – она будет вызываться каждый раз при обнаружении прерывания. В нашей программе мы будем использовать два прерывания: одно от таймера, а второе – внешнее прерывание (External Interrupt). При срабатывании прерывания от таймера флаг TMR0IF будет устанавливаться в high, чтобы сбросить это прерывание, мы должны установить TMR0IF=0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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; } } |
Аналогичным образом при срабатывании внешнего прерывания флаг INTF будет устанавливаться в high, чтобы сбросить это прерывание мы должны установить INTF=0. Используя эти два прерывания (от таймера и внешнее) мы можем определять время, за которое колесо совершает один полный оборот и затем на основании этого времени определить скорость движения транспортного средства и пройденное им расстояние.
После расчета скорости и пройденного пути они будут отображаться на экране ЖК дисплея с помощью специальных функций, разработанных нами ранее для работы с ЖК дисплеем. Более подробно про них вы можете прочитать в этой статье.
Тестирование работы проекта
В демонстрационных целях мы использовали потенциометр для управления скоростью вращения колеса. Если проект работает правильно, то на экране ЖК дисплея должны отображаться рассчитанные значения скорости и пройденного пути как показано на следующем рисунке.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
/* Speedometer and Odometer for PIC16F877A * Code by: B.Aswinth Raj * Dated: 27-07-2017 * More details at: www.CircuitDigest.com */ #define _XTAL_FREQ 20000000 #define RS RD2 #define EN RD3 #define D4 RD4 #define D5 RD5 #define D6 RD6 #define D7 RD7 #include <xc.h> #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); //очищаем экран ЖК дисплея Lcd_Cmd(1); //перемещаем курсор на 1-ю позицию } 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; //младшие 8 бит y = temp & 0x0F; //старшие 8 бит Lcd_Cmd(z); //устанавливаем строку Lcd_Cmd(y); //устанавливаем столбец } else if(a== 2) { temp = 0xC0 + b - 1; z = temp>>4; //младшие 8 бит y = temp & 0x0F; // старшие 8 бит Lcd_Cmd(z); //устанавливаем строку Lcd_Cmd(y); //устанавливаем столбец } } 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); //выбираем строку 1 Lcd_Cmd(0x00); //очищаем строку 1 Lcd_Cmd(0x0C); //выбираем строку 2 Lcd_Cmd(0x00); //очищаем строку 2 Lcd_Cmd(0x06); } void Lcd_Print_Char(char data) //передаем 8 бит используя 4-битный режим дисплея { 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]); //разделяем строку на отдельные символы используя указатели и по очереди передаем каждый символ } /*****End of LCD Functions*****/ /****функция для обработки прерываний ****/ 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; // (считаем что радиус колеса у нас 30cm) INTF = 0; // очищаем флаг внешнего прерывания milli_sec=0; distance= distance+028.2; } } /****End of Interrupt Function****/ int main() { TRISD = 0x00; //PORTD declared as output for interfacing LCD TRISB0 = 1; //на этом контакте мы будем производить обработку прерывания 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; //разрешаем прерывание от таймера в регистре PIE1 GIE=1; //глобальное разрешение прерываний PEIE=1; //разрешаем прерывания от периферийных устройств INTE = 1; //устанавливаем RB0 в качестве контакта для обработки внешнего прерывания 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; } |