Микроконтроллеры 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 и путем манипуляций и переключений через меню запустить отладку. Автору, конечно, спасибо, но вариант явно не работает.
Дальше несколько раз на разных форумах попадается на глаза такая инструкция.
- Установить в настройках проекта Debug probe — OpenOCD.
- Отредактировать конфиг-файл 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)
ancc
21.10.2024 08:51переходили с STM32 на GD32 (F4), все взлетело из почти из коробки, даже на ST либах (HAL не используем). IDE SW4STM32. Отладка на st-linkV2 прекрасно работает, на V3 уже нет (почему так не разбирались).
almaz1c
21.10.2024 08:51Начиная с 22-го разработали пару устройств на gd32. Полет нормальный.
a3x Автор
21.10.2024 08:51Да, сделать новый проект на GD32 - проблем ноль. Хороший чип.
А вот впилить в старый STM32 проект, новый чип - вот задача.Andy_Big
21.10.2024 08:51Успешно и довольно легко впиливал. Пришлось только кое-где времянки поправить, т.к. мало того, что GD32 умеет работать на более высокой частоте, у него еще и нулевое время ожидания чтения флэша для первых 512 (если не ошибаюсь) кб, так что работает он заметно быстрее даже на той же частоте, что и STM32.
Правда, я работал в IAR (который, кстати, из коробки поддерживает GD32 без всяких дополнительных аддонов), а у вас тут больше про борьбу с проприетарным софтом, а не с различиями между МК :)a3x Автор
21.10.2024 08:51Точно? Насколько я помню, в IAR чтобы выбрать чип GD в настройках проекта - надо именно поставить IAR_GD32Fxx0_ADDON.
Или вы про то, что ST проект из коробки запускается и дебажится на GD в IAR?А так задача была, не бороться с чипами, а запустить один проект на двух железках с минимальными телодвижениями.
Andy_Big
21.10.2024 08:51Вот без всяких аддонов :)
Скрытый текст
Или вы про то, что ST проект из коробки запускается и дебажится на GD в IAR?
Ну, может быть с каким-то отладчиком так и получится, но JLink начинает ругаться на несоответствие идентификатора контроллера при попытке отладить проект под STM32 на GD32. Побороть можно правкой конфигов IAR.
А вообще, тот проект был единственным, который я переводил на поддержку и STM32 и GD32, это как раз был тот чипогеддон, когда STM32 исчезли или их стоимость взлетела до небес. После него я уже делал проекты изначально под GD32.
alcotel
21.10.2024 08:51Поймал в 400й серии проблему, не описанную в errata - задержка от включения UART до физической передачи плавала случайно от 0 до 11 бит. Пришлось костылить хардкорный сброс UART (вместе с приёмником) перед каждым пакетом.
Но вообще основное отличие GD от остальных флеш-мк в том, что он не флеш-мк. Внутри корпуса 2 кристалла - мк и SPI-флеш. По-моему @BarsMonster его вскрывал, или ещё кто-то, не помню точно. Прошивка из SPI-флеши при старте кэшируется в теневое ОЗУ, и оттуда уже выполняется.
Из-за этого появляется несколько нюансов:
-
Время старта прошивки увеличивается, т.к. прошивку ещё надо скопировать.
-
Кэшируется не вся флеш, а только часть, которая влазит в теневое ОЗУ. Его объём одинаковый у каждой серии, независимо от объёма флеши. Хотя и довольно большой.
Зато отсутствие флеши на том-же кристалле позволяет повысить тактовую частоту, и главное - уменьшить время случайного доступа к программной памяти. JMPы, циклы, загрузка длинных констант становятся заметно быстрее.
BarsMonster
21.10.2024 08:51Да, с флешем все так: https://zeptobars.com/en/read/GD32F103CBT6-mcm-serial-flash-Giga-Devices
Gryphon88
21.10.2024 08:51насколько я помню, вроде даже можно настраивать теневой ОЗУ: сколько использовать под "флеш", а сколько в качестве честной оперативки.
Andy_Big
21.10.2024 08:51Встречал в сети такое утверждение от анонимусов, но ни разу не видел его подтверждения :)
Gryphon88
21.10.2024 08:51Давайте засаммоним @COKPOWEHEU он с китайцами всяко больше нашего работал :)
/me потапал бутылку на пиво
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), то кеша нет.
alcotel
21.10.2024 08:51Если предусмотрены wait-state (пара битов в конце FLASH->ACTLR), то кеша нет.
Не факт. У GD они предусмотрены, видимо, чтобы программные задержки были совместимы с кодом для ST. А при выходе за пределы кэша проц увеличивает задержку молча, безо всяких настроек.
COKPOWEHEU
21.10.2024 08:51Вот это интересно. Получается, вы проверяли скорость доступа к началу флеша и концу? А подскажите, какой у gd32 объем кеша.
alcotel
21.10.2024 08:51Не проверял. В даташите посмотрел.
https://habr.com/ru/companies/selectel/articles/851478/comments/#comment_27443304
COKPOWEHEU
21.10.2024 08:51Не очень понял. 30к кешируется или 256к? Впрочем, тему вы подняли интересную, надо будет проверить
alcotel
21.10.2024 08:51В этом семействе максимум до 3М флеши. 256к кэшируется независимо от конкретного объёма флеши.
COKPOWEHEU
21.10.2024 08:51256к? Тогда не проверю. У vf103 всей памяти максимум 128к
alcotel
21.10.2024 08:51GD32F103RKT6 - 3 МБ
Минимум 16кБ в этой серии. Цифры я не выдумываю, всë в даташите есть.
COKPOWEHEU
21.10.2024 08:51Если у вас есть gd32f103rk - отлично, можете проверить скорость доступа и рассказать что там с кешированием. У меня же под рукой только gd32vf103c8. Из всех riscv контроллеров gd у него больше всего памяти - целых 128 кБ. Но из того, что я от вас услышал, этого недостаточно.
gev
21.10.2024 08:51Поймал в 400й серии проблему, не описанную в errata - задержка от включения UART до физической передачи плавала случайно от 0 до 11 бит. Пришлось костылить хардкорный сброс UART (вместе с приёмником) перед каждым пакетом.
Можно этот момент описать поподробней? Спасибо!
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. И, соответственно, инициализировать заново.
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, только на прерываниях
gev
21.10.2024 08:51Я так понимаю TE происходит когда трансмит буффер освободился и можно туда писать новые данные на передачу, а вот TC когда уже полностью данные переданы. Я их импользую оба для RS-485
alcotel
21.10.2024 08:51ТЕ и UE - я имел в виду биты управления, а не состояния.
Они у ST и у GD слегка по-разному называются) Хотя суть скопирована почти 1:1
-
gev
21.10.2024 08:51О, мы тоже собирались переходить с PIC32 на STM32 а перешли в итоге на GD32. В процессе заодно перешли на GCC, а с С на Haskell. После перехода на Haskell привычный процесс отладки стал не нужен в принципе! По пути отказались и от RTOS! Код пишем в VSCode, но это не принципиально.
a3x Автор
21.10.2024 08:51У меня нервная система в порядок не пришла, после попыток писать под nrf52833 в VSCode. Очень своеобразный этот VSCode, к нему надо привыкнуть после эклипсоподобных IDE. Но скорее конечно, дело во мне и надо привыкнуть.
gev
21.10.2024 08:51Мы как раз уходили от всех этих визардов в IDE для конфигурирования проектов
a3x Автор
21.10.2024 08:51На Haskel все через библиотеку STM32-Zombie же делается? Правильно я понимаю, что идея такая же как на Rust?
gev
21.10.2024 08:51Нет, мы используем EDSL Ivory – безопасное подмножество AST С99 на Haskell. На выходе получаем C99 и его уже компилируем с помощью GCC
Gryphon88
21.10.2024 08:51Почитал, немного понял - как, но так и не понял - зачем) поясните, чем это лучше написания конфигурации руками или внешним конфигуратором?
gev
21.10.2024 08:51Безопасность за счет системы типов Haskell. Нельзя разыменовать
null
указатели, нельзя выйти за границы массива, опечататься и спутать=
с==
, забыть вставитьbreak
вswitch/case
, аллоцировать память там где запрещено и многое другое. Подробнее можно посмотреть в этой презентацииПереносимость кода.
Весь платформенно-зависимый код, "подтягивается", настраивается и инициализируется сам, нам достаточно описать бизнес-логику работы устройства и предоставить ей ресурсы платформы.
Использовать внешние конфигураторы для одного проекта, вероятно, удобно. Для нескольких десятков с пересекающимся функционалом становится очень сложно, особенно если разные платформы и компиляторы.
Исключение муторного процесса отладки с программатором и брейкпоинтами. Забыли что такое дебаг совсем!
Скрипты сборки вместо CMake и Make так же на Haskell с использованием библиотеки Shake. Компилятор, его параметры и переменные окружения описаны и статически типизированы и встроены в единый пайплайн от генерации кода получения бинарников.
Gryphon88
21.10.2024 08:51У меня такой вопрос про фп на мк: фп старательно избегает состояний, а как с этим быть в мк, где состояния есть и их под ковер не заметешь, а основное время мк занят ожиданием события? Нельзя же всё подряд оборачивать в монаду IO.
gev
21.10.2024 08:51Мета-программа на Haskell строит AST-дерево программы на подмножестве C99. Потом AST-дерево сериализуется в исходный код C99 и комплируется, например, GCC
gev
21.10.2024 08:51Нельзя же всё подряд оборачивать в монаду IO.
В
IO
работает только код сохранения C99 кода =)
gev
21.10.2024 08:51Если интересна тема безопасного C99, то есть еще вот такое (тоже от Галуа изначально)
https://github.com/leepike/Copilot
fivlabor
21.10.2024 08:51О_о, Haskell в embedded. А можете показать кусок кода? Например, как мигать светодиодом в прерывании таймера?
Писать-то можно на чем угодно, но там же библиотеки всякие иногда удобно взять (ethercat/modbus/freertos/Lvgl) Или у вас самописное всё?
gev
21.10.2024 08:51https://www.youtube.com/watch?v=dhBYPcK23Dg&list=PLM_iD1tGoZA4MOGg5Eb3CwDnf7pQgRZuH&ab_channel=Би-2
Из библиотек используем, например, LwIP
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; }
gev
21.10.2024 08:51addHandler $ 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)
Gryphon88
21.10.2024 08:51Основное отличие между ними — это цена, так как GD32 обычно дешевле, что делает его привлекательным для проектов с ограниченным бюджетом.
Имхо, если оценивать устройство на этапе запуска первой серии и эта серия менее 1000-10000 изделий, то цена МК не является решающей, если, конечно, это не что-то толстое уровня Н7 или специализированное. Да, если серии большие и мы уже на этапе оптимизации - можно попробовать отыграть 1-2 бакса на мк, иначе смысла мало. Для меня более важным является доступность; сейчас у STM и GD (у них "голова" в США, кстати) она примерно одинаковая, но для перестраховки я б переходил на континентальных китайцев - WCH и Artery, причем на RISC-V для новых проектов.
a3x Автор
21.10.2024 08:51Да, вы скорее правы. В статье шла речь, про чиппагедон 22 года, когда STM32 пропали, а GD32 резко стали "предлагаться" дистрибьюторами в качестве pin2pin замены.
На Artery уже смотрели для новых проектов, но как то страшновато пока, нет пока большого опыта разбираться в китайском коде и китайских схемах. Но скорее всего это неизбежно.
Gryphon88
21.10.2024 08:51GD тоже пропадали примерно в то же время, в 22 было окно поставок примерно на полгода. Они были доступнее в то время в основном в силу меньшей популярности: хвосты стмок в магазинах подъели быстро, а гдшки залежались. В 20м была примерно такая же история - пропало всё сразу, со сроком поставки в 40-60 недель.
Что WCH, что AT в обоих версиях (RISC/ARM) стараются сохранять имена и раскладку регистров и pin2pin совместимость с STM, по поводу документации и инструментов... ну, сильно скромнее STM, но из-за исполнения где не понял - можно читать STшную документацию, обычно работает :)
nixtonixto
21.10.2024 08:51Из критичных отличий - более слабые порты, то есть 10 мА оптрон MOC3052 он может открыть только при температуре выше нуля. И гнилой АЦП - ниже 0,3 В его приходится линеаризировать по таблице, а входное сопротивление во всём диапазоне раз в 10 меньше, из-за чего сигналы приходилось буферизировать. У нас использовалась почти вся периферия Ф103 и общие впечатления негативные.
И, несмотря на более высокую тактовую частоту, по сравнению с ART-акселератором СТМ32 в реальных приложениях заметного прироста производительности нет. Это особенности работы конвейера и теневого ОЗУ.
Serge78rus
21.10.2024 08:51Позвольте Вас дополнить ссылкой на мой комментарий по результатам сравнения STM32F103C8T6 и GD32F103C8T6 https://habr.com/ru/news/676068/comments/#comment_24512920
ovn83
21.10.2024 08:51Не вижу статей про замену stm32 младших серий на risc v, поморгали светодиодом и успокоились, никто особо переезжать не хочет. Путь с заменой на gd32 прошли два года назад.
a3x Автор
21.10.2024 08:51Дайте на арме освоиться, что за зверь risc v - вообще не знаю.
strvv
21.10.2024 08:51В принципе, как я пробовал, так как большой нужды не было -
если пишешь на С без извратов, и выносишь аппаратную (этакий HAL) отдельно - пофиг.
Я не загонялся с таймерами и прочим - простая State-Machine.
То есть на многих задачах разницы не будет, если грамотно подходить к ним.DrGluck07
21.10.2024 08:51Мы так и сделали, бизнес-логика на С++, можно использовать на любом контроллере, а HAL на C уже для каждого свой собственный. У нас тут получилась более забавная ситуация: всё писали на GD32, а потом один проект нужно было на STM32. В общем, никаких проблем не возникло.
rsashka
Кажется где то писали, что 1.3 последняя версия, где можно изменить CPUTAPID в конфиге. В более старших версиях он захардкожен в самом IDE.
a3x Автор
Вот я искал искал и не нашел и проверил. Но поскольку мне и в 1.3 это не понадобилось, то указал как есть. Возможно это надо делать в 1.3 с другими "клонами", а GD работает и так.