Чтение из fla sh, не являющегося частью приложения - PullRequest
0 голосов
/ 30 мая 2020

Я программирую встроенное ПО без ОС, поэтому c ОС и т. Д. на STM32L4 (ARM Cortex M4). У меня есть отдельная страница во fla sh, которая написана загрузчиком (это не и не должно быть частью двоичного файла моего приложения, это обязательно). На этой странице я храню параметры конфигурации, которые будут использоваться в моем приложении. Эта страница конфигурации может измениться, но не во время выполнения, после изменения я перезагружаю процессор.

Как лучше всего получить доступ к этим данным во fla sh?

Мое определение nice - (в этом порядке приоритета): - поддержка (u) int32_t, (u) int8_t, bool, char [фиксированный размер] - небольшие накладные расходы по сравнению с #define PARAM (1) или constexpr - типизированное использование (т.е. uint8_t var = CONFIG_CHAR_ARRAY должен выдавать по крайней мере предупреждение) - нет копии RAM - читаемость параметров конфигурации во время отладки (с использованием STM32CubeIDE)

Решение должно масштабироваться для всех возможных 2048 байтов флэш-страницы. Генерация кода в любом случае является частью процесса.

Пока что я протестировал два варианта (я исхожу из простого C, но использую (потенциально современный) C ++ в этом проекте). Мой текущий тестовый пример -

    if (param) function_call();

, но он также должен работать для других случаев, таких как

    for(int i = 0; i < param2; i++)
  1. определить с приведением указателя

    #define CONF_PARAM1 (*(bool*)(CONFIG_ADDRESS + 0x0083))
    

    Что приводит к (с использованием -Os):

    8008872:    4b1b        ldr r3, [pc, #108]  ; (80088e0 <_Z16main_applicationv+0xac>)
    8008874:    781b        ldrb    r3, [r3, #0]
    8008876:    b10b        cbz r3, 800887c <_Z16main_applicationv+0x48>
    8008878:    f7ff ff56   bl  8008728 <_Z10function_callv>
    
    80088e0:    0801f883    .word   0x0801f883
    
  2. const переменная

    const bool CONF_PARAM1 = *(bool*)(CONFIG_ADDRESS + 0x0083);
    

    ведущая к

    800887c:    4b19        ldr r3, [pc, #100]  ; (80088e4 <_Z16main_applicationv+0xb0>)
    800887e:    781b        ldrb    r3, [r3, #0]
    8008880:    b10b        cbz r3, 8008886 <_Z16main_applicationv+0x52>
    8008882:    f000 f899   bl  8008728 <_Z10function_callv>
    
    80088e4:    200000c0    .word   0x200000c0
    

Мне не нравится вариант 2, так как он добавляет копию ОЗУ (плохо масштабируется для 2048 байт конфигурации), вариант 1 выглядит как очень старый стиль c и не помогает при отладке. Я изо всех сил пытаюсь найти другой вариант с использованием сценария компоновщика, поскольку я не нахожу способа, чтобы переменная не попала в двоичный файл приложения.

Есть ли лучший способ сделать это?

Ответы [ 4 ]

1 голос
/ 30 мая 2020

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

#include <cstdint>
#include <iostream>

template <typename T>
const T& configOption(uintptr_t offset)
{
    const uintptr_t CONFIG_ADDRESS = 0x1000;
    return *reinterpret_cast<T*>(CONFIG_ADDRESS + offset);
}

auto& CONF_PARAM1 = configOption< bool >(0x0083);
auto& CONF_PARAM2 = configOption< int >(0x0087);

int main()
{
    std::cout << CONF_PARAM1 << ", " << CONF_PARAM2 << "\n";
}

G CC довольно хорошо оптимизирует: https://godbolt.org/z/r27o5Q

0 голосов
/ 01 июня 2020

Вы можете изолировать рассматриваемые переменные в отдельном разделе. Есть несколько способов сделать это. Инструменты собираются нормально и выполняют всю работу по адресации. Как и при использовании структур в доменах компиляции, вам нужно быть предельно осторожным и, вероятно, выполнять проверки в коде, но вы можете создать двоичный файл и загружать только его или все, кроме другого содержимого fla sh, тогда в то время или позже вы можете изменить ЗНАЧЕНИЯ переменных в другом разделе, а затем построить и изолировать их в своей собственной нагрузке.

Проверка теории

vectors.s

.globl _start
_start:
.word 0x20001000
.word reset


.thumb_func
reset:
    bl main
    b .

.globl dummy
.thumb_func
dummy:
    bx lr

итак. c

extern volatile unsigned int x;
extern volatile unsigned short y;
extern volatile unsigned char z[7];
extern void dummy ( unsigned int );
int main ( void )
{
    dummy(x);
    dummy(y);
    dummy(z[0]<<z[1]);
    return(0);
}

flashvars. c

volatile unsigned int x=1;
volatile unsigned short y=3;
volatile unsigned char z[7]={1,2,3,4,5,6,7};

fla sh .ld

MEMORY
{
    rom0 : ORIGIN = 0x08000000, LENGTH = 0x1000
    rom1 : ORIGIN = 0x08002000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom0
    .vars : { flashvars.o } > rom1
}

build

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 vectors.s -o vectors.o
arm-none-eabi-ld -nostdlib -nostartfiles -T flash.ld vectors.o so.o flashvars.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy -R .vars -O binary so.elf so.bin

изучить

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000009    stmdaeq r0, {r0, r3}

08000008 <reset>:
 8000008:   f000 f802   bl  8000010 <main>
 800000c:   e7fe        b.n 800000c <reset+0x4>

0800000e <dummy>:
 800000e:   4770        bx  lr

08000010 <main>:
 8000010:   4b08        ldr r3, [pc, #32]   ; (8000034 <main+0x24>)
 8000012:   b510        push    {r4, lr}
 8000014:   6818        ldr r0, [r3, #0]
 8000016:   f7ff fffa   bl  800000e <dummy>
 800001a:   4b07        ldr r3, [pc, #28]   ; (8000038 <main+0x28>)
 800001c:   8818        ldrh    r0, [r3, #0]
 800001e:   b280        uxth    r0, r0
 8000020:   f7ff fff5   bl  800000e <dummy>
 8000024:   4b05        ldr r3, [pc, #20]   ; (800003c <main+0x2c>)
 8000026:   7818        ldrb    r0, [r3, #0]
 8000028:   785b        ldrb    r3, [r3, #1]
 800002a:   4098        lsls    r0, r3
 800002c:   f7ff ffef   bl  800000e <dummy>
 8000030:   2000        movs    r0, #0
 8000032:   bd10        pop {r4, pc}
 8000034:   0800200c    stmdaeq r0, {r2, r3, sp}
 8000038:   08002008    stmdaeq r0, {r3, sp}
 800003c:   08002000    stmdaeq r0, {sp}

Disassembly of section .vars:

08002000 <z>:
 8002000:   04030201    streq   r0, [r3], #-513 ; 0xfffffdff
 8002004:   00070605    andeq   r0, r7, r5, lsl #12

08002008 <y>:
 8002008:   00000003    andeq   r0, r0, r3

0800200c <x>:
 800200c:   00000001    andeq   r0, r0, r1

выглядит хорошо

hexdump -C so.bin
00000000  00 10 00 20 09 00 00 08  00 f0 02 f8 fe e7 70 47  |... ..........pG|
00000010  08 4b 10 b5 18 68 ff f7  fa ff 07 4b 18 88 80 b2  |.K...h.....K....|
00000020  ff f7 f5 ff 05 4b 18 78  5b 78 98 40 ff f7 ef ff  |.....K.x[x.@....|
00000030  00 20 10 bd 0c 20 00 08  08 20 00 08 00 20 00 08  |. ... ... ... ..|
00000040

как и это.

arm-none-eabi-objcopy -j .vars -O binary so.elf sovars.bin

hexdump -C sovars.bin 
00000000  01 02 03 04 05 06 07 00  03 00 00 00 01 00 00 00  |................|
00000010  47 43 43 3a 20 28 47 4e  55 29 20 39 2e 33 2e 30  |GCC: (GNU) 9.3.0|
00000020  00 41 30 00 00 00 61 65  61 62 69 00 01 26 00 00  |.A0...aeabi..&..|
00000030  00 05 43 6f 72 74 65 78  2d 4d 30 00 06 0c 07 4d  |..Cortex-M0....M|
00000040  09 01 12 04 14 01 15 01  17 03 18 01 19 01 1a 01  |................|
00000050  1e 02                                             |..|
00000052

ха, ладно, еще немного поработайте. * намного лучше.

Я настоятельно рекомендую не использовать структуры в доменах компиляции, и они попадают в эту категорию, поскольку сборка для реальных данных выполняется отдельно, и между сборкой кода и сборкой данных вы можете получить данные, которые не приземляться так же, когда я делаю такие вещи, я ставлю защиту, чтобы поймать проблему em во время выполнения, прежде чем он уйдет с рельсов (или лучше во время сборки). Это не тот случай, если это когда. Определенная реализация означает определенную реализацию.

Но подумав о своем вопросе, это стало простым решением. И да, технически эти данные доступны только для чтения, const то или иное, но 1) неужели volatile и const go вместе? и 2) вы действительно хотите / должны это делать?

Должен ли он вообще быть изменчивым? Скорее всего, нет, просто с самого начала ударил по этому поводу. При переключении на const инструмент помещает их в .rodata. Что ж, мой инструмент зависит от того, как вы пишете свой скрипт компоновщика, и я думаю, что версия binutils.

так. c

extern const unsigned int x;
extern const unsigned short y;
extern const unsigned char z[7];
extern void dummy ( unsigned int );
int main ( void )
{
    dummy(x);
    dummy(y);
    dummy(z[0]<<z[1]);
    return(0);
}

flashvars. c

const unsigned int x=1;
const unsigned short y=3;
const unsigned char z[7]={1,2,3,4,5,6,7};

fla sh .ld

MEMORY
{
    rom0 : ORIGIN = 0x08000000, LENGTH = 0x1000
    rom1 : ORIGIN = 0x08002000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom0
    .vars : { flashvars.o(.rodata) } > rom1
}

вывод

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000009    stmdaeq r0, {r0, r3}

08000008 <reset>:
 8000008:   f000 f802   bl  8000010 <main>
 800000c:   e7fe        b.n 800000c <reset+0x4>

0800000e <dummy>:
 800000e:   4770        bx  lr

08000010 <main>:
 8000010:   4b08        ldr r3, [pc, #32]   ; (8000034 <main+0x24>)
 8000012:   b510        push    {r4, lr}
 8000014:   6818        ldr r0, [r3, #0]
 8000016:   f7ff fffa   bl  800000e <dummy>
 800001a:   4b07        ldr r3, [pc, #28]   ; (8000038 <main+0x28>)
 800001c:   8818        ldrh    r0, [r3, #0]
 800001e:   f7ff fff6   bl  800000e <dummy>
 8000022:   4b06        ldr r3, [pc, #24]   ; (800003c <main+0x2c>)
 8000024:   7818        ldrb    r0, [r3, #0]
 8000026:   785b        ldrb    r3, [r3, #1]
 8000028:   4098        lsls    r0, r3
 800002a:   f7ff fff0   bl  800000e <dummy>
 800002e:   2000        movs    r0, #0
 8000030:   bd10        pop {r4, pc}
 8000032:   46c0        nop         ; (mov r8, r8)
 8000034:   0800200c    stmdaeq r0, {r2, r3, sp}
 8000038:   08002008    stmdaeq r0, {r3, sp}
 800003c:   08002000    stmdaeq r0, {sp}

Disassembly of section .vars:

08002000 <z>:
 8002000:   04030201    streq   r0, [r3], #-513 ; 0xfffffdff
 8002004:   00070605    andeq   r0, r7, r5, lsl #12

08002008 <y>:
 8002008:   00000003    andeq   r0, r0, r3

0800200c <x>:
 800200c:   00000001    andeq   r0, r0, r1

hexdump -C so.bin
00000000  00 10 00 20 09 00 00 08  00 f0 02 f8 fe e7 70 47  |... ..........pG|
00000010  08 4b 10 b5 18 68 ff f7  fa ff 07 4b 18 88 ff f7  |.K...h.....K....|
00000020  f6 ff 06 4b 18 78 5b 78  98 40 ff f7 f0 ff 00 20  |...K.x[x.@..... |
00000030  10 bd c0 46 0c 20 00 08  08 20 00 08 00 20 00 08  |...F. ... ... ..|
00000040

hexdump -C sovars.bin 
00000000  01 02 03 04 05 06 07 00  03 00 00 00 01 00 00 00  |................|
00000010
0 голосов
/ 01 июня 2020

Нет необходимости заново изобретать колесо - размещение данных во fla sh - довольно распространенный вариант использования во встроенных системах. При работе с такими данными fla sh необходимо учитывать некоторые важные моменты:

  • Все данные должны находиться по одному и тому же адресу, с одним и тем же типом, от случая к случаю. Это означает, что struct проблематично c из-за заполнения (и тем более class). Если вы выровняете все данные по 32-битным границам, это не должно быть проблемой, поэтому я настоятельно рекомендую вам это сделать. Затем программа становится переносимой между компиляторами.
  • Все эти переменные и указатели на них должны быть объявлены с квалификатором volatile, иначе оптимизатор может go сбиться с толку. Такие вещи, как (*(bool*)(CONFIG_ADDRESS + 0x0083)), хрупкие и могут сломаться в любой момент, если вы не добавите volatile.
  • Вы можете разместить данные в фиксированном месте в памяти, но как это сделать - компилятор / компоновщик -specifi c. А поскольку это не стандартизовано, всегда сложно исправить это. Для компиляторов с g cc это может быть что-то вроде: __attribute__(section(".dataflash")), где .dataflash - ваш настраиваемый сегмент, для которого вы должны зарезервировать место в скрипте компоновщика. Вам нужно будет внимательнее изучить, как это сделать с помощью вашей специфической c инструментальной цепочки (другие вместо этого используют #pragmas et c), здесь я буду использовать __attribute__ только для иллюстрации.

    Если этот раздел загружается вместе с исполняемым двоичным файлом или только через загрузчик, решать вам. Сценарии компоновщика обычно имеют опцию «no init».

Таким образом, вы можете сделать что-то вроде:

// flashdata.h

typedef struct
{
  uint32_t stuff;
  uint32_t more_stuff;
  ...
} flashdata_t;

extern volatile const flashdata_t flash_data __attribute__(section(".dataflash"));

А затем объявить это как:

// flashdata.c

volatile const flashdata_t flash_data __attribute__(section(".dataflash"));

И теперь вы можете использовать его как любую структуру, flash_data.stuff.

Если вы используете C, вы даже можете разделить каждый фрагмент uint32_t с помощью объединения, например typedef union { uint32_t u32; uint8_t u8 [4]; } и т.п., но это невозможно в C ++, потому что это не позволяет каламбур союзного типа.

0 голосов
/ 01 июня 2020

Как было предложено @old_timer в комментарии выше, я предпочитаю это решение:

В файле компоновщика я поместил

    CONF_PARAM = _config_start + 0x0083;

В моем config.hpp я поставил

    extern const bool CONF_PARAM;

, к которому затем можно легко получить доступ в любом исходном файле

    if (CONF_PARAM)

Это в основном выполняет все мои "хорошие" определения, насколько я могу видеть.

...