Особенности работы Arduino nano с памятью

Свободная память

В качестве хобби, разрабатываю бортовой компьютер для своего автомобиля УАЗ патриот. Ну как компьютер..громко звучит. Функций не много, но те которых не хватает в повседневном использовании.. Но сейчас не об этом, о БП выпущу потом отдельную статью.

А сейчас поговорим о самой прошивке. Использую в разработке дисплей на базе чипсета SSD1306. Сначала заметил, что в то время как разрастается код программы, на экране случайным образом начинают появляться различные артефакты, в виде «белого шума». Дальше — больше, заполняю массив в одном месте программы, и через какое то время он начинает затираться случайными данными. Одно время даже уже даже почти отчаялся, И уж никак не приходило в голову, что всё это следствие простой не хватки оперативной памяти Arduino.

Хотя по идее, это первое что должно было придти в голову. Но! во всем виноват Яндекс ;). Он подсовывал ссылки в которых рассказывалось о подобных проблемах, но ни в одной из них не было рекомендации подумать в сторону памяти. Всякий бред типа «подпаяйте резистор, нет контакта, плохая плата, не правильно передаёте массив, не так работаете со строками» и .д. и т.п. Однако, решил на всякий случай измерить остаток оперативной памяти в процессе выполнения программы. Код для измерения следующий:

Запустил… и оказалось что проблемы с экраном начинаются, когда свободно памяти становится меньше 168 байт. Проблемы с очисткой и замусоривание переменных — когда остается меньше 140 байт. А сейчас самое время вспомнить, что в arduino nano всего 2кб оперативной памяти и 32кб flash (ну на самом деле на разных чипах по разному, но конкретно на используемой мной — Atmega 327p именно столько). И этого мало! Любое объявление переменной тратит память. Выход? Arduino способен хранить данные и во Flash. Но храниться они будут несколько своеобразно: фактически в оперативной памяти будет храниться ссылка на ячейку памяти Flash где хранятся данные. Показать переменной что она будет храниться во Flash памяти призван модификатор PROGMEM. Например, для того чтобы объявить что данные для отображения графики будут храниться во Flash памяти можно так:

И в принципе всё. Данные, будут помещены во Flash память. PROGMEM может работать со всеми целочисленными типами (8, 16, 32, 64 бита), float и char.
Важное замечание — PROGMEM применяется только к глобальным переменным, которые расположены ВНЕ функций.

Чтение переменных

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

  • pgm_read_byte(data) – для 1-го байта (char, byte, int8_t, uint8_t)
  • pgm_read_word(data) – для 2-х байт (int, word, unsigned int, int16_t, int16_t)
  • pgm_read_dword(data) – для 4-х байт (long, unsigned long, int32_t, int32_t)
  • pgm_read_float(data) – для чисел с плавающей точкой

,где data это адрес (или указатель) блока данных

Вот как например можно прочитать массив из флэш памяти:

Кроме того, есть встроенная функция — помогалка F(), которая при использовании компилируется в строку — константу во флеш памяти, и подставляется в нужном месте сама. Например код вида:

Serial.println(F(«Я занимаю одинаковое количество оперативной памяти вне зависимости от своей длины»));

Будет соответственно занимать 2 байта при любой длине строки.

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

Решено: Нужна помощь зала. Не объяснимая ошибка

Прошу очень помощи зала. Ситуация следующая: пишу прошивку «Бортовой компьютер» автомобиля
на основе Arduino Nano (хобби у меня такое, не работа).
Минимальный пример, для воспроизведения ошибки (удалил по максимуму всё, лишь бы ошибка воспроизводилась):

https://wokwi.com/projects/390348182761575425

Проблема в следующем, не обьяснимым образом, теряются данные в переменных при вызове функции translate.

Например при использовании вот так (строки 141-146):

На экран выводится «Bridge». Однако стоит убрать строчку

Как волшебным образом, на экран начинает выводится и Transmission: и Bridge:.

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

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

Update: си не умеет возвращать строки. Только массивы символов. Поэтому верный код что-то вроде:

Arduino: передача массива строк в качестве функции

За что я не очень люблю СИ, так за то что можно споткнуться на пустом месте и долго думать почему так? И в этот раз тоже, долго гуглил, почему у меня не получается передать массив строк в функцию, массив внутри «обнулялся». Наконец наткнулся, что СИ это не умеет. Умеет только передавать указатель на массив. Поэтому функция может выглять подобным образом:

Arduino Nano: использование сдвигового регистра, пример

Сдвиговые регистры — это два вида микроскхем 74HC595 и 74HC165. первый соответственно предназначен для расширения пинов для выхода (например помигать светодиодиками, включать выключать реле и т.п.), а второй для расширения пинов для входа (чтение состояние кнопочек, датчики движения и т.п.). Наиважнейшим моментом отмечу, что считывать/посылать состояние можем только в виде 0 и 1. Соответственно подключать можем устройства которые могут принимать только два состояния: вкл и выкл. Рассмотрим схему 74HC595

Соответственно на выходы Q0-Q7 мы сможем подавать состояние включено-выключено (1/0), отослав на ногу DS маску из 8 бит. Например 01010101. Остальные ноги:

  • GND — земля
  • MR — сброс значений регистра. Сброс происходит при получении LOW
  • SH_CP (SRCLK) — линия синхроимпульса для передачи данных из DS во внутренние ячейки (вход для тактовых импульсов).
  • SH — shift, CP — clock pin ST_CP (RCLK) — линия синхроимпульса для передачи данных из внутренних ячеек, во внешние (синхронизация выходов).
  • ST — storage, CP — clock pin OE — инверсный, разрешение на вывод данных с внешних ячеек (вход для переключения состояния выходов из высокоомного в рабочее)
  • DS (SER) — линия последовательных данных (Data Serial)
  • VCC — питание, 5В

Пример подключения 8 светодиодов в виде схемы:

И в виде сборки на макетных платах:

Простейший скетч для такой схемы подключения:

Русификация дисплея SSD1306

В интернете полно инструкций по русификации данного дисплея. Фактически все рекомендации сводятся к замене в папке C:\Users\USER\Documents\Arduino\libraries\Adafruit_GFX_Library (у вас другой путь) файла glcdfont.c вот этим файлом, и далее использованием вот этой функции для конвертации UTF-8 в кодировку windows-1251:

У меня увы, этот метод не сработал, как бы я не бился. И файл со скетчем переводил и в UTF-8 и в кодировку windows-1251 и чего только не делал. Ну не работает и всё тут. Потом на какое-то время заработало, потом опять перестало. Разобраться почему я так и не смог. В результате пошел другим путем — написал онлайн конвертор UTF-8 в восьмеричный формат кодировки windows-1251, после чего стало возможным вставлять в IDE код вида:

Пусть не сильно читаемо в коде, но зато работает максимально стабильно. Конвертер ниже:



Кроме того, обнаружил еще одну не объяснимую ошибку при работе с данным дисплеем: если в программе интенсивно используется вывод в консоль при помощи Serial.println то на дисплее возникают артефакты в виде шума.

1 2 3 4