C ++ классы для абстракции выводов ввода / вывода

13

Я ищу абстракции C ++ для аппаратных точек ввода / вывода или выводов. Такие вещи, как in_pin, out_pin, inout_pin, может быть open_collector_pin и т. Д.

Я, конечно, могу сам придумать такой набор абстракций, поэтому я не ищу ответы типа «эй, ты мог бы сделать это таким образом», а скорее «посмотри на эту библиотеку, которая использовалась в этом, этом и этот проект'.

Google не обнаружил ничего, возможно, потому что я не знаю, как другие назвали бы это.

Моя цель состоит в том, чтобы создать библиотеки ввода-вывода, которые основаны на таких точках, но также обеспечивают такие точки, поэтому было бы легко, например, подключить HD44780 LCd либо к выводам ввода-вывода микросхемы, либо к I2C (или SPI) Расширитель ввода / вывода или любая другая точка, которой можно каким-либо образом управлять, без каких-либо изменений в классе LCD.

Я знаю, что это на грани электроники / программного обеспечения, извините, если это не принадлежит здесь.

@leon: проводка Это большой пакет программного обеспечения, мне нужно будет присмотреться. Но, похоже, они не используют абстракцию булавки, как я хочу. Например, в реализации клавиатуры я вижу

digitalWrite(columnPins[c], LOW);   // Activate the current column.

Это подразумевает, что есть одна функция (digitalWrite), которая знает, как записать на вывод ввода / вывода. Это делает невозможным добавление нового типа вывода ввода / вывода (например, на MCP23017, поэтому его необходимо записать через I2C) без перезаписи функции digitalWrite.

@Oli: Я погуглил пример Arduino IO, но похоже, что он использует тот же подход, что и библиотека Wiring:

int ledPin = 13;                 // LED connected to digital pin 13
void setup(){
    pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}
Воутер ван Оойен
источник
О каком микроконтроллере мы говорим?
Майенко
Это не имеет значения; для конкретного микроконтроллера io контакты этого uC будут реализовывать соответствующие интерфейсы. Но это для C ++, так что подумайте о 32-битных чипах, таких как ARM, Cortex и MIPS.
Wouter van Ooijen
1
Я никогда не использовал один, но разве Arduino не абстрагирует все булавки, как это? Вы можете (или не можете) получить некоторую полезную информацию, глядя на то, как они сделали вещи.
Оли Глейзер
1
А что касается переписывания функции digitalWrite - посмотрите на «перегрузку» в C ++. Несколько минут назад я написал перегруженную функцию digitalWrite для платы расширения ввода-вывода для Arduino. Пока вы используете разные параметры (я заменил первый «int» на «struct»), он выберет ваш digitalWrite вместо предпочтительного по умолчанию.
Majenko
1
Я говорил на встрече C ++ в Берлине о моей работе над этой темой. Его можно найти на youtube: youtube.com/watch?v=k8sRQMx2qUw С тех пор я перешел на немного другой подход, но разговор все еще может быть интересным.
Воутер ван Оойен

Ответы:

3

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

Почему С ++?

  1. Компилятор может использовать динамическое вычисление выражения размера слова. C распространяется на Int. Вашу байтовую маску / сдвиг можно сделать быстрее / меньше.
  2. Встраивание.
  3. Операции с шаблонами позволяют вам изменять размер слова и другие свойства с безопасностью типов.
Джим Фриммель
источник
5

Позвольте мне бесстыдно подключить мой проект с открытым исходным кодом https://Kvasir.io . Часть Kvasir :: Io предоставляет функции управления выводами. Сначала вы должны определить свой пин с помощью Kvasir :: Io :: PinLocation следующим образом:

constexpr PinLocation<0,4> led1;    //port 0 pin 4
constexpr PinLOcation<0,8> led2;

Обратите внимание, что на самом деле это не использует RAM, потому что это переменные constexpr.

Во всем коде вы можете использовать эти положения выводов в функциях «фабрики действий», таких как makeOpenDrain, set, clear, makeOutput и так далее. «Фабрика действий» фактически не выполняет действие, а возвращает Kvasir :: Register :: Action, который можно выполнить с помощью Kvasir :: Register :: apply (). Причина этого заключается в том, что apply () объединяет переданные ему действия, когда они воздействуют на один и тот же регистр, что дает выигрыш в эффективности.

apply(makeOutput(led1),
    makeOutput(led2),
    makeOpenDrain(led1),
    makeOpenDrain(led2));

Поскольку создание и объединение действий выполняется во время компиляции, это должно привести к тому же коду ассемблера, что и к типичному эквиваленту, закодированному вручную:

PORT0DIR |= (1<<4) | (1<<8);
PORT0OD |= (1<<4) | (1<<8);
odinthenerd
источник
3

Проект Wiring использует такую ​​абстракцию:

http://wiring.org.co/

и компилятор написан на C ++. Вы должны найти множество примеров в исходном коде. Программное обеспечение Arduino основано на проводке.

Леон Хеллер
источник
ответил в теле вопроса
Wouter van Ooijen
2

В C ++ можно написать класс, чтобы вы могли использовать порты ввода-вывода, как если бы они были переменными, например

  PORTB = 0x12; / * Запись в 8-битный порт * /
  if (RB3) LATB4 = 1; / * Чтение одного бита ввода / вывода и условно запись другого * /

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

  if (IO_PORTS.bbRB3) IO_PORTS.bbLATB4 = 1;

который в свою очередь будет преобразован во что-то вроде:

  if (!! (PORTB & 8)) (1? (PORTB | = 16): (PORTB & = ~ 16));

Компилятор должен иметь возможность замечать константное выражение в операторе?: И просто включать «истинную» часть. Можно было бы уменьшить количество свойств, создаваемых макрокомандой, до чего-то вроде:

  if (IO_PORTS.ppPORTB [3]) IO_PORTS.ppPORTB [4] = 1;

или

  if (IO_PORTS.bb (addrPORTB, 3)) IO_PORTS.bbPORTB (addrPORTB, 4) = 1;

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

Я ни в коем случае не хочу подразумевать, что использование портов ввода-вывода, как если бы они были переменными, - это всегда хорошая идея, но, поскольку вы упоминаете C ++, это полезная уловка. Моим собственным предпочтением в C или C ++, если совместимость с кодом, использующим вышеупомянутый стиль, не требуется, вероятно, будет определять некоторый тип макроса для каждого бита ввода-вывода, а затем определять макросы для «readBit», «writeBit», «setBit» и «clearBit» при условии, что аргумент идентификации битов, передаваемый этим макросам, должен быть именем порта ввода / вывода, предназначенного для использования с такими макросами. Например, приведенный выше пример будет записан как

  if (readBit (RB3)) setBit (LATB4);

и переводится как

  if (!! (_ PORT_RB3 & _BITMASK_RB3)) _PORT_LATB4 | = _BITMASK_LATB4;

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

Supercat
источник
3
Цитата из вопроса: «Я не ищу« эй, вы могли бы сделать это таким образом »типа ответов» ...
Wouter van Ooijen
Я думаю, я не совсем понимаю, что вы ищете. Конечно, я ожидал бы, что многим людям, которые интересуются классами для реконструкции выводов ввода / вывода, также было бы интересно узнать, что используя свойства, можно сделать код, написанный для одного стиля ввода / вывода, практически любым другим. Я использовал свойства, чтобы сделать заявление вроде "LATB3 = 1;" отправить запрос ввода-вывода в поток TCP.
суперкат
Я постарался прояснить свой вопрос: я хочу иметь возможность размещать новые типы выводов ввода-вывода, не переписывая код, использующий выводы ввода-вывода. Вы пишете о пользовательских преобразованиях типов и операторах присваивания, которые, безусловно, интересны, я использую их все время, но не решение моей проблемы.
Воутер ван Оойен
@Wouter van Ooijen: Какие «новые типы выводов ввода / вывода» вы ожидаете? Если исходный код написан с синтаксисом вроде «if (BUTTON_PRESSED) MOTOR_OUT = 1;», я ожидаю, что практически для любого механизма, с помощью которого процессор может считывать элемент управления кнопки или мотора, можно написать библиотеку, поэтому приведенный выше источник код включит двигатель, если кнопка нажата. Такая библиотека может быть не самым эффективным способом включения двигателя, но она должна работать.
суперкат
@Wouter van Ooijen: Можно было бы повысить эффективность, если бы требовалось, чтобы исходный код вызывал макрос UPDATE_IO () или UPDATE_INPUTS () перед чтением любого ввода, и выполнял UPDATE_IO () или UPDATE_OUTPUTS () через некоторое время после любого вывода, с семантика того, что входные данные могут быть выбраны либо в коде, который их читает, либо в предыдущем вызове UPDATE_INPUTS () / UPDATE_IO (). Аналогично, выходы могут происходить немедленно или быть отложенными. Если ввод / вывод реализован с использованием чего-то вроде регистра сдвига, отсроченные действия позволят объединить несколько операций.
Суперкат
1

Если вы ищете что-то действительно классное для абстрагирования аппаратного обеспечения и уверены в своих навыках C ++, попробуйте этот шаблон:

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

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

Франсиско Родригес
источник
За годы, прошедшие с этого поста, я работал над отдельными «классами» для pin_in, pin_out, pin_oc и pin_in_out. Для оптимальной производительности (размер и скорость) я использую статические классы, передаваемые в качестве параметров шаблона. Я говорил об этом на Встрече C ++ в Берлине
Wouter van Ooijen