Команда и программы ардуино

В прошлой статье мы разобрали устройство платы Arduino, научились подключать ее к компьютеру и загрузили свой первый скетч! Называется он Blink и отвечает за мигание встроенного светодиода в плату с определенной частотой. Если вы начинающий, и только начали изучать мир Arduino, то вам некоторые функции в коде могут быть непонятны. Поэтому давайте разберем все по порядку. Структура кода в Arduino IDE – начинаем!

Программный код состоит из двух обязательных частей – функций. Первая часть называется void setup() (в переводе на русский – установка) и в ней прописывается код, который отработает всего один раз. Во второй обязательной функции void loop() прописывается код, работающий в бесконечном цикле. Сюда прописывается то, что будет происходить циклично (то есть с определенной частотой в определенный промежуток времени). Еще перед этими двумя функциями, то есть в самом начале кода, иногда прописываются различные директивы или переменные, которые будут необходимы для дальнейшей работы кода. Они будут постоянны на протяжении всего кода (это различные библиотеки, введенные названия устройств и т.д.)

Если перенестись на минутку в реальный мир, то эту структуру легко можно представить на простых вещах. Например, вы включаете свет в комнате, когда темно. Для этого, вы нажали на выключатель и лампочка загорелась. Также и в коде: сначала мы задаем постоянные данные, которые нам необходимы, то есть прописываем, что существует некое устройство (лампочка), прописываем для нее низкой значение

Таким образом, структура кода будет всегда одна и та же, вы ее сейчас видите ниже (от нее уже и нужно будет отталкиваться в дальнейшем, в зависимости от того, что вы хотите сделать и запрограммировать)

В нашем первом коде Blink есть некая функция PinMode(), а также delay() и digitalWrite()

Разберемся с каждой из них в отдельности.

В нашем коде встроенный светодиод мигает с частотой в одну секунду. Он подключается к выводу 13. В прошлом статье мы уже говорили, что на плате Arduino имеются выводы для подключения различных устройств и периферии – они делятся на цифровые и аналоговые. По отдельности их мы разберём чуть позднее, а пока скажем, что у каждого вывода есть номер.

На плате Arduino Uno вверху расположены выводы, пронумерованные от 0 до 13. Это цифровые выводы, к ним подключают обычно различные светодиоды. Однако, вывод номер 13 отвечает за работу встроенного светодиода на плате и служит, чаще всего, для проверки ее работоспособности. Указав в скетче номер вывода, мы можем работать с устройством, которое подключено к нему.

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

Функция PinMode(pin, mode); состоит из двух значений. Это pin — номер вывода (например у нас светодиод подключен к выводу 13) и mode — режим входа/выхода (INPUT/OUTPUT)

void loop()
<
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
>

На этом наш второй урок по программированию на Arduino подходит к концу. Сегодня мы познакомились со структурой кода на примере Blink, узнали о новых функциях и как их использовать, а впереди нас ожидает еще много всего интересного и познавательного. Всем удачной компиляции и отличного настроения!

Данная статья — является собственностью интернет-магазина РОБОТОТЕХНИКА

Источник: robototehnika.ru

Как выполнять параллельные задачи (Threads) в программе для Arduino

Print Friendly, PDF </li><li>1 светодиод (вот из такого набора, например);</li><li>1 пьезопищалка (вроде этой);</li><li>соединительные провода (рекомендую вот такой набор);</li><li>макетная плата (breadboard);</li><li>персональный компьютер со средой разработки Arduino IDE.</li></ul><p><mark><strong>Ардуино уроки программирование для начинающих с 9 лет [с нуля] #ДомаВместе</strong></mark></p><p><iframe width=

Arduino для начинающих. Начало работы

Инструкция по созданию параллельных потоков в программе для Arduino

1 Схема подключения для демонстрации потоков в работе с Arduino

Вообще говоря, Arduino не поддерживает настоящее распараллеливание задач, или мультипоточность. Но можно при каждом повторении цикла loop() указать микроконтроллеру проверять, не наступило ли время выполнить некую дополнительную, фоновую задачу. При этом пользователю будет казаться, что несколько задач выполняются одновременно.

Например, давайте будем мигать светодиодом с заданной частотой и параллельно этому издавать нарастающие и затихающие подобно сирене звуки из пьезоизлучателя. И светодиод, и пьезоизлучатель мы уже не раз подключали к Arduino. Соберём схему, как показано на рисунке.

Если вы подключаете светодиод к цифровому выводу, отличному от «13», не забывайте о токоограничивающем резисторе примерно на 220 Ом.

Схема подключения к Arduino для демонстрации параллельных потоков

2 Управление светодиодом и пьезоизлучателемс помощью оператора delay()

Напишем вот такой скетч и загрузим его в Ардуино.

const int soundPin = 3; /* объявляем переменную с номером пина, на который подключён пьезоэлемент */ const int ledPin = 13; // объявляем переменную с номером пина светодиода void setup() pinMode(soundPin, OUTPUT); // объявляем пин 3 как выход. pinMode(ledPin, OUTPUT); // объявляем пин 13 как выход. > void loop() // Управление звуком: tone(soundPin, 700); // издаём звук на частоте 700 Гц delay(200); tone(soundPin, 500); // на частоте 500 Гц delay(200); tone(soundPin, 300); // на частоте 300 Гц delay(200); tone(soundPin, 200); // на частоте 200 Гц delay(200); // Управление светодиодом: digitalWrite(ledPin, HIGH); // зажигаем delay(200); digitalWrite(ledPin, LOW); // гасим delay(200); >

После включения видно, что скетч выполняется не совсем так как нам нужно: пока полностью не отработает сирена, светодиод не мигнёт, а мы бы хотели, чтобы светодиод мигал во время звучания сирены. В чём же здесь проблема?

Дело в том, что обычным образом эту задачу не решить. Задачи выполняются микроконтроллером строго последовательно. Оператор delay() задерживает выполнение программы на указанный промежуток времени, и пока это время не истечёт, следующие команды программы не будут выполняться. Из-за этого мы не можем задать разную длительность выполнения для каждой задачи в цикле loop() программы. Поэтому нужно как-то сымитировать многозадачность.

3 Параллельные процессы без оператора «delay()»

Вариант, при котором Arduino будет выполнять задачи псевдо-параллельно, предложен разработчиками Ардуино. Суть метода в том, что при каждом повторении цикла loop() мы проверяем, настало ли время мигать светодиодом (выполнять фоновую задачу) или нет. И если настало, то инвертируем состояние светодиода. Это своеобразный вариант обхода оператора delay().

const int soundPin = 3; // переменная с номером пина пьезоэлемента const int ledPin = 13; // переменная с номером пина светодиода const long ledInterval = 200; // интервал мигания светодиодом, мсек. int ledState = LOW; // начальное состояние светодиода unsigned long previousMillis = 0; // храним время предыдущего срабатывания светодиода void setup() pinMode(soundPin, OUTPUT); // задаём пин 3 как выход. pinMode(ledPin, OUTPUT); // задаём пин 13 как выход. > void loop() // Управление звуком: tone(soundPin, 700); delay(200); tone(soundPin, 500); delay(200); tone(soundPin, 300); delay(200); tone(soundPin, 200); delay(200); // Мигание светодиодом: // время с момента включения Arduino, мсек: unsigned long currentMillis = millis(); // Если время мигать пришло, if (currentMillis — previousMillis >= ledInterval) < previousMillis = currentMillis; // то запоминаем текущее время if (ledState == LOW) < // и инвертируем состояние светодиода ledState = HIGH; >else < ledState = LOW; >digitalWrite(ledPin, ledState); // переключаем состояние светодиода > >

Читайте также:
Flipaclip похожие программы на ПК

Существенным недостатком данного метода является то, что участок кода перед блоком управления светодиодом должен выполняться быстрее, чем интервал времени мигания светодиода «ledInterval». В противном случае мигание будет происходить реже, чем нужно, и эффекта параллельного выполнения задач мы не получим. В частности, в нашем скетче длительность изменения звука сирены составляет 200+200+200+200 = 800 мсек, а интервал мигания светодиодом мы задали 200 мсек. Но светодиод будет мигать с периодом 800 мсек, что в 4 раза больше того, что мы задали.

Вообще, если в коде используется оператор delay(), в таком случае трудно сымитировать псевдо-параллельность, поэтому желательно его избегать.

В данном случае нужно было бы для блока управления звуком сирены также проверять, пришло время или нет, а не использовать delay(). Но это бы увеличило количество кода и ухудшило читаемость программы.

4 Использование библиотеки ArduinoThreadдля создания параллельных потоков

Чтобы решить поставленную задачу, воспользуемся замечательной библиотекой ArduinoThread, которая позволяет с лёгкостью создавать псевдо-параллельные процессы. Она работает похожим образом, но позволяет не писать код по проверке времени – нужно выполнять задачу в этом цикле или не нужно. Благодаря этому сокращается объём кода и улучшается читаемость скетча. Давайте проверим библиотеку в действии.

Библиотека ArduinoThread

Первым делом скачаем с официального сайта архив библиотеки и разархивируем его в директорию libraries/ среды разработки Arduino IDE. Затем переименуем папку ArduinoThread-master в ArduinoThread.

Схема подключений останется прежней. Изменится лишь код программы.

#include // подключение библиотеки ArduinoThread const int soundPin = 3; // переменная с номером пина пьезоэлемента const int ledPin = 13; // переменная с номером пина светодиода Thread ledThread = Thread(); // создаём поток управления светодиодом Thread soundThread = Thread(); // создаём поток управления сиреной void setup() pinMode(soundPin, OUTPUT); // объявляем пин 3 как выход. pinMode(ledPin, OUTPUT); // объявляем пин 13 как выход. ledThread.onRun(ledBlink); // назначаем потоку задачу ledThread.setInterval(1000); // задаём интервал срабатывания, мсек soundThread.onRun(sound); // назначаем потоку задачу soundThread.setInterval(20); // задаём интервал срабатывания, мсек > void loop() // Проверим, пришло ли время переключиться светодиоду: if (ledThread.shouldRun()) ledThread.run(); // запускаем поток // Проверим, пришло ли время сменить тональность сирены: if (soundThread.shouldRun()) soundThread.run(); // запускаем поток > // Поток светодиода: void ledBlink() static bool ledStatus = false; // состояние светодиода Вкл/Выкл ledStatus = !ledStatus; // инвертируем состояние digitalWrite(ledPin, ledStatus); // включаем/выключаем светодиод > // Поток сирены: void sound() static int ton = 100; // тональность звука, Гц tone(soundPin, ton); // включаем сирену на «ton» Гц if (ton else < // по достижении 500 Гц ton = 100; // сбрасываем тональность до 100 Гц >>

В программе мы создаём два потока – ledThread и soundThread, каждый выполняет свою операцию: один мигает светодиодом, второй управляет звуком сирены.

В каждой итерации цикла для каждого потока проверяем, пришло ли время его выполнения или нет. Если пришло – он запускается на исполнение с помощью метода run(). Главное – не использовать оператор delay(). В коде даны более подробные пояснения.

Параллельное выполнение потоков на Arduino

Загрузим код в память Ардуино, запустим. Теперь всё работает в точности так, как надо!

Последнее изменениеПятница, 01 Ноябрь 2019 19:29 Прочитано 87741 раз

Поблагодарить автора:

Поделиться

Print Friendly, PDF enabledНапример:
if (что-то) myThread.enabled = true;
>
else myThread.enabled = false;
>
Возможно стоит подумать в этом направлении.

Леонид Кукленко 26.01.2020 03:12 Комментировать Здравствуйте. Скопировал ваш пример — сам по себе он работает хорошо. Заменил рабочие части обоих новых loop на свои (по отдельности они работают отлично) и программа выводит в монитор порта только информацию из участка setup. Есть ли какие-то ограничения на использование каких либо функций в новых loop (delay используется один раз)? И если кто-то сталкивался, подскажите, пожалуйста, как это решить.

aave1 26.01.2020 16:17 Комментировать Леонид, возможно в вашем скетче есть какой-то участок кода, из которого программа никогда не выходит. Без самого кода трудно сказать точнее. Можете разместить здесь хотя бы псевдокод участка, который не срабатывает?

Леонид Кукленко 26.01.2020 17:55 Комментировать Здравствуйте, aave1. Вот один из void к примеру, Обычный вольтметр, в цикле идет усреднение за период времени. Остальные части как в примере
int Uphre = A0;
float Uanalog = 0;
float Udigital[Kolichestvo_izmereniy];
float Uavgdigital = 0;
int i = 0;

void Dannie()
for(i=0; i

Леонид Кукленко 26.01.2020 17:57 Комментировать Kolichestvo_izmereniy; i++) Uanalog = analogRead(Uphre);
Udigital[i] = Uanalog*5.0/1024.0;
Uavgdigital += Udigital[i];
delay(Vremya_izmereniya_min*60000/Kolichestvo_izmereniy);
>
Serial.println(Uavgdigital/Kolichestvo_izmereniy);
Uavgdigital = 0;
>

Леонид Кукленко 26.01.2020 23:32 Комментировать Извините за беспокойство, ошибка была в разделе setup, действительно цикл мешал, спасибо за наводку

aave1 27.01.2020 18:03 Комментировать Рад, что вы разобрались! Я ещё не успел вникнуть в вашу проблему.

Сергей 05.07.2020 04:39 Комментировать понятия потока тут недопустимо, даже таймеры грузят МК, это вам не СТМ32, у которого конвейер.

aave1 06.07.2020 19:43 Комментировать Сергей, конечно, это не поток в классическом понимании, но тем не менее. Даже библиотека называетсяThreads.

Олег 14.10.2020 07:31 Комментировать Друзья, с отцом сварили ворота для двора дома, в электронике соображаю хорошо а вот с программированием все плохо, подскажите мне скетч, который можно использовать в моём случае
1.есть ворота и один двигатель переменного тока на 220 вольт.
2.есть релюхи для реверса двигателя.
3.есть симисторы и оптотранзисторы для управления симисторами
4. есть ардуино UNO
5. есть 4 датчика холла

Антон4ик 24.01.2021 19:37 Комментировать всем привет!
Хочу сразу представиться — я дилетант-любитель, которому стало интересна тема с ардуино. Поставил себе задачу сделать умную теплицу для начала. Как обычно бывает, хочется чтобы она могла всё из одной коробочки. На первом этапе хочу создать одновременную работу реле от датчика температуры и реле от датчика температуры. Столкнулся потребностью в многозадачности на ардуино.
Почитав тут и на Гидхабе про эту библиотеку, я создал скетч (точнее на что меня хватило), но что-то не работает. может подскажите что не так в скетче? Или обратить внимание на железо? Буду очень благодарен в помощи. P.S. Скетч прилагается:

#include «DHT.h» // подключение библиотеки DHT
#include «Thread.h» // подключение библиотеки ArduinoThread

long vlaga;
Thread vlagaThread = Thread(); // создаём поток управления датчиком движения

long Temperatura;
Thread TemperaturaThread = Thread(); // создаём поток управления градусником

void setup()
<
Serial.begin(9600);
vlaga = map(analogRead(A0),0,1023,0,100);

void loop()
Serial.println((String(«показания») + String(map(analogRead(A0),0,1023,0,100)) + String(» градусы») + String(dht_4.readTemperature())));
// Проверим, пришло ли время переключиться по земле:
if (vlagaThread.shouldRun())
vlagaThread.run(); // запускаем поток

// Проверим, пришло ли время температуры воздуха:
if (TemperaturaThread.shouldRun())
TemperaturaThread.run(); // запускаем поток
>

// Поток датчика земли:
void vlagaZemli()
<
if (vlaga = 30) digitalWrite(5,HIGH);
>
else digitalWrite(5,LOW);
>
>

aave1 24.01.2021 21:00 Комментировать Антон4ик, добрый день! Попробуйте в функции vlagaZemli() заменить «=» на «==». А также в функции loop() уберите вывод в монитор порта «Serial.println», т.к. там ошибка, или замените на
Serial.println(«показания» + (String)map(analogRead(A0),0,1023,0,100) + » градусы» + (String)dht_4.readTemperature());

Anton4ik 31.01.2021 07:22 Комментировать aave1,
Я воспользовался Вашими советами и сделал как рекомендовали, но у меня так и не переключается реле при изменении значения теипературы.
если будет возможность, подскажите пожалуйста, что не так со скетчем? Буду очень благодарен
P.s. не рубите с плеча сырое дарование

#include «DHT.h»
#include «Thread.h» // подключение библиотеки ArduinoThread
#include «Adafruit_Sensor.h»

long vlaga;
Thread vlagaThread = Thread(); // создаём поток управления датчиком движения

long Temperatura;
Thread TemperaturaThread = Thread(); // создаём поток управления градусником

void setup()
Serial.begin(9600);
vlaga = map(analogRead(A0),0,1023,0,100);

void loop()
Serial.println((String(«показания») + String(map(analogRead(A0),0,1023,0,100)) + String(» градусы») + String(dht_4.readTemperature())));
// Проверим, пришло ли время переключиться по земле:
if (vlagaThread.shouldRun())
vlagaThread.run(); // запускаем поток

// Проверим, пришло ли время температуры воздуха:
if (TemperaturaThread.shouldRun())
TemperaturaThread.run(); // запускаем поток
>

// Поток датчика температуры:
void TemperaturaVozduha() <
if (Temperatura >= 27) digitalWrite(7,1);
>
else digitalWrite(7,0);
>
>

// Поток датчика земли:
void vlagaZemli() <
if (vlaga >= 50) digitalWrite(3,LOW);
>
else digitalWrite(3,HIGH);
>
>

Читайте также:
Где посмотреть в 1 с версию программы

aave1 01.02.2021 08:39 Комментировать Anton4ik! Тут есть несколько ошибок.
1) задавать pinMode() не надо, т.к. влага и температура это не пины (ножки на плате), а просто переменные, которые хранят измеренные значения.
3) Самое главное — вы не назначаете потокам задачу. Вы создаёте поток и запускаете, но он не знает, что делать. Чтобы указать ему задачу, нужно добавить vlagaThread.onRun(vlagaZemli). Аналогично нужно дать задачу и потоку измерения температуры.

MikiBot 09.12.2021 11:40 Комментировать Недостатки замечательной библиотеки ArduinoThread:
Если остановить поток (thread.enabled = false;),
а потом, в нужный момент запустить (thread.enabled = true;),
то он НЕ начнется сначала, может тут же и закончится,
что не соответствует вашим ожиданиям.
Время изменить тоже не позволяет.

aave1 09.12.2021 18:31 Комментировать Спасибо за дополнение!

Игорь 16.02.2022 08:20 Комментировать Добрый день!
Многопоточность со светодиодами и звуковыми излучателями, это хорошо и просто.
У меня вопрос, как осуществить при такой многопоточности сравнение сигналов низкого уровня на двух и более входных портах от кнопок, чтобы при этом отрабатывали выходные порты один, два или три?

AlexeyHolik 06.07.2022 12:51 Комментировать Я хотел сделать часы на Ардуино. Я попытался воспользоваться вашей библиотекой Thread. Нихрена не работает. И вообще сколько бы я не устанавливал разных библиотек для разных скетчей, никогда ничего не работает. Похоже, я слишком тупой.

aave1 06.07.2022 19:08 Комментировать Алексей, ну зачем же вы так. Возможно из-за большого количества библиотек и бессистемного подхода ничего не получается. Попробуйте взяться за какую-нибудь одну и довести дело до конца. Например, эта библиотека точно проверена.

Но ещё дело может быть в том, что сами библиотеки постоянно обновляются, и статья, написанная несколько месяцев назад, уже может содержать устаревшие данные. К сожалению, это так. Но в статьях описан сам принцип и подход к данным вопросам. Просто наберитесь терпения, и всё у вас получится!

Источник: soltau.ru

В Arduino по-хардкорному, часть 2. Продолжаем программировать для ATmega 2560 на примере сигнализации

В прош­лой статье мы крат­ко прош­лись по архи­тек­туре и инс­тру­мен­там раз­работ­ки для ATmega 2560, написа­ли прос­тень­кую прог­рам­мку и даже про­шили ее в кон­трол­лер. Но, как ты понима­ешь, Hello world — это толь­ко цве­точ­ки, поп­робую угос­тить тебя ягод­ками. Сегод­ня в меню: пре­рыва­ния, работа с EEPROM, работа с UART и дис­крет­ными вхо­дами.

warning

Ре­дак­ция и автор не несут ответс­твен­ности за воз­можный при­чинен­ный вред здо­ровью и иму­щес­тву при несоб­людении тех­ники безопас­ности работы с элек­троп­рибора­ми.

Постановка задачи

Ме­сяц назад мы сде­лали малень­кий main, который зас­тавлял весело мор­гать све­тоди­од на пла­те. В этот раз мы пой­дем даль­ше, наш про­ект будет соот­ветс­тво­вать сле­дующим тре­бова­ниям:

  • ус­трой­ство дол­жно иметь два режима инди­кации: режим ожи­дания и «ава­рий­ный»;
  • ус­трой­ство дол­жно перехо­дить в ава­рий­ный режим, если зам­кну­лись опре­делен­ные кон­такты на пла­те, и воз­вра­щать­ся обратно при их раз­мыкании;
  • поль­зователь дол­жен иметь воз­можность нас­тро­ить инди­кацию по сво­ему вку­су.

Так как сухая пос­танов­ка задачи скуч­на, мы при­дума­ем жиз­ненную ситу­ацию. Нап­ример, пока ты сидишь и смот­ришь кино в науш­никах, в холодиль­ник на кух­не про­ника­ет неиз­вес­тный враг и похища­ет отту­да кол­басу. Никог­да не стал­кивал­ся с таким? А ведь это очень воз­можно, и нуж­но быть готовым ко все­му заранее! Нуж­на сиг­нализа­ция. Для ее реали­зации дверь холодиль­ника обо­рудуй кон­так­тным или маг­нитным дат­чиком (нап­ример, ИО-102-16/2, но на кус­тарном уров­не сго­дят­ся и два про­вода витой пары, прик­леен­ные скот­чем так, что­бы при зак­рытой двер­це холодиль­ника они замыка­лись), Arduino положи в ком­нате на вид­ном мес­те, заведи про­вода от дат­чика к Arduino по сле­дующей схе­ме (какой кон­крет­но про­вод куда под­клю­чать — зна­чения не име­ет):

  • один про­вод на колод­ке DIGITAL на любой из кон­тактов GND ;
  • вто­рой про­вод на колод­ке DIGITAL на кон­такт 43 .

План решения задачи

Что­бы отсле­живать, зам­кну­ты ли про­вода, необ­ходимо пери­оди­чес­ки опра­шивать сос­тояние вход­ного сиг­нала на мик­рокон­трол­лере.

В задаче ука­зано, что поль­зователь дол­жен иметь воз­можность нас­тра­ивать инди­кацию. Вос­поль­зуем­ся памятью EEPROM мик­рокон­трол­лера и при запус­ке будем читать отту­да нас­трой­ки. А для записи нас­тро­ек напишем неболь­шой инте­рак­тивный тер­минал, к которо­му мож­но под­клю­чить­ся любой тер­миналь­ной прог­раммой по пор­ту RS-232.

Вста­ет воп­рос: где взять RS-232 на Arduino и на компь­юте­ре? Все очень прос­то, если ты не потерял схе­му пла­ты Arduino. USB-разъ­ем пла­ты Arduino заведен на UART0 мик­рокон­трол­лера через мик­росхе­му‑пре­обра­зова­тель USB — COM (на самом деле, как ты пом­нишь, там сто­ит ATmega16U, он‑то и игра­ет роль это­го пре­обра­зова­теля).

Ин­дикацию из основно­го цик­ла прог­раммы при­дет­ся уби­рать, потому что вре­мя обра­бот­ки команд у нас неп­ред­ска­зуемо, а зна­чит, мы будем иметь проб­лемы с выдер­живани­ем вре­мени мор­гания све­тоди­одом.

info

В реаль­ной жиз­ни для ана­лиза сос­тояния вход­ных сиг­налов исполь­зуют аппа­рат­ные и прог­рам­мные филь­тры, так как мир неидеален и при раз­мыкании/замыка­нии кон­тактов воз­ника­ет так называ­емый дре­безг кон­такта.

Решение

Периферия

На­пом­ню, что ATmega 2560 явля­ется пред­ста­вите­лем SoC. Это озна­чает, что на одном крис­талле мик­рокон­трол­лер содер­жит раз­личную перифе­рию. Перечис­лю, что при­годит­ся для решения нашей задачи (пол­ный перечень, как всег­да, в докумен­тации):

  • тай­меры — основное их пред­назна­чение, как нес­ложно догадать­ся, — отсчи­тывать вре­мя и совер­шать какие‑то дей­ствия по резуль­татам этих изме­рений. Очень час­то тай­меры про­сят генери­ровать пре­рыва­ние при отме­рива­нии какого‑то крат­ного интерва­ла вре­мени для реали­зации часов или опре­деле­ния задер­жек;
  • UART — основной для неболь­ших и малень­ких мик­рокон­трол­леров канал свя­зи с внеш­ним миром;
  • GPIO — самый базовый класс перифе­рии, поз­воля­ющий нап­рямую работать с ногами мик­рокон­трол­лера.

Ввод/вывод

Настройка таймера

Нач­нем, как при­нято, с кон­ца :). Как ты понял, раз из основно­го цик­ла мы уби­раем управле­ние све­тоди­одом, то куда‑то его надо вста­вить. Самое логич­ное — это повесить обра­бот­чик пре­рыва­ния по тай­меру и в нем отсчи­тывать вре­мя зажига­ния или гашения све­тоди­ода, а так­же собс­твен­но зажигать/гасить све­тоди­од. При­веду неболь­шой кусочек кода, который ини­циали­зиру­ет тай­мер на генера­цию пре­рыва­ния один раз в 1 мс:

TCCR1A = 0x00 ;
TCCR1B = ( 1 < < WGM12 ) | ( 1 < < CS11 ) | ( 1 < < CS10) ;
TCCR1C = 0x00;
TCNT1 = 0x0000;
OCR1A = 250;
OCR1B = 0x0000;
OCR1C = 0x0000;
TIMSK1 |= ( 1 < < OCIE1A) ;

Что тут про­исхо­дит? Что­бы не занимать­ся неудоб­ными перес­четами в прог­рамме, про­ще нас­тро­ить тай­мер на сра­баты­вание раз в 1 мс. Тай­меры в ATmega — шту­ка кру­тая и име­ют кучу воз­можнос­тей, о которых ты можешь почитать в ману­але, я выб­рал режим работы тай­мера по срав­нению со сбро­сом ( 1

Пос­ле нас­трой­ки тай­мера выс­тавляй флаг раз­решения пре­рыва­ния по срав­нению Timer1 TIMSK1 |= ( 1

Те­перь объ­являй обра­бот­чик пре­рыва­ния, выг­лядит это сле­дующим обра­зом:

ISR ( TIMER1_ COMPA_ vect )
// Вызываем пользовательский обработчик прерывания по таймеру
user_ timer_ ISR ( ) ;

Таблица векторов прерываний

Век­тор пре­рыва­ния — это не что иное, как адрес, на который мик­рокон­трол­лер переда­ет управле­ние при воз­никно­вении пре­рыва­ния. По это­му адре­су рас­полага­ется инс­трук­ция перехо­да в фун­кцию — обра­бот­чик пре­рыва­ния или (не обя­затель­но) инс­трук­ция воз­вра­та из пре­рыва­ния. Зачас­тую все сре­ды раз­работ­ки пред­лага­ют запол­нять по умол­чанию таб­лицу век­торов пре­рыва­ний инс­трук­цией воз­вра­та из пре­рыва­ния, что­бы при ошиб­ке во вре­мя раз­работ­ки (ты вклю­чил пре­рыва­ние или забыл / не пла­ниро­вал писать обра­бот­чик) выпол­нение прог­раммы вер­нулось в нор­маль­ное рус­ло. Таб­лица век­торов пре­рыва­ний рас­полага­ется по млад­шим адре­сам Flash мик­рокон­трол­лера. По нулево­му адре­су рас­полага­ется так называ­емый Reset-век­тор, на этот век­тор мик­рокон­трол­лер переда­ет управле­ние при стар­те или при перезаг­рузке.

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

// Моргаем светодиодом
blink_ led ( ) ;
// Анализируем состояние дискретных входов
process_ input ( ) ;

Обработка состояния дискретного входа

Для начала надо ини­циали­зиро­вать GPIO мик­рокон­трол­лера, ска­зав ему, что ты хочешь исполь­зовать опре­делен­ные ноги как получа­тель вход­ных сиг­налов. Дела­ется это сле­дующим обра­зом:

// Включаем внутреннюю подтяжку к «1»
PORTL | = _BV ( PL6 ) ;
// Настраиваем пин PL6 на вход

Что такое внут­ренняя под­тяжка? Перек­лючив ногу на вход, ты ска­зал мик­рокон­трол­леру, что он дол­жен сни­мать сиг­нал с этой ноги. Но что делать, если нога «бол­тает­ся в воз­духе», то есть кон­так­тный дат­чик разом­кнут?

Читайте также:
Начало раздела программы в котором описывают типы используемых переменных

info

Су­щес­тву­ют мик­рокон­трол­леры, поз­воля­ющие делать под­тяжку к обо­им зна­чени­ям: или к 1, или к 0.

Да­вай опре­делим­ся для начала, какое сос­тояние будет счи­тать­ся зам­кну­тым. Я выб­рал замыка­ние ноги на GND . Сей­час объ­ясню почему.

Мик­рокон­трол­лер понима­ет толь­ко два сос­тояния дис­крет­ного вхо­да: к ноге при­ложе­но нап­ряжение (логичес­кая еди­ница) или нога зам­кну­та на зем­лю (логичес­кий ноль), все осталь­ное есть неоп­ределен­ность. То есть каж­дый раз, счи­тывая сос­тояние, ты не можешь точ­но ска­зать, какое же оно на самом деле, и мик­рокон­трол­лер может пос­читать его как за 0, так и за 1.

Для раз­решения этой дилем­мы исполь­зуют под­тяжки к 1 (через огра­ничи­тель­ный резис­тор замыка­ют на линию питания) или к 0 (замыка­ют ногу на зем­лю), то есть зада­ется зна­чение по умол­чанию. Таким обра­зом, если нога «висит в воз­духе», то, зная, к какому зна­чению у тебя под­тяжка, ты можешь точ­но под­твер­дить факт «нога в воз­духе» и получить впол­не опре­делен­ное зна­чение.

В ATmega 2560 (как и во всем семей­стве мик­рокон­трол­леров mega) сущес­тву­ет фун­кция внут­ренней под­тяжки к 1, то есть мик­рокон­трол­лер сам раз­реша­ет ситу­ацию «нога в воз­духе». Теперь ты зна­ешь ответ на воп­рос, зачем исполь­зовать сос­тояние «зам­кну­то» кон­так­тно­го дат­чика как замыка­ние на GND. Если нога «в воз­духе», то ты про­чита­ешь 1, если нога зам­кну­та на зем­лю, то ты про­чита­ешь 0.

Чте­ние сос­тояния дис­крет­ного вхо­да осу­щест­вля­ется чте­нием регис­тра PINx и мас­кирова­нием соот­ветс­тву­юще­го бита:

uint8_ t cur_ state = ( PINL

Даль­ше ана­лизи­руем сос­тояние и в зависи­мос­ти от него уста­нав­лива­ем нуж­ный режим:

// Изменилось, начинаем моргать в соответствии с новым режимом
if ( cur_ state == 0 )
ch_ blink_ mode ( wm_ alarm ) ;
ch_ blink_ mode ( wm_ normal ) ;

warning

При работе с мик­рокон­трол­лером пос­тарай­ся убрать все метал­личес­кие пред­меты, что­бы пре­дот­вра­тить слу­чай­ное корот­кое замыка­ние и выход пла­ты из строя.

А поговорить?

Те­перь давай зай­мем­ся реали­заци­ей тер­минала, то есть орга­низу­ем обще­ние с мик­рокон­трол­лером. Как я уже упо­минал, исполь­зовать мы будем UART0 . UART0 явля­ется перифе­рий­ным устрой­ством, и для ука­зания мик­рокон­трол­леру, что ты хочешь имен­но его, необ­ходимо про­извести некото­рые манипу­ляции, то есть нас­тро­ить ско­рость, про­вер­ку чет­ности, дли­ну сло­ва.

Для облегче­ния жиз­ни я сде­лал неболь­шой мак­рос (он стан­дар­тный, ничего инно­ваци­онно­го) для нас­трой­ки регис­тра ско­рос­ти:

#define UART_CALC_BR( br ) ( ( uint16_t )( ( F_CPU / ( 16UL * ( br) ) ) — 1 ) )

Нас­тро­им сам USART0 .

uint16_ t br = UART_ CALC_ BR ( 9600 ) ;
// Настройка скорости обмена
UBRR0H = br > > 8 ;
UBRR0L = br
// 8 бит данных, 1 стоп-бит, без контроля четности
UCSR0C = ( 1 < < USBS0 ) | ( 1 < < UCSZ01 ) | ( 1 < < UCSZ00 ) ;
// Разрешить прием и передачу данных
UCSR0B = ( 1 < < TXEN0 ) | ( 1 < < RXEN0 ) | ( 1 < < RXCIE0 ) ;

Не подскажете, как пройти в библиотеку?

Раз­работ­чики стан­дар­тных биб­лиотек для встра­иваемых решений позабо­тились о приб­лижении этих биб­лиотек к стан­дарту С/С++. Клю­чевое сло­во тут «приб­лижение». Реали­зация мно­гих фун­кций дос­таточ­но уре­зана вви­ду огра­ничен­ности ресур­сов мик­рокон­трол­леров, а некото­рые зак­рыты заг­лушка­ми для сов­мести­мос­ти.

Так, фун­кции по обра­бот­ке строк ( sscanf , sprintf и подоб­ные) дос­таточ­но тре­бова­тель­ны к исполь­зованию сте­ка. При наличии на бор­ту 4 Кб опе­ратив­ной памяти это дос­таточ­но кри­тич­но. Поэто­му, если ты решишь исполь­зовать ту или иную фун­кцию, читай опи­сание к ней не в стан­дар­тных ману­алах, а в докумен­тации на кон­крет­ную биб­лиоте­ку.

Те­перь позабо­тим­ся об удоб­ном обме­не дан­ными. Так как у нас будет реали­зация тер­минала, я решил, что удоб­нее все­го исполь­зовать стан­дар­тные биб­лиотеч­ные фун­кции printf и fgets . Для того что­бы эти фун­кции зарабо­тали в том виде, как они задума­ны, необ­ходимо соз­дать свой поток и реали­зовать фун­кции отправ­ки и при­ема бай­та:

// Объявляем поток ввода/ вывода, который будем использовать для перенаправления stdio
static FILE uart_ stream = FDEV_ SETUP_ STREAM ( uart_ putc, uart_ getc, _ FDEV_ SETUP_ RW ) ;

А так­же перенап­равить stdio в наш поток

stdout = stdin =

Рас­смот­рим чуть бли­же при­ем бай­та из пор­та:

int uart_ getc ( FILE * file )
// Ждем, пока появятся данные в FIFO, если там ничего нет
while ( FIFO_ IS_ EMPTY ( uart_ rx_ fifo ) ) ;
__ builtin_ avr_ cli ( ) ; // Запрещаем прерывания
ret = FIFO_ FRONT ( uart_ rx_ fifo ) ;
FIFO_ POP ( uart_ rx_ fifo ) ;
__ builtin_ avr_ sei ( ) ; // Разрешаем прерывания
return ret ;

Для того что­бы фун­кция gets работа­ла, при­ходит­ся ждать, пока оче­ред­ной байт пос­тупит в FIFO. При извле­чении байт из FIFO рекомен­дует­ся отклю­чать пре­рыва­ния, что­бы во вре­мя извле­чения бай­та не про­изош­ло пре­рыва­ние и один ресурс (FIFO) не начали исполь­зовать с двух сто­рон.

Зай­мем­ся отправ­кой бай­та.

int uart_ putc ( char c, FILE * file )
__ builtin_ avr_ cli ( ) ; // Запрещаем прерывания
if ( ! FIFO_ IS_ FULL ( uart_ tx_ fifo ) ) <
// Если в буфере есть место, то добавляем туда байт
FIFO_ PUSH ( uart_ tx_ fifo, c ) ;
// и разрешаем прерывание по освобождению передатчика
UCSR0B | = ( 1 < < UDRIE0 ) ;
ret = -1; // Буфер переполнен
__builtin_avr_sei(); // Разрешаем прерывания
return ret;

Вот тут встре­чает­ся инте­рес­ный момент: вклю­чение пре­рыва­ния по осво­бож­дению передат­чика. Так как не очень‑то хочет­ся вруч­ную скла­дывать бай­ты в передат­чик, то вос­поль­зуем­ся пре­рыва­нием по очис­тке передат­чика. Повесим на это пре­рыва­ние обра­бот­чик, который кла­дет в передат­чик оче­ред­ной байт из FIFO, если в FIFO что‑то есть:

ISR ( USART0_ UDRE_ vect )
if ( FIFO_ IS_ EMPTY ( uart_ tx_ fifo ) ) <
// Если данных в FIFO больше нет, то запрещаем это прерывание
// Иначе передаем следующий байт
char txbyte = FIFO_FRONT( uart_tx_fifo ) ;
FIFO_POP( uart_tx_fifo ) ;
UDR0 = txbyte;

Для завер­шения кар­тины при­веду код обра­бот­чика по при­ему бай­та:

ISR ( USART0_ RX_ vect )
unsigned char rxbyte = UDR0 ;
if ( ! FIFO_ IS_ FULL ( uart_ rx_ fifo ) ) <
FIFO_ PUSH ( uart_ rx_ fifo, rxbyte ) ;

Отладка

Так как мы огра­ничи­ваем­ся толь­ко самими Arduino и не покупа­ем желез­ный отладчик, то реали­зован­ный класс работы с UART удоб­но исполь­зовать для отладки тво­ей прог­раммы. Прос­то в нуж­ных мес­тах встав­ляешь трейс‑вывод с помощью printf . Толь­ко не увле­кай­ся и пом­ни про пожира­ние сте­ка и памяти биб­лиотеч­ными фун­кци­ями. Для вывода зна­чения регис­тров и зна­чения перемен­ных в нуж­ных мес­тах это­го впол­не дос­таточ­но.

Хранение настроек

Вот мы и подош­ли к самому, с моей точ­ки зре­ния, любопыт­ному. Нас­трой­ки хотелось бы хра­нить даже пос­ле перезаг­рузки или отклю­чения питания устрой­ства. Для этой цели в мик­рокон­трол­лере есть EEPROM. Для работы с этой памятью при­сутс­тву­ет биб­лиоте­ка eeprom. h . Мож­но пой­ти аль­тер­натив­ным путем и реали­зовать запись/чте­ние самос­тоятель­но, это нес­ложно. Но если есть уже готовое решение, то пред­лагаю им и вос­поль­зовать­ся.

Итак, в арсе­нале име­ются фун­кции eeprom_read_byte , eeprom_write_byte , eeprom_read_block , eeprom_write_block . У EEPROM есть одна осо­бен­ность — она быва­ет занята, поэто­му раз­работ­чики биб­лиоте­ки (и в этом я к ним при­соеди­няюсь) рекомен­дуют вызывать eeprom_busy_wait или про­верять готов­ность фун­кци­ей eeprom_is_ready .

Так как софт для мик­рокон­трол­лера — это вещь авто­ном­ная, то нас­тоятель­но рекомен­дует­ся вся­чес­ки защищать нас­трой­ки кон­троль­ными бло­ками от слу­чай­ных или несан­кци­они­рован­ных изме­нений. В нашем при­мере я исполь­зую один кон­троль­ный байт на блок нас­тро­ек, который сиг­нализи­рует о том, что дан­ные мною были записа­ны. Соот­ветс­твен­но, если этот байт равен опре­делен­ному зна­чению (в нашем слу­чае это 0), то это озна­чает, что дан­ные в бло­ке вер­ны. Для безопас­ности перед записью этот байт я перево­жу в сос­тояние 1, пос­ле окон­чания записи воз­вра­щаю это зна­чение в 0″. Дан­ные манипу­ляции нуж­ны для того, что­бы во вре­мя записи при вне­зап­ной перезаг­рузке мик­рокон­трол­лер не заг­рузил мусор из памяти, а взял нас­трой­ки по умол­чанию.

danger

Ста­тичес­кое элек­три­чес­тво смер­тель­но для мик­росхем, избе­гай работы с мик­рокон­трол­лерами в син­тетичес­кой и шер­стя­ной одеж­де, по воз­можнос­ти исполь­зуй зазем­ляющие брас­леты.

3, 2, 1, поехали!

Итак, теперь про­шива­ем кон­трол­лер, запус­каем тер­минал

#minicom -D `ls / dev/ serial/ by-id/ *arduino*` -c on -b 9600

Arduino выводит сооб­щение о стар­те и дает приг­лашение:

Для тер­минала я реали­зовал сле­дующий набор команд (help не реали­зовы­вал):

Источник: xakep.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru