Arduino по-взрослому: Си и иже с ним. Первая программа. Порты ввода-вывода

ВИШ RosCanSat Junior. Лекция 2.

Препроцессор, функции/процедуры, цикл while, задержка, DDR, PORT, PIN, необходимые логические операции.

Часть информации, рассказанной в видеолекции содержится в статье 1.

Теория

Каждый микроконтроллер имеет некоторое количество портов ввода-вывода. Их функционал, назначение и состояние определяется некоторыми регистрами. Вообще, все в микроконтроллерах определяется регистрами, и в этом нет ничего страшного. Все выводы объединены в группы по 8 штук. В нашем случае, это группы B, С и D. Для взаимодействия с портами используются регистры DDR, PORT и PIN. Соответственно, DDRB для группы B, DDRC для группы C и так далее.

Схема внутреннего подключения порта ввода-вывода МК AVR

У регистра DDRx очень простая роль – он определяет, что есть выход. То есть, если мы подаем логическую единицу на 0-й бит регистра DDRD, то ножка PD0 будет выходом, а если логический нольвходом.

В регистре PINx хранится состояние тех ножек, которые являются входами. То есть, если на ножку PD1, которая осталась входом, подадим извне логическую единицу, то в регистре PIND первый бит будет единицей.

Самый интересный регистр – регистр PORTx. С первого взгляда покажется, что у него самая простая функция – устанавливать логический уровень. Собственно, это так и есть, пока Вы управляете ножкой, которая настроена как выход. Ну а если же в регистре DDRx установлен ноль, и ножка является входом, то регистр управляет включением и отключением подтягивающего резистора к плюсу питания. Следовательно, пока нога «висит в воздухе», на ней может быть установлена логическая единица, ну а если что-то более сильное «прижмет» линию к нулю, то наш микроконтроллер не будет сопротивляться и логический уровень линии будет равен нулю.

Более полно и достаточно понятно теория рассказана в Лекции 2 (вводная МК) ВИШ Юниор.

Практика на языке Си

В программировании считается, что каждый, кто хочет стать программистом, должен в первую очередь написать программу, которая выводит на экран надпись «Hello World». В аналоговой электронике вместо этого принято собирать схему симметричного мультивибратора. В цифровой технике своеобразным «хелловорлдом»  значится так называемый проект «Blink» — мигающий светодиод. Что же, прежде чем написать в своём резюме «Инженер-разработчик робототехнических систем с применением искусственного интеллекта», давайте сделаем этот самый Блинк.

#define F_CPU 16000000 //Программу желательно начинать с определения константы F_CPU - тактовой частоты

#include <avr/io.h> //Библиотека ввода-вывода
#include <util/delay.h> //Библиотека задержек

//Тут можно объявлять переменные

int main(void) //Начало основного алгоритма
{

while(1) //Основной цикл
{

}
}

Строки, начинающиеся на # — команды препроцессора. Эти команды подготавливают текст программы к компиляции. Пользовательские константы препроцессора, объявленные однажды и используемые один или несколько раз подставляются в нужные места, выполняется проверка условий препроцессора. То есть, фрагмент кода


#define MY_CONSTATNT 5

...

_delay_ms(MY_CONSTANT);

 

#if 0

do_something();

#endif

 

#if 1

do_another_algotithm();

#endif

преобразуется в


_delay_ms(5);

 

do_another_algotithm();

Строка #define F_CPU 16000000 определяет константу F_CPU, равную 16 000 000 (Гц) — тактовая частота микроконтроллера на большинстве плат Arduino. Эта константа важна в расчетах задержек и частот.

Строки #include <avr/io.h> и #include <util/delay.h> подключают соответствующие файлы библиотек, в которых описаны некоторые функции.

int main(void) — объявление процедуры. Строка объявления процедуры строится так: тип_возвращаемого_значения название_процедуры (тип_аргумента_1 значение_аргумента_1) { }. Одной процедуре можно передавать один, несколько аргументов, или не передавать вообще. Процедура может возвращать одно значение или не возвращать вовсе. Если процедура не принимает аргументов, в скобках ставится ключевое слово void — ничего. Если процедура ничего не возвращает, то перед названием ставится ключевое слово void — ничего. Внутри фигурных скобок записываются команды, из которых состоит процедура.

Конструкцию while(условие) { команды } можно перевести на русский язык как «Пока_выполняется (условие) {выполнять команды}«.

Теперь напишем программу, которая будет попросту мигать светодиодом:

#define F_CPU 16000000 //Программу желательно начинать с определения константы F_CPU - тактовой частоты

#include <avr/io.h> //Библиотека ввода-вывода
#include <util/delay.h> //Библиотека задержек

//Тут можно объявлять переменные

int main(void) //Начало основного алгоритма
{

DDRB |= 0b00100000;

while(1) //Основной цикл
{

PORTB |= (1<<5);
_delay_ms(1000);
PORTB &=~ (1<<5);
_delay_ms(1000);

}
}

Видно, что в начале основного алгоритма я установил бит 5 регистра DDRB в 1.

Затем в бесконечном цикле я что-то делаю с регистром PORTB, затем жду 1000 мс (1 с), затем снова что-то с регистром PORTB, и опять 1000 мс. И дальше по кругу. Да, функция _delay_ms() обеспечивает задержку выполнения на указанное количество миллисекунд. Также существует похожая функция _delay_us() — задержка в микросекундах.

Что же именно я делаю с регистром PORTB? Конструкция |= означает «логическое ИЛИ с присваиванием«. По-русски можно сказать так: если определенный бит левого числа (операнда) равен нулю, а этот же бит в правом — единица, то в левом тоже поставить единицу, сохраняя все остальные биты. Конструкция &=~ означает «логическое ИЛИ-НЕ с присваиванием«. По-русски можно сказать так: если определенный бит левого числа (операнда) равен единице, этот же бит в правом — единица, то в левом поставить ноль, сохраняя все остальные биты. Конструкция (1<<5) читается как «единица, сдвинутая влево на пять«. Конечно, имеется в виду двоичный сдвиг. Также существует конструкция ^= . Эта конструкция инвертирует биты в соответствии с правым операндом в качестве маски и сохраняет значение в регистр.  Покажу нагляднее на примерах:

x = 0b01011010;          //х равен 0b01011010
x |= 0b00000011;  //х станет равен 0b01011011
x &=~ 0b00000011; //х станет равен 0b01011001
x ^= 0b00001100;  //х станет равен 0b01010101

x = (1<<3); //х равен 0b00001000
x = (1<<5)|(1<<3)|(1<<2); //х равен 0b00101100

Тот же пример можно записать короче:

#define F_CPU 16000000 //Программу желательно начинать с определения константы F_CPU - тактовой частоты

#include <avr/io.h> //Библиотека ввода-вывода
#include <util/delay.h> //Библиотека задержек

//Тут можно объявлять переменные

int main(void) //Начало основного алгоритма
{

DDRB |= 0b00100000;

while(1) //Основной цикл
{

PORTB ^= (1<<5);
_delay_ms(1000);

}
}

Учебные проекты

В конце лекции я хочу предложить несколько проектов с использованием информации, приведенной в этом и предыдущем уроках.

  • Подключи светодиод к выводу PB0 и измени программу из урока для работы со светодиодом на PB0.
  • Подключи два светодиода к разным выводам и напиши программу для поочерёдного мигания обоими
  • Подключи три светодиода к разным портам и напиши программу «Светофор», которая будет поочередно переключать светодиоды, аналогично автодорожному светофору.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

*

code