В данной статье мы рассмотрим создание устройства на основе микроконтроллера PIC для мониторинга напряжения батареи автомобиля. Также для данного устройства мы разработаем печатную плату с помощью симулятора EASYEDA. Напряжение батареи автомобиля будет контролироваться помощью гнезда прикуривателя. Также наше устройство можно будет использоваться в качестве автономного вольтметра, не привязанного к бортовой сети автомобиля. Для этого в нашем устройстве предусмотрен отдельный блок выводов, к которому можно подключать сторонние источники напряжения.
Необходимые компоненты
- Микроконтроллер PIC18F2520.
- Фабричная печатная плата.
- USB коннектор.
- 2-пиновый коннектор.
- 4-х символьный семисегментный дисплей (4-Digit 7 Segment Display).
- Транзисторы BC557 (4 шт.) (купить на AliExpress).
- Резисторы 1 кОм (6 шт.), 2 кОм, 100 Ом (8 шт.) (купить на AliExpress).
- Конденсаторы 1000 мкФ и 10 мкФ (купить на AliExpress).
- Конденсаторы 33 пФ – 2 шт. (купить на AliExpress).
- Держатель микросхем на 28 контактов.
- Микросхема регулятора напряжения 7805 (купить на AliExpress).
- Автомобильная USB зарядка.
- Светодиод (купить на AliExpress).
- Диоды Зенера (стабилитроны) на 5,1 В (2 шт.).
- USB кабель (B-типа или Arduino UNO совместимый).
- Кварцевый генератор на 20 МГц (купить на AliExpress).
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Общие принципы работы проекта
В большинстве случаев у нас нет необходимости измерять напряжение на аккумуляторе автомобиле, но это желательно делать во время его зарядки чтобы определить идет процесс заряда или нет. Благодаря этому мы можем предотвратить поломку аккумулятора из-за неисправности зарядной системы автомобиля. Напряжение на 12-вольтовой аккумуляторной батарее автомобиля во время ее заряда составляет величину порядка 13.7v. Соответственно, по значению этого напряжения мы можем определить хорошо ли заряжается наша батарея или нет.
В данном проекте мы рассмотрим создание вольтметра на основе микроконтроллера PIC для измерения напряжения на аккумуляторе автомобиля. Напряжение с выхода автомобильной USB зарядки через делитель напряжения будет подаваться на вход АЦП (аналого-цифрового преобразователя) микроконтроллера, где будет измеряться и выводиться на 4-х символьный семисегментный дисплей. Схема будет позволять измерять напряжения до 15v.
Если аккумулятор автомобиля будет находиться в процессе заряда, то тогда значение напряжения на нем должно составлять величину 13,7 В. Если же двигатель автомобиля не работает или его зарядная система неисправна, тогда напряжение на аккумуляторе будет составлять величину 12 В.
Эту же самую схему можно использовать для измерения любых других напряжений до 15 В. Для этой цели в составе нашей печатной платы предусмотрен 2-х пиновый коннектор (зеленого цвета). На видео, приведенном в конце статьи, вы можете увидеть как с помощью данного устройства производилось измерение напряжения на различных источниках.
Схема проекта
Схема устройства мониторинга напряжения аккумулятора автомобиля на микроконтроллере PIC представлена на следующем рисунке.
Напряжение с выхода аккумуляторной батареи через делитель напряжения будет подаваться на контакт AN0 (28) микроконтроллера PIC, где будет измеряться его АЦП. Для защиты работы схемы используется диод Зенера (стабилитрон) на 5.1v.
4-х символьный семисегментный дисплей, используемый для отображения измеренного значения напряжения, подключен к портам PORTB и PORTC микроконтроллера. Регулятор напряжения 5v на основе микросхемы LM7805 используется для питания всей схемы проекта, в том числе и семисегментного дисплея. Кварцевый генератор 20 МГц используется для подачи тактовой частоты на микроконтроллер. На микросхему LM7805 напряжение питания поступает от автомобильной USB зарядки, для этого в нашу печатную плату мы добавили USB порт, к которому мы непосредственно можем подключать автомобильную зарядку.
Автомобильная USB зарядка обеспечивает стабилизированное напряжение 5v, получаемое за счет преобразования напряжения 12v бортовой сети автомобиля. Но нам необходимо в нашей схеме необходимо измерять напряжение 12v, а не 5v с выхода зарядки, поэтому нам зарядку необходимо немного модифицировать. Для этого вам нужно открыть корпус USB зарядки, найти там выводы 5v (output) и 12v (input) и удалить соединение с 5v, замотав его изоляционной лентой или чем-нибудь еще, и затем соединить напрямую выходы зарядки со входами 12v. Как показано на следующем рисунке мы удалили соединение, показанное красным цветом (для вашей зарядки картинка может отличаться).
Для настройки АЦП на контакте AN0 мы будем использовать внутреннее опорное напряжение 5 В и частоту f/32.
Для расчета напряжения аккумулятора автомобиля мы будем использовать формулу:
1 2 3 4 5 |
Voltage= (ADC value / resistor factor) * reference Voltage Где: ADC value= напряжение с выхода делителя напряжения (преобразуется в цифровой вид в микроконтроллере) Resistor factor = 1023.0 / (R2/R1+R2) // 1023 максимальное значение на выходе АЦП (10-bit) Reference Voltage= 5 volts // внутреннее опорное напряжение 5v |
Расчет делителя напряжения
В нашем проекте напряжение с выхода аккумуляторной батареи автомобиля может составлять величину 12v-14v, соответственно, максимальное напряжение для нашего проекта вольтметра мы примем равным 15v.
В нашем делителе напряжения резисторы R1 и R2 будут иметь следующие номиналы:
R1= 2K
R2=1K
Соответственно, подставляя в формулу их значения, получим:
Resistor factor= 1023.0 * (1000/2000+1000)
Resistor factor=1023.0 * (1/3)
Resistor factor= 341.0 для напряжений до 15 Вольт
Соответственно, окончательная формула для расчета измеряемого нами напряжения будет выглядеть следующим образом:
1 |
Voltage= (ADC value / 341.0) * 5.0 |
Дизайн и заказ печатной платы для нашего проекта
Для разработки дизайна печатной платы авторы проекта (ссылка на оригинал приведена в конце статьи) использовали бесплатный симулятор EasyEDA. В данном симуляторе вы можете сделать разработанный вами дизайн платы видимым всем остальным пользователям сети. Разработанный авторами дизайн печатной платы для этого проекта доступен по следующей ссылке:
Внешний вид (сверху) данного дизайна печатной платы приведен на следующем рисунке. Соответственно, открыв данный дизайн в редакторе EasyEDA, вы сможете рассмотреть печатную плату со всех сторон.
Также из редактора EasyEDA можно заказать изготовление печатной платы, использовав свой разработанный дизайн печатной платы или загрузив имеющиеся у вас Gerber файлы в редактор. При заказе изготовления печатной платы необходимо указать количество необходимых экземпляров печатной платы, число ее слоев, толщину, цвет и другие параметры.
Спустя несколько дней после заказа изготовления печатной платы ее авторы получили изготовленные три экземпляра платы в следующем виде:
![]() |
![]() |
После этого автор проекта припаял на нее необходимые компоненты и получил собранную конструкцию проекта в следующем виде:
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы рассмотрим его основные фрагменты. Для написания кода программы мы будем использовать MPLAB X IDE, а для компиляции и компоновки проекта – компилятор XC8. Код программы будет написан на языке C.
В коде программы мы будем считывать значение напряжения, подаваемого на аналоговый контакт микроконтроллера PIC, а для передачи данных на 4-х разрядный семисегментный дисплей мы будем использовать процедуру обработки прерывания от таймера (Timer Interrupt Server Routine). Вычисление измеренного значения напряжения будет производиться в основной части программы.
Первым делом в программе мы подключим необходимые заголовочные файлы и настроим биты конфигурации (фьюзы) микроконтроллера PIC.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include<xc.h> //xc8 is compiler // CONFIG1H #pragma config OSC = HS #pragma config FCMEN = OFF #pragma config IESO = OFF // CONFIG2L #pragma config PWRT = ON #pragma config BOREN = SBORDIS #pragma config BORV = 3 // CONFIG2H #pragma config WDT = OFF #pragma config WDTPS = 32768 .... ..... ..... ..... |
Затем объявим контакты, к которым подключен семисегментный дисплей.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
unsigned int counter2; unsigned char position = 0; unsigned char k[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; int digit1=0,digit2=0,digit3=0,digit4=0; #define TRIS_seg1 TRISCbits.TRISC0 #define TRIS_seg2 TRISCbits.TRISC1 #define TRIS_seg3 TRISCbits.TRISC2 #define TRIS_seg4 TRISCbits.TRISC3 #define TRIS_led1 TRISAbits.TRISA2 #define TRIS_led2 TRISAbits.TRISA5 #define TRIS_led3 TRISAbits.TRISA0 #define TRIS_led4 TRISAbits.TRISA1 #define TRIS_led5 TRISAbits.TRISA .... .... ..... ..... |
Далее запрограммируем функцию обработки прерывания от таймера для вывода данных на семисегментный дисплей. Более подробно об использовании таймеров в микроконтроллерах PIC вы можете прочитать в данной статье.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void interrupt low_priority LowIsr(void) { if(TMR0IF == 1) { counter2++; if(counter2>=1) { if(position ==0) { seg1=0; seg2=1; seg3=1; seg4=1; .... .... ..... ..... |
В основной функции void main() инициализируем таймер.
1 2 3 4 5 6 7 |
GIE = 1; //GLOBLE INTRRUPT ENABLE PEIE = 1; //peripheral intrupt flag T0CON = 0b000000000; //prescaler value put TMR0IE = 1; //interrupt enable TMR0IP = 0; //interrupt priority TMR0 = 55536; //start counter after this value TMR0ON = 1; |
Затем в цикле while мы будем считывать значение напряжения с аналогового входа и производить необходимые вычисления.
1 2 3 4 5 6 7 8 9 10 11 12 |
while(1) { adc_init(); for(i=0;i<40;i++) { Value[i]=adc_value(); adcValue+=Value[i]; } adcValue=(float)adcValue/40.0; convert(adcValue); delay(100); } |
Функция adc_init() будет использоваться для инициализации АЦП.
1 2 3 4 5 6 7 |
void adc_init() { ADCON0 = 0b00000011; //select adc channel ADCON1 = 0b00001110; //select analog and digital i/p ADCON2 = 0b10001010; //eqisation time holding cap time ADON = 1; } |
Функция adc_value() будет использоваться для считывания значения с выхода АЦП и расчета значения напряжения, соответствующего ему.
1 2 3 4 5 6 7 8 |
float adc_value(void) { float adc_data=0; while(GO/DONE==1); //higher bit data start conversion adc value adc_data = (ADRESL)+(ADRESH<<8); //Store 10-bit output adc_data =((adc_data/342.0)*5.0); return adc_data; } |
Функция convert(float f) будет использоваться для преобразования значения напряжения в формат, необходимый для его вывода на семисегментный дисплей.
1 2 3 4 5 6 7 8 9 10 |
void convert(float f) { int d=(f*100); digit1=d%10; d=d/10; digit2=d%10; d=d/10; digit3=d%10; digit4=d/10; } |
Исходный код программы
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 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
#include<xc.h> //xc8 is compiler // CONFIG1H #pragma config OSC = HS // Oscillator Selection bits (HS oscillator) #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled) #pragma config IESO = OFF // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled) // CONFIG2L #pragma config PWRT = ON // Power-up Timer Enable bit (PWRT disabled) #pragma config BOREN = SBORDIS // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled)) #pragma config BORV = 3 // Brown Out Reset Voltage bits (Minimum setting) // CONFIG2H #pragma config WDT = OFF // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit)) #pragma config WDTPS = 32768 // Watchdog Timer Postscale Select bits (1:32768) // CONFIG3H #pragma config CCP2MX = PORTC // CCP2 MUX bit (CCP2 input/output is multiplexed with RB1) #pragma config PBADEN = OFF // PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset) #pragma config LPT1OSC = OFF // Low-Power Timer1 Oscillator Enable bit (Timer1 configured for higher power operation) #pragma config MCLRE = ON // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled) // CONFIG4L #pragma config STVREN = ON // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset) #pragma config LVP = OFF // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled) #pragma config XINST = OFF // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode)) // CONFIG5L #pragma config CP0 = OFF // Code Protection bit (Block 0 (000800-001FFFh) not code-protected) #pragma config CP1 = OFF // Code Protection bit (Block 1 (002000-003FFFh) not code-protected) #pragma config CP2 = OFF // Code Protection bit (Block 2 (004000-005FFFh) not code-protected) #pragma config CP3 = OFF // Code Protection bit (Block 3 (006000-007FFFh) not code-protected) // CONFIG5H #pragma config CPB = OFF // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected) #pragma config CPD = OFF // Data EEPROM Code Protection bit (Data EEPROM not code-protected) // CONFIG6L #pragma config WRT0 = OFF // Write Protection bit (Block 0 (000800-001FFFh) not write-protected) #pragma config WRT1 = OFF // Write Protection bit (Block 1 (002000-003FFFh) not write-protected) #pragma config WRT2 = OFF // Write Protection bit (Block 2 (004000-005FFFh) not write-protected) #pragma config WRT3 = OFF // Write Protection bit (Block 3 (006000-007FFFh) not write-protected) // CONFIG6H #pragma config WRTC = OFF // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected) #pragma config WRTB = OFF // Boot Block Write Protection bit (Boot block (000000-0007FFh) not write-protected) #pragma config WRTD = OFF // Data EEPROM Write Protection bit (Data EEPROM not write-protected) // CONFIG7L #pragma config EBTR0 = OFF // Table Read Protection bit (Block 0 (000800-001FFFh) not protected from table reads executed in other blocks) #pragma config EBTR1 = OFF // Table Read Protection bit (Block 1 (002000-003FFFh) not protected from table reads executed in other blocks) #pragma config EBTR2 = OFF // Table Read Protection bit (Block 2 (004000-005FFFh) not protected from table reads executed in other blocks) #pragma config EBTR3 = OFF // Table Read Protection bit (Block 3 (006000-007FFFh) not protected from table reads executed in other blocks) // CONFIG7H #pragma config EBTRB = OFF // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) not protected from table reads executed in other blocks) #define TRIS_seg1 TRISCbits.TRISC0 #define TRIS_seg2 TRISCbits.TRISC1 #define TRIS_seg3 TRISCbits.TRISC2 #define TRIS_seg4 TRISCbits.TRISC3 #define TRIS_led1 TRISAbits.TRISA2 #define TRIS_led2 TRISAbits.TRISA5 #define TRIS_led3 TRISAbits.TRISA0 #define TRIS_led4 TRISAbits.TRISA1 #define TRIS_led5 TRISAbits.TRISA4 #define TRIS_PORTB TRISB #define TRIS_adcpin TRISAbits.TRISA0 #define adcpin PORTAbits.RA0 #define TRIS_dot TRISBbits.TRISB7 #define dot PORTBbits.RB7 #define seg1 PORTCbits.RC0 #define seg2 PORTCbits.RC1 #define seg3 PORTCbits.RC2 #define seg4 PORTCbits.RC3 #define led1 PORTAbits.RA2 #define led2 PORTAbits.RA5 #define led3 PORTAbits.RA0 #define led4 PORTAbits.RA1 #define led5 PORTAbits.RA4 void delay(unsigned long Delay) { int i,j; for(i=0;i<Delay;i++) for(j=0;j<1000;j++); } void adc_init(); float adc_value(void); void convert(float); unsigned int counter2; unsigned char position = 0; unsigned char k[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; int digit1=0,digit2=0,digit3=0,digit4=0; /*************************************timer0*******************************************/ void interrupt low_priority LowIsr(void) { if(TMR0IF == 1) { counter2++; if(counter2>=1) { if(position ==0) { seg1=0; seg2=1; seg3=1; seg4=1; } if(position ==1) { seg1=1; seg2=0; seg3=1; seg4=1; } if(position==2) { dot=0; seg1=1; seg2=1; seg3=0; seg4=1; } if(position==3) { seg1=1; seg2=1; seg3=1; seg4=0; } if(position == 0) PORTB = k[digit1]; if(position == 1) PORTB = k[digit2]; if(position == 2) { PORTB = k[digit3]; PORTB&=~(0x80); } if(position == 3) PORTB = k[digit4]; position++; if(position>=4) position = 0; counter2 =0; } TMR0 =55536; TMR0IF=0; } } void main() { float adcValue=0; float Value[40]; int i=0; ADCON1 = 0b00001111; //all port is digital GIE = 1; //глобальное разрешение прерываний PEIE = 1; //разрешение прерываний от периферийных устройств T0CON = 0b000000000; //prescaler value put TMR0IE = 1; //разрешение прерываний TMR0IP = 0; //приоритет прерываний TMR0 = 55536; //начальное значение таймера TMR0ON = 1; TRIS_seg1 =0; TRIS_seg2 =0; TRIS_seg3 =0; TRIS_seg4 =0; TRIS_led1 = 0; TRIS_led2 = 0; TRIS_led3 =0; TRIS_led4 =0; TRIS_led5 =0; TRIS_PORTB = 0; TRIS_adcpin = 1; TRIS_dot = 0; while(1) { adc_init(); for(i=0;i<40;i++) { Value[i]=adc_value(); adcValue+=Value[i]; } adcValue=(float)adcValue/40.0; convert(adcValue); delay(100); } } void adc_init() { ADCON0 = 0b00000011; //выбор канала АЦП ADCON1 = 0b00001110; //select analog and digital i/p ADCON2 = 0b10001010; //eqisation time holding cap time ADON = 1; } float adc_value(void) { float adc_data=0; while(GO/DONE==1); //higher bit data start conversion adc value adc_data = (ADRESL)+(ADRESH<<8); //Store 10-bit output adc_data =((adc_data/342.0)*5.0); return adc_data; } void convert(float f) { int d=(f*100); digit1=d%10; d=d/10; digit2=d%10; d=d/10; digit3=d%10; digit4=d/10; } |