Плата TTGO T-Call

 Arduino  Комментарии к записи Плата TTGO T-Call отключены
Ноя 272019
 

На протяжении многих лет компания LilyGo выпускала платы TTGO, обычно основанные на базе Espressif ESP8266 или ESP32. Вот еще один вариант, который может быть полезен, если конечно в вашей стране еще не отказались от сотовой сети 2G: TTGO T-Call. Continue reading »

Регулярные выражения в PHP.

 Arduino  Комментарии к записи Регулярные выражения в PHP. отключены
Окт 262019
 

Регулярные выражения в PHP.

Регулярные выражения позволяют найти в строке последовательности, соответствующие шаблону. Например шаблон «Вася(.*)Пупкин» позволит найти последовательность когда между словами Вася и Пупкин будет любое количество любых символов. Если надо найти шесть цифр, то пишем «[0-9]{6}» (если, например, от шести до восьми цифр, тогда «[0-9]{6,8}»). Здесь разделены такие вещи как указатель набора символов и указатель необходимого количества: Continue reading »

Полный список команд языка Ардуино

 Arduino  Комментарии к записи Полный список команд языка Ардуино отключены
Окт 172019
 

Полный список команд языка Ардуино

На этой странице представлен список всех (или почти всех) доступных “из коробки” команд для Arduino с кратким описанием и примерами. Часть информации взята из Гугла, в основном некоторые особенности языка, часть получена методом проб и ошибок. Полную информацию о том, как этим пользоваться, можно получить из уроков или официального reference. Также изо всех сил рекомендую вот этот онлайн справочник по C++, из него можно узнать гораздо больше о некоторых особенностях использования операторов и типов данных.

Continue reading »

Arduino проект выходного дня – футболка на светодио дах SK6812

 Arduino  Комментарии к записи Arduino проект выходного дня – футболка на светодио дах SK6812 отключены
Сен 162019
 

Arduino проект выходного дня – футболка на светодиодах SK6812

Continue reading »

Китайский повышающий блок

 Arduino  Комментарии к записи Китайский повышающий блок отключены
Сен 092019
 

Здравствуйте. Имеется идея изготовления повышающих dc-dc преобразователей, которые продаются на Ali-express, в домашних условиях.

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

Схему нашёл на просторах ютуба у одного человека на канале, добра ему.

Помогите определить номинал C1, C2, C7, C8, C9, C10

tl494.jpg

Изготовление плат

 Arduino  Комментарии к записи Изготовление плат отключены
Авг 212019
 

Запротоколировал процесс сборки нескольких девайсов с паяльной пастой и печкой – собственно, ничего принципиально нового знакомые с этим процессом тут не увидят, а вот для незнакомых будет интересно.

Итак, вводная – надо собрать дюжину плат с SMD-деталями типоразмера 0603 и Bluetooth-модулем на CC2541. Плату я нарисовал в DipTrace, заказал в Резоните изготовление собственно плат, а в OSH Stencils – трафарета из полиимидной пленки. Детали куплены частично на алиэкспрессе (собственно сами Bluetooth-модули), а частично – в Чип-и-Дипе и Электронщике. И вот со всей этой фигней мы попытаемся взлететь 🙂

paste-printer

По-хорошему, нужен трафаретный принтер, который натягивает трафарет и правильно позиционирует его относительно платы, и есть даже неплохие “самодельные” варианты – но для кустарных условий сойдет и такое приспособление из обрезков других плат той же толщины.

paste-ready

Паста размазывается чем-нибудь типа шпателя или пластиковой карты, при небольшом навыке это делается довольно быстро и непринужденно – на 12 плат у меня ушло меньше 10 минут.

placing

Расстановка деталей – довольно муторный этап, особенно, если делать это обычным пинцетом – но с перерывами я расставил две сотни деталей за два часа. Для сравнения – у самого-самого простого “любительского” установщика типа какого-нибудь Liteplacer заявленная производительность составляет 500-600 деталей в час (хотя с учетом времени на его программирование этот процесс занял бы примерно то же время).

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

into-the-oven

Дальше загружаем платы в печку.

oven-running

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

oven-done

В течение еще нескольких минут платы остывают.

boards

Готово! Остается запаять лишь пару разъемов.

Монтаж

 Arduino  Комментарии к записи Монтаж отключены
Авг 212019
 

Монтаж

И вот тут, конечно, начинается самое интересное — платы есть, компоненты есть, дальше-то что?

Главный тезис: современные платы паяльником вручную не собирают.

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

На современных платах абсолютное большинство компонентов — SMD, и более того, когда вы попробуете минимально пристойные методики сборки, вы будете плакать каждый раз, когда будете вынуждены ставить THD-компонент.

SMD-компоненты паяются пастой — смесью из флюса и микронного размера шариков припоя (мы используем Multicore CR36, но это не обязательно).

Паста наносится строго на контактные площадки компонентов одним из двух способов:

  • пневматическим — компрессором на 4-6 атмосфер (подойдёт любой с ресивером, чтобы обеспечить стабильность давления) и диспенсером типа такого. Диспенсер стоит у китайцев баксов сто и обеспечивает простую вещь — при нажатии педали подаёт в шприц с пастой воздух под заданным давлением в течение заданного времени, выжимая заданное количество пасты. Немного муторно, но среднего размера плату вы заплюёте минут за пятнадцать-двадцать, причём при определённой сноровке можно работать даже с компонентами 0402. Паста для диспенсеров продаётся уже в шприцах, мы используем EFD SolderPlus (точнее, почти не используем, ибо диспенсер минимум 364 дня в году просто покрывается пылью).
  • по трафарету — листу стали или пластика с вырезанными в нём отверстиями контактных площадок, толщиной 100-125 мкм. Трафарет кладётся сверху на плату, на него вываливается шлепок паяльной пасты и размазывается шпателем или просто пластиковой карточкой. В идеале для этого процесса нужен ручной трафаретный принтер, обеспечивающий вертикальное поднимание и опускание трафарета, но в небольших масштабах можно просто закреплять плату и трафарет малярным скотчем на столе. Обработка одной платы занимает, очевидно, всего пару минут.

Всякие варианты с намазыванием зубочисткой, ручным давлением из шприца и т.п. забудьте сразу — вы повеситесь на первой же своей плате.

wydtchrcepcswghlapp16zdsv_m.jpeg

Данная часть была бы не столь весёлой — трафарет штука довольно дорогая — если бы не одно чудесное изобретение человечества: режущий плоттер Silhouette Curio, предназначенный для вырезания аппликаций для скрапбуков, но также прекрасно справляющийся с «прозрачками» для проекторов. Последние имеют толщину 100 мкм — думаете, это просто так, совпадение?

Плоттер принимает на вход файлы DXF (причём в бесплатной версии софта; платная добавляет поддержку очень нужного для скрапбуков SVG) и без проблем справляется с компонентами размером вниз до SOIC и 0603. Пассивка 0402 выходит похуже, но ещё приемлемо, а во всяких QFN приходится убирать отдельные ножки и делать сплошную прорезь.

Это, конечно, не трафарет для производства десяти тысяч устройств. Но возможность таки сделать трафарет, вполне себе подходящий для большинства несложных конструкций, на оборудовании за 14 тысяч рублей, без мокрой химии и вообще каких-либо значимых отходов, а также за пятнадцать-двадцать минут времени — бесценна.

После вырезания и намазывания идёт расстановка деталей.

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

Поэтому базовый вариант — расстановка вручную. Самый простой и дешёвый вариант — вакуумный пинцет типа такого, то есть, по сути, ручка с аквариумным компрессором. Ручка имеет сменную иглу на конце и дырку на боку, закрываете дырку пальцем — игла притягивает детали, отпускаете палец — воздух идёт через дырку, деталь отваливается.

В отличие от обычных пинцетов, эта штука позволяет брать компоненты любой формы и с любой поверхности, в том числе прямо из лент, а в отличие от механических вакуумных пинцетов с кнопкой и резиновой грушей внутри, не даёт рывка при отпускании детали. Стоит такой пинцет копейки — меньше 1500 рублей у китайцев, а при желании можно сделать и самому натурально из аквариумного воздушного компрессора. Иголки со временем забиваются паяльной пастой, но без проблем покупаются отдельно, как наборами разного диаметра, так и пакетами одного диаметра по 10-20-50-100 штук.

Обычным пинцетом останется расставить только самые крупные компоненты, вроде процессоров в LQFP-корпусах.

Важный момент: на дворе XXI век, расстановку по бумажной распечатке из CAD’а никто уже не делает.

Для ручной расстановки есть небольшая и крайне полезная программа VisualPlace, которая всасывает в себя герберы, перечень компонентов и их координаты на плате — и показывает вам, где какой компонент должен стоять, а также в какой ориентации:

evkvnpabpeihivvznqryiljfoxa.png

Более того, VisualPlace умеет группировать компоненты по полю value, так что, взяв в руки катушку транзисторов SI2333 и ткнув в группу с ними, вы увидите сразу все места, где они должны стоять.

Авторами утилита писалась явно в первую очередь для себя (и, кстати, они же делают прекрасную маленькую терминалку Termite), поэтому, с одной стороны, она не перегружена ненужным хламом и красивыми иконками, с другой — есть много шероховатостей в понимании единиц измерения (дюймы или миллиметры), точки отсчёта координат в герберах (рекомендую всегда ставить начало на нижний левый угол платы) и т.п. На это иногда накладываются ещё и особенности CAD’ов — например, DipTrace на системе с русской локалью сохраняет числа с десятичным разделителем «,», а VisualPlace ждёт «.» независимо от локали.

Впрочем, подгонка выхлопа CAD под VisualPlace занимает от силы несколько минут, а пользу утилиты переоценить невозможно. Утилита официально бесплатна для коммерческого использования.

Где-то примерно в этот момент вы почувствуете всю прелесть такого подхода к монтажу. Сравните — паяльник:

  • в правую руку паяльник, в левую — припой
  • поставить каплю припоя на одну площадку резистора
  • отложить припой, отложить паяльник
  • вытряхнуть резистор из ленты, перевернуть маркировкой вверх
  • в левую руку пинцет, взять резистор пинцетом, поставить на плату
  • не отпуская резистор, взять в правую руку паяльник, прихватить ту площадку, на которую был нанесён припой
  • отложить пинцет, взять припой
  • прихватить вторую площадку
  • повторить для остальных 146 компонентов

Паста:

  • намазать всю плату пастой
  • взять вакуумный пинцет, расставить все компоненты прямо из лент и поддонов
  • сунуть плату в печку

О, да. После того, как вы расставили компоненты (ставить надо с небольшим усилием, чтобы они впечатались в пасту), приходит пора печки.

Задача печки — нагреть пасту с заданной скоростью до 140-160 градусов (температура активации и испарения флюса), потом до 220-230 (плавление припоя), подержать недолго и охладить.

Строго говоря, на совсем штучных изделиях можно прогревать пасту феном, но это годится только буквально на 5-10 экземпляров, а также подвергает компоненты серьёзному стрессу из-за больших градиентов температур. Печка же греет равномеро и плавно.

Опять же, что очень радует — хотя китайцы делают вполне себе недорогие печки, совсем самодельную можно сделать из совершенно грошовой электродуховки (я не рекомендую данную конкретную модель, это просто ссылка) или аэрогриля, добавив к ним сделанный хоть на Arduino контроллер температуры, выдерживающий нужный профиль по градусам и по минутам. Стоимость такого решения в наколенном варианте будет меньше 5 тысяч рублей за всё в сумме.

Собственно, всё. После запекания остаётся на компонентах с мелким шагом проверить ножки на предмет возможных залипаний (вопреки утверждениям в многочисленных руководствах — нет, паяльная паста далеко не всегда сама идеально собирается точно на ножке, если её чуть больше, чем надо, или размазана она сильнее, чем надо — поверхностного натяжения не хватит, чтобы разорвать перемычку между соседними ножками), всё найденное убрать обычным паяльником, на чипах с длинными ногами и мелким шагом, вроде LQFP или TSSOP с шагом 0,5 мм, — с оплёткой для выпайки.

Изменение размера изображения средствами PHP

 Arduino  Комментарии к записи Изменение размера изображения средствами PHP отключены
Авг 212019
 

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

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

<?php class SimpleImage { var $image; var $image_type; function load($filename) { $image_info = getimagesize($filename); $this->image_type = $image_info[2]; if( $this->image_type == IMAGETYPE_JPEG ) { $this->image = imagecreatefromjpeg($filename); } elseif( $this->image_type == IMAGETYPE_GIF ) { $this->image = imagecreatefromgif($filename); } elseif( $this->image_type == IMAGETYPE_PNG ) { $this->image = imagecreatefrompng($filename); } } function save($filename, $image_type=IMAGETYPE_JPEG, $compression=75, $permissions=null) { if( $image_type == IMAGETYPE_JPEG ) { imagejpeg($this->image,$filename,$compression); } elseif( $image_type == IMAGETYPE_GIF ) { imagegif($this->image,$filename); } elseif( $image_type == IMAGETYPE_PNG ) { imagepng($this->image,$filename); } if( $permissions != null) { chmod($filename,$permissions); } } function output($image_type=IMAGETYPE_JPEG) { if( $image_type == IMAGETYPE_JPEG ) { imagejpeg($this->image); } elseif( $image_type == IMAGETYPE_GIF ) { imagegif($this->image); } elseif( $image_type == IMAGETYPE_PNG ) { imagepng($this->image); } } function getWidth() { return imagesx($this->image); } function getHeight() { return imagesy($this->image); } function resizeToHeight($height) { $ratio = $height / $this->getHeight(); $width = $this->getWidth() * $ratio; $this->resize($width,$height); } function resizeToWidth($width) { $ratio = $width / $this->getWidth(); $height = $this->getheight() * $ratio; $this->resize($width,$height); } function scale($scale) { $width = $this->getWidth() * $scale/100; $height = $this->getheight() * $scale/100; $this->resize($width,$height); } function resize($width,$height) { $new_image = imagecreatetruecolor($width, $height); imagecopyresampled($new_image, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight()); $this->image = $new_image; } } ?>

Скачать клаcc SimpleImage

Теперь после того как мы поместили данный файл класса SimpleImage к себе на сервер посмотрим как его можно использовать.

Следующий участок кода загрузит изображение image.jpg, изменить его ширину до 400 пикселей и высоту до 200 пикселей, а затем сохранит как image1.jpg.

<?php include('classSimpleImage.php'); $image = new SimpleImage(); $image->load('image.jpg'); $image->resize(400, 200); $image->save('image1.jpg'); ?>

Если необходимо изменить размеры изображения, основываясь только на ширине и при этом сохранить его пропорции, то сценарий сам выберет необходимую высоту. Для этого необходимо использовать метод resizeToWidth.

<?php include('classSimpleImage.php'); $image = new SimpleImage(); $image->load('image.jpg'); $image->resizeToWidth(250); $image->save('image1.jpg'); ?>

Возможно вы пожелаете изменить размер в процентном соотношении от его оригинала. Для этого существует метод scale, в качестве параметра которому передаются проценты.

<?php include('classSimpleImage.php'); $image = new SimpleImage(); $image->load('image.jpg'); $image->scale(50); $image->save('image1.jpg'); ?>

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

<?php header('Content-Type: image/jpeg'); include('classSimpleImage.php'); $image = new SimpleImage(); $image->load('image.jpg'); $image->resizeToWidth(150); $image->output(); ?>

Автор данного класса Simon Jarvis, на своем сайте предлагает следующий пример для изменения размера изображения загруженного через форму.

<?php if (isset($_POST['submit']) ) { include('classSimpleImage.php'); $image = new SimpleImage(); $image->load($_FILES['uploaded_image']['tmp_name']); $image->resizeToWidth(150); $image->output(); } else { $form = '<form action="upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="uploaded_image" /> <input type="submit" name="submit" value="Upload" /> </form>'; echo $form; }

Вот такой очень маленький, но довольно функциональный получился класс SimpleImage, который очень пригодиться любому разработчику.

Режимы для фрез/сверл/резьбофрез по металлам, ал юминию, дереву, пластикам

 Arduino  Комментарии к записи Режимы для фрез/сверл/резьбофрез по металлам, ал юминию, дереву, пластикам отключены
Авг 152019
 

Режимы для фрез/сверл/резьбофрез по металлам, алюминию, дереву, пластикам

Дерево/Пластики Алюминий (стр.58) Металлы (стр.59) Резьбофрезы (стр.61) Свёрла (стр.62)

Гироскутер

 Arduino  Комментарии к записи Гироскутер отключены
Авг 112019
 

Этим опусом я начну и постараюсь довести до читателя полную структуру программы изменяющую гироскутер в любое иное изделие для нужд потребителя и вашей инженерной мысли.
Прежде всего хочу выразить благодарность за разработку а главное за выкладывание полной рабочей версии программы ссылка
если вы знаете английский напишите ему мое спасибо буду очень вам признателен :-)
ссылка

Что может программа:
1. Управление от RC передатчика ppm-sum в этом случае работают два колеса не зависимо друг от друга (вперед, назад, вправо и в лево).
y4eacz_thumb.jpeg
uhfhmj_thumb.jpeg
приемник подключается на прямую к контроллеру гироскутера.

2. Управление от джойстика необходим джойстик от видеоигры плейстейшен. (вперед,назад, вправо и в лево).
uxm0x2_thumb.jpeg

3. Управление переменным сопротивлением, вперед, назад и переключение скоростей информация предоставлена Viktor_7
ссылка
1mfppw1_thumb.jpeg
Подходит для самокатов и машинок можно ставить ограничение по скорости максимальная скорость 25 км час.

4. То что использую Я это управление arduino по протоколу uart.

5. Танковое управление от двух джойстиков организуется только при помощи двух контроллеров.

Выбирайте на ваше усмотрение.
Дальше больше :exactly:

Сам контроллер гироскутера
1350e45_thumb.jpeg

Так как программа написана на языке С++ ее надо скомпилировать для так сказать волшебного превращения в прошивку для контроллера гироскутера.

рассмотрим настройку программы под свои проекты.
это файл в программе где вы делаете свои установки config.h

#pragma once
#include «stm32f1xx_hal.h»

// ############################### Менять данные запрещено 100% выход из строя контроллера ну или его не работа

#define PWM_FREQ 16000 // PWM частота 16 кГц
#define DEAD_TIME 32 // PWM deadtime

#define DELAY_IN_MAIN_LOOP 5 // in ms. default 5. it is independent of all the timing critical stuff. do not touch if you do not know what you are doing.

#define TIMEOUT 5 // number of wrong / missing input commands before emergency off

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

// ############################### GENERAL ###############################

// How to calibrate: connect GND and RX of a 3.3v uart-usb adapter to the right sensor board cable (be careful not to use the red wire of the cable. 15v will destroye verything.). if you are using nunchuck, disable it temporarily. enable DEBUG_SERIAL_USART3 and DEBUG_SERIAL_ASCII use asearial terminal.

// Battery voltage calibration: connect power source. see <How to calibrate>. write value nr 5 to BAT_CALIB_ADC. make and flash firmware. then you can verify voltage on value 6 (devide it by 100.0 to get calibrated voltage).
#define BAT_CALIB_REAL_VOLTAGE 43.0 // установить напряжение измеренное тестером на вашем аккумуляторе (в данный момент используется 43 вольта)

#define BAT_CALIB_ADC 1704 // BAT_CALIB_ADC -это калибровочный коэффициент для расчёта напряжения, т.е. число 1704 — это количество
отсчётов, при подаче на вход делителя напряжения НАПРЯЖЕНИЯ = 43.0

#define BAT_NUMBER_OF_CELLS 10 // количество ячеек в аккумуляторе: 10s
#define BAT_LOW_LVL1_ENABLE 0 // вкл/выкл звука при низком напряжении, 1 or 0
#define BAT_LOW_LVL1 3.6 // пищит на этом уровне напряжения. [В/эл.]
#define BAT_LOW_LVL2_ENABLE 1 // to beep or not to beep, 1 or 0
#define BAT_LOW_LVL2 3.5 // your battery is almost empty. Charge now! [V/cell]
#define BAT_LOW_DEAD 3.37 // undervoltage poweroff. (while not driving) [V/cell]

#define DC_CUR_LIMIT 15 // DC current limit in amps per motor. so 15 means it will draw 30A out of your battery. it does not disable motors, it is a soft current limit.

// Board overheat detection: the sensor is inside the STM/GD chip. it is very inaccurate without calibration (up to 45В°C). so only enable this funcion after calibration! let your board cool down. see <How to calibrate>. get the real temp of the chip by thermo cam or another temp-sensor taped on top of the chip and write it to TEMP_CAL_LOW_DEG_C. write debug value 8 to TEMP_CAL_LOW_ADC. drive around to warm up the board. it should be at least 20В°C warmer. repeat it for the HIGH-values. enable warning and/or poweroff and make and flash firmware.
#define TEMP_CAL_LOW_ADC 1655 // temperature 1: ADC value
#define TEMP_CAL_LOW_DEG_C 35.8 // temperature 1: measured temperature [В°C]
#define TEMP_CAL_HIGH_ADC 1588 // temperature 2: ADC value
#define TEMP_CAL_HIGH_DEG_C 48.9 // temperature 2: measured temperature [В°C]
#define TEMP_WARNING_ENABLE 0 // to beep or not to beep, 1 or 0, DO NOT ACTIVITE WITHOUT CALIBRATION!
#define TEMP_WARNING 60 // annoying fast beeps [В°C]
#define TEMP_POWEROFF_ENABLE 0 // to poweroff or not to poweroff, 1 or 0, DO NOT ACTIVITE WITHOUT CALIBRATION!
#define TEMP_POWEROFF 65 // overheat poweroff. (while not driving) [В°C]

#define INACTIVITY_TIMEOUT 8 // установка времени отключения если вы не используете включено устройство

// ############################### LCD DEBUG ###############################

Здесь вы включаете или выключаете работу дисплея ( дисплей 128х64 i2c ssd1306 или 1602 i2c
выводит напряжение и обороты каждого колеса.

//#define DEBUG_I2C_LCD // standard 16×2 or larger text-lcd via i2c-converter on right sensor board cable

// ############################### UART протокол ###############################

#define DEBUG_SERIAL_USART3 // PB10,PB11 disable if I2C (nunchuck or lcd) is used!
#define DEBUG_BAUD 115200 // UART
//#define DEBUG_SERIAL_SERVOTERM
#define DEBUG_SERIAL_ASCII // «1:345 2:1337 3:0 4:0 5:0 6:0 7:0 8:0\r\n» сам протокол

// ############################### INPUT ###############################

// ###### CONTROL VIA UART (serial) ######
//#define CONTROL_SERIAL_USART2 // left sensor board cable, disable if ADC or PPM is used!

скорость передачи общения между контроллером и Ардуино
#define CONTROL_BAUD 19200 // control via usart from eg an Arduino or raspberry

эту строчку надо вписать в ваш скетч для управления ардуино.
// for Arduino, use void loop(void){ Serial.write((uint8_t *) &steer, sizeof(steer)); Serial.write((uint8_t *) &speed, sizeof(speed));delay(20); }

// ###### использование PPM-SUMM######
// left sensor board cable. Channel 1: steering, Channel 2: speed.
//#define CONTROL_PPM // use PPM-Sum as input. disable DEBUG_SERIAL_USART2!
//#define PPM_NUM_CHANNELS 6 // количество каналов

// ###### CONTROL VIA TWO POTENTIOMETERS ######
// ADC-calibration to cover the full poti-range: connect potis to left sensor board cable (0 to 3.3V) (do NOT use the red 15V wire in the cable!). see <How to calibrate>. turn the potis to minimum position, write value 1 to ADC1_MIN and value 2 to ADC2_MIN. turn to maximum position and repeat it for ADC?_MAX. make, flash and test it.
#define CONTROL_ADC // use ADC as input. disable DEBUG_SERIAL_USART2!
#define ADC1_MIN 0 // min ADC1-value while poti at minimum-position (0 — 4095)
#define ADC1_MAX 4095 // max ADC1-value while poti at maximum-position (0 — 4095)
#define ADC2_MIN 0 // min ADC2-value while poti at minimum-position (0 — 4095)
#define ADC2_MAX 4095 // max ADC2-value while poti at maximum-position (0 — 4095)

// ###### CONTROL VIA NINTENDO NUNCHUCK ######
// left sensor board cable. keep cable short, use shielded cable, use ferrits, stabalize voltage in nunchuck, use the right one of the 2 types of nunchucks, add i2c pullups. use original nunchuck. most clones does not work very well.
//#define CONTROL_NUNCHUCK // use nunchuck as input. disable DEBUG_SERIAL_USART3!

// ############################### DRIVING BEHAVIOR ###############################

// inputs:
// — cmd1 and cmd2: analog normalized input values. -1000 to 1000
// — button1 and button2: digital input values. 0 or 1
// — adc_buffer.l_tx2 and adc_buffer.l_rx2: unfiltered ADC values (you do not need them). 0 to 4095
// outputs:
// — speedR and speedL: normal driving -1000 to 1000
// — weakr and weakl: field weakening for extra boost at high speed (speedR > 700 and speedL > 700). 0 to ~400

#define FILTER 0.1 // lower value == softer filter. do not use values <0.01, you will get float precision issues.
#define SPEED_COEFFICIENT 0.5 // higher value == stronger. 0.0 to ~2.0?
#define STEER_COEFFICIENT 0.5 // higher value == stronger. if you do not want any steering, set it to 0.0; 0.0 to 1.0
#define INVERT_R_DIRECTION
#define INVERT_L_DIRECTION
#define BEEPS_BACKWARD 1 // 0 or 1

//Turbo boost at high speeds while button1 is pressed:
//#define ADDITIONAL_CODE \
if (button1 && speedR > 700) { /* field weakening at high speeds */ \
weakl = cmd1 — 700; /* weak should never exceed 400 or 450 MAX!! */ \
weakr = cmd1 — 700; } \
else { \
weakl = 0; \
weakr = 0; }

// ###### МАЛЕНЬКАЯ МАШИНКА ######
// for better bobbycar code see: ссылка
// #define FILTER 0.1
// #define SPEED_COEFFICIENT -1
// #define STEER_COEFFICIENT 0

// #define ADDITIONAL_CODE \
if (button1 && speedR < 300) { /* drive backwards */ \
speedR = speedR * -0.2f; \
speedL = speedL * -0.2f; } \
else { \
direction = 1; } \
if (button1 && speedR > 700) { /* field weakening at high speeds */ \
weakl = speedR — 600; /* weak should never exceed 400 or 450 MAX!! */ \
weakr = speedR — 600; } \
else { \
weakl = 0; \
weakr = 0; }

// ###### КРЕСЛО ######
// #define FILTER 0.05
// #define SPEED_COEFFICIENT 0.5
// #define STEER_COEFFICIENT -0.2

// #define ADDITIONAL_CODE if (button1 && scale > 0.8) { /* field weakening at high speeds */ \
weakl = speedL — 600; /* weak should never exceed 400 or 450 MAX!! */ \
weakr = speedR — 600; } \
else {\
weakl = 0;\
weakr = 0;

// ############################### VALIDATE SETTINGS ###############################

#if defined CONTROL_SERIAL_USART2 && defined CONTROL_ADC
#error CONTROL_ADC and CONTROL_SERIAL_USART2 not allowed. it is on the same cable.
#endif

#if defined CONTROL_SERIAL_USART2 && defined CONTROL_PPM
#error CONTROL_PPM and CONTROL_SERIAL_USART2 not allowed. it is on the same cable.
#endif

#if defined DEBUG_SERIAL_USART3 && defined CONTROL_NUNCHUCK
#error CONTROL_NUNCHUCK and DEBUG_SERIAL_USART3 not allowed. it is on the same cable.
#endif

#if defined DEBUG_SERIAL_USART3 && defined DEBUG_I2C_LCD
#error DEBUG_I2C_LCD and DEBUG_SERIAL_USART3 not allowed. it is on the same cable.
#endif

#if defined CONTROL_PPM && defined CONTROL_ADC && defined CONTROL_NUNCHUCK || defined CONTROL_PPM && defined CONTROL_ADC || defined CONTROL_ADC && defined CONTROL_NUNCHUCK || defined CONTROL_PPM && defined CONTROL_NUNCHUCK
#error only 1 input method allowed. use CONTROL_PPM or CONTROL_ADC or CONTROL_NUNCHUCK.
#endif

есть еще один файл установки main но он для продвинутых пользователей его я разберу позже.

забегу немного в перед эта прошивка ссылка работает с RPiZeroW платформа для робота.
tkzbbj.jpeg

Пишите, спрашивайте что знаю то отвечу.


* 1ow004c.jpeg (290.12 кБ, 2000×1230 — просмотрено 21565 раз.)

MH-Z19B

 Arduino  Комментарии к записи MH-Z19B отключены
Авг 072019
 

Чтобы окончательно решить исход вековечной вражды в офисе между «теми, кому дует» и «теми, кому душно» решил разориться на бюджетный датчик содержания CO2 в атмосферном воздухе и прикрутить к нему сирену. Поскольку цена на такие датчики — совсем негуманная, выбрал вариант «MH-Z19B», который оказался самым бюджетным.

К сожалению, датчик подарил головоломку. Подробности — под катом.

(Внимание — в обзоре много «программизма», ругани в адрес китайских даташитов, присутствует шестнадцатеричная математика — так что если тема DIY вам не близка, проходите мимо, иначе будете разочарованы).

Датчик «MH-Z19B» сделан китайской компанией «Winsen» (даташит, PDF) и неоднократно упоминался на Habrahabr и Geektimes. Это вторая ревизия, с буквой «b» в названии, по результатам китайской «работы над ошибками». Первую ревизию одним неверным движением можно было ввести в режим калибровки, для которой требовалась атмосфера с нулевым содержанием CO2. Бедолагам, которые в это влетали, приходилось искать баллон с чистым азотом или кислородом, чтобы её организовать. В ревизии «b» китайцы сделали так, что модуль «эталонной» считает смесь с 400ppm углекислоты — то есть его можно, теоретически, перекалибровать просто в лесу или в парке.

Модуль пришёл в паре с небольшим шлейфом, основная плата имеет надпил, позволяющий отломать кусок с разъёмом и вместо этого припаять гребёнки. Модуль работает в двух режимах — UART (передавая показания по последовательному порту на 9600 бод) и PWM, контакты слева и справа, соответственно:

1183e04671.jpg

Для удобства я обрезал шлейф и насадил на провода дюпоновские наконечники. Правда, выяснилось, что часть проводов вообще ни к чему не подключена. Напротив, PWM оказался не выведен на шлейф, к контакту пришлось дополнительно подпаиваться, завернув из предосторожности датчик в плёнку поплотнее:

2cf726.jpg

Датчик работает по следующему принципу — он получает по UART девятибайтовые команды (последний байт — CRC) и отвечает также девятибайтовыми пакетами. Замер концентрации СО2 выполняется командой с байтом 0x86:

c71935.jpg

Также показания можно прочитать, померяв ширину PWM-сигнала:

2e5c04.png

Тут подстерегала первая проблема — как узнать текущую размерность измерений? Даташит упоминает, что датчик может мерять в диапазаонах от нуля как до 2000, так и от нуля до 5000ppm:

039492.jpg

Запросить текущее значение у датчика нельзя, поэтому остаётся только явно задавать размерность при каждом старте. Даташит описывает формат команды, которая скажет модулю, в какой шкале работать:

2a7f4c.jpg

Почему-то ни одна инструкция в интернете этого вопроса не касается — никто толком не интересовался настройками датчика и просто принимают их как данность. Кряхтим и пишем код для Ардуино, который пошлёт модулю нужную команду:

  • 2000 ppm: «2000» в десятичной системе это «0x07 d0» в шестнадцатеричной, получается — третий байт команды будет 0x07, четвёртый байт — 0xD0, CRC (девятый байт) 0x8F
  • 5000 ppm: третий байт 0x13, четвёртый байт 0x88, CRC (девятый байт) 0xCB

(CRC вычисляем по формуле из того же даташита, (NOT(Byte1 + Byte2 + Byte3 + Byte4 + Byte5 + Byte6 + Byte7)) +1)

Пробуем и так, и эдак, но в результате получаем на выходе дикие неправдоподобные значения концентрации СО2, вылетающие за паспортные диапазоны показаний датчика в разы. Уже испугавшись, что запороли дорогой прибор, через какое-то время нагугливаем ссылку revspace.nl/MHZ19 со словами

According to the MH-Z19B datasheet, you can configure the measurement range by putting the desired range in byte 3 and 4. However, unlike what the MH-Z19B datasheet says, you can set the range using the following command (in this case 0x07d0 = 2000 ppm in byte 6 and 7)

Замечательные китайцы ухитрились ошибиться в даташите! Материмся, и вместо третьего и четвёртого байта пишем в шестой и седьмой. Благодаря подсказке неизвестного голландца — модуль воскресает.

Поскольку теперь доверия модулю нет никакого, решил разобраться с его показаниями досконально и сравнить результаты по UART и PWM. Пишем код под Ардуино, который сначала, в блоке setup, даёт команду установки размерности, а потом в цикле loop делает замеры. Модуль располагаем на сквозняке у форточки.
Код (финальный, уже включающий все позднейшие *открытия*

Все замеры проводим, подключив модуль к питанию от 5 вольт; при 3.3 вольтах он выдаёт очевидно некорректные значения по верху диапазона.

В режиме PWM при заданном диапазоне значений от 0 до 2000ppm получаем, в условиях центра города, заполночь у форточки во двор, 1208ppm, что, безусловно, завышено. UART выдаёт нам близкое значение 1227ppm — различия вполне объяснимы ошибками оцифровки PWM-показаний.

Для сравнения, вот примерные дапазоны концентраций СО2, найденные в интернете:

— 350 — 450 ppm: Нормальный уровень на открытом воздухе.
— < 600 ppm: Приемлемые уровни. Уровень. рекомендованный для спален, детских садов и школ.
— 600 — 1000 ppm: Жалобы на несвежий воздух, возможно снижение концентрации внимания.
— 1000 ppm: Максимальный уровень стандартов ASHRAE (American Society of Heating, Refrigerating and Air-Conditioning Engineers) и OSHA (Occupational Safety & Health Administration).
— 1000 — 2500 ppm: Общая вялость, снижение концентрации внимания, возможна головная боль.
— 2500 — 5000 ppm: Возможны нежелательные эффекты на здоровье.

Переключаем датчик в диапазон 0 — 5000ppm. В режиме PWM у той же форточки во двор получаем 478ppm, что гораздо больше похоже на правду. Но вот в режиме UART наш датчик снова выдаёт совершенно неправдоподобное значение 1213ppm.

Десять раз перепроверив формулы, по близости показаний начинаю догадываться, что датчик считает с ошибками в арифметике. Модифицирую код, чтобы формула расчёта концентрации CO2 по данным PWM рассчитывалась с подстановкой всех вариантов верхнего предела значений. В момент какого-то озарения также дополнительно модифицирую код, чтобы значение, полученное по UART, дополнительно выводилось домноженным на 2000/5000:

диапазон 0 — 2000ppm

Range was set! (bytes 6 and 7) 1227 <- ppm (UART) 490 <- two fifths of it 606 <- Milliseconds PWM is HIGH 1208 <- ppm2 (PWM) with 2000ppm as limit 3020 <- ppm3 (PWM) with 5000ppm as limit

диапазон 0 — 5000ppm

Range was set! (bytes 6 and 7) 1213 <- ppm (UART) 484 <- two fifths of it 241 <- Milliseconds PWM is HIGH 478 <- ppm2 (PWM) with 2000ppm as limit 1195 <- ppm3 (PWM) with 5000ppm as limit

Делаем выводы:

  • в качестве рабочего диапазона работы датчика надо задавать 0 — 5000ppm
  • несмотря на это, для расчёта значений, полученных через PWM, надо в формулу подставлять константу 2000, а не 5000
  • для получения правдивых показаний от UART надо полученное значение умножать на 2/5
  • китайским даташитам, даже на вид добротным, верить нельзя
  • готовые скетчи для Ардуино из интернета брать нельзя
  • показания всех китайских «показометров» надо проверять хотя бы на соответствие здравому смыслу и внутреннюю непротиворечивость

Разбираемся с датчиками CO и метана MQ-4 и MQ-7

 Arduino  Комментарии к записи Разбираемся с датчиками CO и метана MQ-4 и MQ-7 отключены
Авг 062019
 

Разбираемся с датчиками CO и метана MQ-4 и MQ-7

20160814_100333В бытность появления у меня набора Arduino, в поисках объекта для автоматизации, я как-то сам собой задумался над тем, что неплохо бы получать информацию о том, не является ли опасным уровень CO (угарный газ) в зимнее время в котельной загородного дома. В холодные зимние деньки и особенно ночи, газовое оборудование работает в интенсивном режиме и жжет природный газ для поддержания теплоты в доме. А вдруг у меня плохая вентиляция? Или в трубе застрял валенок? И каждый раз входя в котельную и находясь там некоторое время, я подвергаю свою драгоценную жизнь опасности. Да и от утечек природного газа тоже никто не застрахован. Тут вообще можно полдома взорвать, просто включив свет. Их хорошо бы тоже контролировать и как-то отслеживать.

Поэтому было решено собрать систему по мониторингу уровня CO и метана в воздухе котельной на основе Arduino или совместимой платы. Помимо простой сигнализации, хотелось бы собирать еще и статистику, например, о том, как связаны концентрации опасных газов с работой газового оборудования. В принципе, задача реализуется на современном уровне культуры и техники, причем за очень небольшие деньги. В качестве источника расхода природного газа я использовал импульсы со встроенного в газовый счетчик датчика, а для анализа воздуха применил два чрезвычайно популярных в среде разработчиков Arduino датчика MQ-4 и MQ-7. MQ4 «нюхает» воздух на предмет содержания метана, а MQ7 проводит измерения в отношении CO.

Но для того чтобы пойти дальше, оказалось, что нужно конкретно углубиться в детали. Поскольку мало кто из пользователей Arduino и аналогов понимает, что это за датчики такие MQ-4 и MQ-7, и как ими вообще пользоваться. Ну так, приступим потихоньку к увлекательному повествованию.

Что такое ppm

Чтобы как следует оперировать со значениями, которые я буду приводить ниже, нужно для себя уяснить единицы измерений. У нас, на территории бывшего Советского Союза, показатели принято измерять в процентах (%) или же непосредственно в массе к объему (мг/м3). А вот в некоторых зарубежных странах применяет такой показатель как ppm.

Сокращение ppm расшифровывается как parts per million или в вольном переводе «частей на миллион» (хорошо, что тут не используют фунты на галлоны и империалы к саженям). В принципе, от процента показатель не сильно отличается, вернее, отличается только размерность. 1 ppm = 0,0001%, соответственно 3% = 30.000 ppm.

Перевод из процентов или ppm в мг/м3 уже сложнее, тут нужно учитывать молярную массу газа, давление и температуру. В целом формула для пересчета выглядит следующим образом P x VM=R x T, где P – давление, VM – молярный объем, R – универсальная газовая постоянная, T – абсолютная температура в Кельвинах (не Цельсиях и не Фаренгейтах). Но дабы не мучить читателя школьным курсом химии, сразу приведу несколько значений. А самые опытные бурители интернетов могут найти на просторах великой паутины онлайн-калькуляторы для самостоятельного расчета.

CO: 3% = 30.000 ppm = 34695.52 мг/м3
CO2: 3% = 30.000 ppm = 54513.22 мг/м3

Данные приведены для нормального атмосферного давления и комнатной температуры. Обратите внимание, что CO2 при сравнимом процентном соотношении почти вдвое тяжелее CO. Напомню, что молекула CO2 содержит на один атом больше, отсюда и разница. И именно благодаря этой разнице CO2 скапливается в низинах, а CO у потолка.

Различие СО и CO2

Для начала стоит разобраться что же такое есть CO и в чем его отличие от CO2. Во-первых, CO это монооксид углерода, который также называют угарным газом, окисью углерода или оксидом углерода (II). СО газ весьма коварный. Он чрезвычайно ядовит, но при этом не обладает ни цветом, ни запахом. Попав в помещение с угарным газом, вы только по косвенным симптомам поймете, что подвергаетесь воздействию яда. Сначала головная боль, головокружение, одышка, сердцебиение, потом посинение трупа. Угарный газ соединяется с гемоглобином крови, отчего последний перестает переносить кислород тканям вашего организма, и первым страдает головной мозг и нервная система.

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

Основным источником выделения СО, как ни странно, является сгорание углеродного топлива при недостаточном количестве кислорода. Углерод «не догорает» и вместо углекислого газа CO2, в атмосферу выбрасывается угарный газ CO. В бытовом понимании отличным источником СО, при неправильной эксплуатации, могут выступать дровяные печи, газовые конфорки, газовые котлы и прочая отопительная техника, работающая на углеродном топливе. Не стоит забывать и про автомобили, в выхлопе бензинового двигателя СО может быть до 3%, а по гигиеническим нормам его должно быть не более 20 мг/м³ (около 0,0017%).

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

Оборотная сторона датчиков MQ-4 и MQ-7 от RobotDyn (цифровые и аналоговые).

Оборотная сторона датчиков MQ-4 и MQ-7 от RobotDyn (цифровые и аналоговые).

CO2, он же диоксид углерода, углекислый газ, двуокись углерода, оксид углерода (IV) или просто угольный ангидрид, не менее интересный газ. С углекислым газом мы встречаемся гораздо чаще в повседневной жизни, нежели с угарным газом. Мы пьем газированную воду, в которой растворяют диоксид углерода. Мы пользуемся сухим льдом для сохранения мороженого в парке жарким летним полднем, мы, наконец, выдыхаем двуокись углерода в сумасшедших объемах. Да и природные объекты, типа вулканов, болот или свалок способны генерировать изрядное количество углекислого газа.

Но не стоит думать, что CO2 газ нежнее и безопаснее газа CO. Высокие концентрации CO2 приводят к не менее тяжелым последствиям, вплоть до летального исхода. А поднять концентрацию можно легко и непринужденно всего лишь закрыв форточку в спальне на ночь. Более того, в отличие от CO, угольный ангидрид тяжелее воздуха и опасно скапливается в низинах, подвалах, подполах и прочих неожиданных местах. Документально зафиксированы случаи гибели людей, случайно попадающих в лощины полные углекислого газа, натекшего из соседнего вулкана. Двигатель автобуса глохнет, воздуха начинает не хватать и все. CO2 газ тоже без цвета, запаха и вкуса, посему его наличие определить органолептически почти и невозможно, кроме как контролировать наступление явно выраженного удушья.

И тот и другой газы состоят всего из двух видов элементов. Из кислорода (О) и углерода (С), вопрос только в количестве атомов кислорода. Знающий читатель может догадаться, что один газ в другой может превращаться с легкостью необыкновенной. Да, может, но не совсем с легкостью и не совсем обыкновенной. Нужно прилагать усилия. Так, например, в каталитических нейтрализаторах современных бензиновых автомобилей происходит процесс дожигания (превращения) CO в CO2. Процесс проходит при высокой температуре и в присутствии катализаторов (например, платины). Возможен и обратный процесс, но опять же непростой.

Кстати, в интернет есть сайт CO2.Earth отображающий динамику и текущую концентрацию углекислого газа в атмосфере Земли. Да, концентрация не такая уж и низкая. Ведь при скоплении углекислого газа в районе 2-4% человек теряет работоспособность, чувствует сонливость и слабость. А при концентрациях около 10% начинает ощущаться удушье.

Мы немного отклонились от темы, но вывод тут такой: не стоит путать два различных газа, равно как и последствия от них, но контролировать их присутствие в атмосфере помещений однозначно стоит.

Конструкция электрохимических датчиков

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

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

Вот и состоит электрохимический датчик из некой подложки с чувствительным материалом, нагревателя подложки и собственно выводных контактов. Сверху на датчик натянута металлическая сетка, все же подложка ощутимо греется, да и всяческие газы горючие могут быть вокруг датчика, тот же CO. Для этого сетка и требуется. Безопасность превыше всего. Кстати, натягивать сетку на опасные элементы при применении во взрывоопасных средах придумал некий Гумфри Дэви еще для шахтеров в начале IXX века.

Схема электрохимического датчика

Схема электрохимического датчика

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

Будучи по натуре любопытным персонажем, я покопался в спецификациях HANWEI и свел все доступные датчики серии MQ, материал подложки и тип детектирования в единую таблицу.

Датчик Газ Подложка
MQ-2 LPG SnO2
MQ-3 Alcohol SnO2
MQ-4 CH4 SnO2
MQ-5 LPG, natural gas SnO2
MQ-6 LPG, propane SnO2
MQ-7 CO SnO2
MQ-9 CH4, LPG SnO2
MQ-131 O3 SnO2
MQ-135 Air Quality SnO2
MQ-136 Air Quality SnO2
MQ-137 Air Quality SnO2
MQ-138 Multi-purpose SnO2
MQ-303A Alcohol ???
MQ-306 LPG, LNG ???

За исключением 300-й серии датчиков MQ все они используют один и тот же материал для подложки. Именно для той самой подложки которая и определяет концентрацию газа в атмосфере, именно для той подложки, которая меняет свое сопротивление. Во всех датчиках она используется одна и та же. У 300-й серии информация о чувствительном материале скромно опущена.

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

Прошу заметить, что все датчики имеют определенный и весьма небольшой срок жизни, который составляет порядка 5 лет. Причем 5 лет — это не только непосредственно работа, но и хранение. А если ваш датчик хранится без соответствующей упаковки, то срок его годности еще меньше. Дело в том, что чувствительный химический элемент, без нагрева, будет насыщаться углеродом, который постепенно его весь и разрушит. Именно по этой причине новые датчики рекомендуется «прокаливать» держа в рабочем состоянии на протяжении суток, а еще лучше двух. Тот углерод, что успел въесться в оксид олова (IV) «выгорит» и датчик сможет определять показания с более высокой точностью.

Если приглядеться к списку измеряемых газов или назначению датчиков, то видно, что все они, так или иначе, завязаны на углерод (метан, природный газ, пропан, угарный газ, сжиженный газ, алкоголь и даже датчики качества воздуха измеряют наличие углерода в соединениях в воздухе). И только датчик озона (MQ-131) стоит особняком, хотя и использует тот же самый чувствительный элемент с SnO2. Дело в том, что все датчики серии MQ рассчитаны на работу в атмосфере со стабильным уровнем кислорода. Спецификация говорит нам, что содержание кислорода должно быть 21%, что есть некая усредненная норма. А если кислорода меньше или больше, то показания будут плавать, вплоть до полной неспособности датчика выдавать вразумительные результаты при содержании кислорода на уровне 2% и ниже. Еще бы, в этом случае углерод совсем никак выгорать на подложке не будет, окислителя-то недостаточно. Видимо, на этом эффекте и рассчитано измерение озона электрохимическим датчиком.

Зависимость точности показаний датчика от влажности и температуры

Зависимость точности показаний датчика от влажности и температуры

Но точность показаний датчиков серии MQ зависит не только от кислорода. Показания хорошо меняются в зависимости от влажности воздуха и от его температуры. Расчетные показатели даны для влажности в 65% и температуры 20 градусов Цельсия. А при влажности выше 95% датчик перестанет адекватно выдавать показания. Жалко, что вот только не указана в спецификации какая влажность используется: относительная или абсолютная. Интуиция подсказывает, что все же относительная.

Помимо показателей окружающей среды на точность показаний датчиков MQ не хуже остальных параметров влияет еще и срок службы самих датчиков. Со временем их показания плывут. «Засоряется» продуктами измерения чувствительный слой, изменяются характеристики нагревателя и изменяется сопротивление при эталонных показателях. В какую сторону оно изменяется непонятно, но производитель рекомендует, во-первых, проводить калибровку датчика после покупки и первичного «отжига», а затем проводить регулярные перекалибровки на протяжении всего срока службы датчика. А единственный нормальный способ калибровки — сравнение результатов показания датчика с уже откалиброванным прибором. Понятное дело, что такого прибора нет ни у конечного потребителя-частника (а профи будут использовать несколько другие датчики, подороже), ни у многих производителей плат. Некоторые производители об этом заявляют честно прямо на своем сайте:

Заявление о невозможности нормальной калибровки датчиков

Заявление о невозможности нормальной калибровки датчиков

«И как же мне узнать, какова концентрация того или иного газа при помощи сенсора MQ?» — вопросит нетерпеливый читатель? Поскольку в большинстве случаев потребитель использует измеритель напряжения, впрочем с сопротивлением все аналогично, но меньше на один шаг, то у потребителя существует потребность в том, как вольты или кванты ЦАПа Arduino перевести в заветные ppm или хотя б проценты. Проделать сию операцию можно исключительно при помощи невнятных графиков из спецификации на датчик.

График: сопротивление в зависимости от концентрации газов.

График: сопротивление в зависимости от концентрации газов.

Взглянув на график из спецификации видно, что, во-первых, в нем есть как минимум одна логарифмическая область. А, во-вторых, помимо основного газа, датчик преспокойно улавливает еще и все остальные схожие (углеродсодержащие). Разобраться с графиком и понять какое ppm соответствует какому сопротивлению датчика — занятие для практикующих самураев, поскольку прямая пересекающая несколько разных логарифмических зон явно будет не прямой в реальности.

График зависимости сопротивления датчика MQ-7 от концентрации исследуемых газов.

График зависимости сопротивления датчика MQ-7 от концентрации исследуемых газов.

На этом хочется подвести промежуточный итог. Итак, к плюсам датчиков серии MQ можно отнести их крайне и категорически демократичную цену. А вот минусов намного больше:

  • Фактически идентичные датчики использующие один и тот же чувствительный элемент и различающиеся используемым номиналом подстроечных резисторов.
  • Зависимость результатов измерения от множества факторов: температуры, влажности, концентрации кислорода.
  • Отсутствие заявляемой селективности по измеряемым газам, реагирует на все с углеродом (а, вполне возможно, и на другие элементы вступающие в реакцию с подложкой).
  • Высокое энергопотребление (нагреватель).
  • Необходимость в первичном «отжиге» датчика.
  • Нестабильность показаний по времени.
  • Необходимость первичной и повторяющейся калибровки.
  • Практическая невозможность получения осмысленных значений в виде ppm или %.

Ну что же, пойдем дальше.

Цифровой или аналоговый?

Рынок знает свое дело и если на какой-то продукт есть спрос, то этот спрос будет удовлетворен. Рано или поздно, но будет обязательно. А с использованием шустрых китайских товарищей спрос удовлетворяется скорее рано, чем поздно. Так и появилось великое множество производителей с Китаю, производящие готовые платы с электрохимическими датчиками серии MQ. Давайте рассмотрим по возрастающей, какие могут быть вообще варианты поставки.

Чистый датчик

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

Датчик MQ-4

Датчик MQ-4

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

Аналоговый датчик

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

Аналоговый датчик

Аналоговый датчик

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

Цифровой датчик

Казалось бы, если датчик цифровой, то он должен выдавать информацию в цифровом виде. Однако, все цифровые датчики с сенсорами MQ, что мне попадались, не имели такой возможности. «Цифровой» в их названии означает только то, что датчик имеет цифровой выход, который переключается в режим HIGH при превышении некоего порога концентрации измеряемого газа. А основной съем значений пользователь осуществляет тем же самым аналоговым способом, как и с обыкновенным аналоговым датчиком.

Датчик с цифровым и аналоговым интерфейсом

Датчик с цифровым и аналоговым интерфейсом

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

Цифровой датчик с цифровой шиной

Пожалуй, это самый Hi End среди подобных датчиков. Подключение и передача данных осуществляется посредством цифровой шины I2C. И к одному устройству съема информации (например, Arduino) можно подключить аж сотню таких датчиков. Только нужно иметь в виду, что датчики потребляют весьма много тока и его необходимо подавать отдельно. Настроечный резистор, само собой, присутствует.

Цифровой датчик с цифровым интерфейсом

Цифровой датчик с цифровым интерфейсом

Судя по коду примера, предлагаемого производителем датчиков, сам датчик посылает данные в сыром виде и уже программно они переводятся в значения ppm. В целом от аналогового варианта датчик отличается только наличием цифровой шины.

Питание

Выше я уже упоминал, что для работы нагревателя датчиков MQ требуется подводить к нему качественное питание и в достаточно объеме. По спецификации датчики потребляют около 150 мА. В реальности потребление может плавать в весьма широком пределе. В принципе, 150 мА не такой уж и большой ток до тех пор, пока устройство (или несколько) с таким потреблением не пытаются скрестить с чем-то вроде Arduino. Подключив даже один такой датчик к питанию на плате, уже рискуешь получит неработоспособное устройство, которому не будет хватать напряжения для нормальной работы. При работе сами сенсоры нагреваются, не существенно, но градусов до сорока вполне могут раскочегариться. Если сравнить эту температуру с 60-70 градусами на стабилизаторе, питающем эти датчики, то температуру сенсоров можно считать сносной.

Для обеспечения нормальной работоспособности нагревателя и как следствие самого датчика необходимо подавать питание отдельно для этих датчиков. Например, использовать независимый источник питания на 1 или 2 А и 5V для питания датчиков (не все датчики потребляют 5V). Либо использовать специальную плату, преобразующую напряжение 9-12V в требуемое для питания датчиков.

Дишманский пульт мониторинга CO из платы Arduino, датчика MQ7 и планшета.

Дишманский пульт мониторинга CO из платы Arduino, датчика MQ7 и планшета.

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

Вариант калибровки датчика и преобразования показаний в ppm

Блуждая по сети в поисках решения по калибровке и получения достоверных результатов с датчика, я наткнулся на весьма любопытный пост от некоего Davide Gironi, который столкнулся с точно такой же проблемой, как и я. Davide попытался разобраться, каким образом можно получить с его датчика MQ-135 (Air Quality) показания в виде ppm.

Согласно исследованиям, проведенным блоггером для калибровки, достаточно иметь представление о концентрации какого-то газа в атмосфере и опираясь на эти данные попробовать подобрать резистор для попадания в нужный сектор по графику. Davide использовал датчик MQ-135 который предназначен для определения качества воздуха, среди контролируемых газов которого есть и CO2. И именно углекислый газ больше всего интересовал блоггера. Используя информацию с сайта co2now.org, он смог вычислить требуемый номинал резистора. Согласитесь, что метод весьма далек от идеала, но все равно лучше, чем ничего.

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

float ppm = ((10000.0 / 4096.0) * raw_adc) + 200;

Приведенный выше код, между прочим, из примера для датчика MQ-4 с цифровым интерфейсом I2C. Заметьте, что это лучше, чем ничего. Ведь многие просто не в состоянии дойти и до такого преобразования и ограничиваются лишь просто некими пороговыми значениями. Например, при значении 750 (единица измерений отсутствует, это квант), нужно врубать красный светодиод, в диапазоне 350-750 достаточно желтого, а когда ниже 350 пускай горит зеленый светодиод.

Альтернативы?

Если датчики MQ так уж плохи, то есть ли какая альтернатива для использования в домашних проектах? На самом деле есть. Даже много. Методов измерения концентрации газов не один и не два. Только вот датчики, обладающие высокой точностью, стоят приличных денег. И порой от такой стоимости наступает амфибиотропная асфиксия. Разница в стоимости может достигать тысячи и десятки тысяч раз. Тут невольно призадумаешься.

Один из вариантов миниатюрного датчика CO2 с инфракрасным методом измерения. Датчик MH-Z19.

Один из вариантов миниатюрного датчика CO2 с инфракрасным методом измерения. Датчик MH-Z19.

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

Если же требуется детектирование других газов, но с применением недорогих устройств, то доступных вариантов на текущий момент (лето 2016 года) не так много, если не сказать прямо, что их совсем мало. Альтернативой можно считать использование серии MQ, правда обходиться придется только порогами значений (о точности перевода в ppm я уже высказался выше).

Сигнализатор CO. Применяется электрохимичский датчик и измерение в ppm.

Сигнализатор CO. Применяется электрохимичский датчик и измерение в ppm.

Многие сразу же возразят, дескать, я лично использовал такой датчик, и он работает. В качестве примеров приводят опыты сродни «подышать на датчик», подержать вокруг него руку, пустить облачко сигаретного дыма. Да, показания датчика сразу же изменятся, значения поползут вверх. Да, датчик отразит то, что он нагрелся, то что увеличилась влажность, то, что в атмосфере стало больше углерода и меньше кислорода. Но насколько больше, какое количество исследуемого газа сейчас в атмосфере и самое важное какого именно газа? Вот на этот вопрос ответ при помощи датчиков серии MQ дать уже нельзя. Лучше уж приобрести обыкновенный бытовой сигнализатор опасных газов, того же СО. За вполне сопоставимые деньги вы получите устройство заводского исполнения, с громкой сигнализацией и низким потреблением энергии.

Датчики близнецы

И в завершение я хочу подвести итог. Я расстроен тем, что такие доступные по цене датчики никоим образом не могут быть использованы в каком-либо более-менее серьезном проекте. Да, можно потренироваться в программировании и в подключении датчиков, но вот искомые достоверные значения, получить с их помощью уже не выйдет. И ценность датчиков очень скоро устремится к нулю.

Более того, я лично убежден, что все датчики MQ не имеют достаточного уровня селективности, отличаются только внешним дизайном и рекомендациями по подбору резисторов. Датчики реагируют на все содержащее углерод и тем сильнее реагируют, чем более активен углерод в соединении и чем он легче вступает в реакцию с подложкой. Я не верю, что производитель добавляет в подложку дополнительные элементы, повышающие селективность и при этом ничего не пишет в спецификацию. Зато я предполагаю, что один датчик можно превратить в другой, путем использования разных резисторов и смотрения на графики сопротивления и концентрации.

Графики показаний с датчиков MQ-4 и MQ-7 лежащих рядом друг с другом на столе

Графики показаний с датчиков MQ-4 и MQ-7 лежащих рядом друг с другом на столе

А ведь все началось с того, что я подключил два датчика (MQ-4 и MQ-7) к одному устройству и начал заливать результаты их работы на ThingSpeak. Один из датчиков должен измерять уровень ядовитого СО, а второй показывать сколько есть в воздухе метана. Меня очень заинтересовали графики, которые повторяли друг друга больше чем почти полностью. Да, один датчик выдавал показания на уровне 100-150 единиц, а второй на уровне 350-400. Пики и плато совпадали по времени от разных датчиков, а всплески лишь оттеняли неминуемую закономерность.

График корреляции показаний датчиков MQ-4 и MQ-7 лежащих на одном столе

График корреляции показаний датчиков MQ-4 и MQ-7 лежащих на одном столе

Я свел показания обоих датчиков в единый график корреляции и понял, что они показывают одни и те же результаты, правда в разных диапазонах. И задался вопросом – зачем мне датчик метана, который реагирует на все? Начиная от угарного газа и заканчивая алкоголем. Зачем мне датчик СО, который помимо самого СО еще больше реагирует на LPG и водород? Вот именно – незачем.

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

датчик, газ, Mq, mq4, mq-4, спиралька

Внутренности датчика MQ-4

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

mq7, датчик, mq-7, mq, газ, анализатор, ардуино

Внутренности датчика MQ-7

Несмотря на другой внешний вид, внутренности MQ-7 идентичны внутренностям MQ-4. А нагреваемая бобышка сероватого цвета, есть ни что иное, как искомый оксид олова, который при нагревании и присутствии углерода или водорода (как раз те самые газы) частично восстанавливается, стремясь стать металлическим оловом, и соответственно изменяет свое сопротивление.

Пример простой программы на Си для микроконтрол лера AVR

 Arduino  Комментарии к записи Пример простой программы на Си для микроконтрол лера AVR отключены
Июл 172019
 

Язык Си (без ++) — один из основных языков для программирования микроконтроллеров, поскольку здесь требуется высокая скорость, а оперативной памяти не бывает много.

Пример простой программы на Си для микроконтроллера AVR

Это текст программы компиляторов типа AvrStudio, CodeVisionAVR и т.п.

 #include // заголовочный файл для ввода-вывода #include #define BV(x) (1 << x) int main(void) { DDRC=0xFF; // порт PORTC настроен на выход PORTC=0xFF; // установка уровней на порте PORTC while(1) // начало цикла { PORTC=~(BV(5)); // переключение 5-го бита порта PORTC _delay_ms(1000); // задержка 1 секунда PORTC=BV(5); // переключение 5-го бита порта PORTC обратно _delay_ms(1000); } return 0; }  

Функция main — это точка входа в программу, с которой компьютер начинает выполнение программы.

Допускается из main возвращать void, хотя это не по стандарту, так что лучше int.

В функцию main можно передавать аргументы командной строки:

int main(int argc, char* argv[]) { }  

Вообще говоря, мы можем писать программу для MK AVR также на языке Processing/Wiring. Это тот же Си, но упрощенный. Но компилироваться это будет только в Arduino IDE или т.п., а потом можно загружать полученный hex в наш микроконтроллер. При этом не обязательно, чтобы МК стоял на плате Arduino. Разницы то нет.

Вот так выглядит аналогичная программа на Processing/Wiring:

int ledPin = 13; void setup() { pinMode(ledPin, OUTPUT); } void loop() { digitalWrite(ledPin, HIGH); delay(1000); digitalWrite(ledPin, LOW); delay(1000); }  

Здесь не надо подключать хеддеры для МК, т.к. они подключатся автоматом. Но для внешних модулей могут понадобится. Короче, про Ардуино подробнее читайте здесь

http://ar.com/basic/uno

а пока мы вернемся к языку Си.

Общая структура памяти программы на Си

— куча — для динамического выделения памяти

— стек — локальные переменные класса памяти auto (включая аргументы функций)

— DATA — константы

— CODE — исполняемый код, инструкции процессора

Типы данных в Си

-Базовые типы данных: char, int, float, double.

-Модификаторы знака: signed, unsigned.

-Модификаторы знака: long, short.

void — тип без значения

При этом следущие типы равны:

int = signed int = signed // 16 или 32 бит (зависит от платформы ) unsigned = unsigned int char = signed char	// 8 бит (от -128 до 127) (ASCII) unsigned char // 8 бит (от 0 до 255) wchar_t // UNICODE  

В Си логический тип реализован неявно (с помощью int): false = нуль, true = не нуль.

Введение псевдонимов для ранее описанных типов данных:

typedef тип имя

где тип — любой существующий тип данных, имя — новое имя для этого типа.

Пример: typedef unsigned char byte;

Преобразование типов:

Если операнды операции имеют разные типы, то происходит неявное приведение типов:

double a = 1.222; int i = a; // дробная часть отсекается! double x = 2/5;	// результат будет 0 !  

(чтобы здесь получить 0.4 нужно было бы написать x=2.0/5 или 2/5.0)

Явное приведение типов:

int a=2, b=5; double b = (double)a / b;	// результат будет b=0.4  

Принудительное преобразование типов:

(тип) выражение;

(желательно вообще избегать преобразования типов)

Переменные и константы

Переменная представляет собой блок памяти, на который мы ссылаемся по её имени (идентификатору).

Декларация переменных (вместе с инициализацией):

[класс памяти] [квалификаторы] [модификаторы] тип идентификатор = инициатор;

Например,

static const unsigned char x = 100;  

Здесь «;» — составляющая часть конструкции, завершающая часть.

Допустима (хотя и редко используется) запись: const x = 100; (по умолчанию int).

Квалификаторы (или «модификаторы доступа»): const, volatile.

const — означает, что переменные не могут изменяться во время выполнения программы; инициалиировать можно только при декларации;

volatile — содержимое переменной может измениться само собой (используется в многопоточных программах при взаимодействии процессов)

Возможен вариант const volatile, когда писать могут только снаружи.

Спецификторы хранения (описатель класса памяти): auto, register, extern, static.

auto — локальные переменных (по умолчанию) — программный стек.

register — просьба компилятору положить переменную в регистр ЦПУ (но он эту просьбу редко выполняет);

extern — объявление (declaration) переменных, но не определение (definition) (определение где-то в другом месте); определение может идти ниже по файлу (но как глобальная) или в другом файле.

static — статические локальные переменные, которые хранят своё значение между вызовами функций (они предпочтильнее, чем глобальные переменные). Статические глобальные переменные видны только в пределах данного файла.

Внешние и статические объекты существуют и сохраняют свои значения на протяжении всего времени выполнения программы.

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

Описание области действия идентификаторов (имен):

— внутреннее (локальное) — внутри блока {…}

— внешнее (глобальное) — вне всех блоков

Идентификатор, описанный внутри блока, известен только в этом блоке (локальный идентификатор).

Идентификатор, описанный на самом внешнем уровне, известен от места появления этого описания до конца входного файла, в котором он описан (глобальный идентификатор).

Стоит избегать использования глобальных имен.

Переменные с классом памяти static видны только в пределах текущего блока (для локальных) или в пределах файла (для объявленных глобально).

Статические переменные хранятся в сегменте данных (data) и по умолчанию инициализируются нулем. Т.е. память под static-переменные выделяется при старте программы и существует до конца программы.

Замечание: Инициализация выполняется одни раз при выделении памяти!

void f(void) { static int a = 1; /* - это инициализация (но не присваивание!), т. е. переменная инициализурется единицей только один раз при старте программы! (или 0 по умолчанию, т.е. если бы было просто static int a;) */ a++; }  

Статическими могут быть также функции. Такая ф-ция может исп-ся только внутри данного файла.

Следует различать присваивание и инициализацию:

— Присваивание: имя_переменной = выражение;

— Многочисленное присваивание: x = y = z = 0;

— Инициализация переменных: тип имя_переменной = константа;

Константы

Константы являются частью машинных команд и под них память не выделяется.

Константы бывают:

— целые:

10-я система: 127; -127; +127;

8-я система: 0127; (начинается с нуля — значит 8-ричная!)

16-я система: 0x7F; (x или X, f или F — регистр не влияет)

— вещественные: 3.14; 2. ; .25 (0 можно опускать); 2E3; 2e3; 2E-3; 2.0E+3;

— символьные: 8-битные ASCII: ‘A’, ‘=’, ‘\n’, ‘\t’, ‘\370’, ‘\xF8’ (символ градуса);

— строковые литералы (в двойных кавычках): «Hello, world!\n». Строки заканчиваются нулевым байтом — ‘\0’.

Макроопределения:

 #define WIDTH 80	//(подробнее ниже)  

Операции и операторы

Оператор (инструкция, англ. statement) — это единица выполнения программы.

В языке Си любое выражение, заканчивающееся символом «точка с запятой» (;), является оператором.

Фигурные скобки { } — это составной оператор.

Например,

{y = x; x++;}  

Кроме того { } является отдельным блоком и в нем можно определять локальные переменные.

; — пустой оператор.

Операции:

— Арифметические операторы: — + * / %

— Инкрименты и декрименты: ++a, —a, a++, a— (могут выполняться быстрее)

— Операторы сравнения (отн-ний): > >= < <= == != (возвращают 1 или 0)

— Логические операторы: && || ! (возвращают 1 или 0)

— Битовые операторы: & | ^ ~ >> <<

— Оператор ?: x ? y : z, напр.: r = 10>9 ? 100 : 200

sizeof — унарный оператор для вычисления размера переменной или типа

, — оператор запятая (последовательное вычисление): a = (b=3, b+2);

Порядок выполнения операторов:

— Унарные операторы выполняются справа-налево.

— Бинарные выполняются слева-направо.

— Присваивание выполняется справа-налево.

Порядок можно менять с помощью скобок!

Примеры:

a=10; r=!!a==a; // результат будет 0, т. к. !!a вернет 1;  

Выражение а + b + c интерпретируется как (а + b) + с.

r = (2==2==2); // результат будет 0, т. к. 2==2 вернет 1 и сравнит с 2; - и вычисляется слева направо. 5 < 3 < 2;	// результат будет 1, т. к. 5<3 вернет 0; a = b = c = 2;	// сначала c=2, потом b=c, потом a = b;  

sizeof() — возвращает длину в байтах переменной или типа; sizeof(int); sizeof(a);

sizeof выражение; — само выражение не вычисляется.

Оператор запятая:

x = (y = 3, y+1);  

левая сторона оператора вычисляется как void и не выдаёт значения, переменной x присвается значение выражения в правой стороне, т.е. y+1.

Указатели и ссылки в Си

& — оператор «получение адреса» объекта;

* — доступ к значению объекта по указанному адресу;

p = &number; // - получение адреса переменной number; q = *p; // - получение значения переменной по указанному адресу: st.a; // - обращение к полю структуры st; pst->a;	// - обращение к полю структуры по её указателю;  

Указатели:

Указатель

Форматный вывод на Си для микроконтроллеров.

 Arduino  Комментарии к записи Форматный вывод на Си для микроконтроллеров. отключены
Июл 172019
 

Форматный вывод на Си для микроконтроллеров.

Алгоритмы и программные решения
Форматированный ввод-вывод применяется очень широко, в первую очередь это, конечно, взаимодействие с пользователем, а так-же отладочный вывод, логи, работа с различными текстовыми протоколами и многое другое. В этой статье рассматриваются особенности применения форматированного вывода (ввод оставим на потом) для микроконтроллеров.
Первая программа написанная для ПК традиционно называется «Hello, world» и естественно пишет в стандартный вывод эту знаменитую фразу:

#include <stdio.h> int main(){ printf("%s", "Hello, world"); return 0; }

Первая программа для микроконтроллера обычно зовётся «Blinky» и она просто мигает светодиодом. Дело в том, что заставить работать традиционный «Hello, world» на микроконтроллере не так уж и просто для начинающего. Во первых, нет стандартного устройства вывода и его функциональность ещё нужно реализовать. Во вторых, не всегда бывает очевидно как подружить стандартные функции вывода с целевым устройством. Ситуация усугубляется тем, что в каждом компиляторе (тулчейне) это делается каким-то своим способом.

Форматный вывод.

Что-же, в общем, за зверь такой форматный вывод? Упрощенно говоря, это — вывод значений различных типов в виде текстовых полей.

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

Требования и особенности ввода-вывода для МК.

1. Гибкость. В отличии от старших братьев, для МК нет и не может быть стандартного устройства ввода-вывода. В каждой системе будет что-то своё, с уникальными требованиями и особенностями. В одной системе будет вывод в USART, во второй — на ЖК дисплей, в третей — в файл на SD карточке, в четвёртой — всё сразу. Значит система форматированного ввода-вывода должна быть достаточно гибкой, настраиваемой и независимой от аппаратных особенностей целевой платформы.

2. Требования к ресурсам. В МК ресурсов бывает мало и очень мало. В идеале, в прошивку МК должна попасть только та функциональность, которая реально используется. Скорость и размер кода имеют значение, под час решающее. Если мы не используем вывод типов с плавающей точкой, то и в полученной прошивке не должно быть кода, который его осуществляет.

3. Стойкость к ошибкам кодирования. В идеале, хорошо было-бы если ошибка кодирования приводила бы сразу к ошибке компиляции, ну или по крайней мере к предупреждению. Чтоб не надо было заливать программу в железо и ходить отладчиком вылавливать место, где там, в функцию предался неправильный аргумент.

4. Доступность. Библиотека ввода-вывода должна быть в наличии для целевой платформы.

5. Функциональность часто ставится на последнее место в угоду скорости и компактности.

Стандартная библиотека Си.

Стандартным и единственно доступным средством форматированного вывода в Си является семейство функций: printf, sprintf, snprintf и fprintf.
Рассматривать будем функцию printf, как наиболее типичного и часто используемого представителя функций форматного вывода, при необходимости переходя к другим. Итак фунция printf имеет следующий синтаксис:

int ptintf(const char *fmt, ...);

Это обычная функция с переменным числом параметров. Здесь первый аргумент fmt является форматной строкой, которая содержит, как обычный текст, так и специальные управляющие последовательности. Троеточие (…) обозначает список дополнительных параметров, их может быть от нуля и до сколько поместится в стеке. Да, да все параметры в функцию printf передаются преимущественно в стеке (за исключением архитектуры ARM, где первые 4 параметра передаются в регистрах). Запихивает их в стек вызывающая функция, она-же инициализирует кадр стека перед вызовом и очищает после. Сама printf узнать сколько и каких параметров ей было передано может узнать только из форматной строки. При передачи параметров размером меньше (signed char, unsigned char, char и на некоторых платформах signed/unsigned short), чем int, они будут расширенны соответствующим расширением(знаковым или без-знаковым) до int-a. Это сделано для того, чтобы стек был всегда выровнен по границе машинного слова, а так-же уменьшает количество возможных ошибок при нестыковке размера фактического параметра и ожидаемого из форматной строки. Так-же параметры типа float при передаче в функции с переменным числом аргументов, приводятся к типу double.
Форматная строка содержит как символы непосредственно выводимые в поток, так и специальные управляющие последовательности, которые начинаются со знака «%». Управляющие последовательности имеют следующий формат:

%[флаги][ширина][.точность|длинна]спецификатор[дополнительный модификатор]

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

Флаги определяют дополнительные параметры форматирования (их может быть несколько):
Таблица 2. Флаги.

Ширина — десятичное число, задаёт минимальное количество символов выводимых для соответствующего параметра. Если выводимое значение содержит меньше символов, чем указано, то оно будет дополнено пробелами (или нулями если есть флаг «0») до нужной ширины слева или справа, если указан флаг «-«. Например:

printf("a =%10d", 123); Будет выведено ("_" - подчерк считать пробелом): a_=_ _ _ _ _ _ _123 printf("a =%-10d", 123); a_=123_ _ _ _ _ _ _ printf("a =%010d", 123); a_=0000000123 

Если вместо десятичного числа указать «*», то значение ширины будет считанно из дополнительного целочисленного параметра. Это позволяет задавать значение ширины поля вывода из переменной:

for(int i=1; i < 10; i++) printf("i =%0*d", i, i);

Здесь первый раз i передаётся в качестве ширины поля, второй — значения.
Точность или длинна — десятичное число после знака точки «.». В случае вывода целых чисел этот элемент означает минимальное количество записанных знаков, если выводимое число короче указанной длинны, то оно дополняется ведущими нулями, если число длиннее, то оно не урезается.

printf("a = %.6d", 123); a = 000123

Таким образом есть уже два способа вывести целое с нужным количеством ведущих нулей.
Для чисел с плавающей точкой в форматах «e», «E» и «f» этот элемент означает число знаков после десятичной точки. Результат округляется.

printf("f = %.2f", 12.3456); f = 12.35

Для форматов «g» и «G» это — общее количество выведенных значимых цифр.

printf("f = %.2g", 12.3456); f = 12

Для строк «s» этот элемент называется длинна и означает максимальное количество выведенных символов, обычно строки выводятся пока не встретится нулевой завершающий символ.

printf("%.5s\n", "Hello, world"); Hello

Также как и в элемента «ширина», в место точности можно поставить звёздочку и передать её значение в виде дополнительного целочисленного параметра:

char str[] = "Hello, world"; for(unsigned i = 1; i < sizeof(str); i++) printf("%.*s\n", i, str); H He Hel Hell Hello Hello, Hello, Hello, w Hello, wo Hello, wor Hello, worl Hello, world

Дополнительный модификатор служит для указания размерности типа:
h — применяется со спецификаторами i, d, o, u, x и X для знаковых и без-знаковых коротких целых short и unsigned short).
l — совместно со спецификаторами i, d, o, u, x и X означают длинные целые long и unsigned long).
l — совместно со спецификаторами s и c «широкие» многобайтные строку и символ соответственно.
L — обозначает тип long double, применяется со спецификаторами e, E, f, g и G.
В компиляторах поддерживающих тип long long, таких как GCC и IAR, часто есть для него нестандартный модификатор ll.
В стандарте С99 добавлены модификаторы «t» и «Z» для типов ptrdiff_t и size_t соответственно.

Работа над ошибками

Основным недостатком функций семейства printf считается вовсе не громоздкость и неторопливость — размер кода и накладные расходы на запихивание параметров в стек и разбор форматной строки обычно считаются приемлемыми, а подверженность ошибкам кодирования. Самое неприятное в этих ошибках то, что они могут быть неочевидными и проявляться не всегда, или не на всех платформах.
Большинство ошибок связано с несоответствием спецификаторов указанных в форматной строке с количеством и типами фактических аргументов. При этом можно выделить следующие ситуации:
— занимаемый в стеке размер параметров ожидаемых из форматной строки меньше размера фактически переданных. Типы фактические параметров совпадают с ожидаемыми. В этом случае просто выведутся параметры указанные в форматной строке, а лишние будут проигнорированы.
— занимаемый в стеке размер параметров ожидаемых из форматной строки меньше размера фактически переданных. Типы фактические параметров не совпадают с ожидаемыми. В этом случае параметры просто будут интерпретированы в соответствии с форматной строкой и в общем случае будет выведен мусор.
— размер фактических параметров меньше ожидаемых. Здесь поведение не определено и зависит от кучи разных факторов — от платформы, от компилятора, от содержимого стека на момент вызова printf. Поведение printf при этом может быть от внешне корректной работы и до чтения и даже записи произвольных участков памяти со всеми вытекающими последствиями.
Многие ошибки возникают при переносе кода с одной платформы на другую у которых отличаются размеры типов. Например:

printf("A = %x", 0x12345678); 

На платформах, где int имеет 32 бита этот код работает правильно, а где int — 16 бит — будут выведены только 2 младших или старших (в зависимости от порядка следования байт) байта.
К счастью некоторые компиляторы, например GCC, знают printf «в лицо» и выдают предупреждения в случае несовпадения фактических параметров с форматной строкой. Однако это работает только если форматная строка задана литералом. А если она хранится в переменной (например extern указатель инициализируемый в другом модуле), то компилятор бессилен и проследить соответствеи параметров может быто очень не просто.

Особенности реализаций

AVR

AVR достаточно неудобная платформа для работы со строками из-за того, что они могут располагаться в различных адресных пространствах и когда работаешь со строкой всегда нужно помнить где именно она расположена. В первую очередь нас естественно интересуют строки в RAM и во Flash.
Примеры для AVR рассчитаны для запуска на симуляторе sImulavr и используют его механизм отладочного вывода как стандартный вывод. Это позволит не отвлекаться на инициализацию устройств выводи и т. д. К тому-же не надо загружать прошивку в устройство для запуска.

AVR-GCC он-же WinAVR

В AVR-GCC, а точнее в avr-libc самая удачная, на мой взгляд, реализация стандартной библиотеки ввода-вывода Си. В ней имеется возможность выбирать необходимый функционал функций семейства printf. Они прекрасно работают со строками как RAM так и во Flash. Все функции семейства, включая snprintf и fprintf разделяют общий код и очень хорошо оптимизированы как по скорости, таки по объёму кода.
Для поддержки находящихся во Flash памяти строк введен новый спецификатор форматной строки %S — S — заглавная, строчная s по-прежнему означает строку в RAM. Но во Flash памяти может быть и сама форматная строка, для этого есть специальные модификации функций с суффиксом «_P» printf_P, fprintf_P, sprintf_P и т. д., которые ожидают форматную строку во Flash.
Для того чтобы printf заработала, нужно написать функцию вывода символа и определить файловый дескриптор стандартного вывода.

#include <avr/io.h> // для printf #include <stdio.h> // для PROGMEM #include <avr/pgmspace.h> // порт для отладочного вывода - определен в параметрах запуска симулятора #define special_output_port (*((volatile char *)0x24)) // прототип функции вывода символа static int my_putchar(char c, FILE *stream); // определяем дескриптор для стандартного вывода static FILE mystdout = FDEV_SETUP_STREAM( my_putchar, // функция вывода символа NULL, // функция ввода символа, нам сейчас не нужна _FDEV_SETUP_WRITE // флаги потока - только вывод ); // функция вывода символа static int my_putchar(char c, FILE *stream) { special_output_port = c; return 0; } // форматная строка во flash PROGMEM char str1[] ="Format str from flash\nlong = %15ld\nFlash string = %10S\n"; // просто строка во flash PROGMEM char str2[] = "string form flash"; __attribute((OS_main)) int main(void) { // инициализируем стандартный дескриптор stdout = &mystdout; // форматная строка в RAM и выводим строку из RAM printf("Format str from ram\nlong = %15ld\nRam string = %10s\n", 1234567890l , "string from RAM"); // форматная строка в flash и выводим строку из flash printf_P(str1, 1234567890l , str2); return 0; }

Помимо stdout есть стандартные дескрипторы stdin и stderr для стандартного ввода и стандартного вывода ошибок соответственно. Используя функцию fprintf можно явно указывать нужный файловый дескриптор и при необходимости можно определить сколько угодно устройств вывода:

static FILE lcd = FDEV_SETUP_STREAM( my_lcd_putchar, NULL, _FDEV_SETUP_WRITE); static FILE usart= FDEV_SETUP_STREAM( my_usart_putchar, NULL, _FDEV_SETUP_WRITE); … stdout = &lcd; stderr = &usart; … fprintf(stderr, "Hello, world!"); fprintf(stdout, "Hello, world!");

В avr-libc имеется три уровня функциональности библиотеки ввода-вывода:
1. нормальный — поддерживается вся упомянутая выше функциональность, кроме вывода чисел с плавающей точкой. Этот режим включен по умолчанию и каких либо опций для него указывать не надо. Поддержка чисел с плавающей точкой занимает много места, а нужна сравнительно редко. Приведенный выше пример, скомпилированный с этим уровнем функциональности, занимает порядка 1946 байт Flash памяти.
2. минимальный — поддерживаются только самые базовые вещи: целые, строки, символы. Флаги, ширина поля и точность, если они есть в форматной строке, разбираются корректно, но игнорируются, поддерживается только флаг «#». Пример, скомпилированный с этим уровнем функциональности, занимает порядка 1568 байт Flash памяти. Его вполне можно было-бы применить на контроллере с 2 Кб flash памяти. Включается указанием в командной строке компоновщика («Linker options» в AVRStudio, а не компилятора, как расплывчато указано в документации avr-libc) следующих опций:

-Wl,-u,vfprintf -lprintf_min

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

-Wl,-u,vfprintf -lprintf_flt -lm

Скомпилированный пример занимает при этом 3488 байт.
Функции семейства printf из avr-libc не используют буферизацию, не считая буферов для конвертации чисел, и выводят символы в устройство по мере их обработки. Поэтому, если буферизация нужна, то ее можно реализовать самостоятельно в функции вывода символа, там можно реализовать и кольцевой буфер и все, что угодно. Также в этих функциях не используется динамическая память, что в нашем случае очень хорошо, зато активно используется стек. Попробуем определить максимальное использование стека в них. Для этого в приложенном архиве заходим в каталог AvrGccPrintf, компилируем проект посредством AVRStudio и запускаем симуляцию с помощью runSimul.cmd.

После открывает образовавшийся файл trace.log, находим значение указателя стека (SP) после входа в main (после пролога), находим минимальное значение SP (стек растёт вниз) и вычитаем из первого второе. У меня получилось 0x455 — 0x429 = 0x2c = 44 байта использует сама функция fprintf, плюс еще 8 байт в стеке занимают ее параметры, итого 52 байта. Еще 14 байт занимает один файловый дескриптор и ещё 6 байт три стандартных указателя на файловые дескрипторы (stdout, stdin, stderr). Итого 72 байта RAM только на вызов fprintf, без учета всего остального.
Также из файла trace.log можно узнать общее время выполнения функций и где процессор проводит его больше всего.

Подробнее о стандартной библиотеке ввода-вывода avr-libc можно здесь:
www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html

IAR for AVR

Здесь есть несколько версий Си-шных библиотек, с отличающейся функциональностью:
— CLIB — относительно маленькая, но ограниченная библиотека. Нет поддержки файловых дескрипторов, локалей и многобайтных символов. Считается устаревшей.
— Normal DLIB — более новая. Так-же нет поддержки файловых дескрипторов, локалей и многобайтных символов, но есть некоторые плюшки из стандарта С99.
— Full DLIB — полная библиотека Си. Поддерживает всё согласно стандарту С99, но при этом очень объёмная. Рассматривать этот вариант не буду, так, как размер функции printf отсюда превышает доступные 4 Кб кода для бесплатной версии IAR KickStart for AVR.
В IAR имеется возможность выбирать возможности для printf. Для этого заходим с меню Project->Options далее в диалоге General Options->Library Configuration. В списке «Printf formatter» можно выбрать необходимый уровень функционала. Для CLIB это Large, Small, Tiny, для DLIB добавляется еще Full. По возможностям эти уровни примерно соответствуют аналогичным в avr-gcc, поэтому расписывать их не буду.

Для того чтобы printf заработала, надо определить функцию вывода символа:

int putchar(int outchar);

Разберём полный пример. Он также предназначен для запуска на симуляторе.

#define special_output_port (*((volatile char *)0x24)) #define special_exitcode_port (*((volatile char *)0x020)) #include <ioavr.h> #include <stdio.h> // для printf_P #include <pgmspace.h> __flash char str1[] ="Format str from flash\nlong = %15ld\nFlash string = %10S\n"; __flash char str2[] = "Hello"; int putchar(int outchar) { special_output_port = outchar; return outchar; } int main(void) { printf("Format str from ram\nlong = %15ld\nRam string = %10s\n", 1234567890l , "Hello"); printf_P(str1, 1234567890l , str2); special_exitcode_port = 0; return 0; }

Тут выясняются две неприятные особенности. Во-первых функции printf и printf_P видимо не разделяют общий код и printf_P всегда использует максимальный форматтер, независимо от того, что выбрали в настройках для printf, занимая порядка 3500 байт кода. Поэтому приведенный пример не помещается в четыре бесплатных килобайта. Для проверки одну из функций надо закомментировать.
Во-вторых, ни printf, ни printf_P не умеют читать строки из flash памяти — для них нет спецификатора.
Размеры printf для различных уровней функциональности примерно соответствуют аналогичным у avr-gcc, где чуть меньше, где чуть больше — непринципиально. А вот использование стека в разы выше, минимальный размер стека данных, при котором printf заработала, составил 200 байт для DLIB и около 150 для CLIB. Так, что на контроллерах с 2 кб flash, 128 RAM использовать эти функции не получится.
Демо-проект находится в каталоге AvrIarPrintf. Для запуска симуляции, а точнее преобразования генерируемого IAR-ом hex-а в пригодный для потребления simulavr-ом elf, на машине должен быть установлен WinAVR (и прописан в переменной окружения PATH, естественно).

Mspgcc

В стандартной библиотеке mspgcc для форматного вывода реализованы только функции printf и sprintf, плюс еще нестандартная функция uprintf, которая принимает указатель на функцию вывода символа в качестве первого аргумента. Файловых дескрипторов там нет и в помине, выбирать уровень функциональности форматтеров тоже нельзя. При этом printf «весит» порядка двух килобайт так, что запустить её, скажем, на MSP43 Launchpad-е не получится.
Для работы printf нужно, вполне ожидаемо, определить функцию int putchar(int c):

int putchar(int c) { // вывод символа ... return c; } 
IAR for MSP430, IAR for ARM и может еще какой IAR

Вообще в реализациях стандартной библиотеки от IAR Systems всё довольно однообразно для различных платформ, что не может не радовать. Как правило есть минимум две версии стандартной библиотеки — одна урезанная без поддержки файловых дескрипторов, вторая — полная, соответственно, с их поддержкой. Зовутся они как правило «Normal» и «Full» соответственно. Так-же в каждой из них можно выбирать необходимый функционал разбора форматной строки, поддерживаемый функциями семейства printf. Варианты уже уже описаны для IARfor AVR: Large, Small, Tiny и Full.
Если выбрать вариант библиотеки без файловых дескрипторов, то для работы функций вывода нужно определить лишь функцию int putchar(int outchar).
Если используем вариант с дескрипторами, то определить нужно функции __write, __lseek, __close и remove.
Минимальная их реализация, например, для STM32 может выглядеть так:

// Посылает один символ в USART1. static void Putch(uint8_t c) { if(c == '\n') Putch('\r'); while(( USART1->SR & USART_SR_TXE) == 0); USART1->DR = c; } // Вывод буфера в файл/поток, где // handle - номер открытого файла, // buf - буфер с данными, // bufSize - размер буфера, // возвращает количество фактически записанных символов. size_t __write(int handle, const unsigned char *buf, size_t bufSize) { for (int i = 0; i < bufSize; i++) { Putch(buf[i]); } return bufSize; } // Установка текущей позиции в потоке. // Нам сейчас не нужно. int __lseek(int handle, int ptr, int dir) {return 0; } // Закрыть поток. int __close(int handle) { return -1; } // Удалить файл. // Не знаю зачем printf требует определения этой функции. int remove(const char *fname) { return -1; } 
ARM + NewLib

Большинство сборок GCC под ARM используют NewLib в качестве стандартной библиотеки Си. Это достаточно взрослый проект и хорошо соответствует Си-шным стандартам, но при этом она относительно «тяжела» и требовательна к ресурсам. Для настройки библиотеки под свои нужды используется механизм системных вызовов. О нём уже немного писалось тут ispolzuem-libc-newlib-dlya-stm32, поэтому подробно на них останавливаться не буду, а упомяну об особенностях.
Первое это требования к памяти. Все stdio функции из NewLib используют хитрую буферизацию и динамически распределяют память для своих внутренних нужд. А значит им нужна куча и не маленькая, а в целых 3 Кбайт. Плюс примерно 1500 байт на статические структуры и плюс около 500 байт стека. Итого только чтоб напечатать «Hello, world!» нужно порядка 5 Кб оперативки. Что как-бы чуть больше, чем много для STM32-Discovery, на которой я запускал тестовый пример, с её 8 килобайтами. Также при использовании таких функций как printf по зависимостям тянется практически вся stdio плюс функции поддержки плавающей точки. В итоге тестовая прошивка занимает чуть меньше 30 Кб памяти программ. Если отказаться от использования чисел с плавающей точкой, то вместо printf можно использовать её облегченный аналог iprintf. В этом случае объём тестовой прошивки будет около 12 Кб.
Если какая либо из функций stdio не сможет выделить необходимую её память из кучи, то она тихонечко свалится в HardFault без объяснений причин.
Еще один момент это буферизация. Она может несколько запутать. Вызываем:

printf("Hello, World!");

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

printf("Hello, World!\n"); ... printf("Hello again!"); fflush(stdout); 

Режимом буферизации можно управлять с помощью функции setvbuf. Например, чтоб отключить буферизацию нужно сделать такой вызов до того, как в целевой поток был произведен какой либо вывод тыц:

setvbuf(stdout, NULL, _IONBF, 0); 

При этом потребление памяти кучи уменьшится более чем на 1.5 Кб.

Xprintf

Это реализация функций похожих на printf от товарища Chan-а. Качаем отсюда:
elm-chan.org/fsw/strf/xprintf.html
Библиотека содержит следующие функции для форматированного вывода — аналоги функций стандартной библиотеки Си:

void xputc (char c); void xputs (const char* str); void xfputs (void (*func)(unsigned char), const char* str); void xprintf (const char* fmt, ...); void xsprintf (char* buff, const char* fmt, ...); void xfprintf (void (*func)(unsigned char), const char* fmt, ...); 

Все функции написаны целиком на Си и их можно использовать практически на любом микроконтроллере. Однако они не учитывают особенностей некоторых МК, например, нет никакой поддержки строк во Flash памяти для семейства AVR, что не добавляет удобства использования. Для использования xprintf необходимо инийиализировать указатель xfunc_out, присвоив ему адрес функции вывода символа. Рассмотрим пример. Компилятор avr-gcc, проект AvrStudio, рассчитан на запуск в симуляторе simulavr.

#include <avr/io.h> // для PROGMEM и strncpy_P #include <avr/pgmspace.h> #include "xprintf.h" #define special_output_port (*((volatile char *)0x24)) #define special_input_port (*((volatile char *)0x25)) #define special_abort_port (*((volatile char *)0x22)) #define special_exitcode_port (*((volatile char *)0x020)) // функция вывода символа static void my_putchar(unsigned char c) { special_output_port = c; } // строки во Flash памяти PROGMEM char str1[] ="Format str from flash\nlong = %15ld\nFlash string = %10s\n"; PROGMEM char str2[] = "Hello form flash"; // буферы для временного копирования строк char buffer[60]; char buffer2[20]; __attribute((OS_main)) int main(void) { // инициализируем укзатель нашей функцией вывода символа xfunc_out = my_putchar; xprintf("Format str from ram\nlong = %15ld\nRam string = %10s\n", 1234567890l , "Hello"); xfprintf(my_putchar, "Hello, world!\n"); strncpy_P(buffer, str1, sizeof(buffer)); strncpy_P(buffer2, str2, sizeof(buffer2)); xprintf(buffer, 1234567890l , buffer2); special_exitcode_port = 0; return 0; }

Здесь для вывода строк из Flash памяти их приходится предварительно скопировать в оперативку.
Из достоинств этой библиотеки можно выделить компактность кода (~1600 байт для приведённого примера) и лёгкость использования на различных платформах и модифицировать. На этом достоинства заканчиваются. Из недостатков стоит отметить
относительно медленную работу, примерно в полтора-два медленнее чем стандартная printf из avr-libc, и несоответствие стандартам — не поддерживаются некоторые флаги (‘пробел’, ‘ #’, ‘+’ ), спецификаторы (i, p n), не считая флагов для чисел с плавающей точкой и т.д.
Потребление стека порядка 38 байт не считая аргументов.

Описание примеров.

В приложенном архиве находятся следующие примеры:

AvrGccPrintf

Проект для AvrStudio 4. Запускается на симуляторе Simulavr. Чтоб запустить нужно скомпилировать и выполнить скрипт «runSimul.cmd». В логе симуляции «trace.log» можно посмотреть времена выполнения функций и оценить расход стека.

AvrIarPrintf

Тоже, что и в предыдущем примере, только компилируется в IAR for AVR.

AvrXprintf

Проект для AvrStudio 4. Запускается на симуляторе Simulavr. Для вывода используется библиотека xprintf от ELM Chan.

Msp430GccPrintf

Коппилируется mspgcc. Проект для Code::Blocks. Запускать планировалось на MSP430 Launchpad с контроллером MSP430G2231. Пример должен считывать температуру со встроенного датчика и посылать ее через програмый usart. Но к сожалению функция printf из библиотеки mspgcc не помещается в имеющиеся 2 Кб памяти. По этому используется функция puts. которая выводит строчку «Hello, world!».

Msp430IarPrintf

IAR для MSP430. Запускается на MSP430 Launchpad. В отличии от mspgcc, минимальная версия printf умещается в 2Кб флеша MSP430G2231.

Stm32Format

GCC + NewLib. Работает на STM32 Discovery. собирается с помощью makefile. проверял с Yagarto и CodeSourcery.

IarArm

Работает на STM32 Discovery. Собирается с помощью IAR for ARM.

Итоги.

Преимущества использования стандартной библиотеки Си для форматного вывода:
— Стандартность. Этим всё сказано.
— Доступность. В каком-то виде есть практически в любом Си компиляторе.
— Разделение формата вывода и выводимых данных. Форматную строку легко вынести в отдельный файл/модуль, например для дальнейшей локализации.
— Хорошая функциональность.
Недостатки:
— Полностью стандартная реализация в большинстве случаев слишком «тяжела» для микроконтроллеров.
— Использование лишь одной функции, например printf, тянет за собой значительную часть библиотеки вывода, даже если реально используются только ограниченные возможности.
— Неаккуратное использование функций с форматной строкой может привести к трудно обнаруживаемым ошибкам кодирования.
— В каждом компиляторе используется какой-то свой способ для определения низкоуровневых функций вывода.

Текстовые строки в Ардуино. Конвертирование данн ых в строки и наоборот. Класс String.

 Arduino  Комментарии к записи Текстовые строки в Ардуино. Конвертирование данн ых в строки и наоборот. Класс String. отключены
Июл 172019
 

Текстовые строки в Ардуино. Конвертирование данных в строки и наоборот. Класс String.

02.10.2016 Автор: ЭДУАРД

Язык C

Текстовые строки в Ардуино.

Текстовые строки в Ардуино.

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

В Ардуино признаком конца строки является число 0, в текстовом виде ‘\0’. При объявлении строковых переменных в некоторых случаях необходимо явно указывать признак конца строки, а в некоторых он формируется по умолчанию.

Способы объявления и инициализации текстовых строк.

char myStr1[10];

Объявлен символьный массив определенного размера. При заполнении его символами необходимо позаботиться о записи в конце строки байта со значением 0 – признака окончания строки.

char myStr2 [6] = {‘S’, ‘t’, ‘a’, ‘r’, ‘t’};

Объявлен массив и присвоено значение элементам. В конце строки компилятор прибавит признак конца строки автоматически.

char myStr3[6] = {‘S’, ‘t’, ‘a’, ‘r’, ‘t’, ‘/0’};

То же самое, только завершающий признак мы объявили явно.

char myStr4 [ ] = “Start”;

Массив инициализирован строковой константой. Компилятор автоматически задаст размер массива и добавит завершающий символ.

char myStr5 [6 ] = “Start”;

То же самое, только размер массива указан явно.

char myStr 6[20 ] = “Start”;

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

  • Строковые константы объявляются внутри двойных кавычек (”Start”).
  • Отдельные символы задаются внутри одинарных (‘S’).

Длинные строки допускают объявление в таком виде:

char myStr7 [] = “Текстовая строка может быть”
“ объявлена”
“ таким образом”;

Ничего не мешает задавать массивы строк. Поскольку сами строки являются массивами, массивы строк будут двумерными массивами.

char * myStrArray = { “Сообщение 1”, “Сообщение 2”, “Сообщение 3”, “Сообщение 4”, “Сообщение 5”, “Сообщение 6”};

Массивы строк объявляют как массив указателей. Это связано с тем, что строки могут иметь разную длину, и приходится резервировать двумерный массив данных, рассчитанный на самую длинную строку. А указатели требуют одинаковое количество ячеек памяти.

Управляющие символы.

В текстовых строках могут содержаться не только текстовые символы, но и управляющие. Управляющие символы не отображаются на экране в графическом виде. Они используются для управления передачей данных и выводом на экран. Таких символов несколько десятков. Я выделю наиболее важные.

Код символа в HEX (DEC) Название Обозначение Описание
0 (0) Конец строки \0 Признак конца строки
0D (13) Возврат каретки \r Перемещает курсор в крайнюю левую позицию
0A (10) Перевод строки \n Перемещает курсор на одну строку вниз

Для того чтобы вывести текст с новой строки необходимо использовать символы ‘\r’ и ‘\n’.

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

// управляющие символы
void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
Serial.println(«\r\n Begin \r\n next string»
«\r\n 3 empty strings \r\n\r\n\r\n»); // вывод сообщения в нескольких строках
delay(1000);
}

На экране монитора последовательного порта увидим:

Сообщение
Часто управляющие символы ‘\r’ и ‘\n’ применяют для завершения команды в символьном виде, например AT команды. Такой способ управления будем использовать в следующем уроке для драйвера шагового двигателя.

Конвертирование различных типов данных Ардуино в текстовую строку.

Задача преобразования различных типов данных в символьный вид и наоборот возникает:

  • при выводе данных на дисплей или индикаторы;
  • передаче данных на другие устройства, например компьютер;
  • некоторые электронные компоненты требуют обмена данными с помощью AT команд в символьном виде, например GSM модемы, WiFi модули и т.п.

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

Конвертирование данных в строку через встроенные функции классов ввода-вывода.

Самый удобный, предпочтительный способ преобразования данных в символьный вид. Многие библиотеки ввода-вывода имеют функции преобразования данных в текстовый формат.

Если необходимо передавать данные через последовательный порт, почему бы не воспользоваться стандартными функциями класса Serial (урок 12).

Преобразование Функция класса Sreial Описание
int в DEC текст print(int d) Преобразует переменную int в строку с десятичным представлением числа
int в DEC текст print(int d, DEC) Преобразует переменную int в строку с десятичным представлением числа
int в HEX текст print(int d, HEX) Преобразует переменную int в строку с шестнадцатеричным представлением числа
int в OCT текст print(int d, OCT) Преобразует переменную int в строку с восьмеричным представлением числа
int в BIN текст print(int d, BIN) Преобразует переменную int в строку с двоичным представлением числа
float в текст print(float d) Преобразует переменную float в строку с двумя знаками после запятой
float в текст print(float d, N) Преобразует переменную float в строку с N знаками после запятой

Например, конвертирование числа int в строку будет выглядеть так.

int x= 24562;
Serial.print(x);

Преобразование переменной float в строку можно выполнить так.

float x= 12.657;
Serial.print(x);

Преобразование будет выполнено при передаче данных на другое устройство. Функции класса Serial подробно описаны в уроке 12.

Если выводите данные на LCD дисплей с помощью библиотеки LiquidCristal, то для преобразования данных типа int можно использовать метод print (урок 23).

Преобразование Функция класса LiquidCristal Описание
int в DEC текст print(int d, DEC) Преобразует переменную int в строку с десятичным представлением числа
int в HEX текст print(int d, HEX) Преобразует переменную int в строку с шестнадцатеричным представлением числа
int в OCT текст print(int d, OCT) Преобразует переменную int в строку с восьмеричным представлением числа
int в BIN текст print(int d, BIN) Преобразует переменную int в строку с двоичным представлением числа

В библиотеке LiquidCristal нет функции для вывода данных типа float. Можно воспользоваться способом, предложенным в уроке 20 для библиотеки Led4Digits. Стандартная функция класса Led4Digits позволяет отображать на LED индикаторах только целые числа, но добавив простые вычисления можно легко выводить данные с плавающей запятой.

Конвертирование целочисленных данных в строку через функции itoa, ltoa, ultoa.

Функции простые, позволяют конвертировать числа целых форматов в текстовую строку.

itoa (int data, char* string, int radix); // преобразование int

ltoa (long data, char* string, int radix); // преобразование long

ultoa (unsigned long data, char* string, int radix); // преобразование unsigned long

  • data – это конвертируемая переменная;
  • char* string – указатель на строку (имя массива);
  • radix – система исчисления результата в строке:
    • 10 для DEC;
    • 8 для OCT;
    • 16 для HEX;
    • 2 для BIN.

Например, конвертирование переменой x типа int в строку myStr1 можно сделать так.

itoa(x, myStr1, 10); // в десятичном виде
itoa(x, myStr1, 8); // в восьмеричном виде
itoa(x, myStr1, 16); // в шестнадцатеричном виде
itoa(x, myStr1, 2); // в двоичном виде

Вот программа для проверки работы этих функций.

// проверка преобразования числа в текстовую строку
int x=0; // переменная, которая выводится
char myStr[20]; // текстовый массив

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
// подготовка буфера строки
for (int i=0; i<20; i++) {myStr[i]=’ ‘;} // заполнение пробелами
myStr[18]=’\r’; // возврат каретки
myStr[19]=’\n’; // перевод строки

// преобразование переменной int x
itoa(x, myStr, 10); // int -> DEC
//itoa(x, myStr, 8); // int -> OCT
//itoa(x, myStr, 16); // int -> HEX
//itoa(x, myStr, 2); // int -> BIN

// преобразование переменной long x
//ltoa(x, myStr, 10); // long -> DEC
//ltoa(x, myStr, 8); // long -> OCT
//ltoa(x, myStr, 16); // long -> HEX
//ltoa(x, myStr, 2); // long -> BIN

// преобразование переменной unsigned long x
//ultoa(x, myStr, 10); // long -> DEC
//ultoa(x, myStr, 8); // long -> OCT
//ultoa(x, myStr, 16); // long -> HEX
//ultoa(x, myStr, 2); // long -> BIN

Serial.write(myStr, 20);
x++;
delay(500);
}

В цикле каждые 0,5 секунд происходит:

  • Текстовая строка myStr заполняется пробелами, в конце добавляются управляющие символы возврат каретки и перевод строки.
  • Переменная x конвертируется одной из функцией. Результат оказывается в буфере myStr.
  • Функция Serial.write(myStr, 20); передает через последовательный порт 20 байтов массива myStr в виде байтов.
  • Прибавляется 1 к переменной x.

Чтобы проверить нужную функцию необходимо освободить ее от признака комментарий. Я проверил все.

Для вывода чисел с плавающей запятой можно опять предложить метод из урока 20.

Конвертирование данных в строку с помощью функции sprintf.

Это самый удобный, универсальный метод. Недостаток такого способа заключается в том, что функция sprintf просто пожирает ресурсы микроконтроллера. В критичных по времени и объему памяти приложениях ее лучше не применять.

sprint это функция форматированного вывода. Ее широко используют в приложениях на компьютерах. Она дает самые широкие возможности для преобразования данных в строку. Но в системе Ардуино sprintf не поддерживает формат чисел с плавающей запятой.

int sprintf( char *string, const char *format , argument1, argument2 … )

Функция возвращает число преобразованных символов. В случае ошибки возвращает число – 1.

  • argument – это переменные, которые необходимо преобразовать;
  • format – управляющая строка:

% [флаг] [ширина] тип_формата

Флаг и ширина — необязательные поля.

Тип формата Тип выходных данных
c Символ
s Символьная строка
d, i Целое десятичное число
u Целое без знаковое десятичное число
o Целое восьмеричное число
x Целое шестнадцатеричное число

Флаги.

Знак Действие
Выравнивание результата влево
+ Выводит знак числа
Пробел Выводит знак пробел перед положительными числами
0 Заполняет поле 0

Ширина – минимальный размер поля для вывода символов. Если длина числа меньше, то добавляются пробелы. Если перед шириной стоит 0, то добавляются нули.

На примерах из таблицы все должно быть понятно.

int x= 125; int y= 34;

Функция Выведет в myStr
sprintf(myStr2,»%d»,x ); 125
sprintf(myStr2,»%5d»,x ); 125
sprintf(myStr2,»%05d»,x ); 00125
sprintf(myStr2,»%+05d»,x ); +00125
sprintf(myStr2,»Цикл %d закончен»,x ); Цикл 125 закончен
sprintf(myStr2,»Цикл %d закончен y= %d»,x,y ); Цикл 125 закончен y= 34
sprintf(myStr2,»%o»,x ); 175
sprintf(myStr2,»%x»,x ); 7d

Можете загрузить следующую программу и проверить работу sprintf в реальном контроллере Ардуино.

// проверка преобразования числа в текстовую строку
// с помощью sprintf
int x=0; // переменная, которая выводится
char myStr[20]; // текстовый массив

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
// подготовка буфера строки
for (int i=0; i<20; i++) {myStr[i]=’ ‘;} // заполнение пробелами
myStr[18]=’\r’; // возврат каретки
myStr[19]=’\n’; // перевод строки

// преобразование переменной int x в строку
sprintf(myStr,»%d»,x ); // int -> DEC
//sprintf(myStr,»%5d»,x ); // int -> DEC
//sprintf(myStr,»%05d»,x ); // int -> DEC
//sprintf(myStr,»%+05d»,x ); // int -> DEC
//sprintf(myStr,»Cycle %d is over»,x ); // int -> DEC с текстом
//sprintf(myStr,»%o»,x ); // int -> OCT
//sprintf(myStr,»%x»,x ); // int -> HEX

Serial.write(myStr, 20);
x++;
delay(500);
}

Конвертирование данных типа float в текстовую строку.

Самый простой способ преобразования float в текстовую строку – использование функции dtostrf.

char* dtostrf(double data, signed char width, unsigned char prec, char *string)

  • data – это конвертируемая переменная;
  • width – число значащих разрядов;
  • prec – число разрядов после запятой;
  • char* string – указатель на строку (имя массива).

float x= 12.728;
dtostrf(x, 2, 3, myStr3); // выводим в строку myStr3 2 разряда до, 3 разряда после запятой

Вот программа для проверки такого способа.

// проверка преобразования числа в текстовую строку
// с помощью dtostrf
float x=0; // переменная, которая выводится
char myStr[20]; // текстовый массив

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
// подготовка буфера строки
for (int i=0; i<20; i++) {myStr[i]=’ ‘;} // заполнение пробелами
myStr[18]=’\r’; // возврат каретки
myStr[19]=’\n’; // перевод строки

// преобразование переменной float x в строку
dtostrf(x, 2, 3, myStr);

Serial.write(myStr, 20);
x+= 0.01;
delay(500);
}

У меня работает.

Конвертирование текстовой строки в различные типы данных.

В следующем разделе речь идет об обратном преобразовании – текстовой строки в число.

Преобразование строки в данные с помощью функций atoi, atol, atof.

Хороший, удобный метод. Функции простые, имеют вид:

int atoi(const char* string); // преобразование в int

long atol(const char* string); // преобразование в long

double atof(const char* string); // преобразование в float

В качестве аргумента функций указывается указатель на строку с числом в десятичном виде. Возвращают – конвертированное значение числа.

Например, преобразование строки myStr4 в переменную x типа int будет выглядеть так.

int x;
x= atoi(myStr4);

Для чисел с плавающей запятой.

float x;
x= atof(myStr4);

Вот программа проверки atoi и atol.

// проверка преобразования текстовой строки в число
// с помощью atoi
char myStr[]= «123»; // текстовый массив

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
Serial.println(atoi(myStr)); // преобразование строки в int
//Serial.println(atol(myStr)); // преобразование строки в long
delay(1000);
}

А в этой программе я проверил работу atof.

// проверка преобразования текстовой строки в число
// с помощью atof
char myStr[]= «123.456»; // текстовый массив

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
Serial.println(atof(myStr),3); // преобразование строки в float
delay(1000);
}

Конвертирование текстовой строки в числа с помощью функции sscanf.

Функция является обратной функцией для sprintf с такими же недостатками и достоинствами. Но она позволяет конвертировать числа в восьмеричном и шестнадцатеричном форматах. Тип float эта функция на Ардуино не поддерживает.

int sscanf( char *string, const char *format , address1, address2 … )

Все аргументы и форматы такие же, как у sprintf. Только указываются адреса переменных (address1, address2 … ).

Конвертирование строки myStr5 в переменную x типа int будет выглядеть так.

int x;
sscanf(myStr5,»%d», &x); // для десятичных чисел
sscanf(myStr5,»%o», &x); // для восьмеричных чисел
sscanf(myStr5,»%x», &x); // для шестнадцатеричных чисел

Вот скетч проверки функции для целочисленных операций.

// проверка преобразования текстовой строки в число
// с помощью sscanf
int x;
char myStr[]= «123»; // текстовый массив

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
sscanf(myStr,»%d», &x);
Serial.println(x); // преобразование строки в int
delay(1000);
}

Класс String Ардуино.

В Ардуино существует класс String. Он предоставляет более широкие возможности для работы с текстовыми строками.

Надо четко различать:

  • char myStr [ ] = “Start”; — строка символов, т.е. массив типа char с завершающим признаком в конце;
  • String MyStr = Start”; — экземпляр класса String.

Принято имена текстовых строк начинать как обычные переменные с маленькой буквы (myStr), а экземпляры String – с большой буквы (MyStr).

Экземпляры класса String занимают больше памяти, медленнее обрабатываются, но зато они позволяют расширять , объединять строки, производить поиск, замену символов и многое другое. Пользоваться текстовыми строками или классом String – решать Вам. В оптимальных по ресурсам приложениях лучше использовать строки.

Несколько основных функций класса String.

Я опишу только минимум функций, необходимых для работы с классом String.

String()

Конструктор, создает экземпляр класса String. Объект типа String может быть создан из разных типов данных:

String MyStr1 = «Start»; // инициализация строковой константы
String MyStr2 = String(‘d’); // преобразование символа в объект String
String MyStr3 = String(«Start»); // преобразование строковой константы в объект String
String MyStr4 = String(MyStr1 + » in 10 sec»); // конкатенация двух строк
String MyStr5 = String(67); // использование целочисленной константы
String MyStr6 = String(analogRead(1), DEC); // использование int с основанием 10
String MyStr7 = String(346, BIN); // использование int с основанием 2
String MyStr8 = String(89, HEX); // использование int с основанием 16
String MyStr9 = String(millis(), DEC); // использование long с основанием системы счисления

При создании объекта из числа, сформированная строка будет содержать ASCII (символьное) представление числа. По умолчанию используется десятичная система счисления, но можно указать другую. Т.е. функция String может осуществлять преобразование целочисленных данных в текстовую строку.

toCharArray(*buf, length)

Копирует текст экземпляра класса String в указанный массив.

MyStr.toCharArray(*buf, length)

  • buf – указатель на массив;
  • length – количество символов.

MyStr.toCharArray(myStr, 10); // копируем 10 символов в массив myStr

int length()

Функция возвращает длину строки String в символах без учета завершающего признака нуля.

int len =MyStr.length(); // получаем длину строки MyStr

long toInt()

Функция преобразовывает объект String в целое число.

int x = MyStr.toInt(); // конвертирование строки MyStr в int

Конвертирование данных в строку String.

Для целочисленных форматов все очень просто.

String MyStr;
int x;
MyStr= String(x, DEC); // для десятичных чисел
MyStr= String(x, HEX); // для шестнадцатеричных чисел
MyStr= String(x, BIN); // для двоичных чисел

Программа для проверки.

// проверка преобразования числа в String
int x=0; // переменная, которая выводится
String MyStr;

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {

MyStr= String(x, DEC); // int -> DEC
//MyStr= String(x, HEX); // int -> HEX
//MyStr= String(x, BIN); // int -> BIN

Serial.println(MyStr);
x++;
delay(500);
}

Для плавающей запятой надо использовать функцию dtostrf(). С помощью нее получить строку-массив, а затем занести ее в объект String.

float x=2.789;
String MyStr;
char myStr8[10];
dtostrf(x, 2, 3, myStr8); // выводим в строку myStr8 2 разряда до, 3 разряда после запятой
MyStr = myStr8;

Программа для проверки преобразования float.

// проверка преобразования числа в String
float x=0; // переменная, которая выводится
String MyStr;
char myStr[10];

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {

dtostrf(x, 2, 3, myStr); // выводим в строку myStr 2 разряда до, 3 разряда после запятой
MyStr = myStr;

Serial.println(MyStr);
x += 0.01;
delay(500);
}

Конвертирование строки String в различные типы данных.

Для целых чисел используем функцию toInt().

String MyStr = «123»;
int x = MyStr.toInt();

Проверяем.

// проверка преобразования String в число
// с помощью toInt
String MyStr = «123»;

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
Serial.println(MyStr.toInt()); // преобразование строки в int
delay(1000);
}

Для плавающей запятой.

Получим данные из объекта String в массив и выполним преобразование функцией atof().

String MyStr = «34.123»;
char myStr8[10];
MyStr.toCharArray(myStr8, MyStr.length()); // копирование String в массив myStr8
float x = atof(myStr8); // преобразование в float

Программа для проверки.

// проверка преобразования String в число
// с помощью toInt
String MyStr = «34.123»;
char myStr[10];

void setup() {
Serial.begin(9600); // скорость 9600
}

void loop() {
MyStr.toCharArray(myStr, MyStr.length());
Serial.println(atof(myStr)); //
delay(1000);
}

Arduino & Modbus

 Arduino  Комментарии к записи Arduino & Modbus отключены
Июл 172019
 

Arduino & Modbus

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

Modbus — самый известный и распространенный стандарт в промышленной автоматизации, его поддерживают миллионы устройств по всему миру, эти устройства легко интегрируется в единую сеть и стыкуются с огромным количеством готового программного обеспечения. Попробуем использовать его в нашем проекте?

Что нам необходимо знать об этом стандарте?
Протокол Modbus использует последовательные линии связи (например, RS232, RS485), а протокол Modbus TCP рассчитан на передачу данных по сетям TCP/IP.
Протокол Modbus имеет два режима передачи RTU и ASCII, в режиме ASCII каждый байт передается как два ASCII символа его шестнадцатеричного представления.
В сети Modbus есть только один ведущий, который с заданным интервалом опрашивает несколько ведомых устройств, каждое из которых имеет свой уникальный адрес от 1 до 254, адрес 0 широковещательный и на него отвечают все устройства, так как ведущий в сети один у него нет своего адреса.
В спецификации Modbus определено два типа данных, один бит и 16 битное слово. Данные организованны в четыре таблицы с 16 битной адресацией ячеек, адресация в таблицах начинается с 0. Для доступа к данным из разных таблиц предназначены отдельные команды.

Discrete Inputs 1 бит только чтение
Coils 1 бит чтение и запись
Input Registers 16 бит только чтение
Holding Registers 16 бит чтение и запись

Как нам подключить Modbus устройство к OpenHAB? За это отвечает модуль Modbus Tcp Binding, этот модуль работает в режиме ведущего и обеспечивает подключение нескольких ведомых устройств через последовательный порт или TCP/IP сеть.
Для того чтобы связать с ним Arduino нам необходимо реализовать в контроллере ведомое Modbus устройство, воспользуемся для этого библиотекой Modbus-Master-Slave-for-Arduino.

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

Рассмотрим на примере нашего скетча основные шаги необходимые для работы с этой библиотекой.

Все функции библиотеки реализованы в одном файле ModbusRtu.h.
Для взаимодействия с ней, в программе нужно создать объект, задав в его конструкторе Modbus адрес, номер последовательного порта, номер выхода, управляющего передачей (для RS485)

Modbus slave(ID, 0, 0); 

Затем определить массив регистров Modbus

uint16_t au16data[11]; 

После этого, при старте программы настроить последовательный порт ведомого

slave.begin(9600); 

В основном цикле программы необходимо вызывать функцию обработки Modbus сообщений

state = slave.poll(au16data, 11); 

И после этого можно обработать полученные данные и сохранить необходимые переменные в регистрах Modbus.

#include "ModbusRtu.h" #define ID 1 // адрес ведомого #define btnPin 2 // номер входа, подключенный к кнопке #define stlPin 13 // номер выхода индикатора работы // расположен на плате Arduino #define ledPin 12 // номер выхода светодиода //Задаём ведомому адрес, последовательный порт, выход управления TX Modbus slave(ID, 0, 0); boolean led; int8_t state = 0; unsigned long tempus; // массив данных modbus uint16_t au16data[11]; void setup() { // настраиваем входы и выходы io_setup(); // настраиваем последовательный порт ведомого slave.begin( 9600 ); // зажигаем светодиод на 100 мс tempus = millis() + 100; digitalWrite(stlPin, HIGH ); } void io_setup() { digitalWrite(stlPin, HIGH ); digitalWrite(ledPin, LOW ); pinMode(stlPin, OUTPUT); pinMode(ledPin, OUTPUT); pinMode(btnPin, INPUT); } void loop() { // обработка сообщений state = slave.poll( au16data, 11); // если получили пакет без ошибок - зажигаем светодиод на 50 мс if (state > 4) { tempus = millis() + 50; digitalWrite(stlPin, HIGH); } if (millis() > tempus) digitalWrite(stlPin, LOW ); //обновляем данные в регистрах Modbus и в пользовательской программе io_poll(); } void io_poll() { //Копируем Coil[1] в Discrete[0] au16data[0] = au16data[1]; //Выводим значение регистра 1.3 на светодиод digitalWrite( ledPin, bitRead( au16data[1], 3 )); //Сохраняем состояние кнопки в регистр 0.3 bitWrite( au16data[0], 3, digitalRead( btnPin )); //Копируем Holding[5,6,7] в Input[2,3,4] au16data[2] = au16data[5]; au16data[3] = au16data[6]; au16data[4] = au16data[7]; //Сохраняем в регистры отладочную информацию au16data[8] = slave.getInCnt(); au16data[9] = slave.getOutCnt(); au16data[10] = slave.getErrCnt(); } 

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

40f0a878912f4bf4930ded14fe21e707.JPG

Для демонстрации работы с разными регистрами, в процессе работы программы данные из регистра с типом coil будут скопированы в регистр с типом discrete, а из регистров с типом holding в регистры с типом input. Кроме этого состояние кнопки будет сохранено в третий бит регистра au16data[0] (discrete), а значение третьего бита регистра au16data[1] (coil) выведено на светодиод.

Доработаем макет контроллера, который был собран для предыдущих экспериментов, переключим светодиод с 13 на 12 вывод. Обычно на плате самого Arduino уже есть светодиод, подключенный к 13 выводу, в нашей программе он станет индикатором статуса работы. Теперь подключим USB кабель к компьютеру, скомпилируем и загрузим программу в контроллер.

6ff226483d9448a5b787af24c94286c2.JPG

Пора приступать к испытаниям. Значительно облегчает работу на этом этапе эмулятор Modbus мастер-устройства, в сети есть несколько хороших, при этом бесплатных программ, вот некоторые из них:
www.focus-sw.com/fieldtalk/modpoll.html
qmodbus.sourceforge.net
www.mikont.com/products/EAT-Console.html

Среди них можно отметить утилиту EAT-Console которая позволяет не только управлять и опрашивать Modbus устройства, но и отображает данные в графическом виде, что очень удобно при отладке работы с различными датчиками, например датчиками влажности, давления и температуры. Перед началом работы с программой и её конфигуратором рекомендую ознакомиться с документацией.

8184e77240054ca3a681087d35db8388.JPG

Для установки эмулятора нужно скачать архив и распаковать его в папку C:\arduino\EATConsole, затем открыть страницу загрузки Eclipse, скачать Eclipse IDE for Java Developers и распаковать его в папку C:\arduino\eclipse, после этого скопировать файлы из папки C:\arduino\EATConsole\eclipse\plugins в папку C:\arduino\eclipse\plugins.

Для создания конфигурации необходимо запустить C:\arduino\eclipse\eclipse.exe, создать пустой проект, скопировать в него пустой файл C:\arduino\EATConsole\menu.ptmenu и добавить в редакторе пункты в соответствии со следующей таблицей. Если же вы скачали проект из репозитория, то в нём, в папке EATConsole уже есть подготовленный файл menu.ptmenu.

Type Address Bit Name Point Slave
Display Boolean 0 0 DT0 1
Display Boolean 0 1 DT1 1
Display Boolean 0 2 DT2 1
Display Boolean 0 3 BTN 1
Input Boolean 1 0 CL16 1
Input Boolean 1 1 CL17 1
Input Boolean 1 2 CL18 1
Input Boolean 1 3 LED 1
Display Integer 2 INPT3 0 1
Display Integer 3 INPT4 0 1
Display Integer 4 INPT5 0 1
Display Integer 5 HOLD6 0 1
Display Integer 6 HOLD7 0 1
Display Integer 7 HOLD8 0 1

Type — тип элемента меню EATConsole.
Address — адрес регистра данных.
Bit – номер бита в регистре.
Name – название элемента меню.
Point – количество десятичных знаков после точки.
Slave – Modbus адрес контроллера.

add9437d0a2f4e13a987d841bc8c993e.JPG

Теперь сохраним и скопируем файл menu.ptmenu в каталог C:\arduino\EATConsole, для этого можно щёлкнуть правой кнопкой мыши на файле прямо в Eclipse, выбрать в контекстном меню пункт “Copy”, а затем вставить в проводнике в папку C:\arduino\EATConsole.

После этого запустим C:\arduino\EATConsole\EATConsole.exe, настроим последовательное соединение, выбрав пункт меню Файл\Настройки, в диалоговом окне укажем номер порта, скорость 9600, 8 бит данных, 1 стоповый бит.

351170a62856416da0b7a5f82885e5c7.JPG

*Программа работает с портами с 1 по 8 и если USB переходник Arduino встал на порт с большим номером, придётся открыть диспетчер устройств Windows и изменить номер порта для него.

Когда все настройки будут введены, нажмите кнопку “Установить”, сразу после этого программа начнёт опрос устройства и если что-то пошло не так появится сообщение – НЕТ СВЯЗИ. Если же всё было сделано правильно и связь есть в чём можно убедиться по миганию индикатора статуса (светодиод на выводе 13), то пора приступить к испытаниям нашего контроллера.

Попробуем поменять значение в регистрах HLD0…HLD2 и СL0…СL2, при этом должно поменяться значение в соответствующем регистре IN0..IN2 и DT0..DT2, затем нажмём на кнопку макета, при этом должно поменяться значение в поле BTN, щёлкнем по полю LED, при этом должен загореться или потухнуть светодиод.

e458a8cc65be4a059abd2734578c4336.JPG

Что мы получили в результате нашей работы:

1 познакомились с азами протокола Modbus;
2 создали скетч, который превращает Arduino в Modbus slave устройство и позволяет читать и записывать несколько Modbus регистров с разными типами данных;
3 протестировали обмен с контроллером при помощи эмулятора функций Modbus master устройства, для которого создали конфигурацию соответствующую структуре регистров контроллера.

Выводы
Библиотека Modbus-Master-Slave-for-Arduino проста в использовании, позволяет создать ведомое Modbus устройство, которое корректно работает с программным эмулятором. Скомпилированный пример занимает немногим более 5кб памяти программ, так что в контроллере остаётся достаточно места для добавления необходимого функционала.

Стандарт Modbus открыт и популярен, но в нём есть ряд недостатков — в стандарте определено только два типа данных,
протокол требует постоянного обмена между ведущим и ведомыми устройствами, конфигурировать систему приходится вручную.

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

В следующий раз займёмся подключением контроллера к платформе OpenHAB.

Как подружить OpenHAB и Arduino

 Arduino  Комментарии к записи Как подружить OpenHAB и Arduino отключены
Июл 172019
 

Как подружить OpenHAB и Arduino

Мы все хотим сделать жизнь лучше, добавить в наш дом технические новинки, автоматизировать его и сделать более безопасным. Зачастую эти желания превращаются в движение по граблям. Известно, что фирменные системы дороги и часто не совместимы друг с другом, а создание своего решения по силам не каждому профессионалу. Что же делать, как найти простое решение доступное любителям, но и интересное профессионалам?

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

Arduino подходит для локального контроля и управления в доме, в сети есть масса проектов для этого — отлично, но недостаточно, ведь нужно мыслить глобально! Нам нужен выход в сеть и мобильный интерфейс!

Хорошо, что благодаря этой статье мы уже знакомы с OpenHAB — платформой с открытым исходным кодом, объединяющей большое количество устройства с разными протоколами в единую сеть. OpenHAB реализован на Java, поэтому работает в различных ОС, его можно запустить на одноплатном компьютере и даже роутере, в нём есть мобильный и Web интерфейс. Звучит как хороший набор заклинаний против наших, надоевших уже граблей, проверим?

Вначале установим OpenHAB, откроем страницу загрузки, скачаем Runtime core и Demo setup, распакуем их в C:\openhab. При распаковке Demo setup разрешим замену файла README.txt. Если на компьютере отсутствует Java (проверим командой java –version), то инсталлируем ее в соответствии с инструкцией.

Теперь запустим OpenHAB, выполнив C:\openhab\start.bat, подождём немного и откроем Web интерфейс — localhost:8080/openhab.app?sitemap=demo
(если нам потребуется остановить OpenHAB — нажмём Ctl+C или закроем консоль программы)

image

Работает! Продолжим. Наша цель поиск простого решения, поэтому начнём с экспериментов. В одном из комментариев к статье предлагалось подключить контроллер к OpenHub через последовательный порт использовав Serial-Binding. Вполне интересное решение, попробуем его реализовать.

Для установки дополнения открываем страницу загрузки, качаем Addons, распаковываем org.openhab.binding.serial-1.6.1.jar (версия плагина может отличаться) в папку C:\openhab\addons. При желании читаем документацию.

Так как отладка взаимодействия с реальным устройством обычно доставляет немало хлопот, попробуем сначала отправить команды через терминал, соединив его с OpenHAB через эмулятор нуль-модемного кабеля.

Качаем и устанавливаем эмулятор com0com, запускаем утилиту image Setup из меню программы и видим, что при установке уже создана виртуальная пара последовательных портов — у меня это COM35 + COM36. Будем использовать их в нашей работе.

image

В качестве терминала я использовал Hercules SETUP utility, это бесплатная программа позволяющая выбрать порты с большими номерами и ввести несколько команд, отправляя их нажатием соответствующей кнопки. Скачиваем её, запускаем hercules_3-2-8.exe, переходим на вкладку “Serial”, выбираем второй порт виртуальной пары (у меня это COM36) и вводим следующие настройки – скорость 9600, 8 бит, 1 стоповый бит, нет контроля четности. Открываем порт.

45e089736561407590c4e72a3a1b8345.JPG

Теперь нужно сконфигурировать openHAB, для этого воспользуемся его дизайнером. Откроем страницу загрузки, скачаем openHAB Designer и распакуем его в папку C:\openhab\designer, запустим C:\openhab\designer\openHAB-Designer.exe и укажем папку, в которой лежит конфигурация — C:\openhab\configurations.

d75109973e294e7d8f0d3228fd21944c.JPG

Сначала создадим элемент (item) и свяжем его с первым в виртуальной паре последовательным портом (у меня это COM35). Для этого добавим следующий код в конец файла demo.items, после чего щёлкнем правой кнопкой мыши в окне редактора и скомандуем “Save”, вместо этого можно просто нажать ctl+S.

String Serial_string "Текст [%s]" { serial="COM35" } 

55844677eb6348e58daa3c751821998f.JPG

Для тестирования пропишем правило, которое каждую минуту будет отправлять команду созданному элементу. Для этого добавим следующий код в конец файла demo.rules после чего сохраним его.

rule "Test serial string" when System started or Time cron "0 * * * * ?" then Serial_string.sendCommand("Test") end 

3c2e558e31da4803ab1acb084b8405c0.JPG

Если OpenHAB запущен, мы почти сразу начнём принимать текст команды в терминальной программе, а в консоли увидим отчёт о её прохождении:

1d840e45d8d24c8dad6b90be8b78529e.JPG

Попробуем теперь отправить команды в OpenHAB. Введём в терминале, в первое поле панели “Send” текст “ON_1”, а во второе “OFF_1”. Нажмём первую кнопку “Send”, затем вторую. В результате этого в консоли появляются следующие сообщения:

ef1720dbf7764275acbf7125049a4057.JPG

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

rule "Serial control" when Item Serial_string received update then if(Serial_string.state=="ON_1") sendCommand(Light_FF_Corridor_Ceiling, ON) if(Serial_string.state=="OFF_1") sendCommand(Light_FF_Corridor_Ceiling, OFF) if(Serial_string.state=="ON_2") sendCommand(Light_GF_Kitchen_Ceiling, ON) if(Serial_string.state=="OFF_2") sendCommand(Light_GF_Kitchen_Ceiling, OFF) end 

Откроем WEB интерфейс, щёлкнем по пункту “First Floor” затем по пункту “Corridor” или скопируем в браузер ссылку: localhost:8080/openhab.app?sitemap=demo#_FF_Corridor, отправим текст “ON_1”, а потом OFF_1” из терминала. Проконтролируем выключатель в WEB приложении, его состояние должно изменяться.

c76ab6d5c0714466af9ae0759c6eddec.JPG

Теперь нажмём “Home”, выберем пункт “Ground Floor” затем пункт “Kitchen” или скопируем в браузер ссылку: localhost:8080/openhab.app?sitemap=demo#_GF_Kitchen, отправим текст “ON_2”, а затем “OFF_2” из терминала. Проконтролируем выключатель в WEB приложении, его состояние также должно изменяться.

a1406434fb484363a0771592a4592952.JPG

Результат нашего эксперимента можно наблюдать в консоли приложения, в которой появятся следующие сообщения:

e780a15d9096473f8b77e7dc81830063.JPG

На компьютере всё работает, пора подключаться к реальному миру!

В большинство плат Arduino уже встроен переходник с COM порта на USB, поэтому у нас не возникнет вопросов с присоединением к компьютеру или ноутбуку. Из подручных средств соберём макет, для этого нам понадобится контроллер Arduino, макетная плата, провода, кнопка, светодиод, резисторы 10кОм и 1кОм.

43d3ff91a9f140aeb677cf2ec27a132c.JPG

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

Откроем редактор, добавим в него следующий код и сохраним скетч в файле btn.ino.

// задаем константы const int buttonPin = 2; // номер входа, подключенный к кнопке const int ledPin = 13; // номер выхода светодиода // переменные int buttonState = 3; // переменная для хранения состояния кнопки int buttonCnt = 0; // переменная для защиты от дребезга кнопки void setup() { // инициализируем пин, подключенный к светодиоду, как выход pinMode(ledPin, OUTPUT); // инициализируем пин, подключенный к кнопке, как вход pinMode(buttonPin, INPUT); // настраиваем последовательный порт Serial.begin(9600); } void loop(){ // обработка дребезга кнопки delay(1); if (buttonState != digitalRead(buttonPin)) buttonCnt++; else buttonCnt = 0; if (buttonCnt > 100) { // считываем значения с входа кнопки buttonState = digitalRead(buttonPin); buttonCnt = 0; // проверяем нажата ли кнопка // если нажата, то buttonState будет LOW: if (buttonState == LOW) { // включаем светодиод digitalWrite(ledPin, HIGH); // отправляем команду Serial.print("ON_1"); } else { // выключаем светодиод digitalWrite(ledPin, LOW); // отправляем команду Serial.print("OFF_1"); } } } 

c37e5c7d6e6f4701b713ea12f9813023.JPG

Компилируем и загружаем скетч в Arduino, открываем монитор порта, выбираем скорость 9600 и пробуем нажать кнопку на плате. В результате мы можем наблюдать получение команд:

2969056c698445fa92846315c407a974.JPG

Теперь закроем монитор порта и настроим связь OpenHAB с контроллером, для этого отредактируем настройки нашего элемента (в моём случае нужно поменять значение на COM18 так как USB переходник Arduino встал на этот порт).

String Serial_string "Текст [%s]" { serial="COM18" } 

В завершении проконтролируем прохождение команд при помощи консоли и WEB интерфейса.

Что мы получили в результате нашей работы:

1 установили, настроили OpenHAB, научились добавлять элементы (item) и автоматизировать обработку при помощи правил (rule);
2 научились тестировать обмен при помощи программных средств. Эти инструменты помогут в дальнейшем при поиске неисправностей и в постановке собственных экспериментов;
3 сделали простейший скетч для Arduino, подключили его к OpenHAB, создали правило обрабатывающее текстовые команды, протестировали возможность управления через последовательный порт.

Выводы:

Отправка команд в текстовом виде обычное дело в практике Arduino, обработку таких команд можно реализовать в OpenHAB поэтому такой способ взаимодействия заслуживает внимания и обсуждения.

Недостатком полученного решения является то, что команды ничем не разделены между собой кроме паузы, это может приводить к их “слипанию” и ненадёжной обработке. Для решения этой проблемы необходимо доработать скетч и правило обработки команд либо внести изменения в модуль Serial-Binding.

Многие назовут такой вариант взаимодействия с OpenHAB любительским, поэтому прежде чем продолжать работу над ним попробуем связать OpenHAB и Arduino через протокол Modbus.

Об этом в следующих публикациях:
Arduino & Modbus habrahabr.ru/post/249043
Arduino & OpenHAB habrahabr.ru/post/252555
Открытый контроллер умного дома на базе Arduino vk.com/myremoter

RGB панели

 Arduino  Комментарии к записи RGB панели отключены
Июл 072019
 

Test Example Code

by Phillip Burgess

We have example code ready to go for these displays. It’s compatible with the Arduino Uno or Mega…but not other boards like the Leonardo, nor “Arduino-like” boards such as Netduino…programming gurus might be able to port it to other microcontrollers by adapting the C++ source, but as written it does some pretty low-level, non-portable things.

Continue reading »

Операция «пучеглазка». (RGB LED Matrix Panels Test)

 Arduino  Комментарии к записи Операция «пучеглазка». (RGB LED Matrix Panels Test) отключены
Июл 022019
 

В данной заметке речь пойдёт о светодиодных панелях, используемых в рекламных вывесках и архитектурных ТВ экранах.
Вот примерно таких:
bea69c.jpg

Continue reading »

si4463 описание

 Arduino  Комментарии к записи si4463 описание отключены
Мар 292019
 

Описание АТ команд модулей:

Для настройки модуля необходимо перевести его в командный режим, для этого необходимо притянуть контакт «SET» к массе и подождать ~40ms. Контакт «SET» имеет подтягивающий резистор на 10к. В командном режиме последовательный порт сконфигурирован на 9600bps. Если параметры модуля были изменены, после выхода из командного режима, они будут применены через ~80ms.

Continue reading »

DIY Professional Double Sided PCB

 Arduino  Комментарии к записи DIY Professional Double Sided PCB отключены
Мар 232019
 

Nowadays, PCBs can be bought extremely cheap from China. But let’s say you need one within 24 hours, making your own is then the only option. Furthermore, it’s way more challenging and fun!

Continue reading »

Simplify3D. Часть 3. Настройки процесса печати

 Arduino  Комментарии к записи Simplify3D. Часть 3. Настройки процесса печати отключены
Фев 072019
 

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

Continue reading »

Как сделать гравировку. Полная инструкция

 Arduino  Комментарии к записи Как сделать гравировку. Полная инструкция отключены
Фев 022019
 

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

Continue reading »

GYVERMATRIXOS

 Arduino  Комментарии к записи GYVERMATRIXOS отключены
Янв 312019
 

• 29.12.2018 ДОБАВЛЕНА ВЕРСИЯ 1.10
• ВЕРСИЯ 1.3 И ВЫШЕ ПРОШИВКИ GYVERMATRIXOS НЕ ПОМЕЩАЕТСЯ В ARDUINO NANO СО ВСЕМИ ЭФФЕКТАМИ И РЕЖИМАМИ НА МАТРИЦЕ 16Х16! ИСПОЛЬЗУЙТЕ МАТРИЦУ МЕНЬШЕГО РАЗМЕРА ИЛИ ОТКЛЮЧАЙТЕ BLUETOOTH/ТЕКСТ/ЭФФЕКТЫ ИЛИ ИСПОЛЬЗУЙТЕ ARDUINO MEGA/ESP8266! СМ. НИЖЕ.

Continue reading »