Микроконтроллеры STM32 и GigaDevice GD32 часто сравнивают из-за схожей архитектуры и совместимости. GD32 является своеобразным «клоном» STM32, использующим такое же ядро ARM Cortex-M. Основное отличие между ними — это цена, так как GD32 обычно дешевле, что делает его привлекательным для проектов с ограниченным бюджетом. Однако несмотря на схожесть, существуют различия в характеристиках и уровне поддержки, которые могут повлиять на выбор между этими двумя семействами микроконтроллеров.

Различия между STM32 и GD32


Хотя внешнее сходство и одинаковое наименование с микроконтроллерами STMicroelectronics могут навести на мысль о полном копировании, это не совсем так. У чипов от GigaDevice есть и принципиальные отличия от STM. Например:

  • частота работы: до 108 МГц для семейства GD32F1 (против 72 МГц у STM32F1);
  • объем флэш-памяти: до 3 МБ (STM32F2 поддерживает до 1 МБ).

Реальный опыт


Несколько лет назад (не в Selectel) мы разработали контроллер автоматики на STM32F107RCTx. В проекте использовались UART, Ethernet и несколько GPIO (проект под NDA, поэтому секретов не раскрываем). Софт был написан в CubeIDE с использованием FreeRTOS, HAL и lwIP и нескольких сторонних библиотек. Устройство настраивалось через USB и веб-интерфейс. Проект был успешно завершен, а заказчик доволен. Однако начался дефицит микросхем (чиппагедон), контроллеры STM32 резко подорожали и исчезли со складов.

Нам предложили перейти на GD32F107, утверждая, что он полностью совместим pin-to-pin. Писать софт для GD32 можно было в Keil или IAR (необходимо поставить GD32Fx AddOn). Для удобства разработки доступно GD32Fx Firmware Library со всеми драйверами и примерами. Мы приступили к разработке в IAR, написав все ПО заново. Заказчик снова счастлив, все работает.

Но в процессе эксплуатации приходят новые идеи или обнаруживаются баги. В результате возникает необходимость доработки ПО, и начинается увлекательная поддержка двух железок в двух разных IDE.


Реальный опыт х2


Все ведь программисты такие и любят делать работу по два раза? Начинаю искать, а можно ли сделать одно ПО, которое бы работало на обеих железках одновременно. Аппаратно чипы похожи, регистры и ножки одинаковые. Чуть-чуть поискав, нахожу документ под названием GD32 and STM32 Compatibility (compatibility sumup between GD32 and STM32_V2.0.pdf).

В нем перечислены аппаратные отличия между чипами GD32 and STM32 и практические рекомендации по «обходу» этих отличий (своеобразное Errata). Прочитав документ и не увидев там особого «криминала», решаю, что можно прошить в GD бинарник от STM и посмотреть, что из этого получится.

На удивление софт сразу стартует, на дисплее рисуются циферки. Не заработал Ethernet, но это уже мелочи, с этим можно работать. А заработает ли дебаг? При попытке запуска отладки получаем ошибку Could not verify ST device! Ну как бы логично, перед нами не совсем ST.


Не работает дебаг, значит попробуем решить по-другому и будем отлаживать софт бесконтактным способом (собирать бинарники и прошивать). Углубляемся в compatibility sumup. Для начала надо научить софт определять, в каком железе он «проснулся». Это можно делать по Jtag ID.

uint16_t Get_JTAG_ID()
{
	if(*(uint8_t *)(0xE00FFFE8) & 0x08)
	{
	return ((*(uint8_t *)(0xE00FFFD0)&0x0F)<<8) | ((*(uint8_t *)(0xE00FFFE4)&0xFF)>>3) | ((*(uint8_t *)(0xE00FFFE8)&0x07)<<5)+1;
	}
	return 0;
}
Если функция вернула 0x041 это ST, а если 0x7A3 то перед нами GD.

Поскольку самой серьезной «разницей» между чипами была рабочая частота, делаю отдельную функцию SystemClock_Config для GD. В ней с помощью PLL разгоняю частоту до 108 МГц (против 72 МГц у STM).

Собираю бинарник, прошиваю (спасибо, STM32 Link utility не делит чипы на свои и чужие и работает со всеми), дисплей и кнопки работают, Ethernet работает через раз. Путем телепатических усилий перебираю варианты, почему может не работать Ethernet. И примерно через 10 попыток собираю новый бинарник с отключенным autonegotiation, пусть сеть всегда работает на скорости на 100 Мбит (как по ТЗ). Догадка оказывается верной и интерфейс Ethernet начинает работать, IP-адрес по DHCP получается, данные передаются, лампочки моргают.

Дальше еще пара штрихов: надо добавить в веб-интерфейсе признак, что же за железка перед нами (это важно на случай потенциальных проблем и багов). Благо отличать железки по Jtag ID мы уже умеем.

Теперь единый проект теоретически готов, ПО одинаково работает в обеих железках. Но вдруг в будущем возникнут проблемы на GD? Как я буду их чинить без дебага?

Поиск способа отладки ПО, написанного в CubeIDE на железе GD


На YouTube нахожу экзотический вариант. В нем надо запустить несколько разных версий CubeIDE и путем манипуляций и переключений через меню запустить отладку. Автору, конечно, спасибо, но вариант явно не работает.

Дальше несколько раз на разных форумах попадается на глаза такая инструкция.

  1. Установить в настройках проекта Debug probe — OpenOCD.
  2. Отредактировать конфиг-файл C:\ST\STM32CubeIDE_1.3.0\STM32CubeIDE\plugins\ com.st.stm32cube.ide.mcu.debug.openocd_1.3.0.202002181050\ resources\openocd\st_scripts\target\stm32f1x.cfg, добавив туда строку set CPUTAPID 0.

Ноль сообщает OpenOCD, что нужно игнорировать идентификационные номера, а это значит, что все клоны или не STM-микроконтроллеры должны будут работать.

Скачиваю CubeIDE 1.3, благо под Windows можно одновременно поставить несколько разных версий IDE и они не будут друг другу мешать. Делаю новый тестовый проект. В настройках Debug probe выбираю OpenOCD, запускаю Debug. И — о чудо — вижу, что дебаг начался (на GD32F107x), можно походить по шагам и посмотреть переменные в памяти.

Теперь задача — перетащить рабочий проект, сделанный в старшей версии IDE. При открытии воркспейса получаем грозное сообщение, что версия не та и работоспособность не гарантируется. Принимаем на себя риски, открываем проект и видим сообщение про Missing nature (не разобрался, что это и зачем). CubeIDE будто бы предлагает решение, но оно не работает.


Раз проект, сделанный прямо в IDE 1.3, работает, а в более новой версии нет, попытаемся найти разницу в файлах. И она обнаруживается в файле .project. Вот, что у нас есть в проекте из 1.3:

com.st.stm32cube.ide.mcu.MCUCubeIdeServicesRevAProjectNature

А вот, что в проекте из 1.15:

com.st.stm32cube.ide.mcu.MCUCubeIdeServicesRevAev2ProjectNature


Руками меняем в боевом проекте одну строку на другую — и он сразу открывается. Уже лучше, пробуем собрать — опять проблема. Видим, что при сборке проект перестал влезать в отведенные ему области RAM. Но как же так? Что изменилось? Смотрим, кто же отвечает за сборку и компиляцию.

В IDE 1.15, где изначально был сделан проект, использовался toolchain gnu-tools-for-stm32.12.3.rel1, а единственная доступная в версии 1.3 — gnu-tools-for-stm32.7-2018-q2.

На портале community.st.com нахожу информацию, как можно добавить в CubeIDE альтернативную версию toolchain. Нужно просто скопировать с заменой все файлы отсюда:
C:\ST\STM32CubeIDE_1.15.0\STM32CubeIDE\plugins\ com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.12.3.rel1.win32_1.0.100.202403111256

После файлы нужно вставить сюда:
C:\ST\STM32CubeIDE_1.3.0\STM32CubeIDE\plugins\ com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.7-2018-q2-update.win32_1.0.0.201904181610

Также в настройках проекта принудительно необходимо установить Fixed-версию toolchain. Собираем проект — и успех! Запускаю отладку — и тоже успех. По шагам ходим, задача решена.


А как же установка CPUTAPID 0?


Отвечу: никак. Версия CubeIDE 1.3 при отладке через OpenOCD не задает лишних вопросов о происхождении чипа (если он GD32), все работает без этого лайфхака. Я попытался проделать трюк с прописываем CPUTAPID 0 в IDE с версиями 1.8, 1.7 и 1.5 и long story short — ни разу не получилось. Но уже было и не надо.

Заключение


Теперь у меня есть проект, сделанный в CubeIDE 1.3, который можно одинаково успешно запускать и отлаживать на железках на STM32 и GD32. Можно ли так делать в продакшене на реальных проектах? Скорее нет, чем да. По-моему, пользовательское соглашение CubeIDE такие вещи не приветствует. Или я не прав?

Так что все изложенное выше было сделано исключительно в исследовательских целях и в поля не пошло. А две версии ПО для данной железки мы так и продолжаем делать в двух разных IDE, внося все изменения по два раза, ведь все программисты такое любят. :-)

Комментарии (83)


  1. rsashka
    21.10.2024 08:51

    Я попытался проделать трюк с прописываем CPUTAPID 0 в IDE с версиями 1.8, 1.7 и 1.5 и long story short — ни разу не получилось.

    Кажется где то писали, что 1.3 последняя версия, где можно изменить CPUTAPID в конфиге. В более старших версиях он захардкожен в самом IDE.


    1. a3x Автор
      21.10.2024 08:51

      Вот я искал искал и не нашел и проверил. Но поскольку мне и в 1.3 это не понадобилось, то указал как есть. Возможно это надо делать в 1.3 с другими "клонами", а GD работает и так.


  1. ancc
    21.10.2024 08:51

    переходили с STM32 на GD32 (F4), все взлетело из почти из коробки, даже на ST либах (HAL не используем). IDE SW4STM32. Отладка на st-linkV2 прекрасно работает, на V3 уже нет (почему так не разбирались).


    1. a3x Автор
      21.10.2024 08:51

      Тоже видимо вариант, но такое IDE я раньше в глаза не видел.
      Тоже видимо вариант, но такое IDE я раньше в глаза не видел.

      Хотя конечно боязно немного начинать новый, далеко идущий проект в NRND IDE.


      1. ancc
        21.10.2024 08:51

        это просто эклипс с плугинами (openocd и еще по мелочи) чуть причесанный, чтобы все было из коробки.


  1. almaz1c
    21.10.2024 08:51

    Начиная с 22-го разработали пару устройств на gd32. Полет нормальный.


    1. a3x Автор
      21.10.2024 08:51

      Да, сделать новый проект на GD32 - проблем ноль. Хороший чип.
      А вот впилить в старый STM32 проект, новый чип - вот задача.


      1. Andy_Big
        21.10.2024 08:51

        Успешно и довольно легко впиливал. Пришлось только кое-где времянки поправить, т.к. мало того, что GD32 умеет работать на более высокой частоте, у него еще и нулевое время ожидания чтения флэша для первых 512 (если не ошибаюсь) кб, так что работает он заметно быстрее даже на той же частоте, что и STM32.
        Правда, я работал в IAR (который, кстати, из коробки поддерживает GD32 без всяких дополнительных аддонов), а у вас тут больше про борьбу с проприетарным софтом, а не с различиями между МК :)


        1. a3x Автор
          21.10.2024 08:51

          Точно? Насколько я помню, в IAR чтобы выбрать чип GD в настройках проекта - надо именно поставить IAR_GD32Fxx0_ADDON.
          Или вы про то, что ST проект из коробки запускается и дебажится на GD в IAR?

          А так задача была, не бороться с чипами, а запустить один проект на двух железках с минимальными телодвижениями.


          1. Andy_Big
            21.10.2024 08:51

            Вот без всяких аддонов :)

            Скрытый текст

            Или вы про то, что ST проект из коробки запускается и дебажится на GD в IAR?

            Ну, может быть с каким-то отладчиком так и получится, но JLink начинает ругаться на несоответствие идентификатора контроллера при попытке отладить проект под STM32 на GD32. Побороть можно правкой конфигов IAR.

            А вообще, тот проект был единственным, который я переводил на поддержку и STM32 и GD32, это как раз был тот чипогеддон, когда STM32 исчезли или их стоимость взлетела до небес. После него я уже делал проекты изначально под GD32.


            1. a3x Автор
              21.10.2024 08:51

              А у вас какая версия? В 8.3 такого еще не было и надо было ставить аддон.


              1. Andy_Big
                21.10.2024 08:51

                Сейчас - 9.50, скриншот из нее.


                1. a3x Автор
                  21.10.2024 08:51

                  Ясно, значит тут я подотстал.


            1. a3x Автор
              21.10.2024 08:51

              В 8.3 вот так с аддоном.

              Скрытый текст


  1. alcotel
    21.10.2024 08:51

    Поймал в 400й серии проблему, не описанную в errata - задержка от включения UART до физической передачи плавала случайно от 0 до 11 бит. Пришлось костылить хардкорный сброс UART (вместе с приёмником) перед каждым пакетом.

    Но вообще основное отличие GD от остальных флеш-мк в том, что он не флеш-мк. Внутри корпуса 2 кристалла - мк и SPI-флеш. По-моему @BarsMonster его вскрывал, или ещё кто-то, не помню точно. Прошивка из SPI-флеши при старте кэшируется в теневое ОЗУ, и оттуда уже выполняется.

    Из-за этого появляется несколько нюансов:

    • Время старта прошивки увеличивается, т.к. прошивку ещё надо скопировать.

      Это в даташите для F103xC
      Это в даташите для F103xC
    • Кэшируется не вся флеш, а только часть, которая влазит в теневое ОЗУ. Его объём одинаковый у каждой серии, независимо от объёма флеши. Хотя и довольно большой.

      Вот это, например, в мануале для всей серии F10x
      Вот это, например, в мануале для всей серии F10x
    • Зато отсутствие флеши на том-же кристалле позволяет повысить тактовую частоту, и главное - уменьшить время случайного доступа к программной памяти. JMPы, циклы, загрузка длинных констант становятся заметно быстрее.


    1. a3x Автор
      21.10.2024 08:51

      Так далеко про GD32 я еще не копал, спасибо за информацию.



    1. Gryphon88
      21.10.2024 08:51

      насколько я помню, вроде даже можно настраивать теневой ОЗУ: сколько использовать под "флеш", а сколько в качестве честной оперативки.


      1. Andy_Big
        21.10.2024 08:51

        Встречал в сети такое утверждение от анонимусов, но ни разу не видел его подтверждения :)


        1. Gryphon88
          21.10.2024 08:51

          Давайте засаммоним @COKPOWEHEU он с китайцами всяко больше нашего работал :)
          /me потапал бутылку на пиво


      1. Dozer88
        21.10.2024 08:51

        Это было в CH32V/Fxxx


      1. COKPOWEHEU
        21.10.2024 08:51

        На счет именно gd32 сказать не могу, но в некоторых других настраивается.

        Скажем, в ch32v303 можно настроить 288+32 | 256+64 | 224+96 | 192+128. А вся флешка целиком 480к, так что всегда остается некешируемый хвост, который можно использовать для хранения данных. На счет хранения кода не уверен, поскольку настроек wait-state-ов там вроде бы нет. Настраивается это через option-byte-ы, что-то вроде фьюзов.

        А в других контроллерах от тех же wch, такой настройки нет. В ch32v203 (кроме RB) размеры флеш и ОЗУ заданы жестко.

        Думаю, самым простым критерием кешируется ли флеш, будет скорость доступа. Если предусмотрены wait-state (пара битов в конце FLASH->ACTLR), то кеша нет.


        1. alcotel
          21.10.2024 08:51

          Если предусмотрены wait-state (пара битов в конце FLASH->ACTLR), то кеша нет.

          Не факт. У GD они предусмотрены, видимо, чтобы программные задержки были совместимы с кодом для ST. А при выходе за пределы кэша проц увеличивает задержку молча, безо всяких настроек.


          1. COKPOWEHEU
            21.10.2024 08:51

            Вот это интересно. Получается, вы проверяли скорость доступа к началу флеша и концу? А подскажите, какой у gd32 объем кеша.


            1. alcotel
              21.10.2024 08:51

              Не проверял. В даташите посмотрел.

              https://habr.com/ru/companies/selectel/articles/851478/comments/#comment_27443304


              1. COKPOWEHEU
                21.10.2024 08:51

                Не очень понял. 30к кешируется или 256к? Впрочем, тему вы подняли интересную, надо будет проверить


                1. alcotel
                  21.10.2024 08:51

                  В этом семействе максимум до 3М флеши. 256к кэшируется независимо от конкретного объёма флеши.


                  1. COKPOWEHEU
                    21.10.2024 08:51

                    256к? Тогда не проверю. У vf103 всей памяти максимум 128к


                    1. alcotel
                      21.10.2024 08:51

                      GD32F103RKT6 - 3 МБ

                      Минимум 16кБ в этой серии. Цифры я не выдумываю, всë в даташите есть.


                      1. COKPOWEHEU
                        21.10.2024 08:51

                        Если у вас есть gd32f103rk - отлично, можете проверить скорость доступа и рассказать что там с кешированием. У меня же под рукой только gd32vf103c8. Из всех riscv контроллеров gd у него больше всего памяти - целых 128 кБ. Но из того, что я от вас услышал, этого недостаточно.


    1. gev
      21.10.2024 08:51

      Поймал в 400й серии проблему, не описанную в errata - задержка от включения UART до физической передачи плавала случайно от 0 до 11 бит. Пришлось костылить хардкорный сброс UART (вместе с приёмником) перед каждым пакетом.

      Можно этот момент описать поподробней? Спасибо!


      1. alcotel
        21.10.2024 08:51

        Было у меня 2 задачи:

        простая - сформировать таймером сигнал направления передачи для RS485, +-1 бит,
        и сложная - сымитировать дифф.сигнал для обмена с MAX17843. +- 1/4 бита.

        Мк из разных семейств, но я думаю, всё похоже должно быть и у остальных. Везде UART работал через DMA - один раз запустил вместе со стробом, и забыл до следующей передачи. Синхростарт, естественно, делается с запретом прерываний.

        У ST (F105) передатчик UART стартует с задержкой +-1/2 бита, если не отключать UE. Если отключать, то не хуже +-1/16 бита. Отключение TE приводит к фиксированной доп.задержке в 11 бит.

        У GD (F405) 11-битная задержка появляется полностью рандомно, независимо от TE и UE. Точная повторяемость получилась, только если UART перед каждой передачей сбрасывать через RCC. И, соответственно, инициализировать заново.


        1. gev
          21.10.2024 08:51

          Спасибо. Я это делаю через TC (Transmit Complete) флаг и прерыание


          В обработчике прерывания UART ловим полученный байт и если нужно обрабатываем полное завершение трансмита

          handleUART uart onReceive onDrain = do
              handleReceive uart onReceive
              traverse_ (handleDrain uart) onDrain
          handleReceive uart onReceive = do
              rbne  <- getInterruptFlag   uart usart_int_flag_rbne
              when rbne $ do
                  clearInterruptFlag     uart usart_int_flag_rbne
                  onReceive =<< S.receiveData uart
          handleDrain uart onDrain = do
              tc <- getInterruptFlag      uart usart_int_flag_tc
              when tc $ do
                  clearInterruptFlag      uart usart_int_flag_tc
                  disableInterrupt        uart usart_int_tc
                  onDrain

          В обработчике прерывания DMA, если нужно, разрешаем прерыание UART при полном завершении передачи

          handleDMA dmaPer dmaCh uart onTransmit onDrain = do
              f <- getInterruptFlagDMA    dmaPer dmaCh dma_int_flag_ftf
              when f $ do
                  clearInterruptFlagDMA   dmaPer dmaCh dma_int_flag_ftf
                  M.when (isJust onDrain) $ do
                      enableInterrupt     uart usart_int_tc
                  onTransmit

          Есть подобное решение для передачи без DMA, только на прерываниях


          1. alcotel
            21.10.2024 08:51

            Только это работает в разы медленней, чем мне надо.

            Из прерываний уарта я использую только RXIDLE - по приëму пакета. Всë остальное - аппаратно. Выше мегабита/с по-другому не очень получается.


            1. gev
              21.10.2024 08:51

              Интрересное решение, нужно попробовать.


        1. gev
          21.10.2024 08:51

          Я так понимаю TE происходит когда трансмит буффер освободился и можно туда писать новые данные на передачу, а вот TC когда уже полностью данные переданы. Я их импользую оба для RS-485


          1. alcotel
            21.10.2024 08:51

            ТЕ и UE - я имел в виду биты управления, а не состояния.

            Они у ST и у GD слегка по-разному называются) Хотя суть скопирована почти 1:1


  1. gev
    21.10.2024 08:51

    О, мы тоже собирались переходить с PIC32 на STM32 а перешли в итоге на GD32. В процессе заодно перешли на GCC, а с С на Haskell. После перехода на Haskell привычный процесс отладки стал не нужен в принципе! По пути отказались и от RTOS! Код пишем в VSCode, но это не принципиально.


    1. a3x Автор
      21.10.2024 08:51

      У меня нервная система в порядок не пришла, после попыток писать под nrf52833 в VSCode. Очень своеобразный этот VSCode, к нему надо привыкнуть после эклипсоподобных IDE. Но скорее конечно, дело во мне и надо привыкнуть.


      1. gev
        21.10.2024 08:51

        Мы как раз уходили от всех этих визардов в IDE для конфигурирования проектов


        1. a3x Автор
          21.10.2024 08:51

          На Haskel все через библиотеку STM32-Zombie же делается? Правильно я понимаю, что идея такая же как на Rust?


          1. gev
            21.10.2024 08:51

            Нет, мы используем EDSL Ivory – безопасное подмножество AST С99 на Haskell. На выходе получаем C99 и его уже компилируем с помощью GCC


            1. Gryphon88
              21.10.2024 08:51

              Почитал, немного понял - как, но так и не понял - зачем) поясните, чем это лучше написания конфигурации руками или внешним конфигуратором?


              1. gev
                21.10.2024 08:51

                1. Безопасность за счет системы типов Haskell. Нельзя разыменовать null указатели, нельзя выйти за границы массива, опечататься и спутать = с == , забыть вставить break в switch/case , аллоцировать память там где запрещено и многое другое. Подробнее можно посмотреть в этой презентации

                2. Переносимость кода.

                3. Весь платформенно-зависимый код, "подтягивается", настраивается и инициализируется сам, нам достаточно описать бизнес-логику работы устройства и предоставить ей ресурсы платформы.

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

                5. Исключение муторного процесса отладки с программатором и брейкпоинтами. Забыли что такое дебаг совсем!

                6. Скрипты сборки вместо CMake и Make так же на Haskell с использованием библиотеки Shake. Компилятор, его параметры и переменные окружения описаны и статически типизированы и встроены в единый пайплайн от генерации кода получения бинарников.


                1. Gryphon88
                  21.10.2024 08:51

                  У меня такой вопрос про фп на мк: фп старательно избегает состояний, а как с этим быть в мк, где состояния есть и их под ковер не заметешь, а основное время мк занят ожиданием события? Нельзя же всё подряд оборачивать в монаду IO.


                  1. gev
                    21.10.2024 08:51

                    Мета-программа на Haskell строит AST-дерево программы на подмножестве C99. Потом AST-дерево сериализуется в исходный код C99 и комплируется, например, GCC


                  1. gev
                    21.10.2024 08:51

                    Нельзя же всё подряд оборачивать в монаду IO.

                    В IO работает только код сохранения C99 кода =)


                  1. gev
                    21.10.2024 08:51

                    В каментах ниже я пример приводил


          1. gev
            21.10.2024 08:51

            Вот это мы используем https://github.com/GaloisInc/ivory


          1. gev
            21.10.2024 08:51

            Если интересна тема безопасного C99, то есть еще вот такое (тоже от Галуа изначально)
            https://github.com/leepike/Copilot


            1. a3x Автор
              21.10.2024 08:51

              Для меня это вообще что-то новое, ни разу ничего подобного не использовал и не видел. Надо стало быть самообразовываться.


              1. gev
                21.10.2024 08:51

                Если, интересно, пишите в личку, соориентирую =)
                PS Тут на хабре проскакивали статьи про применение Haskell для embedded.


    1. SIWRX
      21.10.2024 08:51

      Haskell? Можете поделиться, каким образом?


      1. gev
        21.10.2024 08:51

        Кодогенерацей. Используем безопасный EDSL чобы генерить код на C99 и собираем его GCC. Вместо make используем shake. Получается Haskell выступает в роли "макросов на стероидах"


    1. fivlabor
      21.10.2024 08:51

      О_о, Haskell в embedded. А можете показать кусок кода? Например, как мигать светодиодом в прерывании таймера?

      Писать-то можно на чем угодно, но там же библиотеки всякие иногда удобно взять (ethercat/modbus/freertos/Lvgl) Или у вас самописное всё?


      1. gev
        21.10.2024 08:51

        https://www.youtube.com/watch?v=dhBYPcK23Dg&list=PLM_iD1tGoZA4MOGg5Eb3CwDnf7pQgRZuH&ab_channel=Би-2

        Из библиотек используем, например, LwIP


        1. gev
          21.10.2024 08:51

          Сори, по запарке, не то скопировал =)


      1. gev
        21.10.2024 08:51

        О_о, Haskell в embedded. А можете показать кусок кода? Например, как мигать светодиодом в прерывании таймера?

        В нашем фреймворке поверх Ivory EDSL будет вот такое описание самой прошивки:

        blink330 :: Formula GD32F3x0
        blink330 = Formula { name           = "blink330"
                           , model          = 0xff
                           , version        = (1, 0)
                           , shouldInit     = false
                           , implementation = blink out_pa_15 out_pa_14 timer_15
                           }

        И сам "блинк", например, такой (тут пара ножек мигать будет, одна в прерывании таймера, другая в основном цикле):

        blink :: (MonadState Context m, MonadReader (Domain p ()) m, Output o, Pull p u, Timer t)
              => (p -> u -> m o) -> (p -> u -> m o) -> (p -> Uint32 -> Uint32 -> m t) -> m ()
        blink out1 out2 timer = do
            let name          = "blink"
            mcu              <- asks mcu
            let peripherals'  = peripherals mcu
            out1'            <- out1 peripherals' $ pullNone peripherals'
            out2'            <- out2 peripherals' $ pullNone peripherals'
            timer'           <- timer peripherals' 1_000 1
            state1           <- value (name <> "_state1") false
            state2           <- value (name <> "_state2") true
        
            addHandler $ HandleTimer timer' $ toggle state1 out1'
            addTask $ delay 100 "blink" $ toggle state2 out2'

        Из этого будет сгенерирован такой C код:

        Hidden text
        /* This file has been autogenerated by Ivory
         * Compiler version  0.1.0.9
         */
        #include "blink330.h"
        
        uint32_t system_time = (uint32_t) 0U;
        
        uint8_t mac[6U];
        
        uint8_t model = (uint8_t) 255U;
        
        struct version_struct version = {.major =(uint8_t) 1U, .minor =(uint8_t) 0U};
        
        bool should_init = false;
        
        bool blink_state1 = false;
        
        bool blink_state2 = true;
        
        void systick_init_init(void)
        {
            SysTick_Config((uint32_t) 83999U);
        }
        
        void SysTick_Handler(void)
        {
            uint32_t n_deref0 = system_time;
            
            system_time = (uint32_t) (n_deref0 + (uint32_t) 1U);
        }
        
        void mac_init(void)
        {
            uint32_t n_r0 = read_addr_32u((uint32_t) 536868832U);
            uint32_t n_r1 = read_addr_32u((uint32_t) 536868780U);
            uint32_t n_r2 = read_addr_32u((uint32_t) 536868784U);
            uint32_t n_r3 = read_addr_32u((uint32_t) 536868788U);
            uint64_t n_cse4 = (uint64_t) ((uint64_t) ((uint64_t) ((uint64_t) n_r0 << (uint64_t) 32U) | (uint64_t) n_r1) % (uint64_t) 281474976710597U);
            uint64_t n_cse15 = (uint64_t) ((uint64_t) ((uint64_t) ((uint64_t) n_r2 << (uint64_t) 32U) | (uint64_t) n_r3) % (uint64_t) 281474976710597U);
            uint64_t n_local4 = (uint64_t) (n_cse4 ^ n_cse15);
            uint64_t *n_ref5 = &n_local4;
            
            for (int32_t n_ix6 = (int32_t) 0; n_ix6 <= (int32_t) 5; n_ix6++) {
                uint64_t n_deref7 = *n_ref5;
                uint64_t n_cse29 = (uint64_t) (n_deref7 & (uint64_t) 255U);
                
                mac[n_ix6] = (uint8_t) ((bool) (n_cse29 <= (uint64_t) UINT8_MAX) ? (uint8_t) n_cse29 : 0);
                *n_ref5 = (uint64_t) (n_deref7 >> (uint64_t) 8U);
            }
        }
        
        void GPIOA_GPIO_PIN_15_init(void)
        {
            rcu_periph_clock_enable(RCU_GPIOA);
            gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
            gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_15);
        }
        
        void GPIOA_GPIO_PIN_14_init(void)
        {
            rcu_periph_clock_enable(RCU_GPIOA);
            gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_14);
            gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_14);
        }
        
        void TIMER15_init(void)
        {
            rcu_periph_clock_enable(RCU_TIMER15);
            timer_deinit(TIMER15);
            
            uint32_t n_cse3 = (uint32_t) (SystemCoreClock / (uint32_t) 1000U);
            uint32_t n_cse8 = (uint32_t) (n_cse3 - (uint32_t) 1U);
            struct timer_parameter_struct n_local0 = {.prescaler =(uint16_t) ((bool) (n_cse8 <= (uint32_t) UINT16_MAX) ? (uint16_t) n_cse8 : 0), .period =0, .alignedmode =TIMER_COUNTER_EDGE, .counterdirection =TIMER_COUNTER_UP, .clockdivision =TIMER_CKDIV_DIV1, .repetitioncounter =(uint8_t) 0U};
            struct timer_parameter_struct *n_ref1 = &n_local0;
            
            timer_init(TIMER15, n_ref1);
            timer_enable(TIMER15);
        }
        
        void TIMER15_irq_init(void)
        {
            nvic_irq_enable(TIMER15_IRQn, (uint8_t) 0U, (uint8_t) 0U);
            timer_interrupt_enable(TIMER15, TIMER_INT_UP);
        }
        
        void TIMER15_IRQHandler(void)
        {
            bool n_r0 = timer_interrupt_flag_get(TIMER15, TIMER_INT_FLAG_UP);
            
            if (n_r0) {
                timer_interrupt_flag_clear(TIMER15, TIMER_INT_FLAG_UP);
                
                bool n_deref1 = blink_state1;
                
                blink_state1 = (bool) !n_deref1;
                if (n_deref1) {
                    gpio_bit_set(GPIOA, GPIO_PIN_15);
                } else {
                    gpio_bit_reset(GPIOA, GPIO_PIN_15);
                }
            }
        }
        
        void blink_task(void)
        {
            bool n_deref0 = blink_state2;
            
            blink_state2 = (bool) !n_deref0;
            if (n_deref0) {
                gpio_bit_set(GPIOA, GPIO_PIN_14);
            } else {
                gpio_bit_reset(GPIOA, GPIO_PIN_14);
            }
        }
        
        void init(void)
        {
            systick_init_init();
            mac_init();
            GPIOA_GPIO_PIN_15_init();
            GPIOA_GPIO_PIN_14_init();
            TIMER15_init();
            TIMER15_irq_init();
        }
        
        void loop(void)
        {
            uint32_t n_local0 = (uint32_t) 0U;
            uint32_t *n_ref1 = &n_local0;
            
            {
                int forever_loop __attribute__((unused));
                
                for (forever_loop = 0; IFOREVER; IFOREVER_INC) {
                    uint32_t n_deref2 = system_time;
                    uint32_t n_deref3 = *n_ref1;
                    
                    if ((bool) ((uint32_t) (n_deref2 - n_deref3) >= (uint32_t) 100U)) {
                        blink_task();
                        *n_ref1 = n_deref2;
                    }
                }
            }
        }
        
        int32_t main(void)
        {
            init();
            loop();
            return (int32_t) 0;
        }
        


        1. gev
          21.10.2024 08:51

          addHandler $ HandleTimer timer' $ toggle state1 out1'

          addTask $ delay 100 "blink" $ toggle state2 out2'

          Собственно сам toggle

          toggle :: Output o => Value IBool -> o -> Ivory eff ()
          toggle state out = do
              v <- deref state
              store state $ iNot v
              ifte_ v (set   out)
                      (reset out)


  1. Gryphon88
    21.10.2024 08:51

    Основное отличие между ними — это цена, так как GD32 обычно дешевле, что делает его привлекательным для проектов с ограниченным бюджетом.

    Имхо, если оценивать устройство на этапе запуска первой серии и эта серия менее 1000-10000 изделий, то цена МК не является решающей, если, конечно, это не что-то толстое уровня Н7 или специализированное. Да, если серии большие и мы уже на этапе оптимизации - можно попробовать отыграть 1-2 бакса на мк, иначе смысла мало. Для меня более важным является доступность; сейчас у STM и GD (у них "голова" в США, кстати) она примерно одинаковая, но для перестраховки я б переходил на континентальных китайцев - WCH и Artery, причем на RISC-V для новых проектов.


    1. a3x Автор
      21.10.2024 08:51

      Да, вы скорее правы. В статье шла речь, про чиппагедон 22 года, когда STM32 пропали, а GD32 резко стали "предлагаться" дистрибьюторами в качестве pin2pin замены.

      На Artery уже смотрели для новых проектов, но как то страшновато пока, нет пока большого опыта разбираться в китайском коде и китайских схемах. Но скорее всего это неизбежно.


      1. Gryphon88
        21.10.2024 08:51

        GD тоже пропадали примерно в то же время, в 22 было окно поставок примерно на полгода. Они были доступнее в то время в основном в силу меньшей популярности: хвосты стмок в магазинах подъели быстро, а гдшки залежались. В 20м была примерно такая же история - пропало всё сразу, со сроком поставки в 40-60 недель.

        Что WCH, что AT в обоих версиях (RISC/ARM) стараются сохранять имена и раскладку регистров и pin2pin совместимость с STM, по поводу документации и инструментов... ну, сильно скромнее STM, но из-за исполнения где не понял - можно читать STшную документацию, обычно работает :)


        1. Stas_mpower
          21.10.2024 08:51

          А как у них с защитой от взлома ?


  1. nixtonixto
    21.10.2024 08:51

    Из критичных отличий - более слабые порты, то есть 10 мА оптрон MOC3052 он может открыть только при температуре выше нуля. И гнилой АЦП - ниже 0,3 В его приходится линеаризировать по таблице, а входное сопротивление во всём диапазоне раз в 10 меньше, из-за чего сигналы приходилось буферизировать. У нас использовалась почти вся периферия Ф103 и общие впечатления негативные.

    И, несмотря на более высокую тактовую частоту, по сравнению с ART-акселератором СТМ32 в реальных приложениях заметного прироста производительности нет. Это особенности работы конвейера и теневого ОЗУ.


    1. Serge78rus
      21.10.2024 08:51

      Позвольте Вас дополнить ссылкой на мой комментарий по результатам сравнения STM32F103C8T6 и GD32F103C8T6 https://habr.com/ru/news/676068/comments/#comment_24512920


  1. ovn83
    21.10.2024 08:51

    Не вижу статей про замену stm32 младших серий на risc v, поморгали светодиодом и успокоились, никто особо переезжать не хочет. Путь с заменой на gd32 прошли два года назад.


    1. a3x Автор
      21.10.2024 08:51

      Дайте на арме освоиться, что за зверь risc v - вообще не знаю.


      1. strvv
        21.10.2024 08:51

        В принципе, как я пробовал, так как большой нужды не было -
        если пишешь на С без извратов, и выносишь аппаратную (этакий HAL) отдельно - пофиг.
        Я не загонялся с таймерами и прочим - простая State-Machine.
        То есть на многих задачах разницы не будет, если грамотно подходить к ним.


        1. a3x Автор
          21.10.2024 08:51

          Ну вообще да, C без извратов - наше все.


        1. DrGluck07
          21.10.2024 08:51

          Мы так и сделали, бизнес-логика на С++, можно использовать на любом контроллере, а HAL на C уже для каждого свой собственный. У нас тут получилась более забавная ситуация: всё писали на GD32, а потом один проект нужно было на STM32. В общем, никаких проблем не возникло.


    1. LAutour
      21.10.2024 08:51

      никто особо переезжать не хочет

      Скорее никто особо статей писать не хочет.