Перейти к содержанию
    

Поиск по массиву в compile time на С

Есть массив регистров, ссылки на переменные с именами и некоторыми параметрами, для удобного доступа по разным интерфейсам и конфигурирования устройства. Допустим вот так объявленный,

 

typedef struct {

    const char        *sym;

    const char        *fmt;
    int            mode;

    union {

        float        *f;
        int        *i;
    }
    link;

    void            (* proc) (const void *reg, void *lval, const void *rval);
}
reg_t;

#define REG_E(link, extra, unit, fmt, mode, proc)    { #link extra "\0" unit, fmt, mode, \
                            { (void *) &link }, (void *) proc }

#define REG(link, unit, fmt, mode, proc)    REG_E(link, "", unit, fmt, mode, proc)

const reg_t        regfile[] = {

    REG(hal.USART_baud_rate,        "",    "%i",    REG_CONFIG, NULL),
    REG(hal.PWM_freq_hz,            "Hz",    "%i",    REG_CONFIG, NULL),
    REG(hal.PWM_dead_time_ns,        "ns",    "%i",    REG_CONFIG, NULL),
    REG(hal.ADC_reference_voltage,        "V",    "%3f",    REG_CONFIG, NULL),
    REG(hal.ADC_current_shunt_resistance,    "Ohm",    "%4e",    REG_CONFIG, NULL),
...
    REG(pm.probe_speed_ramp,    "rad/s",    "%2f",    REG_CONFIG, NULL),
    REG_E(pm.probe_speed_ramp, "_rpm",    "rpm",    "%2f",    REG_NORMAL, &reg_proc_rpm),
...

 

Регистры адресуются по именам или по номерам. Имена достаточно постоянны, а номера могут изменяться в следующих версиях. В этом проблема, нельзя в коде просто взять ссылку на регистр по номеру regfile[n], номер может измениться. Искать по имени в compile time слишком сложно, средствами C и препроцессора не обойтись. По адресам переменных нельзя, они могут повторятся. Какие еще варианты?

 

Сейчас думаю, генерировать из объявления массив макросов вида #define REG_hal_USART_baud_rate 0, но здесь тоже много проблем, надо как-то "спасать" недопустимые символы (точка и др.).

 

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

 

Спасибо.

 

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А зачем так сделано? Выглядит как кусок для внешнего скриптового языка. Но судя по описанию это не так.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Масло-масляное.

Ваш некий реестр (я так понял по названию - константный, т.е compile-time), тогда все это можно сделать через некий h-файл, где через #define описать все параметры проекта.

Тогда нет никакого смысла создавать некий глобальный константный массив-реестр, ведь индексам этого массива все равно придется дать осмысленные имена, т.е. плодить те самые #define.

 

В своих проектах я использую такой файл и называю StaticSettings.h

Всем значениям даны понятные имена, чтобы в коде не фигурировали всякие "магические числа".

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

 

Пример содержимого GlobalSettings.h (Keil), (все "лишнее" вырезал, чтобы не плодить новые вопросы)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Project:        STM32L Bootloader
// Description:    Project settings
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Use Configuration Wizard in Context Menu
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ================================================= SYSTEM ====================================================
//    <h>System

//     <o>Core Frequency [MHz] 
//        <24=> 24 MHz
#define CORE_FREQUENCY_MHZ                        (24)
#define CORE_FREQUENCY_HZ                            (CORE_FREQUENCY_MHZ * 1000000UL)

//     <o>HSE Quartz Frequency [MHz] 
//        <12=> 12 MHz <8=> 8 MHz
#define HSE_FREQUENCY_MHZ                            (12)

//    <o>Interrupts Stack size [bytes] <128-65536>
//    <i> Defines the stack size for all interrupts (incl. nested)
//    <i> Minimum: 128
#define INTERRUPTS_STACK_SIZE_BYTES                    (512)

//    </h>

// ============================================== APPLICATION ==================================================
//    <h>Application

//    <s.32>Application Name
#define APPLICATION_NAME                            "******"

//    <s.32>CPU Chip Name
#define CPU_CHIP_NAME                                "STM32L151RCT6A"

//    <h>Bootloader version:
//    <o>Major
#define BOOTLOADER_VERSION_MAJOR                    (1)

//    <o>Minor
#define BOOTLOADER_VERSION_MINOR                    (0)

//    <o>Build
#define BOOTLOADER_VERSION_BUILD                    (1)

//    </h>

//    <h>Hardware version:
//    <o>Major
#define HARDWARE_VERSION_MAJOR                        (1)

//    <o>Minor
#define HARDWARE_VERSION_MINOR                        (2)

//    </h>

//    </h>


.........

 

Вот так выглядит доступ к этим настройкам этого h-файла в Keil:

 

post-2831-1525349628_thumb.jpg

 

 

 

 

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

Причем, не реализуется просто доступ по некому абстрактному индексу в неком мифическом массиве, а по названию, т. е. через соотв. функции, чтобы ибежать непреднамеренно порчи "чужих" параметров - "реестр" нужно защищать, это вам не винда ;)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Параметры не константные, все разные, есть настройки сохраняемые во флеш, есть текущие параметры работы которые можно только прочитать, и другие типы регистров (виртуальные, для доступа к одним и тем же данным разными способами). Такое описание позволяет предоставить доступ ко всем параметрам, через командный интерфейс по USART, через CAN и тд. Модуль сохранения настроек во флеш тоже пользуется готовым списком.

 

Основной код работающий с этими параметрами использует свои структуры (вот на них и ссылаются регистры) и не знает про массив regfile. Но в некоторых местах хочется попользоваться описанием регистров, для форматированного вывода с указанием единиц измерения например. Вот вопрос как получить ссылку на нужный регистр. Чтобы и в run time не делать много работы (ведь регистр там будет нужен всегда один и тот же), и в то же время не поломать ссылку когда измениться порядок/состав массива regfile.

 

То есть сейчас у меня в коде вот так.

 

printf("R %4e (Ohm)" EOL, &pm.const_R);

 

А хочется вот так.

 

reg_print_fmt(REG_pm_const_R);

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А точка в ваших определениях регистров всегда будет одна? Хотя наверное можно сделать и с несколькими.

 

Нбросал пример на Х макросах:

#define REG_LIST                                    \
    X(hal, USART_baud_rate,        "",    "%i",    REG_CONFIG, NULL) \
    X(hal, PWM_freq_hz,            "Hz",    "%i",    REG_CONFIG, NULL) \
    X(hal, PWM_dead_time_ns,        "ns",    "%i",    REG_CONFIG, NULL) \
    X(hal, ADC_reference_voltage,        "V",    "%3f",    REG_CONFIG, NULL) \
    X(hal, ADC_current_shunt_resistance,    "Ohm",    "%4e",    REG_CONFIG, NULL) \
...
    X(pm, probe_speed_ramp,    "rad/s",    "%2f",    REG_CONFIG, NULL) \
    X(pm, probe_speed_ramp, "_rpm",    "rpm",    "%2f",    REG_NORMAL, &reg_proc_rpm) \
    
#define X(a, b, c, d, e, f) REG((a ## . ## b), (c), (d), (e), (f)),
const reg_t        regfile[] = {
    REG_LIST
};
#undef X

#define X(a, b, c, d, e, f) REG_ ## a ## _ ## b,
typedef enum {
    REG_LIST
} regfile_enum;
#undef X

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Причем, не реализуется просто доступ по некому абстрактному индексу в неком мифическом массиве, а по названию, т. е. через соотв. функции, чтобы ибежать непреднамеренно порчи "чужих" параметров - "реестр" нужно защищать, это вам не винда ;)

Как примерно хотя бы выглядит модуль Settings, не могли бы поделиться? И как обеспечивается защита?

 

И еще вопрос вдогонку.

Допустим, в StaticSettings у меня есть настройка #define FW_REV "v.1.1".

Где-то должен быть, например, const char *rev = FW_REV

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

Прошу прощения за нубские вопросы.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Как примерно хотя бы выглядит модуль Settings, не могли бы поделиться?

Он написан на плюсах и представляет собой набор методов для доступа к нужным параметром.

Список методов в разных проектах различный, но основной принцип неизменный.

В конструкторе - чтение сохраненных параметров. Если они "битые" (проверяется контрольная сумма), то восстанавливаются параметры по-умолчанию.

Сами рабочие параметры хранятся в ОЗУ, при сохранении они переписываются в энергонезависимую память через отдельную функцию/метод отложенно или сразу при изменении параметра (зависит от проекта).

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

 

И как обеспечивается защита?

Один метод = один параметр. (в терминологии С++ "метод", в терминологии С - функция).

Внутри реализации конкретного метода проверяются входные данные.

Более сложные случае реализуются несколько иначе.

 

И еще вопрос вдогонку.

Допустим, в StaticSettings у меня есть настройка #define FW_REV "v.1.1".

Где-то должен быть, например, const char *rev = FW_REV

Сами параметры хранятся естественно в виде одной большой структуры (struct), т.е. никаких элементов некого мифического массива.

Доступ к полям этой структуры ИСКЛЮЧИТЕЛЬНО через методы (функции).

 

В StaticSettings я объявляю рабочие характеристики проекта, которые в процессе работы никогда не меняются, а также даю значения параметрам по-умолчанию (default) для модуля Settings.

 

Т.е. на голом С я бы создал в Settings.c ОДИН экземпляр структуры всех параметров (в ОЗУ) с квалификатором static, разумеется.

В Settings.h объявил бы кучу функций с ОСМЫСЛЕННЫМИ именами.

 

Где вы обычно такую информацию храните?
Это не принципиально, но чаще всего в EEPROM памяти, если нет, то во FLASH.

 

в main.c или какой-то отдельный файл, в котором организуется хранение системных констант в памяти?

Нет, все это сделано внутри файла Settings.cpp

 

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

 

Все остальные части (у меня модули) проекта понятия не имеют о существовании некого Setting (равно как и других модулей), т.е. Settings.hpp инклудится только в одном месте и виден только классу Application.

А для доступа к методам класса Settings используются "делегаты", а Application лишь связывает модули вместе, точнее связывает их делегаты.

Впрочем, это уже совсем другая история и к этой теме не имеет отношения.

 

зы. Я описал чисто свой способ реализации этой задачи - мне ТАК проще. Поэтому прошу не разводить на базе моих выкладок очередной холивар на тему C vs C++.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Функции доступа я тоже мог бы написать, но в этом нет смысла, доступ я получаю напрямую к переменным (pm.const_R). А регистр мне нужно найти для того, чтобы вытащить дополнительную информацию об этой переменной, формат вывода и единицы измерения.

 

Пока только вот такое решение вижу, это поиск по строке в run time. Недостатки, медленно, некрасиво, и ошибки (опечатка в строке) буду выявлены только при выполнении этого кода.

 

reg_print_fmt(reg_search_by_name("pm.const_R"));

 

А точка в ваших определениях регистров всегда будет одна? Хотя наверное можно сделать и с несколькими.

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

 

Точка там не от того, что так захотелось, это же поля структуры. Бывают еще и скобки от индексации массивов. Но суть понятна, объявить список отдельным макросом и развернуть его два раза.

Изменено пользователем amaora

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В своих проектах я использую такой файл и называю StaticSettings.h

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

.....и как только над проектом начинают одновременно работать >=2 программиста, начнутся проблемы с совместной модификацией этого мега-файла настроек. :laughing:

А когда число программистов >=4 начинается вообще полная $опа.

Лучше разбивать настройки по отдельным файлам., принадлежащим разным программным модулям. Естественно - речь о локальных настройках. Глобальных много не должно быть и они должны быть в одном файле. Вобщем - настройки надо делить на группы, по функционалу и не класть всё в одну кучу. И сопровождение будет проще и модификации и использование в других проектах.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Лучше разбивать настройки по отдельным файлам., принадлежащим разным программным модулям. Естественно - речь о локальных настройках. Глобальных много не должно быть и они должны быть в одном файле. Вобщем - настройки надо делить на группы, по функционалу и не класть всё в одну кучу. И сопровождение будет проще и модификации и использование в других проектах.

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

Общие константные настройки остались в одном файле, поскольку Keil умеет нормально отображать такие файлы (см. скриншот выше).

Так получает удобнее и нагляднее видеть все на одной странице экрана, все разбито по группам, "лишние" настройки пр нужде "сворачиваются".

К тому же приучил себя и многих коллег к SVN, это дисциплинирует и приучает к порядку :)

 

А вот привязку пинов в новых проектах уже разнес по соотв. модулям. Это выходит проще (пример):

class PowerController : public Module
{
............

private:

............

    class Thread : public OS::Thread<PowerController, POWER_CONTROLLER_MODULE_STACK_SIZE>
    {
    public:

        inline void powerEnable() { pinPowerEnable.setToHigh(); }
        inline void powerDisable() { pinPowerEnable.setToLow(); }
............
        
    private:

        virtual void initialize();
        virtual void body();

    private:

        Hardware::DigitalOutputPin<PA15>     pinPowerEnable;
        Hardware::DigitalOutputPin<PC13>     pinEnable12V;
        Hardware::DigitalOutputPin<PC6>     pinSlaveEnable;
    
............
    
    } thread;

};

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

К тому же приучил себя и многих коллег к SVN, это дисциплинирует и приучает к порядку :)

SVN хорошо, когда каждый работает над своей группой файлов и общих файлов (модифицируемых разными людьми) как можно меньше.

Потому, что например когда Петя пишет/отлаживает модуль SPI-FLASH и по ходу работы правит ключи ему принадлежащие, в этот момент Вася закончил отладку CAN и фиксит его на SVN (со своей версией общих файлов). А Петя уже поменял ряд констант в общем файле и не факт, что окончательно (а может у него вообще несколько вариантов драйвера с разными алгоритмами работы (ногодрыг/прерывания/DMA/безвестная_либа_надыбанная_в_нете). И он тестит все эти варианты. И теперь ему ещё нужно накатить все изменения с SVN на все эти варианты (каждый со своими изменениями в общих файлах!).

А рядом с Васей сидит Коля, который пишет прикладной уровень рабочего протокола обмена (тоже внеся свои изменения в общие файлы). Т.е. - писал, пока его не прервал, Александр Иванович из ГИП-ов, прибежавший в мыле от Заказчика, когда у того вылез очередной Колин баг и система (с нашими девайсами) встала колом. И Коля бросает текущую задачу и ищет баг. И после того как он смастырит костыль, фиксящий данный баг, ему надо вернуться к прерванной задаче, не забыв накатить все изменения общих файлов и разрешив все коллизии накопившиеся за это время из-за своих, Петиных и Васиных commit-ов в SVN. А ведь кроме Александра Ивановича есть и другие ГИП-ы, возможно более авторитетные, в связи с чем уровень рекурсии Коли может быть существенным.

Вобщем - получается куча лишней пустой работы. :laughing:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вобщем - получается куча лишней пустой работы. :laughing:

Такая ситуация лишь означает:

- не самое рациональное использование SVN как инструмента,

- недостаточно грамотное проектирование проекта

- неудачное разделение областей ответственности.

 

Но это все решаемо. Обычно, по ходу работ отлаживается этот механизм. Было бы желание ;)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот так сделал ;)

 

cat regfile.c | sed -n 's/^[^#]*\(REG_DEF_\?E\?(.\+)\).*$/\U\1/p' | sed 's/REG_DEF(\([^, ]\+\) *,.\+)/ID_\1,/;s/REG_DEF_E(\([^, ]\+\) *,[^,]*\"\([^, ]\+\)\".\+)/ID_\1\2,/;s/\.\|\[\|\]/_/g' > regdefs.h

...

enum {
#include "regdefs.h"
};

...

        reg_print_fmt(&regfile[ID_PM_CONST_LD], 1);
        reg_print_fmt(&regfile[ID_PM_CONST_LQ], 1);

 

То есть генерирую список констант по исходнику с объявлением массива, и делаю из этого enum.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот так сделал ;)

 

cat regfile.c | sed -n 's/^[^#]*\(REG_DEF_\?E\?(.\+)\).*$/\U\1/p' | sed 's/REG_DEF(\([^, ]\+\) *,.\+)/ID_\1,/;s/REG_DEF_E(\([^, ]\+\) *,[^,]*\"\([^, ]\+\)\".\+)/ID_\1\2,/;s/\.\|\[\|\]/_/g' > regdefs.h

...

enum {
#include "regdefs.h"
};

...

        reg_print_fmt(&regfile[ID_PM_CONST_LD], 1);
        reg_print_fmt(&regfile[ID_PM_CONST_LQ], 1);

 

То есть генерирую список констант по исходнику с объявлением массива, и делаю из этого enum.

Что это за (очень мягко говоря) фигня??? Вы добиваетесь монопольного владения проектом и быть незаменимым сотрудником? Тогда для этого есть куча других способов сломать проект и мозги его поддерживающих в будущем :wacko:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну, здесь я обычно не задаю вопросов по своей основной работе, это пока всего лишь проект который делаю один в свободное время. Но все таки у меня нет цели написать непонятный код, наоборот я пытаюсь уменьшить его сложность. А подобные задачи у меня возникали уже не раз, и ничего лучше генерации кода сторонними средствами (sed в данном случае) я не нашел. Иначе получится код в котором внося некоторое изменение нужно подправить еще вот здесь и вот там, и следить чтобы каждая часть соответствовала другой.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...