И как этим пользоваться?
В предыдущей статье создали класс для работы с портами ввода-вывода, проверили. И что дальше? Зачем это все запихивать в класс?
Возьмем для примера простенький опрос кнопок:
Для этой схемы в простейшем случае опрос будет выглядеть так:
int GetKey()
{
volatile uint32_t* addr = reinterpret_cast<uint32_t*>(GPIOA_IDR);
uint32_t ret_val = *addr;
return ret_val & 0x0F;
}
Но, если в схеме поменять порты, подсоединенные к кнопкам, то придется менять и функцию опроса. И так в каждом проекте. Это не всегда удобно. Хочется один раз написать, протестировать и пользоваться.
Перепишем эту функцию под ранее созданный класс:
int GetKey(Pin* p0, Pin* p1, Pin* p2, Pin* p3)
{
int ret_val = p0->Get() + (p1->Get() << 1) + (p2->Get() << 2) + (p3->Get() << 3);
return ret_val;
}
Остается в главной программе инициализировать порты и передать в функцию:
...
using namespace STM32F1xx;
Pin key0('a', 0);
Pin key1('a', 1);
Pin key2('a', 2);
Pin key3('a', 3);
...
int main()
{
key0.ModeInput();
key1.ModeInput();
key2.ModeInput();
key3.ModeInput();
int key_code = GetKey(&key0, &key1, &key2, &key3);
...
return 0;
}
А где же интерфейсы?
А теперь представим, законились контроллеры серии f10x, но есть куча f030. По производительности и количеству выводов хватает, только надо хедер поменять для функции GetKey или воспользоваться… #ifdef. Сделать глобальный заголовочный файл, в котором прописть тип используемого контроллера (что то типа #define STM32F030) и нагородить кучу дефайнов. Нет, не для этого создавались языки высокого уровня, что бы путаться в макросах!
Пойдем другой дорогой. Создадим класс, в котором перечислим виртуальные методы, необходимые нам по жизни для работы с портами:
#pragma once
class iPin
{
public:
virtual void ModeInput() = 0;
virtual void ModeAnalogInput() = 0;
virtual void ModeInputPulled() = 0;
virtual void ModeOutput() = 0;
virtual void ModeOutputOpenDrain() = 0;
virtual void Set(bool st) = 0;
virtual bool Get() = 0;
virtual void Reverse() { Set(!Get());}
void On() { Set(true); }
void Off() { Set(false); }
};
(те методы, которые приравнены 0, должны быть определены в производном классе!)
и будем его использовать как базовый в классе Pin:
...
#include "iPin.h"
...
class Pin : public iPin
...
тогда функция GetKey чуть изменится:
int GetKey(iPin* p0, iPin* p1, iPin* p2, iPin* p3)
{
int ret_val = p0->Get() + (p1->Get() << 1) + (p2->Get() << 2) + (p3->Get() << 3);
return ret_val;
}
Теперь нам любой контроллер нипочем! Даже если это шинный расширитель, работающий по SPI или I2C. Последовательные интерфейсы рассмотрим в следующих статьях.
И что дальше?
Дальше надо оформить класс для работы с системным таймером. Но это уже в следующей публикации.
COKPOWEHEU
Это вы изобрели digitalWrite / digitalRead из Ардуины? И вместо одного такта на запись регистра тратите десяток на разыменования указателей, сравнения и т.п.
Поскольку назначение выводов во время работы кода не меняется, их можно задефайнить:
И соответственно макросы ( ):
Не спорю, макросы выглядят страшно, но менять их надо только при переходе на другое семейство. Точнее, даже не менять, а переписывать, поскольку там другая логика работы портов
lamerok
Да вроде на разыменовывание не тратится ничего… если вы про это...
то все можно заменить на одну строчку без накладных.
Здесь доступ и поход в таблицу виртуальных методов отнимает кучу времени. Вместо обычного полиморфизма можно использовать статический, чтобы накладных не было, к тому же это позволит обойтись без макросов. Ну и так по мелочам, вместо указателей можно ссылки передавать, будет уверенность, что все проинициализировано.
Проблема еще в том, что порты считывается в разный момент времени… а желательно бы одновременно состояние порта считывать, а то пока p0.Get() считается, уже p3.Get() может поменяться… — это конечно, возможно, надумано, но вдруг на низкой частоте все работает или в окружении Операционной системы, прервали вас на 40 мс высокоприоритетной задачей и, вуаля, считали неправильное нажатие кнопки.
Еще возможный недочет в том, что при передаче портов легко спутать их последовательность (так как тип один и тот же), и если этот метод будет вызваться несколько раз то вероятность спутывания увеличивается каждый раз. Поэтому и ссылки желательно убрать тоже либо в список
И потом в одном месте её проинициализировать. Если и будет ошибка, то исправить её можно быстро в одном месте, а не бегать по коду и искать, где вызывается метод GetKey()
Да еще добавлю. Так делать не совсем хорошо:
Принцип Лисков нарушается, потом можно очень удивить кого-то, кто будет использовать этот класс и переопределять этот метод. Когда один пин будет вести по одному, а другой вдруг станет вести по другому. Лучше его сделать либо чисто виртуальным, либо не виртуальным вообще.
belav Автор
да можно заменить, но скорость работы от этого не вырастет. про наглядность — у каждого свой вкус)
а Вы замеряли сколько тактов теряется? при максимальной оптимизации я не заметил особой разницы.
здесь абсолютно согласен, это старая дурацкая привычка) буду исправляться и по тихоньку иссправлять
это был как пример. конечно, лучше списком или структурой или подобным.
хорошее замечание.
спасибо.
belav Автор
Это я привел пример только для пинов. Без прерываний. Боюсь спросить как будет тотже уарт выглядеть на дефайнах. Особенно инетересен обработчик.
Макросы тоже не плохи, раньше пользовался подобным. Но вот предложил другой подход.
BigBeaver
А че там обрадаьывать-то? Если у нас есть DMA, то работа с уартом не сильно отличается от работы с массивом в памяти. Ну а сконфигурировать его макросами не составляет большого труда.
Ну и в принципе на уровне железа макросы не сильно отличаются от инлайнов. Делать же набор функций чтобы дергать ножкой это правда жир, кушающий и такты и стек. Можно, конечно, надеяться, что компилятор соптимизирует.
belav Автор
В теории да. У меня как раз проблема при использовании DMA — как организовать циклический буфер? Как раз в библиотеке ModBus хочется прикрутить DMA.
но и контроллеры стали жирнее.
так ведь для этого и существуют компиляторы.
BigBeaver
У stm32, вроде, есть Circular Mode. (1.1.8 Transfer types)
COKPOWEHEU
Использование примерно такое:
Если интересно, выложу и макросы.
Ну, если бы вы выложили полный проект, готовый для компиляции, можно было бы и сравнить. Мой вариант на макросах разворачивается в 1 ассемблерную команду для GPO_ON / GPO_OFF, примерно столько же на проверки.
belav Автор
Да, конечно интересно. Всегда интересно увидеть разные подходы.
"Ну, если бы вы выложили полный проект, готовый для компиляции..."
Это относилось к виртуальным методам. Бесспорно, макросам такой подход в проигрыше по производительности. Навскидку, будет 6...7 тактов. За все приходится платить.
COKPOWEHEU
Вот реализация UART для stm32l151 если и правда интересно. Изначально задумывалось только как отладочный интерфейс, поэтому над оформлением особо не заморачивался:
github.com/COKPOWEHEU/stm32modules/blob/master/L151/uart.h