Главная » Программирование » PIC - микроконтроллеры » Первый проект в MPLAB X – мигаем светодиодом

Первый проект в MPLAB X – мигаем светодиодом

Цикл статей – PIC начинающим или основы основ

Первый проект в MPLAB X – мигаем светодиодом или ногодрыг I/O

Долгожданное продолжение первого проекта в MPLAB X наконец-то вышло. Плата с микроконтроллером PIC18F45K20, которая идет в упомянутых в предыдущих статьях про PIC наборе DV164131, имеет на борту одну кнопку и восемь светодиодов. Вот и задействую эти элементы и для освоения буржуйской продукции.

С чего начинают изучать микроконтроллер

Одна из основных задач для микроконтроллеров рассматриваемого класса — ногодрыг. Т.е. управление различными нагрузками при помощи изменения логических состояний на выводах I/O. Ниже показана схема платы из набора DV164131:

DV164131 схема

Рис. 1. Схема демонстрационной платы из набора DV164131

Как видно, к порту PORTD подключены светодиоды через резисторы величиной 750 Ом (на схеме указаны кОм — это ошибка).

Светодиоды - включение
В подавляющем большинстве случаев светодиод необходимо подключать через токоограничивающий резистор. Резистор выбираю так, чтобы при заданном напряжении ток через светодиод был номинальным. Для обычных (индикаторных) светодиодов номинальный ток составляет обычно 3…20 мА.

Если на RD0 будет логическая единица, т.е. примерно 3,3В — светодиод DS1 будет светиться. Если на RD0 будет логический 0, т.е. примерно 0В — светодиод DS1 не будет светиться. И так аналогично для остальных светодиодов и портов RDx.

Настройка микроконтроллера PIC18F45K20

На плате отсутствуют некоторые компоненты, в частности нет кварцев и сопутствующей обвязки. Это означает, что микроконтроллер придется конфигурировать для работы от встроенного генератора. Частота ядра пусть будет максимальная, но без PLL (16 MHz).

PLL
PLL — в данном случае Phase-Locked Loop. Схема, позволяющая умножить частоту. В рассматриваемом микроконтроллере PIC18F45K20 PLL имеет множитель 4. Это означает, что задав частоту внутреннего генератора 16 MHz и включив PLL, ядро микроконтроллера будет работать на частоте 64 MHz.

Все настройки можно выполнить в основном си-файле, который уже присутствует в проекте… Но это будет неудобно, поскольку большой кусок кода, написанный раз и навсегда, будет мешаться болтаясь на самом виду. Поэтому напишем все настройки в отдельный h-файл. Создадим h-файл кликнув правой кнопкой мыши по значку «Header Files», выберем пункт «C Header Files»:

Создать новый файл в MPLAB X

Рис. 2. Создать новый файл в MPLAB X

Откроется окно:

Окно добавления нового h-файла в MPLAB X

Рис. 3. Окно добавления нового файла

Введите имя файла «config_bits». Можно указать папку для хранения h-файлов.

Организация файлов проекта
Немного об организации проекта. В нормальных рабочих проектах используется большое количество файлов, поэтому для удобства их раскидывают по нескольким папкам. Делайте как вам удобнее. Например я, все кидаю в одну папку — так мне потом проще открыть разом все c и h файлы в notepad++.

Нажмите Finish. В результате в проекте появился новый заголовочный файл. Удалите из этого файла все содержимое и скопируйте следующий код:

#ifndef CONFIG_BITS_H
#define	CONFIG_BITS_H

#ifdef	__cplusplus
extern "C" {
#endif

#ifdef	__cplusplus
}
#endif

/** C O N F I G U R AT I O N B I T S ******************************/

/* CONFIG1H */
//#pragma config FOSC = INTIO67   // Выводы генератора используются как I/O (1000)
#pragma config FOSC = INTIO7  // Выводы генератора используются для вывода тактовой частоты (деленной на 4)
//#pragma config FOSC = 0b1001 // То что и 1000, но выводы как генератор?
                                /* С помощью FOSC (3:0) настраиваются режимы:
                                XT
                                XT
                                HS
                                HSPLL
                                RC
                                RCIO
                                INTOSC
                                INTOSCIO
                                EC
                                ECIO
                        11xx = External RC oscillator, CLKOUT function on RA6
                        101x = External RC oscillator, CLKOUT function on RA6
                       1001 = Internal oscillator block, CLKOUT function on RA6, port function on RA7
                       1000 = Internal oscillator block, port function on RA6 and RA7
                        0111 = External RC oscillator, port function on RA6 // по умолчанию
                        0110 = HS oscillator, PLL enabled (Clock Frequency = 4 x FOSC1)
                        0101 = EC oscillator, port function on RA6
                        0100 = EC oscillator, CLKOUT function on RA6
                        0011 = External RC oscillator, CLKOUT function on RA6
                        0010 = HS oscillator
                        0001 = XT oscillator
                        0000 = LP oscillator
                                 */
#pragma config IESO = OFF       // режим переключения между внешним и внутрем генератором отключен
#pragma config FCMEN = OFF      // монитор контроля работы основного генератора отключен

/* CONFIG2L */
#pragma config PWRT = OFF       // таймер задежки по включению питания
#pragma config BOREN = SBORDIS  // контроль по снижению напряжения питания
#pragma config BORV = 1         // уровень сброса по снижению питания
                                /*
                                3 = VBOR set to 1.8V nominal
                                2 = VBOR set to 2.2V nominal
                                1 = VBOR set to 2.7V nominal
                                0 = VBOR set to 3.0V nominal
                                 */

/* CONFIG2H */
#pragma config WDTEN = OFF      // сторожевой таймер отключен
#pragma config WDTPS = 32768    // постделитель таймера настроена на 32768

/* CONFIG3H */
#pragma config MCLRE = ON      // 1 = MCLR pin enabled; RE3 input pin disabled
#pragma config LPT1OSC = OFF    // 1 = Таймер1 сконфигурирован для работы с низким энергопотреблением
#pragma config PBADEN = OFF     // PORTB = 1 - аналоговые входы PORTB, иначе цифровые
#pragma config CCP2MX = PORTC   // 1 = CCP2 input/output is multiplexed with RC1, 0 - multiplexed with RB3

/* CONFIG4L */
#pragma config STVREN = ON      // сброс по переполнению стека включен
#pragma config LVP = OFF        // низковольное программирование отключено, вывод используется как порт
#pragma config XINST = OFF      // расширенные инструкции отключены
#pragma config DEBUG = OFF      // режим отладчика отключен

/* CONFIG5L */
#pragma config CP0 = OFF        // Защита кода программы
#pragma config CP1 = OFF
#pragma config CP2 = OFF
#pragma config CP3 = OFF

/* CONFIG5H */
#pragma config CPB = OFF        // Защита кода загрузчика
#pragma config CPD = OFF        // Защита кода EEPROM

/* CONFIG6L */
#pragma config WRT0 = OFF       // Защита памяти от перезаписи
#pragma config WRT1 = OFF       //
#pragma config WRT2 = OFF       //
#pragma config WRT3 = OFF       //

/* CONFIG6H */
#pragma config WRTB = OFF       // Защита от перезаписи загрузчика отключена
#pragma config WRTC = OFF       // Защита от перезаписи конфигурационного регистра(300000-3000FFh) отключена
#pragma config WRTD = OFF       // Защита от перезаписи EEPROM отключена

/*CONFIG7L*/
#pragma config EBTR0 = OFF      // Защита памяти от табличного чтения
#pragma config EBTR1 = OFF
#pragma config EBTR2 = OFF
#pragma config EBTR3 = OFF

/* CONFIG7H */
#pragma config EBTRB = OFF      // Защита памяти от табличного чтения

#endif	/* CONFIG_BITS_H */

Сотрите содержимое файла main_ioled и впишите код:

#include <xc.h> // В этом проекте можно заменить строкой #include <pic18f45k20.h>
#include "config_bits.h"

/*######################################################################################################################
 * Г Л А В Н А Я   Ф У Н К Ц И Я
 */
int main(void)
{
/* Переменные. Действуют внутри функции main() */

/* Включаем кварц */
OSCCON = 0b01110001; // Internal; 16MHz;
OSCTUNEbits.PLLEN = 0; // PLL отключен

/* Настройка портов I/O */
TRISD = 0x00;// Порт D как выход
LATD = 0x00; // Гасим все светодиоды на PORTD

TRISB = 0x00000001; // RB0 как вход (для кнопки)
} // main end

Немного пояснений. В си-шном файле закомментировали файлы stdio.h и stdlib.h, поскольку они не будут использоваться в ближайшее время. Кроме прочего включили файл xc.h, который включит в проект хедер pic18f45k20.h для выбранного микроконтроллера. Строчкой #include «config_bits.h», подключили созданный нами файл config_bits.h с конфигурацией микроконтроллера.

Разные скобки в include
Отличие #include <file.h> от #include «file.h» заключается в том, что #include «file.h» ищет файл file.h сначала в папке с проектом, а потом уже в других папках; а #include <file.h> НЕ ищет файл file.h в папке с проектом. Где именно ищутся файлы определяется настройками компилятора, IDE и т.д,
Зачем нужен заголовочный файл для МК
В заголовочном файле pic18f45k20.h прописаны удобные имена и структуры для использования в проекте. Это позволяет обращаться не по адресу вроде 0x00FA, а так: PORTDbits.RD2. Не нужно изменять заголовочные файлы.

Все остальное должно быть понятно из комментариев в коде. Опять кликаем по кнопке «Clean and Build Main Project»:

Компиляция проекта

Компиляция проекта

и спустя пару секунд наблюдаем лог:

Loading code from C:/DIOD-PRO/TestPrj/io45k20.X/dist/default/production/io45k20.X.production.hex…
Loading symbols from C:/DIOD-PRO/TestPrj/io45k20.X/dist/default/production/io45k20.X.production.elf…
Loading completed

Если у вас ничего не получилось — не отчаивайтесь! Скачайте готовый проект:

Project Io45k20
Project Io45k20
project_io45k20.zip
72.3 KiB
227 Downloads
Детали

Напомню, что у меня проект располагается в папке C:\DIOD-PRO\TestPrj\.

Открыть проект в MPLAB X

Открыть проект очень просто. Для этого вызываем соответствующий диалог «File -> Open Project…»

MPLAB X - Открыть существующий проект

Открыть существующий проект

В диалоге выбираем папку с проектом, указываем проект и жмем кнопку «Open Project»:

Окно открытия проекта

Окно открытия проекта

Обратите внимание, что папки непосредственно содержащие проект, имеют свои пиктограммы. Скомпилируйте открытый проект кнопкой «Clean and Build Main Project». Все, едем дальше!

Вход, выход, защелка, подтяжка

Для управления светодиодом (транзистором, реле и т.п.) пин микроконтроллера нужно настроить как выход. Для этого необходимо отключить альтернативные функции с порта I/O и установить направление порта на выход.

TRIS — отвечает за направление порта. TRISD = 0x00 = 0b00000000 -> весь порт D как выход. TRISD = 0xFF = 0b11111111 -> весь порт D как вход. TRISD = 0x0C = 0b00001100 -> разряд D2 и D3 как вход, остальные как выход. Для управления непосредственно каким-либо разрядом порта используется TRISDbits.TRISDx = 0 или TRISDbits.TRISDx = 1.

TRIS - как запомнить
При использовании TRIS есть способ легко запомнить что 1 похоже на i (in) является входом, а 0 похоже на o (out) является выходом.

Запись в порт производится либо командой записи в порт, либо командой записи в защелку.

  • PORTDbits.RD0 = 1; // запись в порт
  • LATDbits.LATD0 = 1; // запись в защелку

Производитель рекомендует писать в защелку, т.к. это позволяет избежать некоторых неоднозначных ситуаций. Кроме прочего, если записать в защелку (LATx) когда порт настроен как вход или на альтернативную функцию, то при переключении порта на выход, на нем будет записанное в LAT значение.

При настройке порта на выход, есть еще один момент, который нужно учитывать. Зачастую, настройка может иметь варианты:

  • порт Push-pull: логическим состоянием управляют два транзистора — в верхнем и нижнем плече;
  • порт Open-drain: логическим состоянием управляет только один транзистор — в нижнем плече.

Если порт настроен на выход как Open-drain, для нормальной работы обычно требуется внешний подтягивающий резистор или внутренняя «подтяжка». Каким регистром включается внутренняя «подтяжка» — смотри в документации. Иногда, подтягивающий резистор подключен не к питанию микроконтроллера, а к питанию ядра, т.е. бывает так, что микроконтроллер питается от +3,3В, а внутренние подтягивающие резисторы на портах подключены к +1,8В питания ядра.

PIC18 — тест на скорость!

Сейчас программа в файле main_ioled.c ничего не делает: при запуске контроллера вошли в процедуру main(), затем вышли. На этом программа завершена. В реальности, в процедуру main() нужно добавить бесконечный цикл. Тогда наша программа все время будет выполняться в этом цикле. Вот код функции main():

int main(void)
{
/* Переменные. Действуют внутри функции main() */

/* Включаем кварц */
OSCCON = 0b01110001; // Internal; 16MHz;
OSCTUNEbits.PLLEN = 0; // PLL отключен

/* Настройка портов I/O */
TRISD = 0x00;// Порт D как выход
LATD = 0x00; // Гасим все светодиоды на PORTD

TRISB = 0x00000001; // RB0 как вход (для кнопки)
/* цикл */
while(1) // Поскольку условие цикла всегда верно, то и цикл получается бесконечным
  {
  // тут пишем дальнейший код
  } // while(1) end
} // main end

Теперь сделаем несколько тестов — чтобы посмотреть на скорость переключения портов. Если у вас нет осциллографа — нет и смысла делать этот тест, тогда просто примите результаты к сведению.

***
while(1) // Поскольку условие цикла всегда верно, то и цикл получается бесконечным
  {
  LATDbits.LATD0 = !PORTDbits.RD0; // 161кГц
  } // while(1) end
***

*** Звездочки обозначают, что там есть код. Копировать их не нужно.

В такой конструкции порт D0 переключается 322 тысячи раз в секунду. Если строчку «LATDbits.LATD0 = !PORTDbits.RD0;» заменит на «LATDbits.LATD0 = !LATDbits.LATD0;» или на «PORTDbits.RD0 = !PORTDbits.RD0;» результат не изменится.

Подключите PICkit 3 (или что-то еще подходящее) к плате, подайте на плату питание 3,3В, нажмите кнопку на панели инструментов:

Прошиваем из MPLAB X

Прошиваем из MPLAB X

MPLAB X немного ругнется, сделайте как на картинке:

Что-нить да сгорит при неправильных настройках...

Что-нить да сгорит при неправильных настройках…

Если в окне логов весь процесс закончился строчками

Programming…
Programming/Verify complete

значит все в порядке. Один светодиод должен светиться. Если не светится нужно проверить наличие перемычки JP1.

Продолжаем 🙂

Заменим строчку в цикле на строки:

***
  LATDbits.LATD0 = 0; // 1МГц
  LATDbits.LATD0 = 1;
***

В этом случае отсутствует операция инвертирования (!), код выполняется быстрее — что-то похожее на меандр частотой 1MHz.

Еще пара тестов на скорость работы с портами I/O:
1)

***
  LATDbits.LATD0 = 0; // 803кГц
  LATDbits.LATD0 = 1; // Длительность импульса 250нс
  LATDbits.LATD0 = 0;
***

2)

***
  LATDbits.LATD0 = 1; // 803кГц
  LATDbits.LATD0 = 0; // Длительность импульса 250нс
  LATDbits.LATD0 = 1;
***

Результаты в комментариях к коду.

Применение функции Delay

В примерах выше светодиод хоть и мигал, но на такой частоте, что рассмотреть его мигание невозможно. Простейший способ замедлить мигание — применить функцию Delay(). Эта функция практически всегда строится на цикле с переменной, которая определяет количество итераций цикла. Рассмотрим вариант с подключаемой функцией. Для этого в файл main_ioled.c нужно добавить строку

***
#include "delays.h"
***

и добавить описанным выше способом файл delays.h с таким содержимым:

#ifndef __DELAYS_H
#define __DELAYS_H

/* C18 cycle-count delay routines. */

/* Delay of exactly 1 Tcy */
#define Delay1TCY() _delay(1)

/* Delay of exactly 10 Tcy */
#define Delay10TCY() _delay(10)

/* Delay1TCYx */
void Delay1TCYx(unsigned char);

/* Delay10TCYx
 * Delay multiples of 10 Tcy
 * Passing 0 (zero) results in a delay of 2560 cycles.
 */
void Delay10TCYx(unsigned char);

/* Delay100TCYx
 * Delay multiples of 100 Tcy
 * Passing 0 (zero) results in a delay of 25,600 cycles.
 */
void Delay100TCYx(unsigned char);

/* Delay1KTCYx
 * Delay multiples of 1000 Tcy
 * Passing 0 (zero) results in a delay of 256,000 cycles.
 */
void Delay1KTCYx(unsigned char);

/* Delay10KTCYx
 * Delay multiples of 10,000 Tcy
 * Passing 0 (zero) results in a delay of 2,560,000 cycles.
 */
void Delay10KTCYx(unsigned char);

#endif

Обратите внимание, что в delays.h определены сразу несколько вариантов функций задержки с разной дискретностью задержки, а в комментариях даны подсказки по ограничениям этих функций. Итак — еще немного экспериментов.

Заменим содержимое цикла на строки:

***
  LATDbits.LATD0 = 1;
  Delay1TCY();  // На частоте 16МГц дает примерно 500нс
  LATDbits.LATD0 = 0;
***

Компилируем, прошиваем, смотрим осциллографом.

Опять заменим содержимое цикла:

***
  LATDbits.LATD0 = 1;
  Delay10TCY();  // На частоте 16МГц дает примерно 2700нс (2,7мкс)
  LATDbits.LATD0 = 0;
***

Еще один эксперимент:

***
  LATDbits.LATD0 = 1;
  Delay1TCYx(1);  // На частоте 16МГц дает примерно 3мкс
  LATDbits.LATD0 = 0;
***

И еще один:

***
  LATDbits.LATD0 = 1;
  Delay1TCYx(10);  // На частоте 16МГц дает примерно 12,5мкс
  LATDbits.LATD0 = 0;
***

еще один

***
  LATDbits.LATD0 = 1;
  Delay1TCYx(30);  // На частоте 16МГц дает примерно 32мкс
  LATDbits.LATD0 = 0;
***

еще один

***
  LATDbits.LATD0 = 1;
  Delay10KTCYx(50);  // На частоте 16МГц дает примерно 120мс
  LATDbits.LATD0 = 0;
***

Да, хотя и применялась задержка, светодиод мигает слишком быстро и глаз воспринимает это просто как свечение светодиода. Видно только осциллографом =)

Видимое глазом мигание

Наступило самое интересное — видимое глазом мигание! Замените в цикле строки на следующие:

***
  Delay10KTCYx(200); // При такой задержке светодиод мигает 1 раз в секунду
  LATDbits.LATD0 = !PORTDbits.RD0; // т.е. 0,5сек выключен; 0,5сек включен.
***

А теперь не просто мигание, а целый спецэффект! Называется двоичный счетчик. Код:

***
  Delay10KTCYx(20); // При такой задержке светодиод на младшем разряде мигает 10 раз в секунду
  LATD = PORTD + 1;
***

А пояснения даны ниже на рисунке

График двоичного счетчика

Двоичный счетчик

И опять я выкладываю готовый проект для этой программы

Двоичный счетчик
Двоичный счетчик
project_io45k20_2.zip
76.7 KiB
147 Downloads
Детали

Закрыть MPLAB X, удалить имеющуюся папку «io45k20.X», распаковать архив в C:\DIOD-PRO\TestPrj.

 Эффекты

Дальше рассказаны тайные знания, перед которыми меркнут поделки китайцев на поприще новогодних гирлянд 🙂

Для реализации эффектов потребуется применение переменных.

ЭФФЕКТ — СВЕТОДИОДНАЯ ЛИНЕЙКА

В цикл вставляем содержимое:

***
  /* ЭФФЕКТ - СВЕТОДИОДНАЯ ЛИНЕЙКА */
  /* Будет использоваться переменная i для подсчета шагов нашего эффекта */
  i++; // Увеличили шаг
  Delay10KTCYx(100);
  if (i == 1) LATDbits.LATD0 = 1; // Зажигаем светодиод на порте D0
  if (i == 2) LATDbits.LATD1 = 1; // Зажигаем светодиод на порте D1
  if (i == 3) LATDbits.LATD2 = 1; // Зажигаем светодиод на порте D2
  if (i == 4) LATDbits.LATD3 = 1; // Зажигаем светодиод на порте D3
  if (i == 5) LATDbits.LATD4 = 1; // Зажигаем светодиод на порте D4
  if (i == 6) LATDbits.LATD5 = 1; // Зажигаем светодиод на порте D5
  if (i == 7) LATDbits.LATD6 = 1; // Зажигаем светодиод на порте D6
  if (i == 8) LATDbits.LATD7 = 1; // Зажигаем светодиод на порте D7
  if (i == 9)
  {
    LATD = 0; // Гасим ранее зажженные светодиоды
    i = 0; // Обнуляем счетчик шагов бегущего огня
  }
***

Нужно объявить переменную, там где строка /* Переменные. Действуют внутри функции main() */

***
/* Переменные. Действуют внутри функции main() */
unsigned char i = 0;
***

ЭФФЕКТ — БЕГУЩИЙ ОГОНЬ

В цикл вставляем содержимое:

***
  /* ЭФФЕКТ - БЕГУЩИЙ ОГОНЬ */
  /* Будет использоваться переменная i для подсцета шагов нашего эффекта
     Для получения эффекта поочередно зажигающихся светодиодов достаточно
     гасить ранее зажженные светодиоды перед зажиганием следующего */
  i++; // Увеличили шаг
  Delay10KTCYx(100);
  LATD = 0; // Гасим ранее зажженные светодиоды
  if (i == 1) LATDbits.LATD0 = 1; // Зажигаем светодиод на порте D0
  if (i == 2) LATDbits.LATD1 = 1; // Зажигаем светодиод на порте D1
  if (i == 3) LATDbits.LATD2 = 1; // Зажигаем светодиод на порте D2
  if (i == 4) LATDbits.LATD3 = 1; // Зажигаем светодиод на порте D3
  if (i == 5) LATDbits.LATD4 = 1; // Зажигаем светодиод на порте D4
  if (i == 6) LATDbits.LATD5 = 1; // Зажигаем светодиод на порте D5
  if (i == 7) LATDbits.LATD6 = 1; // Зажигаем светодиод на порте D6
  if (i == 8)
  {
    LATDbits.LATD7 = 1; // Зажигаем светодиод на порте D7
    i = 0; // Обнуляем счетчик шагов бегущего огня
  }
***

Или еще один вариант реализации — на битовых сдвигах

***
  // При помощи сдвигов
  if (PORTD == 0b00000000)
    {
    Delay10KTCYx(100);
    LATDbits.LATD0 = 1;
    }
  Delay10KTCYx(100);
  LATD = PORTD << 1;
***

Так намного короче, а результат тот же =)

ЭФФЕКТ — БЕГУЩИЙ ОГОНЬ В РАЗНЫЕ СТОРОНЫ ПООЧЕРЕДНО

***
  // переменная i определяет направление движения
  if (PORTD <= 0b00000001)
    {
    i = 1;
    LATDbits.LATD0 = 0b00000001;
    }
  if (PORTD == 0b10000000) i = 0;
  Delay10KTCYx(100);
  if (i) {LATD = PORTD << 1;} else {LATD = PORTD >> 1;};
***

ЭФФЕКТ — СВЕТОДИОДНАЯ ЛИНЕЙКА ОТ СЕРЕДИНЫ

***
  i++; // Увеличили шаг
  Delay10KTCYx(100);
  if (i == 1) LATD = 0b00011000;
  if (i == 2) LATD = 0b00111100;
  if (i == 3) LATD = 0b01111110;
  if (i == 4) LATD = 0b11111111;
  if (i == 5) LATD = 0b01111110;
  if (i == 6) {LATD = 0b00111100; i = 0;}
***

ЭФФЕКТ — СВЕТОДИОДНАЯ ЛИНЕЙКА ОТ СЕРЕДИНЫ ПООЧЕРЕДНО

***
    /* ЭФФЕКТ - СВЕТОДИОДНАЯ ЛИНЕЙКА ОТ СЕРЕДИНЫ ПООЧЕРЕДНО */
  i++; // Увеличили шаг
  Delay10KTCYx(75);
  if (i == 1) LATD = 0b00001000;
  if (i == 2) LATD = 0b00001100;
  if (i == 3) LATD = 0b00001110;
  if (i == 4) LATD = 0b00001111;
  if (i == 5) LATD = 0b00001110;
  if (i == 6) LATD = 0b00001100;
  if (i == 7) LATD = 0b00001000;
  if (i == 8) LATD = 0b00010000;
  if (i == 9) LATD = 0b00110000;
  if (i ==10) LATD = 0b01110000;
  if (i ==11) LATD = 0b11110000;
  if (i ==12) LATD = 0b01110000;
  if (i ==13) LATD = 0b00110000;
  if (i ==14){LATD = 0b00010000; i = 0;}
***

Всему голова — яркость!

Нет, как показал последний опрос, китайцев еще не переплюнули. Для этого не хватает управления яркостью 🙂 Восполним этот пробел…

Яркость через светодиод можно регулировать либо изменяя силу тока, либо пропуская номинальный ток, но разное время за период, т.е. меняя скважность:

ШИМ - регулировка

ШИМ — регулировка

Итак, внутри цикла вставляем код:

***
	/* УПРАВЛЯЕМ ЯРКОСТЬЮ СВЕЧЕНИЯ */
  /* Когда нет возможности регулировать ток через светодиоды
   * используют например ШИМ (широтно-импульсная манипуляция).
   * Если на светодиод все время поступает логическая единица,
   * светодиод светится на 100%. Если на светодиод подавать меандр,
   * т.е. менять 1 и 0 с равной длительностью, светодиод светится на 50%.
   *   _         _         _
   * _| |_______| |_______| |_ Слабое свечение
   *   ______   ______   _____
   * _|      |_|      |_|      Сильное свечение
   *
   * Частоту на светодиодах нужно выбирать такой,
   * чтобы не было мерцания, например 60Гц.
   */
  /* СВЕТОДИОД НА 50% ЯРКОСТИ */
  LATDbits.LATD0 = 1; // Пусть светится на полную, будем с ним сравнивать свечение других
  LATDbits.LATD1 = 1;
  Delay100TCYx(127);  // Дает 3,25мс
  LATDbits.LATD1 = 0;
  Delay100TCYx(127);  // Дает 3,25мс
  /* В примере частота мерцания светодиода на порту D1 составит 153Гц.
   * На глаз незаметно. Зато заметно, что свечение светодиода на D1
   * меньше, чем на у светодиода на D0.
   */
***

Уменьшим яркость:

***
  /* СВЕТОДИОД НА 10% ЯРКОСТИ */
  /*LATDbits.LATD0 = 1; // Пусть светится на полную, будем с ним сравнивать свечение других
  LATDbits.LATD1 = 1;
  Delay100TCYx(25);
  LATDbits.LATD1 = 0;
  Delay100TCYx(229); */
  /* Частота мерцания не изменилась, но за счет того, что 1 на светодиод подветсяне пол периода
   * как в примере выше, а всего одну десятую периода, светодиод на D1 светит совсем слабо.
   */
***

ЭФФЕКТ — ПЛАВНОЕ УВЕЛИЧЕНИЕ ЯРКОСТИ СВЕТОДИОДА

Для плавного изменения яркости необходимо использовать в функции Delay() изменяющуюся переменную:

***
	/* ЭФФЕКТ - ПЛАВНОЕ УВЕЛИЧЕНИЕ ЯРКОСТИ СВЕТОДИОДА */
  i = i + 1; // Переменная i указывает коэффициент заполнения ШИМ
  if (i == 255) i = 1;

  LATDbits.LATD0 = 1; // Пусть светится на полную, будем с ним сравнивать свечение других
  LATDbits.LATD1 = 1;
  Delay100TCYx(i);
  LATDbits.LATD1 = 0;
  Delay100TCYx(255 - i);
***

Либо пишем такой код и получаем плавное увеличение, затем плавное уменьшение яркости. И так в цикле:

***
	/* ЭФФЕКТ - ПЛАВНОЕ УВЕЛИЧЕНИЕ и УМЕНЬШЕНИЕ ЯРКОСТИ */

    if (way)// Переменная way определяет уменьшать или увеличивать яркость (заполнение ШИМ)
      {
        i = i + 1; // Переменная i указывает коэффициент заполнения ШИМ
        if (i==254) way = 0;
      }else
      {
        i = i - 1;
        if (i==1) way = 1;
      }

  LATDbits.LATD0 = 1; // Пусть светится на полную, будем с ним сравнивать свечение других
  LATDbits.LATD1 = 1;
  Delay100TCYx(i);
  LATDbits.LATD1 = 0;
  Delay100TCYx(255 - i);
***

С одним светодиодом скучно? Тогда задействуем весь порт!

***
	/* ЭФФЕКТ - ПЛАВНОЕ ИЗМЕНЕНИЕ ЯРКОСТИ ВСЕХ СВЕТОДИОДОВ */

    if (way)// Переменная way определяет уменьшать или увеличивать яркость (заполнение ШИМ)
      {
        i = i + 1; // Переменная i указывает коэффициент заполнения ШИМ
        if (i>=254) way = 0;
      }else
      {
        i = i - 1;
        if (i<=1) way = 1;
      }

  LATD = 0b11110000;
  Delay100TCYx(i);
  LATD = 0b00001111;
  Delay100TCYx(255 - i);
***

Для этого эффекта я также выложил готовый проект

Эффект для PORTD
Эффект для PORTD
project_io45k20_3.zip
79.2 KiB
116 Downloads
Детали

А как же кнопка?!

Выше порты рассматривались как выход. Теперь рассмотрим их и как вход. Для этого на плате имеется одна кнопка. Схема включения кнопки:

Схема включения кнопки на плате

Схема включения кнопки на плате

Простейший пример:

***
/* ЗАЖЕЧЬ СВЕТОДИОД ПРИ НАЖАТИИ НА КНОПКУ */
  /* Тут просто присваивается инвертированное значение порта RB0 порту RD0 до тех пор, пока кнопка нажата */
  LATDbits.LATD0 = !PORTBbits.RB0;
***

Пример посложнее: Включаем двоичный счетчик, пока кнопка удерживается:

***
  /* ДВОИЧНЫЙ СЧЕТЧИК ПРИ НАЖАТИИ НА КНОПКУ */
  /* Тут значение порта RB0 используется в операторе if */

  if (PORTBbits.RB0) // Кнопка НЕ нажата - гасим светодиоды
    {
    LATD = 0;
    }else // Кнопка нажата - считаем
    {
    Delay10KTCYx(20);
    LATD = PORTD + 1;
    }
***

После прошивки этого кода, светодиоды не светятся. Чтобы начался счет, нужно нажать и удерживать кнопку. Попробуйте! 🙂

Готовый проект для этого эффекта:

Эффект на кнопке
Эффект на кнопке
project_io45k20_4.zip
79.0 KiB
156 Downloads
Детали
*.hex
Обращаю внимание, что в папке с проектом (..\TestPrj\io45k20.X\dist\default\production) лежит готовый *.hex файл. Его можно прошить не запуская MPLAB X.

Вопросы, пожелания, замечания, предложения — все в комментариях 🙂

Метки::

6 Отзывы Ваш отзыв

  1. Олег #

    Спасибо автору: статья помогает освоить семейство м/к pic18.

  2. Олег #

    Поправка: там где код для кнопки в 3-ей строке не «инфертированое», а «инвертированное».

  3. Сергей #

    Спасибо! Долго искал хоть что то полезное. Главное понятно. Сразу все получилось и стало на свои места. Можно прочитать горы книг…а тут все доходчиво.

  4. Михаил #

    Подскажите пожалуйста, как рассчитываются задержки, в данной программе

    • Функции типа Delay10KTCYx(200) это по сути цикл. В данном случае это цикл на 10000 итераций и выполняется этот цикл 200 раз: 10 000*200 = 2 000 000 итераций. Рассматриваемый контроллер выполняет большинство команд за 4 такта, поэтому на выполнение «Delay10KTCYx(200)» уйдет 2 000 000 * 4 = 8 000 000 тактов тактового генератора. Контроллер настроен на частоту 16 МГц, соответственно 8 000 000 / 16 000 000 = 0,5 секунды. Проверено осциллографом ))

Ваш отзыв