Открыть список
Как стать автором
Обновить

Комментарии 40

Написано интересно и хорошо, мол, меняйте таблицу прерываний под себя, но у 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 увидели???
А какое у этой проверки практическое применение, помимо проверки размера RAM?
Определение объёмов ОЗУ, флэша, EEPROM, наличия периферии на лету.

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

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

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

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



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

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