Наконец у нас есть всё необходимое: программатор, прошивальщик и компилятор. Давайте соберём наш первый прибор и напишем самую первую программу!
Схема бегущего огня на 10 светодиодов
Из документации мы находим, что выводы у нашего микроконтроллера могут поддерживать прямое подключение светодиодов, это хорошо, потому как ещё раз уменьшает нам количество деталей. Кнопка MODE в данном примере использоваться не будет, это мы рассмотрим дальше, но можете её сразу же впаять, чтобы не переделывать плату для следующего примера.
И так, наша задача: друг за другом выключать и включать светодиоды, во включённом состоянии продержать его какое-то время.
Подводные камни в контроллерах
Мы помним, что контроллер — это маленький компьютер с кучей периферии. Даже на представленной схеме видно, что каждый универсальный порт (порт ввода/вывода), в общем «ножка», может быть выходом/входом для встроенной периферии. Причём эти устройства иногда могут иметь более высокий приоритет по умолчанию, не удивляйтесь, если что-то вдруг сразу не заработает, разберём возможные проблемы на данной схеме.
Из документации к контроллеру PIC16F676 находим, что у контроллера есть встроенные компараторы и АЦП, которые имеют более высокий приоритет и будут нам мешать, даже если мы не будем их использовать. Чтобы элементарно использовать ножку контроллера просто как выход или вход (то есть в режиме 1/0), нужно отключить всех остальных конкурентов, которые тоже покушаются на эти ножки.
RA5
Как видим, эту ножку может ещё использовать модуль резонатора. Она может быть входом для тактов, может быть первой ножкой для подключения кварцевого резонатора, либо использоваться дополнительным первым таймером. Нам это всё не нужно, нам нужно чтобы эта ножка включала или выключала наш светодиод. Как тогда это всё настроить?
Самые основные настройки мы делали в «бите конфигурации». Там мы настроили, чтобы контроллер использовал внутренний генератор тактов, поэтому первый и самый главный претендент на эту ножку сразу отпадает. К большому нашему счастью, пользовательских 2 таймера по умолчанию не работают, только если они явно не настроены, поэтому ещё один претендент отпадает.
А вот АЦП (именно его наличием и отличаются 676 и 630), будет нам очень мешать, его необходимо будет отключить, делаем здесь для себя пометку.
RA0 и RA1
Самое первое их предназначение — это быть частью ICSP-интерфейса, для этого по рекомендациям мы отделили их резисторами. То есть светодиоды на этих ножках нам мешать не будут. Далее — это тоже порты АЦП модуля, но мы уже определились, что мы его отключим. И последний претендент на эти ножки — модуль компаратора. Делаем себе пометку, что нужно ещё дополнительно отключить компараторы.
RA2
Эта ножка уже по аналогу может быть частью АЦП и компаратора, для её обычного функционирования мы всё уже решили отключить заранее. Ну и так как мы не будем использовать пользовательские таймеры, за это можно тоже не переживать.
RAx и RCx различия
Дело в том, что у нашего маленького контроллера есть 2 обработчика ножек, точнее — порт A и порт C. Всё это достаточно сложная внутренняя структура микроконтроллера, но в нашем случае мы видим, что именно RA имеют намного больше возможностей. RC у нас только пытается отнимать АЦП. К тому же не всегда надо что-то будет отключать явно, иногда в зависимости от конфигурации (уже самих устройств, а не бита конфигурации) зависит будет ли что-то отключено автоматически, или всё же придётся рвать на себе волосы, потому что что-то не работает.
И так, пока мы допаиваем схему, неспеша с перекурами, обрабатываем полученную информацию, ну вот, плата допаяна, что нужно отключить — выводы сделаны, приступим..
/* * File: main.c * Author: Pti_the_Leader * * Created on 28 ?????? 2019 ?., 21:27 */ // PIC16F676 Configuration Bit Settings // 'C' source line config statements // 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 = OFF // Brown-out Detect Enable bit (BOD disabled) #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) // #pragma config statements should precede project file includes. #define _XTAL_FREQ 4000000 #include <xc.h> void main(void) { TRISA = 0b00000000; // Установим все ножки порта A как выходы TRISC = 0b00000000; // Установим все ножки порта C как выходы ANSEL = 0b00000000; // Выключаем аналоговые регистры (АЦП) CMCON = 0x07; // Отключаем компараторы PORTA = 0; // Сброс порта A (все выходы выключены) PORTC = 0; // Сброс порта C (все выходы выключены) while(1) { RA0 = 1; __delay_ms(200); RA0 = 0; RA1 = 1; __delay_ms(200); RA1 = 0; RA2 = 1; __delay_ms(200); RA2 = 0; RC0 = 1; __delay_ms(200); RC0 = 0; RC1 = 1; __delay_ms(200); RC1 = 0; RC2 = 1; __delay_ms(200); RC2 = 0; RC3 = 1; __delay_ms(200); RC3 = 0; RC4 = 1; __delay_ms(200); RC4 = 0; RC5 = 1; __delay_ms(200); RC5 = 0; RA5 = 1; __delay_ms(200); RA5 = 0; } return; }
И так, мы последовательно выключаем и включаем выходы, в результате чего создаётся эффект «Бегущих огней».
«TRISx» — это объявление, как будут использоваться выводы порта «x», в нашем случае у нас есть порты «A» и «C». Из документации находим, что на каждом порту у нас по 6 выводов, и из той же документации находим, какой бит отвечает какому выводу.
То есть наше действие
TRISA = 0b00000000;
= 0b — побитно; 0 — игнорируется; 0 — игнорируется; 0 — A5; 0 — A4; 0 — A3; 0 — A2; 0 — A1; 0 — A0;
Объявление в «TRISx» — это мы говорим контроллеру, как будут использоваться его «цифровые» ножки. Если бит = 1 — то это будет вход, если бит = 0 — то это будет выход.
Понятие «цифровой» означает, что мы либо подаём питание на эту ножку, либо снимаем.
RA0 = 1; //Подаём питание на RA0 __delay_ms(200); //Задержка 0,2 секунды RA0 = 0; // Снимаем питание с RA0 RA1 = 1; // Подаём питание на RA1
И так далее проходим все ножки микроконтроллера, на которых у нас есть светодиоды.
Так как мы используем встроенную функцию «delay», то необходимо обязательно определить «#define _XTAL_FREQ», и указать частоту в герцах. Если это не сделать, при попытке собрать прошивку мы получим ошибку. Дело в том, что компилятор изначально понятия не имеет, на какой частоте будет работать контроллер (к примеру, мы в бите конфигурации сказали, что будем использовать внутренний генератор). Но эта функция универсальная и может быть использована со всеми кодами ко всем контроллерам, поэтому необходимо в нашем примере определить, что у нас 4МГц.
void main(void)
Это именно та функция, которая в файле «main.c» будет выполнена по умолчанию. VOID — означает, что функция после выполнения ничего не возвращает (более подробно мы это рассмотрим позже), поэтому в конце у нас и стоит чисто символический «return» без ничего.
«#include <xc.h>» — это главная библиотека, которая в зависимости от контроллера, определяет его выводы и поддержку настройки битов. Все эти прелести вида «ANSEL», «CMCON» или «RAx» или «RCx» — это именно результат её работы. Заключённые в угловые скобки, типа больше-меньше, говорит о том, что это глобальная программная библиотека. «Инклуд» тоже может быть и с обычными двойными кавычками, это будет означать, что библиотека локальная, и она должна быть добавлена в проект самостоятельно. О различии всех кавычек и скобок мы тоже поговорим отдельно.
«while(1)» — это признак бесконечного цикла. То есть то, что контроллер будет выполнять пока он работает в штатном режиме. Это обычная практика обозначить основной цикл работы. Настройки в бите конфигурации могут его прервать при определённых условиях, которые имеют самый высокий приоритет.
Разница 0x и 0b
Как вы заметили, для отключения компараторов используется оглавление 0x со странным значением, в то время как 0b достаточно понятно, что это последовательность бит. Методом 0x объявляются шестнадцатеричные значения, каждый символ может быть от 0 до F. 0 понятно, что соответствует 0000, а F — 1111. Иногда это неудобно, перевести можно уже в имеющимся калькуляторе в Windows, если перевести его в режим «Программист».
То есть 0x07 = 0b00000111. Но кому как удобнее и привычнее.
P.S. область памяти микроконтроллера имеет ограниченное количество циклов записи/перезаписи. В контроллерах PIC используется достаточно качественная память, которая… в общем, если вы каждый день по 4 раза будете его перезаписывать, он умрёт через 27 лет. Не всегда необходимо действительно мучить контроллер, для отладки можно использовать симулятор или знаменитый «proteus», однако «протеус» — это платная программа.