Pull to refresh

Comments 41

Написано интересно и хорошо, мол, меняйте таблицу прерываний под себя, но у F0 меняется не только размер ОЗУ от серии к серии, но и периферия (переносишь с F030 под F042, скорей всего придется менять UART, DMA, и т.п., а значит, переносимость кода данные методы не гарантируют и код так и остается в рамках одной серии. А в рамках подсерии VL/MD/HD и так проблем нет.
+«рваное» ОЗУ у старших моделей.

Значит, все равно править под конкретный МК.
а) во всём тексте нет ни единого упоминания таблицы прерываний
б) переносимость кода между двумя произвольными моделями контроллеров вам вообще ничто и никто в этом мире не гарантирует, хотя вообще-то при необходимости сделать программно определяемыми несколько адресов нужной периферии ничто не мешает
Хорошая пасхалка про сову в коде :)

Вообще, спуск до ассемблера навевает воспоминания о MS-DOS и робких попытках писать код на асме с 80x87 FPU. «Когда железки были большими»…
Ну там на самом деле после совы должен идти стандартный обработчик хардфолта, примеров которых в интернете десятки, если не сотни — обычно они просто регистры процессора печатают. Так что реальной шутки поменьше, чем в известной картинке.
Мне больше интересно зачем прятать происходящее от компилятора и потом с ним «воевать».

То есть почему вторая функция выглядит как выглядит, а не как-то примерно так:
bool cpu_check_address(volatile const char *address) {
    /* Cortex-M0 doesn't have BusFault so we need to catch HardFault */
    /* R5 will be set to 0 by HardFault handler */
    /* to indicate HardFault has occured */
    register bool result __asm("r5") = 1; /* Set default return value */
    register uint32_t sign1 __asm("r1") = 0xDEADF00D; /* set magic number */
    register uint32_t sign2 __asm("r2") = 0xCAFEBABE; /* 2nd magic to be sure */
    uint32_t scratch;

    __asm__ (
        "ldrb %[scratch], [%[address]]  \n" /* probe address        */
        :"=r"(result),[scratch]"=l"(scratch)
        :[address]"l"(address),"r"(result),"r"(sign1), "r"(sign2)
    );

    return result;
}


Есть какой-то тайный смысл в том, чтобы, вместо того, чтобы описать всё явно, опираться на знания API и прочего?

Не первый раз вижу вещи, написанные в подобном «хрупком» стиле и всегда удивляюсь: зачем?
Никакого. Но в ближайшие лет десять-двадцать этот API никуда не денется.
Зато компилятор может какой-нибудь временный результат в r0 положить вдруг. Что превратит весь ваш пример в тыкву.

Или «в ближайшие лет десять-двадцать» вы и компилятор обновлять не будете?
Во-первых, вот прямо в этом коде никаких «временных результатов» нет.

Во-вторых, если вы считаете, что ваш код всё описывает явно и не зависит от обновлений API и компилятора, то вы ошибаетесь.
Во-первых, вот прямо в этом коде никаких «временных результатов» нет.
Потому я назвал его не «неправильным», а «хрупким». Временные результаты легко могут появиться если компилятор решит его заинлацнить — и да, атрибут inline для этого не требуется.

Во-вторых, если вы считаете, что ваш код всё описывает явно и не зависит от обновлений API и компилятора, то вы ошибаетесь.
А конкретнее? Представить себе, что какой-то компилятор, после соответствующего обновления станет использовать, скажем, другую инструкцию ldrb можно... нужно было использовать ldrb.n для надёжности... Что-то ещё?

Так-то от ошибок в компилятора никто не застрахован... Но нарываться-то зачем?
LDRB.N или LDRB.W в явном виде, разумеется. И дело тут не в ошибках компилятора, а в том, что если вы не полагаетесь на использование им только нижних регистров, то надо либо все передаваемые в LDRB переменные надо привязать явно к конкретным регистрам, либо указать конкретную инструкцию LDRB — а то их в кортексе три разных.
Я не полагаюсь на использование им нижних регистров — я это заказал явно ("l" vs "r" — это как раз оно).
Скажите, а насколько важно в подобном коде объявлять register? Это действительно нужно или дань традиции?

Это нужно, что бы GCC понял, что это не переменные, а алиасы регистров.

Это совсем другая конструкция нежели стандартный модификатор register.

register typename varname __asm("registername"); — это цельная конструкция для описания переменной, живущей в определённом регистре.

Она действительно немного похожа на обычный register — но имеет заметно другой смысл.
Если между сбросом флага bus fault и его проверкой произойдет какое-нибудь прерывание и в его ISR произойдет обращение к несуществующему адресу, то а) это факт будет пропущен системой, и б) основная процедура вернет ошибочный вердикт о валидности пробного адреса
Да, там стоит запретить прерывания.
Это уменьшает вероятность проблем, но не исключает их. NMI handler, например, может вызываться в STM32 при сбое тактирования (если включен CSS).
В такой статье надо перечислять на каких конкретно чипах тестировалось.
В Kinetis на Cortex-M хаотичное сканирование периферии не инициализированной должным образом приведет к сбросу, без всякого bus fault.
Я извиняюсь, они там сломали базовые функции ядра кортекса? А как и, главное, зачем?
В общем случае размер инструкции на Cortex-M может быть не только 2, но и 4 байта. Поэтому проверку валидности адресов лучше выполнять в специальной функции, и убедиться в дизассемблере, что инструкция, выполняющая чтение по проверяемому адресу, действительно имеет размер 2 байта.
При чём тут вообще размер инструкции?
Из статьи:
читаем из стека значение PC (смещение 0x18), добавляем к нему 2 (2 байта — размер инструкции на Cortex-M) и сохраняем обратно в стек.
В данном случае вариативности в размере инструкции не будет, это ldrb, она 16-битная.
В той же таблице есть и 32-битный вариант ldrb. Если я правильно понял и правильно помню — это вариант той же инструкции с условным выполнением.
Не совсем. Там для регистров выше R7 инструкция 32-битная (LDRB.W), а с R0 по R7 — подходит и та, и другая, но ассемблер в этом случае обязан выбрать 16-битную, если автор в явном виде не указал LDRB.W.

P.S. Добавил в текст поста комментарий.
Ну вот — если компилятор использует для адресации регистр выше R7 — к PC нужно прибавлять 4. Получается inline — функции для проверки валидности использовать нельзя — в разных местах, куда они будут встроены компилятором, для адресации могут использоваться разные регистры. Нужно реализовывать это в одном месте, и отключать для этой функции оптимизацию, чтобы она компилировалась всегда одинаково.
Компилятор к инструкции «ldrb r3, [r0]» никакого отношения не имеет и ничего в ней использовать не может — в ней вообще компилировать нечего, это ассемблер.

Оптимизацию в связи с этим отключать не надо.
А, да, прошу прощения, невнимательно прочитал статью — не обратил внимание, что ассемблер используется не только в обработчике HardFault.
Смутил такой способ сброса флага:
SCB->CFSR |= BFARVALID_MASK;

Тем более, что далее он проверяется:
if ((SCB->CFSR & BFARVALID_MASK) != 0)
Это довольно частый способ сброса флагов в контроллерах — записью в них единицы. Так что нет, не опечатка.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihcfefj.html

The UFSR bits are sticky. This means as one or more fault occurs, the associated bits are set to 1. A bit that is set to 1 is cleared to 0 only by writing 1 to that bit, or by a reset.
A bit that is set to 1 is cleared to 0 only by writing 1 to that bit

т.е. не только «sticky», но еще и «xor» при записи!
Где вы там xor увидели???
Определение объёмов ОЗУ, флэша, EEPROM, наличия периферии на лету.

Берём, например, STM32L1. У него после старта надо все ножки вручную перевести в режим аналогового входа.

Вот как определить, «все» — это какие конкретно, если у нас одна и та же прошивка работает на нескольких моделях контроллера с разным числом портов?
Думаю, что про ножки не совсем корректно. Ведь у STM32F100 серии люди часто находят полнофункциональный третий UART или возможность принять 128 Кб прошивку при официальном наличии 64 Кб Flash. И ничто не мешает ножкам реально существовать, но остаться невыведенными наружу.

И, кстати, это я бухтел в комментариях про статьи о мигании светодиодом, поэтому спасибо за информативную статью!
Регистры GPIOG и GPIOF есть только в 144-ножечных корпусах, попытка постучать в них на других кристаллах кончается басфолтом.

Соответственно, перед постукиванием проверить, туда ли мы стучим — самый простой способ написания универсальной процедуры.
Но почему бы не использовать для этого регистры DBGMCU_IDCODE и FLASH_SIZE?
Потому что через полчаса попыток разобраться, что там чему соответствует и как это всё декодировать, а также таскать с собой, в вашем сердце будет ярким пламенем гореть ненависть к отделу разработки ST Microelectronics.



И это мы ещё не дошли до попытки без натурных экспериментов выяснить, можно ли вообще по даташитам и RM установить, в каких конкретно процессорах таки есть GPIOG и GPIOF, а в каких нет (короткий ответ: нельзя).
Да, эти регистры не дают полной информации о контроллере. Но ненавидеть за это отдел разработки ST — это черезчур, ведь контроллеры в целом хорошие. Но я бы не стал полагаться на проверку корректности адресов — ведь в очередной ревизии ST может решить, что проще перемаркировать контроллер, урезав его там где выявлены дефекты — и тогда проверка введёт в заблуждение. Я бы использовал для идентификации сигнатуру во флэше, получаемую отдельно от прошивки.
Вопрос не в полноте информации, вопрос в том, что ST в своих чипах — не только в микроконтроллерах — очень непоследовательны в деталях реализации.

Вот здесь мы в одной только серии контроллеров для одного только элементарного параметра видим три варианта реализации, понять которые нельзя, их надо запомнить.

О, как раз по этому поводу засабмитил им несколько лет назад багрепорт

Sign up to leave a comment.

Articles