Высококачественная голая металлическая абстракция

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

Обычно каждое производство микросхем обеспечивает заголовок C следующим образом:

//Following structure is POD so we can rely on its memory layout
struct Periphery{
volatile uint32_t reg1;
volatile uint32_t reg2;
};

#define PERIPHERY0BASE 0x000000ab //address where does registers of periphery start
#define PERIPHERY1BASE 0x000000cd
static Periphery* PERIPHERY0 = (Periphery*)(PERIPHERY0BASE );
//or
#define PERIPHERY1 (Periphery*)(PERIPHERY1BASE )

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

template<int addr>
PeripheryDriver{
static inline void doSomething(int foo){
(PERIPHERY0*)(addr)->reg1 = foo;
}
}
//typedefs for different peripheries of the same type
typedef Periphery<PERIPHERY0BASE> Periphery0;
typedef Periphery<PERIPHERY1BASE> Periphery1;

который затем будет использоваться в независимом от платформы модуле, например:

template<class P>
class DriverUser{
DriverUser(){
P::doSomething(0x00);
}
};

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

//normally would be in PCB specific header
typedef Pin<Port0, Pin0> Pin0;
typedef Pin<Port1, Pin7> Pin1;

//application specific code
typedef Pin0 TxPin;
typedef Pin1 RxPin;

void main(){
SoftwareUart<TxPin,RxPin> my_uart(115200);
my_uart.send("hello world");
}

Тогда можно реализовать SoftwareUart, который полностью независим от платформы, но запись High в TxPin будет такой же эффективной, как и в ассемблере, без использования макросов.

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

Мой вопрос: может ли быть какой-нибудь обходной путь, который сохранит эффективность? (Кроме переписывания определений регистров)
в C ++ 11 я бы подумал использовать constexpr для создания функции, которая получала бы адрес статических структур, которые затем могли бы использоваться в качестве параметра шаблона. к сожалению, мы не можем рассчитывать на доступность C ++ 11. Есть идеи? Нужно ли нам изменять / записывать наши собственные определения регистров?

2

Решение

Извините, но трудно понять, что вам на самом деле нужно. Если я вас правильно понимаю, вам нужен универсальный способ получить смещение в конкретной структуре из указателя, предоставленного сторонними заголовками (при условии, что вы знаете выравнивание структуры).
Если вы утверждаете, что можете достичь своей цели с помощью функций constexpr в C ++ 11, попробуйте использовать шаблоны для C ++ 03.

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

template <typename T, T ptr, unsigned TAlignmentMask>
struct AddrRetriever
{
static const int value = (int)ptr & TAlignmentMask;
}

А затем используйте:

typedef Periphery<
AddrRetriever<
volatile void*, // use the type of the pointer vendor provides
PTR_FROM_VENDOR,
KNOWN_ALIGNMENT_MASK>::value
> Periphery0;

Как примечание, я хотел бы рекомендовать читать Практическое руководство по Bare Metal C ++. Он даст вам некоторые идеи по реализации универсальных асинхронных таймеров, UART и других периферийных устройств, как вы хотите.

1

Другие решения

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

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

Чтобы еще больше повысить уровень абстракции, вы можете создать абстрактный базовый класс «UART», который содержит универсальные функции для всех UART, такие как настройка скорости передачи данных и формата связи, настройка аппаратного подтверждения связи при необходимости, отправка, получение и т. Д. Все ваши драйверы UART должны будут наследовать интерфейс функции базового класса.

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

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

0