Мы продолжаем серию наших обучающих статей по программированию микроконтроллеров PIC с помощью программ MPLAB и XC8. В предыдущих статьях на нашем сайте мы уже рассмотрели работу с таймерами в микроконтроллерах PIC, подключение к ним ЖК дисплея и т.д. В этой же статье мы рассмотрим подключение к микроконтроллеру PIC семисегментного индикатора (дисплея).
В большинстве случае ЖК дисплей является более универсальным устройством отображения информации чем семисегментный индикатор (дисплей), однако в некоторых проектах использование семисегментного индикатора выглядит все таки более удобным. Он выигрывает у ЖК дисплея в размере отображаемых символов, лучше различим при ярком свете и имеет большие углы обзора.
Ранее на нашем сайте мы рассматривали использование семисегментного индикатора в следующих проектах:
- счетчик 0-99 на микроконтроллере AVR ATmega32;
- подключение семисегментного дисплея к Arduino Uno;
- часы на Arduino и 4-х разрядном семисегментном индикаторе;
- подключение семисегментного индикатора к Raspberry Pi.
Семисегментный индикатор и 4-х разрядный семисегментный индикатор
Семисегментный индикатор (7 Segment Display) имеет семь сегментов, в каждом из которых расположен светодиод. Зажигая различные сегменты данного индикатора можно отображать на нем различные буквы и цифры. Например, чтобы отобразить на нем цифру «5», необходимо подать на сегменты a, f, g, c и d уровень high. Существуют два типа семисегментных индикаторов: с общим катодом (Common Cathode) и общим анодом (Common Anode). В нашем проекте мы будем использовать семисегментный индикатор с общим катодом.
Структурная схема семисегментного индикатора показана на следующем рисунке.
Теперь мы знаем как отобразить одну цифру или букву на семисегментном индикаторе. Но что делать если нам нужно отобразить несколько букв/цифр? Ответ прост – необходимо использовать многоразрядный семисегментный индикатор, в данном случае мы будем использовать 4-х разрядный семисегментный индикатор, внешний вид которого показан на следующем рисунке, схема которого представлена на следующем рисунке.
Как можно видеть из представленного рисунка, данный индикатор представляет собой 4 семисегментных индикатора, соединенных вместе. Если их соединить просто, то такой семисегментный индикатор имел бы 40 контактов, что сделало бы проблематичным его подключение к большинству современных микроконтроллеров, поэтому мы купили (можно также изготовить его самостоятельно) готовый модуль 4-х разрядного семисегментного индикатора, схема которого показана выше. Он содержит всего 12 контактов.
Для понимания принципов работы 4-х разрядного семисегментного индикатора необходимо внимательно посмотреть на его схему, приведенную выше. На ней видно, что контакты A (также как и контакты B, C….) всех четырех индикаторов соединены вместе. То есть если подать уровень high на контакт A, то должны загореться сегменты A всех четырех индикаторов?
Но этого не происходит поскольку в схему добавлены дополнительные 4 контакта D0, D1, D2 и D3, которые используются для управления всеми 4 индикаторами. Например, если нам нужно зажечь сегменты только на втором индикаторе, то на контакт D1 необходимо подать уровень high, а на остальные контакты (D0, D2 и D3) необходимо подать уровень low. Аналогичным образом мы можем управлять остальными индикатора в составе 4-х разрядного семисегментного дисплея.
Схема проекта
Схема подключения 4-х разрядного семисегментного индикатора к микроконтроллеру PIC16F877A представлена на следующем рисунке.
У модуля 4-х разрядного семисегментного индикатора мы имеем 12 выходных контактов, 8 из которых используются для отображения символов, а остальные 4 – для выбора разряда индикатора. 8 контактов для отображения символов подключены к контактам порта PORTD микроконтроллера PIC, а 4 управляющих контакта – к контактам порта PORTC микроконтроллера.
Примечание: контакт Ground (общий провод/земля) модуля индикатора должен быть подключен к контакту ground микроконтроллера.
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Для тестирования работы проекта мы будем инкрементировать переменную от 0 до 1000 и выводить ее значение на 4-х разрядный семисегментный индикатор. Запустите программу MPLABX, создайте в ней новый проект и произведите настройку в ней битов конфигурации (фьюзов) микроконтроллера. Более подробно вопросы настройки этих битов рассмотрены в данной статье.
1 2 3 4 5 6 7 8 |
#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) |
Далее укажем контакты, с которых мы будем управлять выбором разряда семисегментного индикатора. В нашем случае это будут контакты RC0, RC1, RC2 и RC3 – мы объявим их под именами s1, s2, s3 и s4 соответственно.
1 2 3 4 5 6 |
//***Define the signal pins of all four displays***// #define s1 RC0 #define s2 RC1 #define s3 RC2 #define s4 RC3 //***End of definition**//// |
Затем, внутри функции void main(), инициализируем необходимые переменные.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int i = 0; //the 4-digit value that is to be displayed int flag =0; //for creating delay unsigned int a,b,c,d,e,f,g,h; //just variables unsigned int seg[]={0X3F, //Hex value to display the number 0 0X06, //Hex value to display the number 1 0X5B, //Hex value to display the number 2 0X4F, //Hex value to display the number 3 0X66, //Hex value to display the number 4 0X6D, //Hex value to display the number 5 0X7C, //Hex value to display the number 6 0X07, //Hex value to display the number 7 0X7F, //Hex value to display the number 8 0X6F //Hex value to display the number 9 }; //End of Array for displaying numbers from 0 to 9 |
В нашем случае переменные i и flag используются для хранения отображаемого на дисплее значения и задержки соответственно. Переменные целого типа с a до h используются для разбиения 4-х разрядного числа на отдельные цифры.
Для отображения отдельных цифр от 0 до 9 на разрядах семисегментного индикатора у нас будет использоваться массив «seg[]«. Адрес массива начинается с нуля, в массиве у нас будут храниться числа от 0 до 9 в шестнадцатеричном формате, индекс элемента массива будет соответствовать отображаемой на индикаторе цифре.
seg[0] | seg[1] | seg[2] | seg[3] | seg[4] | seg[5] | seg[6] | seg[7] | seg[8] | seg[9] |
0X3F | 0X06 | 0X5B | 0X4F | 0X66 | 0X6D | 0X7C | 0X07 | 0X7F | 0X6F |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
То есть если на семисегментном индикаторе нам необходимо отобразить цифру 0, нам необходимо вызвать seg[0], а если цифру 6 – то seg[6].
Принцип формирования шестнадцатеричного значения, необходимого для отображения на семисегментном индикаторе нужной нам цифры, показан в следующей таблице.
Далее в коде программы зададим начальную конфигурацию (значения) используемых портов микроконтроллера. Все используемые нами контакты ввода/вывода в наше проекте будут работать в режиме вывода данных.
1 2 3 4 5 6 |
//*****I/O Configuration****// TRISC=0X00; PORTC=0X00; TRISD=0x00; PORTD=0X00; //***End of I/O configuration**/// |
Далее, в бесконечном цикле программы (while(1)) мы будем разделять значение переменной «i» на 4 отдельные цифры и отображать их на семисегментном индикаторе. В следующем фрагменте кода представлено разделение значения переменной «i» на отдельные цифры – для этого мы используем операции деления и остатка от деления.
1 2 3 4 5 6 7 8 9 10 |
//***Splitting "i" into four digits***// a=i%10;//4th digit is saved here b=i/10; c=b%10;//3rd digit is saved here d=b/10; e=d%10; //2nd digit is saved here f=d/10; g=f%10; //1st digit is saved here h=f/10; //***End of splitting***// |
Предположим, что значение переменной «i» равно 4578. Тогда в конце процесса разделения получим следующие значения переменных: g=4, e=5, c=7 и a=8. После этого мы можем отобразить полученные отдельные цифры на разрядах нашего семисегментного индикатора.
1 2 3 4 5 6 7 8 |
PORTD=seg[g];s1=1; //Turn ON display 1 and print 4th digit __delay_ms(5);s1=0; //Turn OFF display 1 after 5ms delay PORTD=seg[e];s2=1; //Turn ON display 2 and print 3rd digit __delay_ms(5);s2=0; //Turn OFF display 2 after 5ms delay PORTD=seg[c];s3=1; //Turn ON display 3 and print 2nd digit __delay_ms(5);s3=0; //Turn OFF display 3 after 5ms delay PORTD=seg[a];s4=1; //Turn ON display 4 and print 1st digit __delay_ms(5);s4=0; //Turn OFF display 4 after 5ms delay |
Но в отдельный момент времени одновременно на всех 4-х разрядах семисегментного индикатора мы можем отобразить только одинаковые цифры (поскольку информационные контакты у всех разрядов общие). Так каким же образом мы можем отобразить на всех 4-х разрядах семисегментного индикатора разные цифры?
Принцип здесь достаточно прост и он заключается в том, что человеческий глаз не может различать быстрые мерцания чего либо, а наш микроконтроллер работает гораздо быстрее чем человеческий глаз. Если мы будем отображать различные значения в разрядах семисегментного индикатора с высокой частотой, то человеческий глаз будет воспринимать эти цифры непрерывно горящими. В нашем случае мы будем использовать задержку в 5ms для переключения между различными разрядами семисегментного индикатора – человеческий глаз не будет замечать такой маленькой задержки между переключениями.
Задержку в 5ms мы будем организовывать с помощью цикла как показано в следующем фрагменте кода.
1 2 3 4 5 |
if(flag>=100) //wait till flag reaches 100 { i++;flag=0; //only if flag is hundred "i" will be incremented } flag++; //increment flag for each flash |
Тестирование работы проекта
Вначале смоделируем работу нашей схемы в симуляторе Proteus. Схема нашего проекта в данном симуляторе представлена на следующем рисунке.
Если в симуляторе все заработало можно подключить к микроконтроллеру PIC программатор PicKit 3, записать в него программу проекта и протестировать работу схемы на «реальном» железе.
Исходный код программы
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 |
// CONFIG #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) // #pragma config statements should precede project file includes. // Use project enums instead of #define for ON and OFF. #include <xc.h> //***Define the signal pins of all four displays***// #define s1 RC0 #define s2 RC1 #define s3 RC2 #define s4 RC3 //***End of definition**//// void main() { unsigned int a,b,c,d,e,f,g,h; //just variables int i = 0; //в этой переменной мы будем хранить 4-х значное число, которое мы будем отображать на индикаторе int flag =0; //for creating delay unsigned int seg[]={0X3F, //шестнадцатеричное значение для отображения цифры 0 0X06, // шестнадцатеричное значение для отображения цифры 1 0X5B, // шестнадцатеричное значение для отображения цифры 2 0X4F, // шестнадцатеричное значение для отображения цифры 3 0X66, // шестнадцатеричное значение для отображения цифры 4 0X6D, // шестнадцатеричное значение для отображения цифры 5 0X7C, // шестнадцатеричное значение для отображения цифры 6 0X07, // шестнадцатеричное значение для отображения цифры 7 0X7F, // шестнадцатеричное значение для отображения цифры 8 0X6F // шестнадцатеричное значение для отображения цифры 9 }; //End of Array for displaying numbers from 0 to 9 //*****конфигурация портов ввода/вывода****// TRISC=0X00; PORTC=0X00; TRISD=0x00; PORTD=0X00; //***End of I/O configuration**/// #define _XTAL_FREQ 20000000 while(1) { //***разделяем значение переменной "i" на 4 отдельные цифры***// a=i%10;//4th digit is saved here b=i/10; c=b%10;//3rd digit is saved here d=b/10; e=d%10; //2nd digit is saved here f=d/10; g=f%10; //1st digit is saved here h=f/10; //***End of splitting***// PORTD=seg[g];s1=1; //включаем разряд 1 и выводим на него 4-ю цифру __delay_ms(5);s1=0; // выключаем разряд 1 после задержки в 5ms PORTD=seg[e];s2=1; //Turn ON display 2 and print 3rd digit __delay_ms(5);s2=0; //Turn OFF display 2 after 5ms delay PORTD=seg[c];s3=1; //Turn ON display 3 and print 2nd digit __delay_ms(5);s3=0; //Turn OFF display 3 after 5ms delay PORTD=seg[a];s4=1; //Turn ON display 4 and print 1st digit __delay_ms(5);s4=0; //Turn OFF display 4 after 5ms delay if(flag>=100) //ждем пока значение flag достигнет 100 { i++;flag=0; //только если flag равен 100 производим инкрементирование переменной "i" } flag++; //increment flag for each flash } } |