Как я могу использовать одну функцию из основного приложения и загрузчика? (Встроенный)

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

У меня есть функции, которые являются общими для загрузчика и основного приложения. Сейчас я компилирую исходные файлы 2 раза один раз для загрузчика и приложения. Но мне не хватает места для двойного банка dfu, и я хотел бы иметь эти функции только один раз в ПЗУ. Есть идеи, как мне этого добиться?

РЕДАКТИРОВАТЬ:

Использование указателей на функции может быть опасным в некоторых случаях, проверьте мои проблемы - Использование функций указателей - 2 отдельных приложения на 1 устройстве

3 ответа

Решение

Это только частичный ответ и предполагает, что вы можете перейти в свой загрузчик из основного кода, используя то же адресное пространство. Затем, обычная техника - предоставить ваш "API загрузчика" в виде таблицы указателей на функции.

Скажем, например, у вас есть следующие функции в вашем загрузчике:

static int do_something(void)
{
    return 42;
}

static int do_something_else(int arg)
{
    return arg+5;
}

Тогда вы бы объявили свой API в заголовке следующим образом:

struct bootloaderApi
{
    int (*do_something)(void);
    int (*do_something_else)(int arg);
};

В реализации вашего загрузчика вы определяете эту таблицу в своем собственном разделе:

// this is GCC syntax, use whatever your compiler provides to specify the section
struct bootloaderApi api __attribute__((section("API"))) = {
    do_something,
    do_something_else
};

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

SECTIONS {
  // standard sections, e.g.:
  .text : { *(.text) }
  .data : { *(.data) } 
  .bss :  { *(.bss)  *(COMMON) }

  // your API table:
  .API 0x10000 : { *(.API) }
}

Теперь предполагается, что ваша таблица API будет размещена на 0x10000, Затем вы можете сделать следующее, чтобы получить доступ к API из вашего основного кода:

struct bootloaderApi *api = (struct bootloaderApi *)0x10000;

api->do_something();

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

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

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

Точный механизм (ы) зависит от архитектуры.

Два возможных сценария:

  1. Встроенный загрузчик. Во всех ядрах Cortex я знаю. Вот пример кода для вызова загрузчика из приложения.

    #define SYSFLASH 0x1FFFD800
    
    void __attribute__((noreturn)) StartBootLoader(void) {
    
        void (*BootLoad)(void) = (void (*)(void))(SYSFLASH + 4);
    
        HAL_RCC_DeInit();  // Only the example foe HAL. PLL has to switched off, peripheral clocks as well 
        HAL_DeInit();      // All interrupts have to be disabled. 
                       // if HAL not used uC should be restored to the power-on state
    
        SysTick -> CTRL = 0;
        SysTick -> LOAD = 0;
        SysTick -> VAL = 0;
    
        __set_PRIMASK(1);
    
        __set_MSP(*(uint32_t *)SYSFLASH);
        BootLoad();
        while(1);
    }
    
  2. Кастомный загрузчик - возможен любой сценарий

Другие вопросы по тегам