Как стать автором
Обновить

Комментарии 13

Это вы изобрели digitalWrite / digitalRead из Ардуины? И вместо одного такта на запись регистра тратите десяток на разыменования указателей, сравнения и т.п.
Поскольку назначение выводов во время работы кода не меняется, их можно задефайнить:
#define BTN1 A, 4, 1, GPIO_PULL //PA4, активный уровень лог.1, подтяжка вниз
GPIO_config( BTN1 );
if( GPI_ON( BTN1 ) ){...}

И соответственно макросы ( ):
#define concat2(a,b,...)  a##b
#define concat3(a,b,c,...)  a#b#c

#define marg1(a,...)  a
#define marg2(a,b,...)  b
#define marg3(a,b,c,...)  c
#define marg4(a,b,c,d,...)  d
#define GPIO(x) concat2(GPIO,x)

#define _GPIO_CONFIG(port, bit, letter, mode) \
  do{\
    uint32_t temp = GPIO##port -> concat2(CR,letter);\
    temp &=~(0b1111<<(((bit)&0x07)*4)); \
    temp |= ( mode <<(((bit)&0x07)*4)); \
    GPIO##port -> concat2(CR,letter) = temp; \
  }while(0)

  
#define GPIO_mode(port, bit, mode)\
  do{ \
    if(bit<8)_GPIO_CONFIG(port,bit,L,mode); \
      else _GPIO_CONFIG(port,bit-8,H,mode); \
  }while(0)
#define GPIO_config(descr) \
  do{ \
    GPIO_mode(marg1(descr), marg2(descr), marg4(descr)); \
    if(marg4(descr) == GPIO_PULL){ \
      if(marg3(descr))GPIO(marg1(descr))->BRR = (1<<marg2(descr)); \
        else GPIO(marg1(descr))->BSRR = (1<<marg2(descr)); \
    } \
  }while(0)

#define GPO_ON(descr) \
  do{ \
    GPIO(marg1(descr))->BSRR = (1<<marg2(descr)<<((1-marg3(descr))*16)); \
  }while(0)
#define GPO_OFF(descr) \
  do{ \
    GPIO(marg1(descr))->BSRR = (1<<marg2(descr)<<((marg3(descr))*16)); \
  }while(0)
#define GPO_T(descr) \
  do{ \
    GPIO(marg1(descr))->ODR ^= (1<<marg2(descr)); \
  }while(0)
#define GPI_ON(descr)  ((GPIO(marg1(descr))->IDR & (1<<marg2(descr)))==(marg3(descr)<<marg2(descr)))
#define GPI_OFF(descr) ((GPIO(marg1(descr))->IDR & (1<<marg2(descr)))!=(marg3(descr)<<marg2(descr)))

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

Да вроде на разыменовывание не тратится ничего… если вы про это...


int GetKey()
{
volatile uint32_t* addr = reinterpret_cast<uint32_t*>(GPIOA_IDR);
uint32_t ret_val = *addr;
return ret_val & 0x0F;
}

то все можно заменить на одну строчку без накладных.


inline int GetKey()
{
  return (*reinterpret_cast<volatile uint32_t*>(GPIOA_IDR)) & 0x0F;
}

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


auto GetKey(const iPin& p0, const iPin& p1, const iPin& p2, const iPin& p3)
{
    const auto ret_val = p0.Get() | (p1.Get() << 1) | (p2.Get() << 2) | (p3.Get() << 3);
    return ret_val;
}

Проблема еще в том, что порты считывается в разный момент времени… а желательно бы одновременно состояние порта считывать, а то пока p0.Get() считается, уже p3.Get() может поменяться… — это конечно, возможно, надумано, но вдруг на низкой частоте все работает или в окружении Операционной системы, прервали вас на 40 мс высокоприоритетной задачей и, вуаля, считали неправильное нажатие кнопки.


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


using KeyPins = PinsList<p1,p2,p3,p4>;
//.. либо в структуру, если с темплейтами неохота заморачиваться..
struct KeyPins
{
  iPin& p1;
  iPin& p2;
  iPin& p3;
  iPin& p4;
} 

И потом в одном месте её проинициализировать. Если и будет ошибка, то исправить её можно быстро в одном месте, а не бегать по коду и искать, где вызывается метод GetKey()


Да еще добавлю. Так делать не совсем хорошо:


virtual void Reverse() { Set(!Get());}

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

то все можно заменить на одну строчку без накладных.

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

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

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

это был как пример. конечно, лучше списком или структурой или подобным.
Да еще добавлю. Так делать не совсем хорошо:
virtual void Reverse() { Set(!Get());}

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

хорошее замечание.
спасибо.
Это я привел пример только для пинов. Без прерываний. Боюсь спросить как будет тотже уарт выглядеть на дефайнах. Особенно инетересен обработчик.
Макросы тоже не плохи, раньше пользовался подобным. Но вот предложил другой подход.
А че там обрадаьывать-то? Если у нас есть DMA, то работа с уартом не сильно отличается от работы с массивом в памяти. Ну а сконфигурировать его макросами не составляет большого труда.

Ну и в принципе на уровне железа макросы не сильно отличаются от инлайнов. Делать же набор функций чтобы дергать ножкой это правда жир, кушающий и такты и стек. Можно, конечно, надеяться, что компилятор соптимизирует.
А че там обрадаьывать-то? Если у нас есть DMA, то работа с уартом не сильно отличается от работы с массивом в памяти. Ну а сконфигурировать его макросами не составляет большого труда.

В теории да. У меня как раз проблема при использовании DMA — как организовать циклический буфер? Как раз в библиотеке ModBus хочется прикрутить DMA.
Делать же набор функций чтобы дергать ножкой это правда жир, кушающий и такты и стек.

но и контроллеры стали жирнее.
Можно, конечно, надеяться, что компилятор соптимизирует.

так ведь для этого и существуют компиляторы.
У stm32, вроде, есть Circular Mode. (1.1.8 Transfer types)
Использование примерно такое:
UART_init(1,32000000/9600);
...
UART1_puts("Test");
if(UART_rx_count(1)!=0){
    if(UART_getc(1) == 'v'){GPO_T(GLED);}
}

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

Ну, если бы вы выложили полный проект, готовый для компиляции, можно было бы и сравнить. Мой вариант на макросах разворачивается в 1 ассемблерную команду для GPO_ON / GPO_OFF, примерно столько же на проверки.

Да, конечно интересно. Всегда интересно увидеть разные подходы.
"Ну, если бы вы выложили полный проект, готовый для компиляции..."
Это относилось к виртуальным методам. Бесспорно, макросам такой подход в проигрыше по производительности. Навскидку, будет 6...7 тактов. За все приходится платить.

Вот реализация UART для stm32l151 если и правда интересно. Изначально задумывалось только как отладочный интерфейс, поэтому над оформлением особо не заморачивался:
github.com/COKPOWEHEU/stm32modules/blob/master/L151/uart.h
А если копнуть чуть глубже, то окажется что есть отдельные регистры чтения, записи, и атомарной установки контактов порта. И всё что требуется — так научится с ними работать.
Фрагмент кода:
int GetKey()
{
  volatile uint32_t* addr = reinterpret_cast<uint32_t*>(GPIOA_IDR);
  uint32_t ret_val = *addr;
  return ret_val & 0x0F;


Выглядит немного странным: функция возвращает int (знаковое), а return использует uint.
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории