А можно вот про этот момент поподробнее, желательно с куском результирующего ассемблерного кода. Как это вообще стыкуется со спецификацией RISC-V ?
Ну, немного про это есть у меня: https://habr.com/ru/articles/866798/ А по существу - с risc-v это не стыкуется никак и сделано WCH-ами через жуткие костыли. Они аппаратно сохраняют временные регистры в одном из 2-4 специальных стеков. Соответственно, обработчик прерывания может выглядеть как обычная функция (естественно, a*, s* регистры он все еще должен сохранять самостоятельно).
Мы здесь не комиссии доказываем, что отечественный контроллер надо принять, а как программисты общаемся. Так что давайте смотреть правде в глаза. Если прерывание требует много программного разбора - зачем оно нужно?
Потому что это стандарт ядра RISC-V, которое изначально отнюдь не для контроллеров проектировалось. А те, кто захотел его адаптировать под контроллеры, городят свои расширения: ECLIC, PFIC, ... Правда, обычно все же сохраняют совместимость.
Вот если использовать C++ код, насколько больше прошивка в отличии от чистого Си
Теоретически, на С++ можно получить более гибкий и оптимизированный код, чем на Си. Но для этого надо гораздо больше знаний и внимательности. Лично я С++ в достаточной мере не знаю.
И есть ли смысл изучать другие системы сборки кроме makefile? Просто хочется один раз настроить и забыть.
Многие вообще пользуются "проектами" тех или иных IDE и не лезут смотреть что там под капотом. Система сборки - наименьшая из проблем: для тех или иных задач ее приходится иногда подстраивать, но это почти всегда элементарные действия. То же встраивание бинарника wav-файла, которое я описывал, пожалуй, одно из самых сложных - и то меньше десятка строчек. А стандартная задача - добавить в "проект" еще один *.c файл - решается и вовсе элементарно.
В принципе на линуксе это можно сделать, а что с виндой?
Во-первых, никто не отменял фирменные IDE. NucleiStudio от GigaDevice, MounRiverStudio от WCH, не говоря уж об обычных Eclipse, Keil и т.п.. Пожалуй, это самый простой способ. Во-вторых, можно развернуть всю систему сборки самостоятельно. Найти инсталляторы соответствующих gcc, библиотеки и т.п. В-третьих, никто, кажется, не отменял WSL. Но с моей стороны это больше теория, поскольку под виндой контроллеры не программировал лет пятнадцать, так что было бы интересно послушать настоящих, опытных виндузятников.
С пустыми скобками тоже допустимо. Насколько я знаю, с C23 пустые скобки сделали синонимом void, а в более старых стандартах - произвольные аргументы. Ни то, ни другое в данном случае не ошибка.
Посмотрите информацию про барьеры памяти/инструкций, про конвейер в современных микроконтроллерах
Atomic это лишь расширение, его может и не быть, поэтому пока не рассматриваем.
А по остальному - немножко теории я об этом знаю, и со сложностями дизассемблирования оптимизированного кода знаком (в частности, когда ковырял загрузчик v307-го, много забавных моментов попалось). И про аппаратное переупорядочивание инструкций, конвейер, проброс значений и инструкцию fence слышал. Но теория это теория. Не будешь же fence после каждого взаимодействия с периферией ставить.
Если обеспечить синхронизацию, то переменную, через которую передаются данные, не нужно отмечать как volatile.
Хотелось бы пример кода.
Сразу и не придумаешь задачу, где бы это было наглядно... Ну допустим, порты ввода-вывода. Стандартный способ работы с ними (и хедер с этим согласен) - объявить volatile. И стандартная проблема, когда нужно разом поменять несколько битов. Если делать это в две операции
GPIOA->OUTDR &=~mask;
GPIOA->OUTDR |= newval;
то между ними будет сбрасывание значения в регистр и повторное чтение. Чтобы этого не было, надо либо записывать в одну строчку GPIOA->OUTDR = (GPIOA->OUTDR &~mask) | newval;, либо руками кешировать во временной переменной. И то и другое не слишком красиво.
volatile не для синхронизации, а для обмена данными между потоками. И про отсутствие оптимизаций вроде упомянул, и про то, что volatile не гарантирует атомарности... Возможно, и правда, не стоило добавлять это в ассемблерную части статьи. Ну ладно, в следующей как раз хотел уже переходить к Си и, наверное, объединить с таймерами. Как раз будет хороший пример софтового обеспечения атомарного чтения 64-битного таймера.
Тогда это, мне кажется, не перемещение с сохранением, а изменение раутинга к регистрам.
Не знаю. В документации подробностей вроде нет. Но называют они это аппаратным стеком.
Это слегка проблемно с точки зрения секьюрити - легко упустить какой-то регистр и сделать утечку внутренних данных.
Утечку куда? Если программисту надо влезть из одного куска своего кода в другой, зачем ему мешать.
Но в полный рост это выстреливает не для аппаратных прерываний, а для сисколлов, где поэтому сохраняют все регистры, кроме возвращаемых данных
Что прерывания, что сисколы это не функции, это именно исключительные ситуации, которые должны быть как можно более прозрачными. К прерываниям это вообще жесткое требование. Сисколы иногда, по соглашению, могут пользоваться a0,a1,a7, но не более.
А говорить о регистрах в контексте безопасности вообще странно: их же используют все подряд. Да и количество, всего 31 штука, можно и руками отследить. Нет, если говорить о безопасности, то в первую очередь это память и CSR-регистры.
Вероятно, в мелких embedded это можно игнорировать, "тут все свои".
Это вообще единая программа. Соглашения по безопасности это скорее "как не отстрелить себе ногу", а не "как не дать левой руке доступ к правому уху".
В ch32 аппаратно сохраняются ra, t0-t6, a0-a7, всего 16 штук, ровно половина. Все остальные вызываемая функция и так обязана сохранить-восстановить сама. Причем, если верить документации, сохраняются они за один такт все, а не по по одному такту на регистр, как в ARM. И, как я понимаю, именно поэтому аппаратных стеков ограниченное количество - это ведь не обычный стек, а набор специальных регистров PFIC. Понятно, что когда весь код прерывания находится в одной единице трансляции, компилятор может распорядиться регистрами более эффективно и не сохранять те, которые не используются. И в обычном RISC-V на это и расчет: если хочешь супер-быстрое прерывание, можно не тратить такты на все 16. Но если вызывается сторонняя функция, то уже никуда не деться, прерывание ведь не знает какие регистры используются там. Впрочем, обычно прерывания заметно длиннее и менее требовательны к времени выполнения, так что это wch-ное извращение можно оставить на совсем уж крайний случай. В gd32 вон подобного делать не стали.
Они так и делают. Слава китайским богам, что они сообразили привести свое расширение в соответствие со стандартом. Только благодаря этому вариант с naked работает не только в патченном gcc, но и в обычном. Но есть ведь FPU-регистры, которые и на аппаратном стеке сохранять невыгодно, и на обычном сложно. Ну и всякие пограничные случаи я не искал, там тоже могут быть проблемы. Впрочем, если кому-то нужна действительно настолько быстрая реакция на прерывания, что даже десятка тактов на сохранение регистров жалко, он, наверное, перепроверит и машинный код.
В учебных целях. Пока идет речь о внутреннем устройстве RISC-V и регистрах контроллера, ассемблер дает лучшее представление. Но скоро буду рассказывать и о Си. Зависит от того, уйдет ли на рассказ о прерываниях целая статья или хватит половины.
Точно не проверял. Возможно, зависит от настроек количества стоповых битов. Хотя, кажется, в документации проскакивало, что количество стоповых битов работает только на передачу. Упоминаний о точном времени возникновения прерывания не видел. Так что только тестировать и надеяться, что во всех контроллерах будет одинаково.
Либо вы невнимательно читали, либо я коряво написал. Попробую еще раз пересказать пункт 1.2. Есть три способа прошить контроллер:
Через программатор по JTAG/SWD. Придется купить или сделать программатор и, возможно, пересобрать openocd из исходников (либо взять уже собранный бинарник от производителя). Зато можно будет поставить выполнение на паузу, посмотреть регистры, память и т.п. Лично мне этот вариант не понравился.
Бутлоадером через USB. Платка втыкается прямо в USB компьютера. Не требует дополнительных плат или устройств, но нужно при каждой прошивке дергать ножки boot0, reset (или выдергивать платку). Не слишком удобно.
Бутлоадером через UART. Платка соединяется с переходником COM-UART (например, на max232) или USB-UART (pl2102, ch340, ft232, ...) и прошивается через него. Переходник купить все-таки придется, зато через тот же UART возможна передача отладочной информации. Довольно удобно, если бы не все та же возня с boot0, reset. 3+. Развитие предыдущего способа. boot0, reset цепляются либо к DTR, RTS переходника, либо к специальным ногам самодельной платы. И stm32flash, и моя wch-isp эти линии дергать умеют. Но для себя я сделал "Каракатицу", которая эмулирует не один UART, а два, на одних и тех же ножках. Через один прошиваем, через второй отлаживаем.
Еще можно написать собственный бутлоадер и прошивать им хоть через захват таймера - но сначала этот бутлоадер все равно придется прошить одним из предыдущих способов.
Если это все же пробел в моих навыках лектора, а не ваших - читателя - буду благодарен если подскажете как сформулировать лучше.
У GD32VF103 обычно есть несколько выводов, поддерживающих USART, например
Именно на счет GD32 не уверен, но CH32 прошиваются только через UART1 (кроме ch32v203g8, который прошивается только через UART2 - не знаю, что курили китайцы, когда это придумывали). В смысле через UART3 их не прошьешь.
без нужды в программаторах для счета или загрузки
Кстати о "счете" (чтении): загрузчик ch32 этого не поддерживает. Можно прошить, можно стереть и можно проверить. Все. Не считая нескольких ch-специфичных функций, которые сейчас интереса не представляют.
Странно что в статье не упоминается применение классического CH340 драйвера, который на раз - два коммуницирует UART или RS232 стандартным терминалом
CH340 ничем не лучше любого другого переходника - да хоть той же FT232. А вообще, как раз следующая статья будет про UART. Материала у меня записано довольно много, в одну статью так и так не влезет. Ну и еще за несколько тем я пока даже не брался.
Кто есть, стандартный CSR-регистр mcause? Есть, конечно. Вот пример использования в прерывании: https://github.com/KarakatitsaRISCV/riscv-asm/blob/main/4.interrupt_ch32/src/main_1_unified.S#L211
Так речь вроде про MIK32 шла?
А здесь, надо полагать, речь о небольшой подгруппе WCH-ей.
Ну, немного про это есть у меня: https://habr.com/ru/articles/866798/
А по существу - с risc-v это не стыкуется никак и сделано WCH-ами через жуткие костыли. Они аппаратно сохраняют временные регистры в одном из 2-4 специальных стеков. Соответственно, обработчик прерывания может выглядеть как обычная функция (естественно, a*, s* регистры он все еще должен сохранять самостоятельно).
Не забывайте, это WCH-специфика. Которая, к тому же, до сих пор нормально не поддерживается gcc.
О да, на целых полтора такта больше (ручной переход по таблице по номеру из mcause.
Потому что это стандарт ядра RISC-V, которое изначально отнюдь не для контроллеров проектировалось. А те, кто захотел его адаптировать под контроллеры, городят свои расширения: ECLIC, PFIC, ... Правда, обычно все же сохраняют совместимость.
Так все равно им никто не пользуется. Даже там, где реализован - gd32, ch32. В контроллерах единственный вектор на все прерывания это неудобно.
Не проверяли ли на контроллерах с кешированием всего кода - v203, v303, ...?
Не проверяли ли DMA?
Ну так зачем там "должны быть void"? Что без этого будет работать неправильно?
Теоретически, на С++ можно получить более гибкий и оптимизированный код, чем на Си. Но для этого надо гораздо больше знаний и внимательности. Лично я С++ в достаточной мере не знаю.
Многие вообще пользуются "проектами" тех или иных IDE и не лезут смотреть что там под капотом. Система сборки - наименьшая из проблем: для тех или иных задач ее приходится иногда подстраивать, но это почти всегда элементарные действия. То же встраивание бинарника wav-файла, которое я описывал, пожалуй, одно из самых сложных - и то меньше десятка строчек. А стандартная задача - добавить в "проект" еще один *.c файл - решается и вовсе элементарно.
Во-первых, никто не отменял фирменные IDE. NucleiStudio от GigaDevice, MounRiverStudio от WCH, не говоря уж об обычных Eclipse, Keil и т.п.. Пожалуй, это самый простой способ.
Во-вторых, можно развернуть всю систему сборки самостоятельно. Найти инсталляторы соответствующих gcc, библиотеки и т.п.
В-третьих, никто, кажется, не отменял WSL.
Но с моей стороны это больше теория, поскольку под виндой контроллеры не программировал лет пятнадцать, так что было бы интересно послушать настоящих, опытных виндузятников.
С пустыми скобками тоже допустимо. Насколько я знаю, с C23 пустые скобки сделали синонимом void, а в более старых стандартах - произвольные аргументы. Ни то, ни другое в данном случае не ошибка.
Atomic это лишь расширение, его может и не быть, поэтому пока не рассматриваем.
А по остальному - немножко теории я об этом знаю, и со сложностями дизассемблирования оптимизированного кода знаком (в частности, когда ковырял загрузчик v307-го, много забавных моментов попалось). И про аппаратное переупорядочивание инструкций, конвейер, проброс значений и инструкцию fence слышал. Но теория это теория. Не будешь же fence после каждого взаимодействия с периферией ставить.
Подскажите что почитать, чтобы не голую теорию.
Хотелось бы пример кода.
Сразу и не придумаешь задачу, где бы это было наглядно... Ну допустим, порты ввода-вывода. Стандартный способ работы с ними (и хедер с этим согласен) - объявить volatile. И стандартная проблема, когда нужно разом поменять несколько битов. Если делать это в две операции
то между ними будет сбрасывание значения в регистр и повторное чтение. Чтобы этого не было, надо либо записывать в одну строчку
GPIOA->OUTDR = (GPIOA->OUTDR &~mask) | newval;, либо руками кешировать во временной переменной. И то и другое не слишком красиво.Как я понимаю, вы знаете способ лучше.
volatile не для синхронизации, а для обмена данными между потоками. И про отсутствие оптимизаций вроде упомянул, и про то, что volatile не гарантирует атомарности... Возможно, и правда, не стоило добавлять это в ассемблерную части статьи. Ну ладно, в следующей как раз хотел уже переходить к Си и, наверное, объединить с таймерами. Как раз будет хороший пример софтового обеспечения атомарного чтения 64-битного таймера.
Не знаю. В документации подробностей вроде нет. Но называют они это аппаратным стеком.
Утечку куда? Если программисту надо влезть из одного куска своего кода в другой, зачем ему мешать.
Что прерывания, что сисколы это не функции, это именно исключительные ситуации, которые должны быть как можно более прозрачными. К прерываниям это вообще жесткое требование. Сисколы иногда, по соглашению, могут пользоваться a0,a1,a7, но не более.
А говорить о регистрах в контексте безопасности вообще странно: их же используют все подряд. Да и количество, всего 31 штука, можно и руками отследить. Нет, если говорить о безопасности, то в первую очередь это память и CSR-регистры.
Это вообще единая программа. Соглашения по безопасности это скорее "как не отстрелить себе ногу", а не "как не дать левой руке доступ к правому уху".
В ch32 аппаратно сохраняются ra, t0-t6, a0-a7, всего 16 штук, ровно половина. Все остальные вызываемая функция и так обязана сохранить-восстановить сама. Причем, если верить документации, сохраняются они за один такт все, а не по по одному такту на регистр, как в ARM. И, как я понимаю, именно поэтому аппаратных стеков ограниченное количество - это ведь не обычный стек, а набор специальных регистров PFIC.
Понятно, что когда весь код прерывания находится в одной единице трансляции, компилятор может распорядиться регистрами более эффективно и не сохранять те, которые не используются. И в обычном RISC-V на это и расчет: если хочешь супер-быстрое прерывание, можно не тратить такты на все 16. Но если вызывается сторонняя функция, то уже никуда не деться, прерывание ведь не знает какие регистры используются там.
Впрочем, обычно прерывания заметно длиннее и менее требовательны к времени выполнения, так что это wch-ное извращение можно оставить на совсем уж крайний случай. В gd32 вон подобного делать не стали.
Они так и делают. Слава китайским богам, что они сообразили привести свое расширение в соответствие со стандартом. Только благодаря этому вариант с naked работает не только в патченном gcc, но и в обычном. Но есть ведь FPU-регистры, которые и на аппаратном стеке сохранять невыгодно, и на обычном сложно. Ну и всякие пограничные случаи я не искал, там тоже могут быть проблемы.
Впрочем, если кому-то нужна действительно настолько быстрая реакция на прерывания, что даже десятка тактов на сохранение регистров жалко, он, наверное, перепроверит и машинный код.
В учебных целях. Пока идет речь о внутреннем устройстве RISC-V и регистрах контроллера, ассемблер дает лучшее представление. Но скоро буду рассказывать и о Си. Зависит от того, уйдет ли на рассказ о прерываниях целая статья или хватит половины.
Точно не проверял. Возможно, зависит от настроек количества стоповых битов. Хотя, кажется, в документации проскакивало, что количество стоповых битов работает только на передачу. Упоминаний о точном времени возникновения прерывания не видел. Так что только тестировать и надеяться, что во всех контроллерах будет одинаково.
Не думаю, что подобные излишние уточнения здесь уместны.
Либо вы невнимательно читали, либо я коряво написал. Попробую еще раз пересказать пункт 1.2. Есть три способа прошить контроллер:
Через программатор по JTAG/SWD. Придется купить или сделать программатор и, возможно, пересобрать openocd из исходников (либо взять уже собранный бинарник от производителя). Зато можно будет поставить выполнение на паузу, посмотреть регистры, память и т.п. Лично мне этот вариант не понравился.
Бутлоадером через USB. Платка втыкается прямо в USB компьютера. Не требует дополнительных плат или устройств, но нужно при каждой прошивке дергать ножки boot0, reset (или выдергивать платку). Не слишком удобно.
Бутлоадером через UART. Платка соединяется с переходником COM-UART (например, на max232) или USB-UART (pl2102, ch340, ft232, ...) и прошивается через него. Переходник купить все-таки придется, зато через тот же UART возможна передача отладочной информации. Довольно удобно, если бы не все та же возня с boot0, reset. 3+. Развитие предыдущего способа. boot0, reset цепляются либо к DTR, RTS переходника, либо к специальным ногам самодельной платы. И stm32flash, и моя wch-isp эти линии дергать умеют. Но для себя я сделал "Каракатицу", которая эмулирует не один UART, а два, на одних и тех же ножках. Через один прошиваем, через второй отлаживаем.
Еще можно написать собственный бутлоадер и прошивать им хоть через захват таймера - но сначала этот бутлоадер все равно придется прошить одним из предыдущих способов.
Если это все же пробел в моих навыках лектора, а не ваших - читателя - буду благодарен если подскажете как сформулировать лучше.
Именно на счет GD32 не уверен, но CH32 прошиваются только через UART1 (кроме ch32v203g8, который прошивается только через UART2 - не знаю, что курили китайцы, когда это придумывали). В смысле через UART3 их не прошьешь.
Кстати о "счете" (чтении): загрузчик ch32 этого не поддерживает. Можно прошить, можно стереть и можно проверить. Все. Не считая нескольких ch-специфичных функций, которые сейчас интереса не представляют.
CH340 ничем не лучше любого другого переходника - да хоть той же FT232. А вообще, как раз следующая статья будет про UART. Материала у меня записано довольно много, в одну статью так и так не влезет. Ну и еще за несколько тем я пока даже не брался.