LINUX.ORG.RU

прерывания в stm32 и костыль

 


0

3

Пишу код для stm32, практикую использование конечных автоматов и решил посмотреть как далеко я смогу зайти с автоматами. Написал вот такую функцию, в прерывании отслеживаю три флага готовности и только после того как все три сработали выхожу из функции.

/* State machine states */
#define SMRCC_DE_INIT_LOCKFREE 0
#define SMRCC_SET_SYS_CLOCL 1
/* rcc_deinit_lockfree flags to prevent magical numbers*/
#define RCC_DEINIT_RESET 0
#define RCC_DEINIT_HSIRDY 1
#define RCC_DEINIT_HSERDY 1
#define RCC_DEINIT_PLLRDY 1
#define RCC_DEINIT_READY 3

static volatile uint8_t SMRCC_state;
static volatile uint8_t de_init_complete;

void RCC_CRS_IRQHandler(void) {
    switch(SMRCC_state) {
        /* RCC_de_init state */
        case SMRCC_DE_INIT_LOCKFREE:
            switch(RCC->CIR & (RCC_CIR_HSERDYF | RCC_CIR_HSIRDYF | RCC_CIR_PLLRDYF)) {
                case RCC_CIR_HSIRDYF:
                    RCC->CIR |= RCC_CIR_HSIRDYC;
                    de_init_complete += RCC_DEINIT_HSIRDY;
                    RCC->CIR &= ~RCC_CIR_HSIRDYC;
                    break;
                case RCC_CIR_HSERDYF:
                    RCC->CIR |= RCC_CIR_HSERDYC;
                    de_init_complete += RCC_DEINIT_HSERDY;
                    RCC->CIR &= ~RCC_CIR_HSERDYC;
                    break;
                case RCC_CIR_PLLRDYF:
                    RCC->CIR |= RCC_CIR_PLLRDYC;
                    de_init_complete += RCC_DEINIT_PLLRDY;
                    RCC->CIR &= ~RCC_CIR_PLLRDYC;
                    break;
            }
        break;
    }
}

void rcc_deinit_lockfree(void) {
    de_init_complete = RCC_DEINIT_RESET;
    NVIC_EnableIRQ(RCC_CRS_IRQn);
    /* Enable interrupts on HSIRDY, HSERDY, PLLRDY */
    RCC->CIR |= (RCC_CIR_HSIRDYIE | RCC_CIR_HSERDYIE | RCC_CIR_PLLRDYIE);
    /*Enable HSI*/
    RCC->CR |= RCC_CR_HSION;
    RCC->CR &= ~RCC_CR_PLLON;
    /* Disable CSS and HSE */
    RCC->CR &= ~(RCC_CR_CSSON | RCC_CR_HSEON);

    RCC->CFGR = 0x00; /* Also this will set SW to HSI */
    RCC->CFGR2 = 0x00;
    RCC->CFGR3 = 0x00;

    while(de_init_complete != RCC_DEINIT_READY) {}
    /* Disable interrupts on HSIRDY, HSERDY, PLLRDY */
    RCC->CIR &= ~(RCC_CIR_HSIRDYIE | RCC_CIR_HSERDYIE | RCC_CIR_PLLRDYIE);
    NVIC_DisableIRQ(RCC_CRS_IRQn);

    de_init_complete = RCC_DEINIT_RESET;
}

У меня есть пара вопросов и сомнений в моем подходе в этом коде. Первое надо ли ресетить RCC_CIR_HSIRDYC, RCC_CIR_HSERDYC и тд (в даташите я про это не нашел). Нормально ли так много кода держать в прерывании (у меня один конечный автомат, в RCC_CRS_IRQHandler, на всю работу с rcc)?

А вот теперь то, что мне не нравиться:de_init_complete += RCC_DEINIT_HSIRDY; и компания с while(de_init_complete != RCC_DEINIT_READY) {}. Мне кажется это каким-то костыльным решением, которое может сломаться, как это сделать по-человечески?

★★

Последнее исправление: snake266 (всего исправлений: 1)
Ответ на: комментарий от anonymous

Про RTOS я знаю, и как раз я хочу понять: на каком моменте мне понадобиться именно RTOS. Из ОП:

и решил посмотреть как далеко я смогу зайти

snake266 ★★
() автор топика

Мне кажется это каким-то костыльным решением, которое может сломаться, как это сделать по-человечески?

Могу ошибаться, но на мой взгляд нужно делать барьеры памяти. volatile может не сработать.

u-235
()
Ответ на: комментарий от snake266

и решил посмотреть как далеко я смогу зайти

Чтобы понять, надо какой-то девайс полноценный собрать. А то можно долго тужиться над абстрактным примером и осилить его, а потом даже на простых девайсах окажется задница.

anonymous
()
Ответ на: комментарий от u-235

Хмм, об этом я не подумал. У меня особо нет опыта написания не последовательного кода, поэтому не заметил. Почитаю об этом.

Хотя изначально вопрос, о том, как проверить что все три флага успешны, без инкремента.

snake266 ★★
() автор топика
Ответ на: комментарий от anonymous

У меня не совсем абстрактный пример — я как раз и делаю девайс.

snake266 ★★
() автор топика

RCC deinit тебе нужен только при перенастройке частот. Т.е. часть из этого смело можешь выкинуть, а часть - запускать в самом начале. Типа так: это обычно не нужно (т.к. восстанавливает дефолт), а это — нужно для старта HSE.

anonymous
()
Ответ на: комментарий от anonymous

Ну, просто de_init первая функция, которая попалась под руку, я хотел попробовать избавиться от блокировок. Изначально было так:

void rcc_deinit(void) {
    /* Enable HSI */
    RCC->CR |= RCC_CR_HSION;

    /* Wait until HSI is ready */
    while((RCC->CR & RCC_CR_HSIRDY) != ENABLE) {}

    /* Clear CFG register */
    RCC->CFGR = 0x00; /* Also this will set SW to HSI */
    RCC->CFGR2 = 0x00;
    RCC->CFGR3 = 0x00;

    /* Wait until it's switched to HSI mode */
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI) {}

    /* Disable PLL */
    RCC->CR &= ~RCC_CR_PLLON;
    while((RCC->CR & RCC_CR_PLLRDY) != DISABLE) {}

    /* Disable CSS and HSE */
    RCC->CR &= ~(RCC_CR_CSSON | RCC_CR_HSEON);
    while((RCC->CR & RCC_CR_HSERDY) != DISABLE) {}

    RCC->CR &= ~(RCC_CR_HSEBYP);
}

Но я понял, на функциях которые настраивают что-то системное, можно и «потупить», так? А так, для меня уже есть польза от этого треда, узнал про __DBM(), надо посмотреть как им пользоваться. Как раз поизучаю ваш код на гитхабе, думаю я там много полезного для себя смогу найти.

Кстати, я вот смотрю все используют разный inline: кто-то __force_inline, кто-то просто inline, кто-то __attribute__((always_inline)) – есть какая-то прям значительная разница, это же все равно рекомендацию компилятору?

snake266 ★★
() автор топика
Последнее исправление: snake266 (всего исправлений: 1)
Ответ на: комментарий от snake266

Но я понял, на функциях которые настраивают что-то системное, можно и «потупить», так?

Дофига девайсов настраивается один раз при включении и больше настройки не меняются.

anonymous
()
Ответ на: комментарий от snake266

есть какая-то прям значительная разница, это же все равно рекомендацию компилятору?

Последнее - gcc'шный вариант. А еще есть всякие кайлы, яры и т.п. проприетарное УГ. В общем, пиши gcc'шный вариант, но не надейся, что 100% отработает: ведь gcc может решить иначе…

anonymous
()
Ответ на: комментарий от anonymous

Ну я пишу по стандарту inline, хотя учитывая что всё равно все собирают gcc, то можно и позволить себе gcc’изм.

snake266 ★★
() автор топика
Ответ на: комментарий от snake266

Ну, по-стандарту достаточно static inline писать. Но это вообще не факт, что gcc заинлайнит. А вот атрибут вероятность заинлайнить повышает почти до 100%.

anonymous
()
Ответ на: комментарий от snake266

хотел попробовать избавиться от блокировок

Ну так и не избавился в итоге.

Запускай HSI и ставь прерывание на HSIRDY, в прерывании просто сбрасывай регистры RCC на дефолтные значения.

Всё. Никаких конечных автоматов, барьеров, while-ов и дрочки вприсядку.

anonymous
()
Ответ на: комментарий от snake266

Wait until it’s switched to HSI mode */ while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI)

А кто тебе сказал, что тут чего-то нужно ждать? Если источник запущен, то переключение происходит мгновенно. Флаги эти — для тебя, чтобы ты мог в любой момент узнать какой сейчас используется источник.

anonymous
()
Ответ на: комментарий от anonymous

Типа так: это обычно не нужно (т.к. восстанавливает дефолт), а это — нужно для старта HSE.

Ясно, Эдик до сих пор не разобрался с клоками и всё ещё всем суёт свою забагованную магическую портянку, которую уже даже разработчик контроллеров уже давно выпилил из стартап-кода.

anonymous
()
while(de_init_complete != RCC_DEINIT_READY) {}

Зачем ты городишь автомат, если все равно блокируешь основной поток на время его работы?

switch(RCC->CIR & (RCC_CIR_HSERDYF | RCC_CIR_HSIRDYF | RCC_CIR_PLLRDYF))
{
    case RCC_CIR_HSIRDYF:
    /*...*/
    case RCC_CIR_HSERDYF:
    /*...*/
}

Ну и кто тебе сказал, что эти флаги не могут возникнуть параллельно?

Что в этом случае твой код делать будет?

shkolnick-kun ★★★★★
()
Ответ на: комментарий от shkolnick-kun

Зачем ты городишь автомат, если все равно блокируешь основной поток на время его работы?

Ну когда я писал этот кусок кода мне казалось, что это будет все равно быстрее. Тот же RCC_CR_HSERDY, судя по даташиту ставиться не сразу, а спустя 6 тактов, и за эти 6 тактов можно делать работу дальше а не ждать в while((RCC->CR & RCC_CR_HSERDY) != DISABLE) {}

Ну и кто тебе сказал, что эти флаги не могут возникнуть параллельно?

Вот про это я вообще не думал. Мне почему-то казалось, что оно не может возникнуть параллельно: будет два параллельных прерывания на два флага – надо будет подумать как это решить.

Что в этом случае твой код делать будет?

Скорее всего, выполнится только один case и я буду вечно крутиться в while(de_init_complete != RCC_DEINIT_READY) {} из-за того что de_init_complete не дойдет до 3.

Сейчас понял, что лучше всего найти книгу по stm32, где рассматриваются такие ошибки и best practise. Ну и еще почитать код других людей.

snake266 ★★
() автор топика
Ответ на: комментарий от anonymous

Ты даташит-то читал с RM? Там рекомендуется дождаться, что бит точно установился. И устанавливается он не «мгновенно»! Клоку нужно какое-то время, чтобы раскачаться. В общем, прежде, чем умничать, почитай документацию, чтобы в лужу не садиться, осел!

anonymous
()
Ответ на: комментарий от anonymous

осел!

Сам-то доки открывал? Дал бы ссылочку, где там говорится, что надо ждать SWS. Заметь, я говорил именно про SWS, а не про старт источников, которые, разумеется, стартуют не мгновенно.

anonymous
()
Ответ на: комментарий от snake266

и за эти 6 тактов можно делать работу дальше

Твой девайс перезагружается по 100 раз каждую микросекунду, что нужно глючную портянку городить из-за 6 (!) тактов?

anonymous
()
Ответ на: комментарий от anonymous

Да, я уже понял, что я написал фигню, как минимум потому что я забыл про:

Клоку нужно какое-то время, чтобы раскачаться

А про 6 тактов, да соглашусь, на тех же 48 МГц это вообще мелочь.

snake266 ★★
() автор топика
Ответ на: комментарий от snake266

Ну когда я писал этот кусок кода мне казалось, что это будет все равно быстрее.

Не будет! Твёрдо и четко!(Ц)

Как минимум из-за сохранения/восттановления контекста при обработке прерывания.

Вот про это я вообще не думал. Мне почему-то казалось, что оно не может возникнуть параллельно: будет два параллельных прерывания на два флага – надо будет подумать как это решить.

Так почитай rm на свой микроконтроллер!

При определённом стечении обстоятельств флаги RCC_CIR_HSERDYF, RCC_CIR_HSIRDYF, RCC_CIR_PLLRDYF могут быть выставлены одновременно, т.к. их сброс происходит только программно.

Скорее всего, выполнится только один case и я буду вечно крутиться

Ничего у тебя не выполнится, и основной поток заблокируется навсегда.

Рекомендую ознакомиться с ISO/IEC 9899:2011 и более старыми версиями стандарта.

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 3)
Ответ на: комментарий от shkolnick-kun

Как минимум из-за сохранения/восттановления контекста при обработке прерывания.

Да, я вот как раз читаю книгу и там вычитал, что прерывания не такие уж и бесплатные, как я думал.

Так почитай rm на свой микроконтроллер!

Читаю, но не всегда получается найти то, что надо. Я, вот например, не смог, в RM для stm32f0x, найти инфу: надо ли сбрасывать RCC_CIR_*RDYC после того как их выставил (хотя не отрицаю, что я плохо искал).

snake266 ★★
() автор топика

Нет никаких ограничений на прерывание, хоть всю программу туда засовывай. Прерывание это простой механизм. Сработало прерывание, вызвалась твоя функция (если сейчас не отрабатывания прерывание более приоритетное), всё. Если ты не отдаёшь контроль, то не будут вызываться другие прерывания с тем же или более низким приоритетом. Вот и все последствия.

Legioner ★★★★★
()
Ответ на: комментарий от u-235

Могу ошибаться, но на мой взгляд нужно делать барьеры памяти. volatile может не сработать.

На одноядерном не надо. Но, конечно, лучше писать как положено, вдруг на многоядерный будешь переносить.

Legioner ★★★★★
()
Ответ на: комментарий от anonymous

Да мне, честно говоря, наплевать.

anonymous
()
Ответ на: комментарий от anonymous

Главное - признавать свою неправоту лишь в том случае, если ты реально не прав. Т.е. практически никогда.

anonymous
()
Ответ на: комментарий от snake266

RCC_CIR_*RDYC нужны как раз для сброса флагов RCC_CIR_*RDYF, у них даже режим доступа write only.

Причем я об этом догадывался, когда писал прошлый ответ, а узнал только что, когда заглянул в референс мануал.

Ты уверен, что достаточно хорошо владеешь английским? Без английского и знания архитектуры ЭВМ тут никуда.

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 1)
Ответ на: комментарий от shkolnick-kun

Моя логика такова, почему у меня возник вопрос про сброс флага RCC_CIR_*RDYC: произошло прерывание, у меня выставляется флаг, например, RCC_CIR_HSIRDYF, я хочу его сбросить с помощью RCC_CIR_HSIRDYC, выставляю его, то есть теперь в RCC_CIR_HSIRDYC единица – и вот в мануале не написано сбрасывается ли в ноль RCC_CIR_HSIRDYC сам или его нужно вручную сбросить. Ни в описании регистра RCC_CIR, ни в главе про RCC я не нашел ответа на свой вопрос. Или то, что оно write-only должно мне намекать на то что флаг выставил и забыл?

P.S. Окей, сейчас в RM в примерах (решил все-таки глянуть что там есть) нашел инициализацию HSE с помощью прерываний и судя по всему сбрасывать RCC_CIR_*RDYC в ноль самому не надо.

snake266 ★★
() автор топика
Ответ на: комментарий от snake266

сбрасывать RCC_CIR_*RDYC в ноль самому не надо

Не надо. Сброс происходит непосредственно лишь при операции записи 1. Состояние таких регистров не имеет значения, потому они и write only.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.