В продолжении своего предыдущего поста, хочу рассказать о своем переходе на WCH и RISC-V. Поскольку QingKeV4 существует аж в 5-и версиях, то SDK ко всему этому великолепию содержит рудименты и нестыковки. Возможно, кому-то мой опыт сэкономит время и нервы. Начнем с прерываний.
Итак, в семействах V2XX/V3XX применяются ядра QingKeV4С/F с PFIC. Основных отличий от NVIC в большинстве популярных STM-подобных контроллеров на нашем рынке, два:
В PFIC всего 3 бита приоритетов прерываний (кроме QingKeV4A, где их 4, но семейств CH32V это не касается).
В PFIC есть аппаратный 3-х уровневый (QingKeV4F) или 2-х уровневый (QingKeV4С) стек
Есть еще отличия, но поскольку на уровне SDK я не встречал примеров применения этих PFIC специфических механизмов, сказать мне тут нечего.
Приоритеты прерываний.
Кол-во вытесняющих приоритетов в системе - это nesting depth. Биты приоритета образуют поле PriorityGroup или Priority configuration. В ядре зарезервировано 8 бит под приоритет каждого прерывания, но в текущих реализациях используются старшие 3 бита регистра. Как и в NVIC, биты делятся на биты приоритета группы (Рreempted bits) и биты приоритета внутри группы (Subpriority bits). И как в NVIC, чем меньше числовое значение приоритета, тем он выше. PriorityGroup прерывания располагается в [7:5] битах регистра приоритета прерывания.
Изменять соотношения бит в PriorityGroup можно только в старших CH32V3xx (QingKeV4F). Nesting depth может быть 0,2,4 или 8. Т.е. кол-во preempted bits от 0 до 3. subpriority bits соответственно будут все оставшиеся, для nesting depth = 8 субриоритетов нет.
На CH32V2xx (QingKeV4С) nesting depth всегда 2. ( preempted bits - 1 шт. ; subpriority bits -2 шт.) По крайне мерее каких-то отсылок, что nesting depth можно сделать 0, я не нашел.

С точки зрения API, имеем функцию NVIC_SetPriority(IRQn_Type IRQn, uint8_t priority), (core_riscv.h). Она просто пишет маску priority в соответствующий регистр. Пользоваться в реальной жизни не очень удобно, надо сдвигать маску на 5 влево и думать про nesting depth.

Проще написать свой API и забыть. Но если решили воспользоваться SDK, то может быть следующие:
Поскольку SDК общий и на RISC-V и на ARM контроллеры, в нем есть сh32vxxx_misс.h., С привычной NVIC_Init(NVIC_InitTypeDef *NVIC_InitStruct), где инициализация идет через формат структуры и можно однозначно прописывать приоритет и субприоритет. И есть функция конфигурации битов NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup), однако она физический ничего не делает, а просто меняет значения внутренней переменной NVIC_PriorityGroup. По значению этой переменной NVIC_Init формирует битовое поле приоритета. И вот тут лезут нестыковки.
CH32V3XX. В файле ch32v3xx_misс, который вылезет из MountRiver при генерации проекта, NVIC_PriorityGroup = INTSYSCR_INEST_EN_4Level. Но физический nesting depth задаётся в стартапе. И для примеров работы с периферией - проблем нет. Вот строчка из стартапа
/* Enable interrupt nesting and hardware stack */
li t0, 0x0b
csrw 0x804, t0
0x804 это адрес регистра INTSYSCR .А вот та же самая строчка, если сгенерить из МountRiver проект c FreeRTOS.
/* Enable nested and hardware stack */
li t0, 0x1f
csrw 0x804, t0
И получается что API сконфигурирован на nesting depth = 4, а на самом деле nesting depth = 8. Соответственно прерывания могут работать слегка не так, как вы ожидайте:)
СH32V2XX. . Тут в текущем SDK V2.2 косяк уже исправлен, по крайне мерее при генерации проекта из под MountRiverIDE. Но в начале 2024 года, ch32v2xx_misc был от QingKeV4A, с его 4 бита приоритета и nesting depth по умолчанию 0. Поэтому при использовании NVIC_Init приоритет игнорировался, а также обрезался младший бит субприоритета. Пока лучше проверять файл ch32v2xx_misc. Может быть в каких-то конфигурациях проекта или примерах он вылезет.
HPE (hardware Prologue/Epilogue)
Это аппаратный механизм однотактового сохранения контекста (16 регистров процессора) при входе в обработчик прерывания и восстановления при выходе. Контекст сохраняется в аппаратном стеке. Т.е. вместо 32-х команд - 2 команды. Для общего случая естественно. Свой обработчик на asm мы тут не рассматриваем. Для CH32V3xx, если включен FPU, его регистры f нужно сохранять в обычный стек в любом случае.
Кстати, либо я что то не понимаю, либо все проекты из MounRiver для CH32V3xx по умолчанию не используют при компиляции FPU. В стартапе, для CH32V3xx FPU всегда включается, а в настройках TragetProcessor проекта по умолчанию - нет...
Обработчик прерывания, для которого нужно использовать HPE, объявляется с атрибутом: __attribute__((interrupt("WCH-Interrupt-fast"))).
Для интересу посмотрел как выглядит в листингах обработчик с HPE и без него. Действительно при использовании HPE сохраняется только контекст FPU. Но вот как из под Си сказать компилятору, что это делать не надо, я не нашел.
HardFault при переполнении HPE.
Очень неприятная штука, возникает если бездумно объявлять прерывания как __attribute__((interrupt("WCH-Interrupt-fast"))). Как это сделал я:). Возможна она только на серии CH32V3xx, которая поддерживают 3-х уровнённый HPE буфер. Возникает, когда nesting depth 4 или 8, и при этом в системе 4 и более прерываний с HPE имеют разный приоритет, Т.е. у нас 3 прерывания заполнили буфер HPE. И тут прилетает 4-е, с самым высоким приоритетом. В ситуации, когда переполнение HPE разрешено ( а оно разрешено в стартапе по дефолту), 4-е прерывание сохранит контекст в программном стеке, но после завершения выполнения, контекст будет загружен из аппаратного стека. Как это происходит, я не где информации не нашел, но в hard fault система падает стабильно:)
UPD. На самом деле все несколько не так. Nesting depth или nesting level - это в том числе и кол-во вытесняющих приоритетов начиная с младшего. Т.е. когда говориться о HPE c nesting level -3, в том числе имеется ввиду, что HPE прерывания не могут быть выше 3-го уровня. При этом nesting level ядра может быть больше. Но все прерывания выше 3-го уровня должны быть обязательно программными. Ниже какого угодно. На картинке ниже сразу смущает interan hardware stack. Но видимо но на само деле не стек, а набор регистров, связанных с 3-мя младшими уровнями прерываний.
Можно либо отключить переполнение в стартапе (тогда, даже если у HPE прерывания будет высокий приоритет, он не будет обрабатываться), либо программно отслеживать переполнения HPE в обработчике. Но самое простое, делать как рекомендует WCH. Объявлять HPE прерывания с самыми низкими приоритетами. Тогда аппаратный стек не сможет переполниться, поскольку низкоприоритетные прерывания никого ни куда не вытеснят. .Получиться вот так:

. С СH32V2 такой проблемы нет, там nesting depth =2 и HPE стек 2-х уровневый.
VTF
Есть замеры, что скорость входа в обработчик используя механизм VTF на 2 такта быстрее, чем через таблицу прерываний. Так что данными механизм предназначен скорее для того, что бы использовать флэш память таблицы векторов прерываний для хранения кода. Сходу из таких задач на ум приходит bootloader. 4-х прерываний вполне хватит, а вот лишний килобайт для кода может быть очень кстати.
WCH порт FreeRTOS.
Порт вполне себе рабочий, но есть нюансы. Прерывание SysTick объявлено c HPE. Приоритет самый низкий - 7. Это важно, если решили использовать HPE. В системе уже одно такое прерывание есть. С таким же приоритетом SoftwateIRQ. Но оно обычное, без HPE.
Хотя в PFIC есть маскирование прерываний, порт FreeRTOS его не поддерживает. Соответственно configLIBRARY_LOWEST_INTERRUPT_PRIORITY в порте нет. Т.е. в отличии от портов ARM, тут нет прерываний, в которых нельзя использовать API FreeRTOS. Как собственно и прерываний, которые могут вклиниваться к критическую секцию операционной системы. portDISABLE_INTERRUPTS() глобально запрещает все прерывания, без всякого маскирования. Видимо порт делался под младшие семейства, и под CH32V3 переехал без исправлений.
В остальном со стороны Си все как на ARM. Самое стременное было переполнение HPE. Отсутствие опыта наложилось на кривое назначение приоритетов и скрытый HPE в SysTick. Но реально, что там было понял только сейчас. Тогда я выключил HPE в своих прерываниях и пошел яростно жмакать клавиши дальше. Ибо путь самурая. Безальтернативно заложен в проект CH32V307. Прототип на столе, аванс потрачен, пути назад нет.