Напомню схему, по которой мы собирали первые «Бегущие огни».

Теперь мы постараемся максимально использовать возможности контроллера и компилятора. Как раз мы будем использовать кнопку «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()» позволяет зажигать и гасить любой светодиод. Можете попробовать самостоятельно придумать другие варианты «бегущих огней» и самостоятельно их реализовать.

Добавить комментарий

Включите изображения, чтобы увидеть вопрос *