Могу ли я сохранить указатель на RAM во флэш-памяти во время компиляции - PullRequest
0 голосов
/ 04 мая 2018

Моя проблема объяснила:

На моем микроконтроллере (Atmel AT90CAN128) у меня осталось около 2500 байт оперативной памяти. В этих 2500 байтах мне нужно хранить 5 раз 100 наборов данных (размер может измениться в будущем). Наборы данных имеют предопределенную, но различную длину от 1 до 9 байтов. Общее количество байтов, которые занимают чистые наборы данных, составляет около 2000 байтов. Теперь мне нужно иметь возможность доступа к наборам данных в массиве, например, путем передачи uint8 в функцию и получения указателя на набор данных в ответ. Но у меня осталось только около 500 байтов, поэтому массив с указателями на каждый набор данных (рассчитанный в начале выполнения) просто невозможен.

Моя попытка:

я использую один большой uint8 array[2000] (в ОЗУ), а длина наборов данных сохраняется во флэш-памяти как const uint8[] = {1, 5, 9, ...};.

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

Во время выполнения это дает мне плохую производительность. Положение наборов данных в большом массиве ИЗВЕСТНО во время компиляции, я просто не знаю, как поместить эту информацию в массив, который компилятор может сохранить во флэш-памяти.

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

Цель:

что-то в этом роде

uint8 index = 57; uint8 *pointer_to_data = pointer_array[57];

Возможно ли это, поскольку компилятор является однопроходным?

(я использую Codevision, а не avr gcc)

Мое решение

Технически правильный ответ на мой вопрос - чисто C-решение / ответ, но он кажется слишком сложным (с моей точки зрения). Идея со сценарием сборки казалась лучше, но Codevision в этом смысле не очень практична. Так что я закончил с небольшим количеством микса.

Я написал javascript, который пишет для меня код на C / определение переменных. Необработанные определения легко редактировать, и я просто копирую, вставляю все это в текстовый файл html, открываю его в браузере и копирую, вставляю содержимое обратно в мой файл C.

В начале мне не хватало ключевого элемента, и это позиция ключевого слова flash в определении. Ниже приведен упрощенный вывод моего javascript, который компилируется так, как мне нравится.

flash uint8 len[150] = {4, 4, 0, 2, ...};

uint8 data1[241] = {0}; //accumulated from above

uint8 * flash pointers_1[150] = {data1 +0, data1 +4, data1 +0, data1 +8, ...};

Уродливая часть (много ручного труда без скрипта) добавляет длину для каждого указателя, так как компилятор будет компилироваться только в том случае, если указатель увеличивается на константу, а не на значение, хранящееся в константном массиве.

Необработанные определения, которые передаются в javascript, выглядят так:

var strings = [
"len[0] = 4;",
"len[1] = 4;",
"len[3] = 2;",
...

Внутри javascript это массив строк, так что я мог бы скопировать в него свои старые определения и просто добавить несколько цитат. Мне нужно только определить те, которые я хочу использовать, индекс 2 не определен, и скрипт использует для него длину 0, но включает его. Макрос должен был бы иметь запись с 0, я думаю, что плохо для обзора в моем случае.

Это не решение в один клик, но оно очень читабельное и аккуратное, что компенсирует копирование-вставку.

Ответы [ 4 ]

0 голосов
/ 05 мая 2018

Вы упоминаете, что длины набора данных предопределены, но не так, как они определены - поэтому я собираюсь сделать предположение о том, как длины, записанные в код, подходят для захвата ..

Если вы определите свой флэш-массив в виде смещений, а не длин, вы должны сразу получить преимущество во время выполнения.

С длиной во вспышке, я ожидаю, что у вас есть что-то вроде этого:

const uint8_t lengths[] = {1, 5, 9, ...};

uint8_t get_data_set_length(uint16_t index)
{
    return lengths[index];
}
uint8_t * get_data_set_pointer(uint16_t index)
{
    uint16_t offset = 0;
    uint16_t i = 0;
    for ( i = 0; i < index; ++i )
    {
        offset += lengths[index];
    }
    return &(array[offset]);
}

С учетом смещений во флэш-памяти массив const изменился с uint8_t до uint16_t, что удваивает использование флэш-памяти, а также добавил дополнительный элемент для ускорения расчета длины последнего элемента.

const uint16_t offsets[] = {0, 1, 6, 15, ..., /* last offset + last length */ };

uint8_t get_data_set_length(uint16_t index)
{
    return offsets[index+1] - offsets[index];
}
uint8_t * get_data_set_pointer(uint16_t index)
{
    uint16_t offset = offsets[index];
    return &(array[offset]);
}

Если вы не можете позволить себе эту дополнительную флэш-память, вы также можете объединить их, имея длины для всех элементов и смещения для доли индексов, например, для каждого 16 элемента в примере ниже, компенсируя время выполнения стоимость против стоимости флеш-памяти.

uint8_t get_data_set_length(uint16_t index)
{
    return lengths[index];
}
uint8_t * get_data_set_pointer(uint16_t index)
{
    uint16_t i;
    uint16_t offset = offsets[index / 16];
    for ( i = index & 0xFFF0u; i < index; ++i )
    {
        offset += lengths[index];
    }
    return &(array[offset]);
}

Чтобы упростить кодирование, вы можете рассмотреть возможность использования x-macros, например,

#define DATA_SET_X_MACRO(data_set_expansion) \
  data_set_expansion( A, 1 ) \
  data_set_expansion( B, 5 ) \
  data_set_expansion( C, 9 )

uint8_t array[2000];
#define count_struct(tag,len) uint8_t tag;
#define offset_struct(tag,len) uint8_t tag[len];
#define offset_array(tag,len) (uint16_t)(offsetof(data_set_offset_struct,tag)),
#define length_array(tag,len) len,
#define pointer_array(tag,len) (&(array[offsetof(data_set_offset_struct,tag)])),

typedef struct
{
    DATA_SET_X_MACRO(count_struct)
}   data_set_count_struct;

typedef struct
{
    DATA_SET_X_MACRO(offset_struct)
}   data_set_offset_struct;

const uint16_t offsets[] = 
{
    DATA_SET_X_MACRO(offset_array)
};

const uint16_t lengths[] = 
{
    DATA_SET_X_MACRO(length_array)
};

uint8_t * const pointers[] = 
{
    DATA_SET_X_MACRO(pointer_array)
};

Препроцессор превращает это в:

typedef struct
{
    uint8_t A;
    uint8_t B;
    uint8_t C;
}   data_set_count_struct;

typedef struct
{
    uint8_t A[1];
    uint8_t B[5];
    uint8_t C[9];
}   data_set_offset_struct;

typedef struct
{
    uint8_t A[1];
    uint8_t B[5];
    uint8_t C[9];
}   data_set_offset_struct;

const uint16_t offsets[] = { 0,1,6, };

const uint16_t lengths[] = { 1,5,9, };

uint8_t * const pointers[] = 
{
    array+0,
    array+1,
    array+6,
};

Это просто показывает пример того, что может расширять x-macro. Короткий main() может показать их в действии:

int main()
{
    printf("There are %d individual data sets\n", (int)sizeof(data_set_count_struct) );
    printf("The total size of the data sets is %d\n", (int)sizeof(data_set_offset_struct) );
    printf("The data array base address is  %x\n", array );
    int i;
    for ( i = 0; i < sizeof(data_set_count_struct); ++i )
    {
        printf( "elem %d: %d bytes at offset %d, or address %x\n", i, lengths[i], offsets[i], pointers[i]);
    }

    return 0;
}

С выходом образца

There are 3 individual data sets
The total size of the data sets is 15
The data array base address is  601060
elem 0: 1 bytes at offset 0, or address 601060
elem 1: 5 bytes at offset 1, or address 601061
elem 2: 9 bytes at offset 6, or address 601066 

Выше требуется, чтобы вы указали «тег» - действительный идентификатор C для каждого набора данных, но если у вас их 500, соединение каждой длины с дескриптором, вероятно, неплохо. При таком количестве данных я бы также рекомендовал использовать файл включения для x-макроса вместо #define, в частности, если определения набора данных можно экспортировать в другое место.

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

0 голосов
/ 04 мая 2018

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

Другими словами, если у вас есть «строки» данных 1, 2 3, 4 5 6 и 7 8 9 10, вы можете упаковать их в массив 1 + 1 + 1 + 2 + 1 + 3 + 1 + 4 + 1 = 15 байтов как 1 1 2 2 3 3 4 5 6 4 7 8 9 10 0.

Функции доступа к указанным последовательностям также довольно просты. В случае OP каждый элемент данных представляет собой uint8:

uint8  dataset[] = { ..., 0 };

Чтобы зациклить каждый набор, вы используете две переменные: одну для смещения текущего набора, а другую для длины:

uint16 offset = 0;

while (1) {
    const uint8  length = dataset[offset];
    if (!length) {
        offset = 0;
        break;
    } else
        ++offset;

    /* You have 'length' uint8's at dataset+offset. */

    /* Skip to next set. */
    offset += length;
}

Чтобы найти определенный набор данных, вам нужно найти его с помощью цикла. Например:

uint8 *find_dataset(const uint16  index)
{
    uint16  offset = 0;
    uint16  count = 0;

    while (1) {
        const uint8  length = dataset[offset];
        if (length == 0)
            return NULL;
        else
        if (count == index)
            return dataset + offset;

        offset += 1 + length;
        count++;
    }
}

Вышеприведенная функция вернет указатель на элемент длины index -го набора (0 относится к первому набору, 1 - ко второму набору и т. Д.) Или NULL, если такого набора нет.

Нетрудно написать функции для удаления, добавления, добавления и добавления новых наборов. (При добавлении и вставке сначала необходимо скопировать остальные элементы в массиве dataset вперед (к более высоким индексам), сначала на элементы 1+ длины; это означает, что вы не можете получить доступ к массиву в контексте прерывания или из второго ядра, в то время как массив модифицируется.)


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

static const uint8   dataset_0[] PROGMEM = { 1 };
static const uint8   dataset_1[] PROGMEM = { 2, 3 };
static const uint8   dataset_2[] PROGMEM = { 4, 5, 6 };
static const uint8   dataset_3[] PROGMEM = { 7, 8, 9, 10 };

#define  DATASETS  4

static const uint8  *dataset_ptr[DATASETS] PROGMEM = {
    dataset_0,
    dataset_1,
    dataset_2,
    dataset_3,
};

static const uint8   dataset_len[DATASETS] PROGMEM = {
    sizeof dataset_0,
    sizeof dataset_1,
    sizeof dataset_2,
    sizeof dataset_3,
};

Когда эти данные генерируются во время компиляции встроенного программного обеспечения, обычно их помещают в отдельный заголовочный файл и просто включают его из основного исходного файла встроенного программного обеспечения .c (или, если встроенное программное обеспечение очень сложно, из конкретного .c исходный файл, который обращается к наборам данных). Если выше dataset.h, то исходный файл обычно содержит, скажем,

#include "dataset.h"

const uint8  dataset_length(const uint16  index)
{
    return (index < DATASETS) ? dataset_len[index] : 0;
}

const uint8 *dataset_pointer_P(const uint16  index)
{
    return (index < DATASETS) ? dataset_ptr[index] : NULL;
}

Т.е. он включает в себя набор данных, а затем определяет функции, которые обращаются к данным. (Обратите внимание, что я специально сделал сами данные static, чтобы они были видны только в текущем модуле компиляции; но функции безопасного доступа dataset_length() и dataset_pointer() доступны из других модулей компиляции (исходные файлы C) тоже.)

Когда сборка контролируется с помощью Makefile, это тривиально. Допустим, сгенерированный файл заголовка dataset.h, и у вас есть сценарий оболочки, скажем generate-dataset.sh, который генерирует содержимое для этого заголовка. Тогда рецепт Makefile будет просто

dataset.h: generate-dataset.sh
    @$(RM) $@
    $(SHELL) -c "$^ > $@"

с рецептами для компиляции исходных файлов C, которые нуждаются в нем, и содержат его в качестве предварительного условия:

main.o: main.c dataset.h
    $(CC) $(CFLAGS) -c main.c

Обратите внимание, что для отступа в Makefiles всегда используются Tab s, но этот форум не воспроизводит их в фрагментах кода. (Тем не менее, вы всегда можете запустить sed -e 's|^ *|\t|g' -i Makefile, чтобы исправить скопированные Makefiles.)

OP упомянул, что они используют Codevision, которая не использует Makefiles (но система конфигурирования на основе меню). Если Codevision не предоставляет ловушку перед сборкой (для запуска исполняемого файла или сценария перед компиляцией исходных файлов), тогда OP может написать сценарий или программу, запущенную на хост-компьютере, возможно, с именем pre-build, которая восстанавливает все созданные файлы заголовков. и запускайте его вручную перед каждой сборкой.


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

Идея состоит в том, что вы сначала объявляете каждый набор данных, но не инициализируете их. Это заставляет компилятор С резервировать ОЗУ для каждого:

static uint8  dataset_0_0[3];
static uint8  dataset_0_1[2];
static uint8  dataset_0_2[9];
static uint8  dataset_0_3[4];
/*                      : :  */
static uint8  dataset_0_97[1];
static uint8  dataset_0_98[5];
static uint8  dataset_0_99[7];
static uint8  dataset_1_0[6];
static uint8  dataset_1_1[8];
/*                      : :  */
static uint8  dataset_1_98[2];
static uint8  dataset_1_99[3];
static uint8  dataset_2_0[5];
/*                    : : :  */
static uint8  dataset_4_99[9];

Далее объявляем массив, который задает длину каждого набора. Установите эту константу и PROGMEM, поскольку она неизменна и переходит во flash / rom:

static const uint8  dataset_len[5][100] PROGMEM = {
    sizeof dataset_0_0, sizeof dataset_0_1, sizeof dataset_0_2,
    /* ... */
    sizeof dataset_4_97, sizeof dataset_4_98, sizeof dataset_4_99
};

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

Наконец, создайте массив указателей на наборы данных. Сам этот массив будет неизменным (const и PROGMEM), но цели, наборы данных, которые были определены выше, являются изменяемыми:

static uint8 *const dataset_ptr[5][100] PROGMEM = {
    dataset_0_0, dataset_0_1, dataset_0_2, dataset_0_3,
    /* ... */
    dataset_4_96, dataset_4_97, dataset_4_98, dataset_4_99
};

В AT90CAN128 флэш-память находится по адресам 0x0 .. 0x1FFFF (всего 131072 байта). Внутренняя SRAM находится по адресам 0x0100 .. 0x10FF (всего 4096 байт). Как и другие AVR, он использует архитектуру Гарварда, где код находится в отдельном адресном пространстве - во Flash. Он имеет отдельные инструкции для чтения байтов с флэш-памяти (LPM, ELPM).

Поскольку 16-разрядный указатель может достигать только половины флэш-памяти, очень важно, чтобы массивы dataset_len и dataset_ptr находились "рядом", в нижних 64 КБ. Ваш компилятор должен позаботиться об этом, хотя.

Чтобы сгенерировать правильный код для доступа к массивам из flash (progmem), как минимум, AVR-GCC требуется некоторый вспомогательный код:

#include <avr/pgmspace.h>

uint8 subset_len(const uint8 group, const uint8 set)
{
    return pgm_read_byte_near(&(dataset_len[group][set]));
}

uint8 *subset_ptr(const uint8 group, const uint8 set)
{
    return (uint8 *)pgm_read_word_near(&(dataset_ptr[group][set]));
}

Код сборки, помеченный счетчиками циклов, который генерирует avr-gcc-4.9.2 для at90can128 и выше, равен

subset_len:
    ldi  r25, 0                     ; 1 cycle
    movw r30, r24                   ; 1 cycle
    lsl  r30                        ; 1 cycle
    rol  r31                        ; 1 cycle
    add  r30, r24                   ; 1 cycle
    adc  r31, r25                   ; 1 cycle
    add  r30, r22                   ; 1 cycle
    adc  r31, __zero_reg__          ; 1 cycle
    subi r30, lo8(-(dataset_len))   ; 1 cycle
    sbci r31, hi8(-(dataset_len))   ; 1 cycle
    lpm  r24, Z                     ; 3 cycles
    ret

subset_ptr:
    ldi  r25, 0                     ; 1 cycle
    movw r30, r24                   ; 1 cycle
    lsl  r30                        ; 1 cycle
    rol  r31                        ; 1 cycle
    add  r30, r24                   ; 1 cycle
    adc  r31, r25                   ; 1 cycle
    add  r30, r22                   ; 1 cycle
    adc  r31, __zero_reg__          ; 1 cycle
    lsl  r30                        ; 1 cycle
    rol  r31                        ; 1 cycle
    subi r30, lo8(-(dataset_ptr))   ; 1 cycle
    sbci r31, hi8(-(dataset_ptr))   ; 1 cycle
    lpm  r24, Z+                    ; 3 cycles
    lpm  r25, Z                     ; 3 cycles
    ret

Конечно, объявление subset_len и subset_ptr как static inline будет указывать компилятору, что вы хотите, чтобы они были встроены, что немного увеличивает размер кода, но может сократить пару циклов за вызов.

Обратите внимание, что я проверил вышеупомянутое (кроме использования unsigned char вместо uint8) для at90can128 с использованием avr-gcc 4.9.2.

0 голосов
/ 05 мая 2018

Вы сказали, что хотите сохранить адрес каждого набора данных, но кажется, что будет гораздо проще, если вы сохраните смещение каждого набора данных. Хранение смещений вместо адресов означает, что вам не нужно знать адрес большого array во время компиляции.

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

const uint8_t data_set_lengths[] = { 1, 5, 9...};

Просто замените его на массив констант, содержащий смещение каждого набора данных в большом массиве.

const uint8_t data_set_offsets[] = { 0, 1, 6, 15, ...};

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

С предварительно рассчитанными смещениями код не будет иметь плохую производительность накопления во время выполнения. И вы можете найти адрес любого набора данных во время выполнения, просто добавив смещение набора данных к адресу большого array. И адрес большого array не должен быть установлен до времени ссылки.

0 голосов
/ 04 мая 2018

Во-первых, вы должны поместить массив предопределенной длины во флэш-память, используя PROGMEM, если вы этого еще не сделали.

Вы можете написать скрипт, используя предопределенный массив длины в качестве входных данных, для генерации файла .c (или cpp), который содержит определение массива PROGMEM. Вот пример в Python:

# Assume the array that defines the data length is in a file named DataLengthArray.c
# and the array is of the format
# const uint16 dataLengthArray[] PROGMEM = {
#      2, 4, 5, 1, 2, 
#      4 ... };

START_OF_ARRAY = "const uint16 dataLengthArray[] PROGMEM = {"
outFile = open('PointerArray.c', 'w')
with open("DataLengthArray.c") as f:
    fc = f.read().replace('\n', '')
    dataLengthArray=fc[fc.find(START_OF_ARRAY)+len(START_OF_ARRAY):]
    dataLengthArray=dataLengthArray[:dataLengthArray.find("}")]
    offsets = [int(s) for s in dataLengthArray.split(",")]
    outFile.write("extern uint8 array[2000];\n")
    outFile.write("uint8* pointer_array[] PROGMEM = {\n")
    sum = 0
    for offset in offsets:
        outFile.write("array + {}, ".format(sum))
        sum=sum+offset
    outFile.write("};")

Что бы вывести PointerArray.c:

extern uint8 array[2000];
uint8* pointer_array[] = {
array + 0, array + 2, array + 6, array + 11, array + 12, array + 14, };

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

...