Напомню схему, по которой мы собирали первые «Бегущие огни».
Теперь мы постараемся максимально использовать возможности контроллера и компилятора. Как раз мы будем использовать кнопку «Mode».
В предыдущем примере мы изучали подводные камни и как включать и выключать светодиоды. Вся наша «программа» просто циклично включала и выключала ножки с задержкой.
Давайте попробуем этот код сделать одновременно и проще, и сложнее. почему и сложно и просто…
Создадим дополнительную функцию перед «main» с таким содержанием:
void set_led(unsigned char led, unsigned char state) { switch (led) { case 1 : RA0 = state; break; case 2 : RA1 = state; break; case 3 : RA2 = state; break; case 4 : RC0 = state; break; case 5 : RC1 = state; break; case 6 : RC2 = state; break; case 7 : RC3 = state; break; case 8 : RC4 = state; break; case 9 : RC5 = state; break; case 10 : RA5 = state; break; } return; }
Эта функция на данный момент делает наш код сложнее, но одновременно и проще. У нас на схеме каждый светодиод пронумерован, поэтому нам по итогу становится немного проще, теперь мы определили, на какой ножке живёт наш светодиод и по его номеру мы можем устанавливать его состояние, либо включить, либо выключить.
То есть предыдущие
RA0 = 0; RA1 = 1; __delay_ms(200); RA1 = 0; RA2 = 1;
уже превращается в
set_led(0, 0); set_led(1, 1); __delay_ms(200); set_led(1, 0); set_led(2, 1);
Соглашусь, что на данном этапе — это сомнительное преимущество. Ну однако в итоге у нас появилась универсальная функция, которая позволяет согласно схеме включать и выключать светодиоды в зависимости их числа, не думая, на какой ножке они расположены, за это отвечает наша новая функция «set_led».
Вот это как раз тот случай, когда стало и проще и сложнее одновременно. Но, чем мы будем идти дальше, тем нам будет проще.
Например, мы теперь сможем переписать нашу главную функцию:
void main(void) { TRISA = 0b00000000; // Установим порт A на выход TRISC = 0b00000000; // Установим порт C на выход ANSEL = 0b00000000; // Отключаем АЦП CMCON = 0x07; // Отключаем компаратор PORTA = 0; // Сброс порта A PORTC = 0; // Сброс порта C unsigned char current_led = 1; unsigned char previous_led = 10; while(1) { set_led(current_led, 1); set_led(previous_led, 0); previous_led = current_led; current_led++; if (current_led > 10) current_led = 1; __delay_ms(200); } return; }
Согласитесь, что основной код стал более понятным. Мы последовательно перебираем светодиоды, включаем новый и выключаем прошлый. «current_led++;» означает, что мы увеличиваем на 1 текущий светодиод. Естественно, так не может продолжаться бесконечно, поэтому если это значение стало больше 10, нам необходимо вернуть всё к первому светодиоду, что мы и делаем «if (current_led > 10) current_led = 1;». На выходе мы получаем точно такую же «Бегущие огни», но мы этим заложили потенциал на будущее.
Изменим направление
Вот теперь давайте будем использовать наши старания и поменяем направление, сейчас у нас светодиоды «бегают» только в одну сторону, а пусть теперь будут бегать в другую…
void main(void) { TRISA = 0b00000000; // Установим порт A на выход TRISC = 0b00000000; // Установим порт C на выход ANSEL = 0b00000000; // Отключаем АЦП CMCON = 0x07; // Отключаем компаратор PORTA = 0; // Сброс порта A PORTC = 0; // Сброс порта C unsigned char current_led = 1; unsigned char previous_led = 10; while(1) { set_led(current_led, 1); set_led(previous_led, 0); previous_led = current_led; current_led--; if (current_led < 1) current_led = 10; __delay_ms(200); } return; }
После этого все светодиоды начнут бегать от последнего к первому. На данный момент у нас разница вообще незначительная, потому что мы облегчили себе жизнь новой функцией. Теперь текущий светодиод идёт на убывание, а при достижении минимального значения назначается самый последний, то есть под номером 10.
Дополнительная функция работает! Сколько бы времени потребовалось переписывать первый пример, последовательно меняя ножки… Но вот она, сила программирования и новой функции.
Давайте теперь соединим обе возможности в одно, добавим новую переменную, и с изменением только её одной мы будем менять направление движения
void main(void) { TRISA = 0b00000000; // Установим порт A на выход TRISC = 0b00000000; // Установим порт C на выход ANSEL = 0b00000000; // Отключаем АЦП CMCON = 0x07; // Отключаем компаратор PORTA = 0; // Сброс порта A PORTC = 0; // Сброс порта C unsigned char led_direction = 0; unsigned char current_led = 1; unsigned char previous_led = 10; while(1) { set_led(current_led, 1); set_led(previous_led, 0); previous_led = current_led; if (led_direction == 0) { current_led++; if (current_led > 10) current_led = 1; } else { current_led--; if (current_led < 1) current_led = 10; } __delay_ms(200); } return; }
У нас появилась новая переменная «unsigned char led_direction = 0;», которую мы можем менять и легко менять направление движения бегущих огней. Теперь логично возникает вопрос, а можно ли это делать, к примеру при нажатии кнопки.. И да, именно к этому мы и подошли. У нас есть для этого кнопка «Mode». Но для этого нам необходимо будет добавить ещё одну функцию и скорректировать «TRIS».
Давайте заменим почти весь главный код «main.c»:
#include <xc.h> unsigned char led_direction = 0; void set_led(unsigned char led, unsigned char state) { switch (led) { case 1 : RA0 = state; break; case 2 : RA1 = state; break; case 3 : RA2 = state; break; case 4 : RC0 = state; break; case 5 : RC1 = state; break; case 6 : RC2 = state; break; case 7 : RC3 = state; break; case 8 : RC4 = state; break; case 9 : RC5 = state; break; case 10 : RA5 = state; break; } return; } void delay_listner() { unsigned char i; for (i = 0; i < 10 ; i++) { if (RA4 == 1) { // Кнопка нажата PORTA = 0; // Сброс порта A, то есть выключаем все светодиоды PORTC = 0; // Сброс порта C, то есть выключаем все светодиоды while (RA4 == 1) {} // Ждём, пока кнопку отпустят if (led_direction == 0) { led_direction = 1; } else { led_direction = 0; } } __delay_ms(20); } return; } void main(void) { TRISA = 0b00010000; // Установим порт A на выход, A4 вход TRISC = 0b00000000; // Установим порт C на выход ANSEL = 0b00000000; // Отключаем АЦП CMCON = 0x07; // Отключаем компаратор PORTA = 0; // Сброс порта A PORTC = 0; // Сброс порта C unsigned char current_led = 1; unsigned char previous_led = 10; while(1) { set_led(current_led, 1); set_led(previous_led, 0); previous_led = current_led; if (led_direction == 0) { current_led++; if (current_led > 10) current_led = 1; } else { current_led--; if (current_led < 1) current_led = 10; } delay_listner(); } return; }
И так, мы добавили новую функцию и скорректировали порт «A», теперь тот вход, на котором распаяна кнопка «Mode», является входом. Новая функция «void delay_listner()» не только создаёт необходимую задержку для переключения светодиодов, но ещё и слушает нашу кнопку. Мы распаяли кнопку так, что в не нажатом состоянии ножка контроллера подтянута на «минус» через резистор 10 кОм, то есть «логический ноль», поэтому при нажатии на кнопку на ножке появляется «логическая единица» (напряжение питания контроллера) и мы можем отследить это событие.
К слову, у контроллера уже есть встроенная защита от дребезга контактов, всё что нас просят в документации, чтобы ножка не висела в воздухе если кнопка не нажата.
Что ж, мы поигрались переключением направление бега «бегущих огней», как это можно сделать ещё лучше? Ну, к примеру, можно сделать, чтобы контроллер запоминал направление движения и при включении огни бежали в прошлом направлении. Да, это можно сделать, для этого есть так называемый EEPROM, некое подобие флешки на компьютере. Из документации мы видим, что у нашего контроллера EEPROM составляет 128 байт, не мало. Но мы будем хранить только один, наше направление бега огней.
Для работы с EEPROM уже есть готовые «родные» функции, и выдумывать велосипед не надо:
eeprom_write([адрес],[значение]) — запись данных;
eeprom_read([адрес]) — возвращает значение по указанному адресу.
Давайте сделаем финальную версию наших бегущих огней:
// CONFIG #pragma config FOSC = INTRCIO // Oscillator Selection bits (INTOSC oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config MCLRE = ON // RA3/MCLR pin function select (RA3/MCLR pin function is MCLR) #pragma config BOREN = ON // Brown-out Detect Enable bit (BOD enabled) #pragma config CP = OFF // Code Protection bit (Program Memory code protection is disabled) #pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled) #define _XTAL_FREQ 4000000 #include <xc.h> unsigned char led_direction = 0; void set_led(unsigned char led, unsigned char state) { switch (led) { case 1 : RA0 = state; break; case 2 : RA1 = state; break; case 3 : RA2 = state; break; case 4 : RC0 = state; break; case 5 : RC1 = state; break; case 6 : RC2 = state; break; case 7 : RC3 = state; break; case 8 : RC4 = state; break; case 9 : RC5 = state; break; case 10 : RA5 = state; break; } return; } void delay_listner() { unsigned char i; for (i = 0; i < 10 ; i++) { if (RA4 == 1) { // Кнопка нажата PORTA = 0; // Сброс порта A, то есть выключаем все светодиоды PORTC = 0; // Сброс порта C, то есть выключаем все светодиоды while (RA4 == 1) {} // Ждём, пока кнопку отпустят if (led_direction == 0) { led_direction = 1; } else { led_direction = 0; } eeprom_write(0,led_direction); //Запишем направление в EEPROM по адресу 0 } __delay_ms(20); } return; } void main(void) { TRISA = 0b00010000; // Установим порт A на выход, A4 вход TRISC = 0b00000000; // Установим порт C на выход ANSEL = 0b00000000; // Отключаем АЦП CMCON = 0x07; // Отключаем компаратор PORTA = 0; // Сброс порта A PORTC = 0; // Сброс порта C led_direction = eeprom_read(0); //Прочитаем значение из EEPROM по адресу 0 unsigned char current_led = 1; unsigned char previous_led = 10; while(1) { set_led(current_led, 1); set_led(previous_led, 0); previous_led = current_led; if (led_direction == 0) { current_led++; if (current_led > 10) current_led = 1; } else { current_led--; if (current_led < 1) current_led = 10; } delay_listner(); } return; }
Всё готово. Единственный момент — при первом включении у нас нет этого значения, поэтому направление движения может не соответствовать ожиданиям. При прошивке контроллера можно отредактировать данные в PICPgm на вкладке «Data Mem»:
Вот как раз наш адрес «0» со значением «01», то есть направление в обратную сторону. Если указать «00» — то направление движения будет в обычную сторону. как вы заметили, в один адрес можно сохранить в шестнадцатеричном виде от «00» до «FF», то есть значение от «00000000» до «11111111», то есть 8 бит или 1 байт, но мы используем только один бит, больше нам и не надо.
Наша функция «void set_led()» позволяет зажигать и гасить любой светодиод. Можете попробовать самостоятельно придумать другие варианты «бегущих огней» и самостоятельно их реализовать.