Сначала короткая предыстория появления этого поста. Относительно давно, помигав светодиодом, захотелось сделать что-то полезное. Так появился Беспроводной программируемый по Wi-Fi комнатный термостат с монитором качества воздуха и другими полезными функциями. Как назло, в это время перестал работать мой промышленный термостат. Меня выручил еще сырой макет, наспех спрятанный в картонную коробочку. За время отопительного сезона напрягал лишь один недостаток прототипа – это необходимость таскать по квартире удлинитель 220В и кабель, который всегда путался под шваброй ногами. Поэтому решил сделать нечто похожее, но автономное, притом, с питанием от батареек, как в серийном образце.
Понятно, что на потреблении устройства в целом сказывается слишком много факторов таких, как энергопотребление подключенных модулей, потребление самого контроллера, который управляет периферией, не последнюю роль тут играет оптимальное построение самого кода и алгоритм работы устройства.
Приступая к задаче, для меня было очевидно одно – вряд ли программы промышленных автономных устройств составлены на платформе Arduino IDE. Где все спрятано в громоздкие тяжеловесные библиотеки, а простые коды (скетчи) занимают в редакторе несколько десятков строк, делая работу в этой среде комфортной и не требующей особых усилий. Уточню сразу – дальше речь о выборе языка программирования между Ардуино, Си или Ассемблером. "Язык Ардуино"- это сленг для краткости. Нет такого языка программирования. Если увидите тут и дальше "язык Ардуино", то — это "Arduino IDE — интегрированная среда разработки для Windows, MacOS и Linux, разработанная на Си и C ++"(Википедия).
В начале пути меня оптимистично настроила статья Почему многие не любят Arduino. Ниже, для наглядности, картинка оттуда с кодом «мигалки».
Пример слева написан в платформе Arduino IDE, а справа — работа непосредственно с регистрами. Скетч выглядит несколько компактней, чем та же «мигалка», но с использованием регистров.
На изображении ниже — компиляция кода «мигалки» на Ассемблере. Как видно, былая компактность испарилась – количество строк в 3 раза больше, чем в Ардуино.
Итак, с 2 картинок выше видно – размер памяти, занимаемой в контроллере кодом «мигалки» одним светодиодом, написанным в платформе Ардуино, составляет 1030 байт, на Си – 176 байт, на Ассемблере – 42 байта.
Теперь взглянем на более сложный код. Поскольку в своих проектах использую модуль давления-температуры BMP280, составил код барометра-термометра на Си, чтобы заодно была какая-то польза.
/*
На распутье - Ардуино, Cи или Ассемблер?
https://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 перейдите по ссылке – тут все виртуальные проекты и коды программ из этой публикации.
Файлы прошивок *.hex находятся в папках Debug соответствующего проекта Atmel Studio 7. В архиве есть проект барометра-термометра на BMP280. Его электрическая схема такая же, как и у барометра-термометра на BMP180. Проект успешно собирается в Atmel Studio 7 и работает "в железе". Для работы "в железе" пришлось внести изменения в строке #define BMP280_ADDR 0x77 файла библиотеки bmp280.c, а именно: заменить начальный адрес 0x77 на 0x76. Не забудьте сделать эту корректировку, если будете использовать в своих проектах код барометра-термометра на BMP280, с подключенной библиотекой bmp280.c.
Ниже — код этого же барометра-термометра в платформе Arduino IDE. Естественно, с другими библиотеками.
/*
На распутье - Ардуино, Cи или Ассемблер?
https://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-16942
Adafruit_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», затрачивая энергию на перезаряд емкости затворов сотен полевых транзисторов, на которых построен кристалл контроллера, а также чтение и записи данных в его память. Длительность периода повтора будет зависеть только от размера кода в памяти контроллера, неважно на каком языке он составлен. Просто на Assembler'е он будет наименьшим, а в Arduino IDE – наибольшим. Соответственно, период цикла для кода на Assembler'е – наименьший, в Arduino IDE – наибольший.
Уменьшить эти затраты можно остановив процессор или программно уменьшив частоту его работы. В Ассемблере переход в "спящий" режим сна выполняет функция управления контроллером 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 IDE с низкоуровневыми вставками. Тем же, кому тесно в Arduino IDE, — в С. Хотя коды на С можно оптимизировать иногда до размеров не намного больше, чем в Assembler’е, все-таки для понимания работы контроллера стоит напрячься и освоить азы Assembler’а. Ведь полезность знаний – это аксиома.
Спасибо за внимание. Всего наилучшего!
Ссылки по теме
- Превращаем Arduino в полноценный AVRISP программатор, HWman
- Почему многие не любят Arduino, HWman
- Atmel Studio (видео)
- PROTEUS 8 для начинающих (видео)
- Код blink в формате *.asm на github
- Простой цифровой термометр/гигрометр на AM2302 (DHT22), ATtiny13 и MAX7219, kdekaluga
- Справочник по командам ассемблера AVR
- AVR — Power management или как правильно спать
- Реальная правда о Программистах ненавидящих Arduino, free_arduino