Pull to refresh

Разработка однофазного измерительного прибора на ESP32

Reading time10 min
Views9.4K

Введение

Ранее рассказывал о разработке 3х фазного измерительного прибора ИРИС. Недавно мы выпустили однофазную версию этого устройства. Изначально задача звучала «По-быстрому, сделать однофазную версию с минимальными изменениям: вырезать же проще, чем разрабатывать новые функции». Но потом прозвучала фраза «А давайте все сделаем на ESP32, зачем нам 2 микроконтроллера в таком простом приборе». В итоге, поменялась как аппаратная, так и программная платформа проекта.

Внешний вид устройства
Внешний вид устройства

Сравнение характеристик

Сравнительная таблица программно-аппаратных отличий первой и второй версий прибора.

Ирис v1

3х фазный

Ирис v2 однофазный

Контроллер

STM32H7 + ESP32

ESP32

Встроенная флеш (код программы)

+

-

QSPI flash

+

+

I2C EEPROM

+

-

АЦП

встроенный в stm32

внешний

Дискретный вход/выход

+

-

Поддержка рюкзаков (модули расширения с дискретными входами/выходами, аналоговыми выходами, ethernet IEC 60870-104 / 61850MMS)

-(*)

+

Количество ног

 

намного меньше

Операционная система

нет

FreeRTOS

* В планах выпуск модификации 3х фазной версии прибора с поддержкой рюкзаков

Архитектура

Архитектура 3х фазного ИРИСа
Архитектура 3х фазного ИРИСа
Архитектура однофазного ИРИСа
Архитектура однофазного ИРИСа

Пришлось переделать:

  • хранение данных, т.к. имеется только один тип памяти

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

Список потоков:

1)      Чтение данных от АЦП

2)      Обработка сигналов (ДПФ, СКЗ, вычисление частоты и т.д.)

3)      Меню (7 сегментные индикаторы и обработка нажатий на кнопку)

4)      Осциллограф

5)      Модбас (RS485)

6)      Модбас (BT)

7)      Диагностика

8)      … 

Самодиагностика

В приборе реализована самодиагностика компонентов:

1)      Отсутствие прерываний от АЦП

2)      Отсутствие связи с рюкзаком

3)      Устройство не откалибровано

4)      Заканчивается память (куча или стек потока)

5)      Внутренняя ошибка (используется в ходе разработки, аналог функции assert())

Для диагностики размера стека нужно в настройках FreeRTOS выбрать: CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y

Код модуля диагностики:

static uint32_t s_taskFreeStack[TASK_COUNT];
static uint32_t s_minFreeHeap;
static uint32_t s_internalErrLine;

void diag_setInternalErr(uint32_t lineNum) {
                s_internalErrLine = lineNum;
}

void diag_setStackUsage(uint32_t taskIdx, uint32_t freeStack)
{
                if (taskIdx < TASK_COUNT) {
                               if (s_taskFreeStack[taskIdx] > freeStack) {
                                               s_taskFreeStack[taskIdx] = freeStack;
                                               if (freeStack < MIN_FREE_STACK_THRESHOLD)
                                                               diag_setError(ERR_FREE_STACK_ERROR, true);
                                               diag_dbg_printf("task %u freeStack %u", taskIdx, freeStack);
                               }
                }

                uint32_t minFreeHeap = xPortGetMinimumEverFreeHeapSize();
                if (s_minFreeHeap > minFreeHeap) {
                               s_minFreeHeap = minFreeHeap;
                               if (s_minFreeHeap < MIN_FREE_HEAP_THRESHOLD)
                                               diag_setError(ERR_FREE_HEAP_ERROR, true);
                               diag_dbg_printf("minFreeHeap %u", s_minFreeHeap);
                }
}

//...
// вызов из функций потока
void task_func(void *arg) {
                while(1) {
                               switch(/*..*/){
                                               case 1: /*..*/ break;
                                               case 2: /*..*/ break;
                                               default:
                                                               diag_setInternallErr(__LINE__);
                               }
                               ...
                               diag_setStackUsage(NINJA_TASK_DIAG, uxTaskGetStackHighWaterMark(NULL));
                }
}

Особенности ESP32

В отличие от STM32 в модулях ESP32-wroom32 отсутствует встроенная в микроконтроллер флеш, вместо нее программный код для исполнения налету извлекается из внешней qspi флеш. Отсюда дешевизна чипа, но вместе с тем и некоторые неудобства в разработке, а именно, весь реалтайм код и обработчики прерываний нужно помещать в ОЗУ, т.к. доступ к внешней qspi флеш «дорогой» по времени.

Сложности: программа стала вылетать с исключением.

Пример вывода

Guru Meditation Error: Core  0 panic'ed (Cache disabled but cached memory region accessed)

Core 0 register dump:

PC      : 0x400ddf74  PS      : 0x00060034  A0      : 0x40082ac0  A1      : 0x3ffbe320 

A2      : 0x00000000  A3      : 0x00000002  A4      : 0x00000000  A5      : 0x4008e788 

A6      : 0x00000000  A7      : 0x13020248  A8      : 0x80082eed  A9      : 0x3ffbe300 

A10     : 0x00000001  A11     : 0x00000000  A12     : 0x3ffca2cc  A13     : 0x3ffca2cc 

A14     : 0x00843004  A15     : 0x3ffca2c8  SAR     : 0x00000020  EXCCAUSE: 0x00000007 

EXCVADDR: 0x00000000  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000 

Core 0 was running in ISR context:

EPC1    : 0x40088321  EPC2    : 0x00000000  EPC3    : 0x40081be0  EPC4    : 0x400ddf74

 

ELF file SHA256: 1a7ec99b2c553435

 

Backtrace: 0x400ddf71:0x3ffbe320 0x40082abd:0x3ffbe340 0x4008831e:0x3ffb7db0 0x40082099:0x3ffb7dd0 0x4008e55d:0x3ffb7df0

Для расшифровки стека используется команда:

xtensa-esp32-elf-addr2line -e <build_file_name>.elf -a 0x400ddf71:0x3ffbe320 0x40082abd:0x3ffbe340 0x4008831e:0x3ffb7db0 0x40082099:0x3ffb7dd0 0x4008e55d:0x3ffb7df0

Ошибка была в том, что для работы с GPIO из прерывания я использовал стандартную функцию gpio_set_level(gpio_num_t gpio_num, uint32_t level), но она не была помечена атрибутам IRAM_ATTR, который указывает на размещение кода в ОЗУ. Пока кода было мало, он с большой вероятностью помещался в кэш, но с увеличением размера программы вероятность исключения возросла. Пришлось заглянуть в реализацию функции и написать свою версию:

#include "hal/gpio_ll.h"
extern gpio_dev_t GPIO;
IRAM_ATTR esp_err_t gpio_set_level2(gpio_num_t gpio_num, uint32_t level) {
                gpio_ll_set_level(&GPIO, gpio_num, level);
                return ESP_OK;
}

Работа с АЦП

С чего программист начинает знакомство с новым железом?

Правильно, с чтения даташита поиска готовых драйверов.

Для нашего АЦП драйвер нашелся в ядре линукса. Осталось только портировать его на ESP32.

Сложности: стандартный драйвер esp-idf\components\driver\spi_master.c оказался слишком медленным и не успевал читать данные из АЦП с требуемой частотой (2000 Гц). Нашлась альтернативная реализация которая такое быстродействие обеспечивает.

Рюкзаки (модули расширения с дискретными входами/выходами, аналоговыми выходами и Ethernet)

Внешний вид прибора с модулем расширения
Внешний вид прибора с модулем расширения

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

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

Три варианта SPI-цепочек:

1)      Однофазный ИРИС с барграфом и рюкзаком дискретных входов/выходов

2)      Однофазный ИРИС без барграфа с рюкзаком дискретных входов/аналоговых выходов

3)      Трехфазный ИРИС с рюкзаком дискретных входов/аналоговых выходов

В итоге, нашел вариант проще: устройство формирует данные так, чтоб до рюкзака они всегда приходили по фиксированному смещению путем добавления фиктивных байтов (в 1-ом варианте 0, во 2-ом 2 байта и в 3-ем 4 байта).

Механизм обновления прошивки

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

В ESP32 «из коробки» доступен альтернативный вариант, когда на флеш размечается несколько «слотов» под прошивку и при обновлении новая версия ПО записывается в очередной свободный слот, после чего он помечается выполняемым. Преимущество такого подхода в том, что для функции обновления доступно всё API основной программы, например, обновление по беспроводному каналу (OTA – update over the air)

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

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

Шифрование микропрограммы

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

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

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

Благодарю за внимание.

PS: статья писалась программистом для программистов поэтому прошу прощения за сленг.

Tags:
Hubs:
Total votes 6: ↑5 and ↓1+5
Comments44

Articles