Оригинал: Guidelines for writing code for the ESP8266 – Daniel Casner
Перевод несколько вольный, но без полной отсебятины. Оригинальный текст под спойлерами. Просьба к асам - просмотреть на предмет смысловых ошибок и некорректностей перевода.
Рекомендации по написанию кода для ESP8266
ESP8266 WiFi SoC от Espressif - безумно популярный чип для проектов IoT, и любительских и профессиональных, потому что объединяет MCU и WiFi в дешевой одно-кристальной схеме. Это обсуждалось во многих блогах, но я хотел дать немного больше технической информации о архитектуре MCU и моих принципов его успешного программирования. Есть некоторые особенности, которые нужно учитывать, программируя, чтобы получить все возможное от SoC.
Архитектура чипа
см. Фотографию в оригинале статьи
Память команд
У ESP8266 есть ROM в которой хранятся немного библиотечного кода и начальный загрузчик. Весь остальной код хранится на внешней SPI flash. 32kb кода могут быть постоянно загружены в RAM. Оставшийся код загружается при необходимости через SPI и кэшируется в добавочных 32kb RAM. Кэшированный код работает быстро, если не подгружать его слишком часто. Признак ICACHE_FLASH_ATTR используется для размещения кода в кэше вне основного 32kb ядра.
Архитектура процессора
В "системе на чипе" используется ядро Tensilica Xtensa lx106 MCU представляющее собой 32bit процессор с командами длиной 16 bit [от переводчика: правильнее сказать 24 и 16 бит] но это не ARM. Тут Гарвардская архитектура с полностью разделенными памятью команд и данных.
Периферия
Вся периферия (UART, I2S, Radio, etc.) была сделана Espressif и она несколько специфична и на нее придется потратить некоторое время
Одно из основных ограничений скорости это то что любые чтения/запись регистров неожиданно медленны поэтому обращения к ним лучше свести к минимуму
DMA: На данный момент имеется информация о поддержке DMA только одним периферийным устройством - I2S. С использованием некоторых хитрых приемов можно использовать его для SPI.
Правильное программирование
Структура кода
- Каждый блок кода должен быть либо Задачей RTOS либо вызываться по таймеру;
- Желательно чтобы каждый блок кода выполнялся менее чем за 2ms, и он не в коем случае не должен выполняться более чем 500ms, поскольку при этом watchdog перезагрузит процессор;
- Не вызывайте блок кода по таймеру чаще чем каждые 5 ms ;
- Имеется три уровня приоритета для задач операционной системы: 0,1,2. Все задачи 2-го уровня будут выполнены до того как выполнятся задачи 1-го уровня, и только после выполнения всех задач 1-го уровня начнут выполняться задачи 0-го уровня. Уровень 2 - зарезервирован за системными (HAL) функциями;
- Вызов по таймеру имеет больший приоритет чем задачи операционной системы;
- Задачи и обработчики таймеров не вытесняют друг друга. Новая задача или обработчик не будут запущены пока не завершится текущ(ая/ий);
- Прерывания не должны выполняться или блокировать процессор более чем на 10μs иначе WiFi поведет себя непредсказуемо. Если не выполнять этого требования вы получите "мистические" ошибки "lmac.c ### / mac ### ", которые означают что прерывания обрабатываются недостаточно быстро;
- Запись и чтение регистров периферийных устройств неожиданно медленны. Везде где возможно(и необходимо) кэшируйте значения регистров в памяти.
Производительность и память
- Любой блок кода не вызывающийся слишком часто должен иметь признак ICACHE_FLASH_ATTR;
- Все обработчики прерываний и любые блоки кода вызываемые из них не должны иметь признака ICACHE_FLASH_ATTR;
- Espressif не слишком быстр, и загрузка кода происходит относительно медленно, но памяти в нем достаточно, поэтому если есть такая возможность, выбирайте решения где меньше кода и больше данных;
- "Куча" доступна через malloc/free однако пользоваться ей нужно экономно и осторожно.
Размер кода
- Учитывая что Espressif использует внешнюю SPI flash допустимый размер кода получается практически неограниченным (зависит только от размера этой SPI flash);
- Однако загрузка кода из SPI flash относительно медленная и в локальной RAM есть весьма ограниченная область используемая как кэш кода. Поэтому внутренние циклы и код, который выполняется часто, должны быть относительно небольшими, чтобы уменьшить частоту загрузки кода в кэш.
Перевод несколько вольный, но без полной отсебятины. Оригинальный текст под спойлерами. Просьба к асам - просмотреть на предмет смысловых ошибок и некорректностей перевода.
Guidelines for writing code for the ESP8266
Espressif’s ESP8266 WiFi SoC is an increasingly popular chip for Internet of Things projects, both hobby and professional, because it combines a capable MCU with a WiFi radio in a single chip for an amazingly low cost. It’s received a lot of attention in many blogs but I wanted to give a little bit more technical depth on the MCU architecture and my best practices for programming it. There are some idiosyncrasies which have to be taken into account when programming to get the most out of the SoC.
Chip Architecture
см. Фотографию в оригинале статьи
Code Memory
The ESP8266 has on die program ROM which includes some library code and a first stage boot loader. All the rest of the code must be stored in external SPI flash. 32kb of code can be loaded into dedicated code RAM. The rest of the code is fetched over SPI and cached in additional 32kb of RAM. Cached code can be fast as long as there isn’t too much churn. The ICACHE_FLASH_ATTR decorator is used to locate code in the SPI flash memory instead of the core 32kb of RAM.
Processor Architecture
The SoC uses a Tensilica Xtensa lx106 MCU which is a 32bit processor with 16 bit instructions but is not ARM. It is Harvard architecture which most significantly means that instruction memory and data memory are completely separate.
Peripherals
All of the MCU peripherals (UART, I2S, Radio, etc.) seem to have been developed custom by Espressif and are a bit idiosyncratic so they take some time to get used to.
One of the most significant speed limitations is that all register reads / writes are surprisingly slow so they should be kept to a minimum.
DMA: So far the only peripheral which has any kind of DMA which has been figured out is the I2S peripheral. With some clever tricks, I2S can also be used for SPI.
Programming Best Practices
Code structure
All application code must be in either a task or a timer
All tasks and timers should complete in less than 2ms and must complete in less than 500ms or the watchdog timer will reset the MCU.
Reoccurring timers should not be scheduled more often than every 5ms.
There are 3 user task levels: 0, 1, 2.
All level 2 tasks queued run before any queued level 1 tasks and so on.
Task level 2 is reserved for HAL functions
All level 2 tasks queued run before any queued level 1 tasks and so on.
Task level 2 is reserved for HAL functions
Timers which are due are higher priority than tasks.
Tasks and timers do not preempt each other. A new task or timer cannot start until the running task / timer has ended.
Interrupts must not be locked out for more than 10μs at a time or WiFi will crash.
No ISR can run for more than 10μs.
This is likely the cause of the mysterious lmac.c ### / mac ### errors which many people have encountered. They happen when the MCU can’t service radio interrupts fast enough.
No ISR can run for more than 10μs.
This is likely the cause of the mysterious lmac.c ### / mac ### errors which many people have encountered. They happen when the MCU can’t service radio interrupts fast enough.
Register reading and writing is surprisingly slow, cache where possible, etc.
Performance and Memory
Application code should have the ICACHE_FLASH_ATTR decorator unless it is executed very often.
All interrupt handlers must not have the ICACHE_FLASH_ATTR decorator and any code which executes very often should not have the decorator.
The Espressif is not especially fast and fetching code is relatively slow, however, it has a relatively large amount of RAM so when making computation/code-size/RAM use trade offs, generally take the path that uses more RAM.
Heep (malloc and free) is available but should be used sparingly and cautiously.
Code size:
Since the Espressif uses external SPI flash, there is effectively unlimited code memory.
However, loading code from SPI flash is relatively slow and there is a limited code cache in local chip RAM. Hence inner loops and code which executes often should be kept relatively small to reduce cache thrashing.