Сначала короткая предыстория появления этого поста. Относительно
давно, помигав светодиодом, захотелось сделать что-то полезное. Так
появился Беспроводной программируемый по Wi-Fi комнатный
термостат с монитором качества воздуха и другими полезными
функциями. Как назло, в это время перестал работать мой
промышленный термостат. Меня выручила еще сырая поделка, наспех
спрятанная в картонную коробочку. За время отопительного сезона
напрягал лишь один недостаток прототипа это необходимость таскать
по квартире удлинитель 220В и кабель, который всегда путался под
шваброй ногами. Поэтому решил сделать нечто похожее, но
автономное, притом, с питанием от батареек, как в серийном образце.
Тут я завис надолго.
Приступая к задаче, для меня было очевидно одно вряд ли
программы промышленных автономных устройств составлены на платформе
Arduino IDE. Где все спрятано в громоздкие тяжеловесные библиотеки,
а простые коды (скетчи) занимают в редакторе несколько десятков
строк, делая работу в этой среде комфортной и не требующей особых
усилий. Уточню сразу дальше речь выборе языка программирования
между Ардуино, Си или Ассемблером. Хотя, понятно, на потреблении
устройства в целом сказывается слишком много факторов таких, как
энергопотребление подключенных модулей, потребление самого
контроллера, который управляет периферией, не последнюю роль тут
играет оптимальное построение самого кода и алгоритм работы
устройства.
направу ехати женату быти; налеву ехати богату быти
В начале пути, перед выбором направления меня оптимистично
настроила статья Почему многие не любят Arduino. Ниже, для
наглядности, картинка оттуда с кодом мигалки.
Пример слева написан на языке Ардуино, а справа работа
непосредственно с регистрами. Код на Ардуино выглядит несколько
компактней, чем та же мигалка, но с использованием регистров.
На изображении ниже компиляция кода мигалки на Ассемблере. Как
видно, былая компактность испарилась количество строк в 3 раза
больше, чем в Ардуино.
И так, с 2 картинок выше видно размер памяти, занимаемой в
контроллере кодом мигалки одним светодиодом, написанным на Ардуино,
составляет 1030 байт, на Си 176 байт, на Ассемблере 42 байта.
Теперь взглянем на более сложный код. Поскольку в своих проектах
использую модуль давления-температуры BMP280, составил код
барометра-термометра на Си, чтобы заодно была какая-то польза.
барометр-термометр на Си
/* На распутье - Ардуино, Cи или Ассемблер? http://personeltest.ru/aways/habr.com/ru/post/547752/ */#include <avr/io.h>#include <avr/interrupt.h>#include <util/delay.h>#include "bmp180/bmp180.c"#include "uart.c"#include <stdio.h>#include <stdlib.h>#include <string.h>#include "nokia/nokia5110.h"int main(void) { serial_init(); DDRD |= (1 << 7); //pin 13, atmega328p PORTD &= ~(1 << 7); nokia_lcd_init(); while (1) { init_sensor(bmp180_mode_0); calculate(); PORTD |= (1 << 7); _delay_ms(100); printf("Temperature: %.2f C, Pressure: %.2f Pa, \n", (float) bmp_180.temperature / 10, (float)bmp_180.pressure); nokia_lcd_clear(); nokia_lcd_write_string("789",1); nokia_lcd_set_cursor(0, 10); nokia_lcd_write_string("22.2", 3); nokia_lcd_render(); _delay_ms(2000); PORTD &= ~(1 << 7); _delay_ms(100); int a=54325; char buffer[20]; itoa(a,buffer,2); // here 2 means binary printf("Binary value = %s\n", buffer); itoa(a,buffer,10); // here 10 means decimal printf("Decimal value = %s\n", buffer); itoa(a,buffer,16); // here 16 means Hexadecimal printf("Hexadecimal value = %s\n", buffer); } return 0;}
В проект входят следующие компоненты: контроллер ATMEGA328P,
модуль давления-температуры BMP180 и дисплей Nokia 3110. ATMEGA328P
принимает инфу с датчика BMP180 и после преобразований отображает
ее на дисплее Nokia 3110, затем спит. Сон задается сторожевым
таймером Watchdog. Проект собирается в Atmel Studio 7 и эмулируется
в Proteus 8 Pro. Этот проект Atmel Studio был создан для отладки
кода в Proteus'e. В библиотеке Proteus 8 Pro модуля BMP280 нет,
поэтому пришлось составить код с включением BMP180. Светодиод в
коде для наглядности, чтобы придать динамику статичной
картинке.
Ниже электрическая схема устройства. При монтаже схемы обращайте
внимание на функциональное назначение выводов контроллера и
модулей. Подключение кварца XTAL1, XTAL2 (ATMEGA328P). Уточню,
схему барометра-термометра на BMP180 я "в железе" не собирал,
поэтому тут могут проявиться проблемы, которые не видны при
эмуляции в Proteus'e.
Для скачивания zip-файла проекта в Atmel Studio 7 перейдите по
ссылке тут все виртуальные проекты и
коды программ из этой публикации.
Файл прошивки bmp180-nokia3110-watchdog-1.hex для Proteus'а
находится в папке Debug. В файле main.c есть закомментированный код
барометра-термометра на BMP280. Его электрическая схема такая же,
как и у барометра-термометра на BMP180. Он успешно собирается в
Atmel Studio 7 и работает "в железе". Для работы "в железе"
пришлось внести изменения в строке #define BMP280_ADDR
0x77 файла библиотеки bmp280.c, а именно: заменить начальный адрес
0x77 на 0x76. Не забудьте сделать эту корректировку, если будете
использовать в своих проектах код барометра-термометра на BMP280 из
main.c, с уже подключенной библиотекой bmp280.c.
Ниже код этого же барометра-термометра в платформе Arduino IDE.
Естественно, с другими библиотеками.
барометр-термометр на Ардуино
/* На распутье - Ардуино, Cи или Ассемблер? http://personeltest.ru/aways/habr.com/ru/post/547752/ */#include <SPI.h>#include <LowPower.h>#include <SimpleTimer.h>#include <Adafruit_BMP280.h>#include <Adafruit_GFX.h> //https:esp8266.ru/forum/threads/esp8266-5110-nokia-lcd.1143/#post-16942#include <Adafruit_PCD8544.h> //https:esp8266.ru/forum/threads/esp8266-5110-nokia-lcd.1143/#post-16942Adafruit_BMP280 bmp280;float Press, Tin; //давление, температураAdafruit_PCD8544 display = Adafruit_PCD8544(5, 7, 6);void setup() { Serial.begin(9600); display.begin(); // display.clearDisplay(); display.setContrast(60); // установка контраста while (!bmp280.begin(BMP280_ADDRESS - 1)) { Serial.println(F("Could not find a valid BMP280 sensor, check wiring!")); delay(100); }}void loop() { // измерение температуры, давления Tin = bmp280.readTemperature(); Press = bmp280.readPressure() / 133.3; Serial.println("Temperature: " + String(Tin) + "*C"); Serial.println("Pressure: " + String(Press) + "mm Hq"); display.clearDisplay(); //давление, мм рт.ст. { display.setTextSize(1); display.setCursor(20, 5); display.println (Press, 0); // нет знаков после запятой display.setCursor(41, 5); display.println("mmHq"); } //температура, *C { display.setTextSize(2); display.setCursor(15, 20); display.println (Tin, 1); // один знак после запятой display.setCursor(66, 20); display.println("C"); } display.display(); LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);}
Ресурсы, потребляемые программой барометра-термометра на Си и в
Arduino IDE наглядно показаны на картинке:
Как видно, эти примеры потребляют 5954 байт (С) и 12956 байт
(Arduino IDE) в Flash. Соотношение изменилось с 6-ти раз для
мигалки до 2-х с небольшим. К сожалению, линейной зависимости нет
чем объемней код, тем меньше соотношение размеров памяти Ардуино к
Си. В идеале на этой картинке должен присутствовать 3 столбец с
кодом на Ассемблере, но такого кода в Интернете я не нашел, а
составить код самому мне не под силу.
Попутно замечу, что использование компилируемых в Arduino IDE
библиотек и функций на С/С++ особо имеет смысл в тех случаях, когда
размер занимаемой памяти превышает или близок к размеру памяти
контроллера. Мне, например, часто удается уходить от
предупреждения: Недостаточно памяти, программа может работать
нестабильно.
Теперь посмотрим еще один вариант это цифровой
термометр-гигрометр на AM2302 (DHT22), ATtiny13 и MAX7219, код
которого составлен на Ассемблере.
Автор статьи задался целью разработать простой
термометр-гигрометр выполненном на одном из самых маленьких
микроконтроллеров ATtiny13 с весьма скромными характеристиками 1Кб
программной памяти, 64 байтами ОЗУ и 5 интерфейсными выводами. Он
решил эту непростую задачку, выбрав Ассемблер, заодно вспомнив те
далекие времена, когда код можно было составлять на низкоуровневых
языках, используя машины типа ZX-Spectrum.
Ниже скриншот со сборкой данного кода в Atmel Studio 7.
Код устройства на Ассемблере занимает 738 байт памяти в
контроллере. Безусловно, программа барометра-термометра, о котором
шла речь выше, будь ее код составлен на Ассемблере, заняла бы
больше места. По нескольким причинам в схеме реализовано управление
дисплеем Nokia3110 по интерфейсу SPI (это 5 линий связи, тут 3),
связь с датчиком BMP280 осуществляется по протоколу I2C (2 линии,
тут 1) и дополнительные символы, которые позволяют не гадать
температура это или другой параметр.
Из того, что я нашел в Интернете, можно утверждать, Ассемблер
даст выигрыш в размере кода для относительно больших проектов
процентов 10-20 по сравнению с Си. Но надо учитывать, что в больших
проектах Си может уменьшить размер кода за счёт лучшей
оптимизации.
Код Ассемблера выполняется практически на машинном уровне: один
цикл одна команда. В качестве аргумента приведу пример из справочника по
командам ассемблера AVR. Установка бита в регистре ввода/вывода
SBI A, b. Эта команда устанавливает заданный бит в регистре
ввода-вывода. На выполнение этой операции контроллерами megaAVR
потребуется 2 цикла и на tinyAVR, XMEGA 1 цикл. Для схемы с
контроллером ATtiny13 и резонатором 9,6 МГц выполнение команды
займет один цикл, то есть 1/9600000 Гц = 0,104 мксек.
Выполнение похожей операции на языке Си, например, задать
состояние порта PORTB = 32; займет в этой же схеме не меньше
времени. А о Ардуино и говорить нечего там придется выполнить
объемную функцию void digitalWrite(uint8_t pin, uint8_t
val);. Подробно о размерах кода в Си и Ардуино читайте
тут.
Поэтому разработчики серийных продуктов, как правило, пишут коды
на низкоуровневых языках. С тем, чтобы разместить программу в
контроллере с меньшей памятью. Тут работают законы экономики
контроллер с меньшими ресурсами стоит дешевле, следовательно
себестоимость изделия становится ниже.
Теперь о энергосбережении немножко издали. Вспомним, что код
Ассемблера выполняется на машинном уровне: один цикл одна или
несколько команд. в зависимости от типа контроллера. Это десятые
доли микросекунды. То есть, на выполнение программы с размером
несколько десятков байт уйдут единицы-десятки микросекунд. Дальше
контроллер бесконечно будет крутить этот набор 0 и 1, затрачивая
энергию на перезаряд емкости затворов сотен полевых транзисторов,
на которых построен кристалл контроллера, а также чтение и записи
данных в его память. Длительность периода повтора будет зависеть
только от размера кода в памяти контроллера, неважно на каком языке
он составлен. Просто на Ассемблере он будет наименьшим, а на
Ардуино наибольшим. Соответственно, период цикла для кода на
Ассемблере наименьший, на Ардуино наибольший.
Уменьшить эти затраты можно остановив процессор или программно
уменьшив частоту его работы. В Ассемблере переход в "спящий" режим
сна выполняет функция управления контроллером SLEEP. В других можно
использовать функцию WDT (WatchDog Timer), а в Ардуино еще и
функцию LowPower.powerDown (SLEEP_1S, ADC_OFF, BOD_OFF), заодно
отключив все лишнее, что не используется в конкретной задаче. В
эффективности этой функции сможет убедиться каждый, заменив в
скетче мигалки (скетч на картинке вначале статьи) функцию отсчета
времени delay(1000); этой функцией и включив в разрыв
питания контроллера амперметр. Да, не забудьте подключить
библиотеку LowPower.h. На Си это сделал автор этой статьи. Ток в цепи
питания attiny13a с паузой 1,5мА, со сном 240мкА. Потребление в
6(!) раз меньше.
Допустим, вы намерены собрать барометр-термометр и задумываетесь
о энергосбережении. Понятно, что давление/температура в заданной
разрядности не изменятся за несколько минут, которые для
контроллера целая вечность. Ему можно выделить это время для сна.
После сна он снова выполнит свою работу: примет информацию с
датчика, преобразует в понятные для человека циферки и выведет все
это на дисплей. И в таком режиме работа-сон он будет крутиться,
пока не сядут батарейки. Объем работы контроллера, вернее время,
которое контроллер будет занят выполнением работы, зависит от того,
на каком языке составлена программа барометра-термометра. Если есть
возможность загрузить в контроллер код на выбор ArduinoIDE, C,
Assembler, с одинаковым временем сна, то в каком из трех
предложенных вариантов батарейки сядут раньше (позже)? Мой ответ
ArduinoIDE (Assembler).
Кстати, настоятельно рекомендую хотя бы вскользь посмотреть
Реальная правда о Программистах ненавидящих Arduino.
Поучительный материал. Автор подробно описывает свою историю от
первых радостей мигания светодиодом и чуда по тем временам вывода
температуры на дисплей от мобилки до Ардуино с дельными
советами.
Так куда же идти? На мой взгляд, для любителей, как я, это
платформа Arduino IDE с низкоуровневыми вставками. Тем же, кому
тесно в Ардуино, в С. Assembler не освоить за несколько месяцев. Да
и осваивать не имеет особого смыла коды на С можно оптимизировать
иногда до размеров не намного больше, чем в Ассемблере.
Спасибо за внимание. Всего наилучшего!
Ссылки по теме