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

Мультиплексирование вывода данных на дисплей с параллельным портом

Время на прочтение 6 мин
Количество просмотров 5.4K
Всего голосов 10: ↑8 и ↓2 +6
Комментарии 39

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

Если Вы выводили в побитном режиме посредством digitalWrite, то конечно, функция весьма медленная из-за множества всяких проверок (да и с режимом обработки прерываний она вольно обращается :-)
Если точно знать, что делать, то можно вот так оформить побитный вывод, который займет считанные машинные циклы (в проекте "нанокомпьютера", откуда этот отрывок, мне была важна именна неприкосновенность прерываний):


void pinWrite(uint8_t pin, uint8_t val) {
  switch (pin) {
    case 3://1 PD3 0x0B
      if (val == 1) {
        asm volatile ("sbi 0x0B, 3 \n"); //HIGH
      }
      else {
        asm volatile ("cbi 0x0B, 3 \n"); //LOW
      }
      break;
    case 4://2 PD4
      if (val == 1) {
        asm volatile ("sbi 0x0B, 4 \n"); //HIGH
      }
      else {
        asm volatile ("cbi 0x0B, 4 \n"); //LOW
      }
      break; 
....
Я использовал макроподстановки типа #define D0_High PORTD |=B00000001, которые делают то же самое, что и предложенный Вами способ.

Нет, не совсем тоже самое.
PORTD |=B00000001 эквивалентно PORTD = PORTD | B00000001, т.е. требуется дополнительная операция.


Это можно проверить, отключив через platform.txt оптимизацию кода:


запись в порт с учетом его состояния


void setup() {
  // put your setup code here, to run once:
  PORTD |= 0x58;
}
void loop() {
  // put your main code here, to run repeatedly:
}

Скетч использует 514 байт (1%) памяти устройства


прямая запись в порт


void setup() {
  // put your setup code here, to run once:
  PORTD = 0x58;
}
void loop() {
  // put your main code here, to run repeatedly:
}

Скетч использует 506 байт (1%) памяти устройства


А sbi и cbi меняют конкретные биты порта, совсем не трогая остальные.

Не буду спорить :) в любом случае параллельный вывод 1 байта быстрее 8-ми выводов бита с логическими операциями определения какой бит надо выставить.
Команда PORTD |=B00000001 и будет оттранслирована компилятором в SBI, а PORTD |= 0x58 нет, так как затрагивает более одного бита. Во всяком случае, это точно работает с компилятором GCC, но в Arduino, вроде, он и используется.

Спасибо, интересно посмотреть, как g++ будет вести себя в этом случае в различных режимах оптимизации.

Ну, если интересно, то смотрите (правда я пользовался не g++, а gcc, но думаю в данном случае без разницы). Исходный код:
#include <avr/io.h>

int main(void)
{
	PORTD |= 0x01;

	return 0;
}

Вообще без оптимизации (O0):
0000007a <main>:
  7a:	cf 93       	push	r28
  7c:	df 93       	push	r29
  7e:	cd b7       	in	r28, 0x3d	; 61
  80:	de b7       	in	r29, 0x3e	; 62
  82:	8b e2       	ldi	r24, 0x2B	; 43
  84:	90 e0       	ldi	r25, 0x00	; 0
  86:	2b e2       	ldi	r18, 0x2B	; 43
  88:	30 e0       	ldi	r19, 0x00	; 0
  8a:	f9 01       	movw	r30, r18
  8c:	20 81       	ld	r18, Z
  8e:	21 60       	ori	r18, 0x01	; 1
  90:	fc 01       	movw	r30, r24
  92:	20 83       	st	Z, r18
  94:	80 e0       	ldi	r24, 0x00	; 0
  96:	90 e0       	ldi	r25, 0x00	; 0
  98:	df 91       	pop	r29
  9a:	cf 91       	pop	r28
  9c:	08 95       	ret

С любой включенной опцией оптимизации (O1, O2, O3, Os) получается одно и то же:
0000007a <main>:
  7a:	58 9a       	sbi	0x0b, 0	; 11
  7c:	80 e0       	ldi	r24, 0x00	; 0
  7e:	90 e0       	ldi	r25, 0x00	; 0
  80:	08 95       	ret

))))))) Мой результат на 9 минут раньше.

Так мне и лет больше. Зато у меня сравнение разных режимов оптимизации, как Вы и просили, а у Вас только O0 и Os.

Я контроллерами AVR редко пользуюсь, поэтому долго искал, где же у меня лежит среда разработки для них. А ведь еще параллельно и работать приходится.

image

P.S. Оптимизатор не всегда придерживается требуемой стратегии s
(хотя конечно gnu.org дает уклончивый ответ — "except those that often increase code size" :-)


PORTD |= 0x03;

дешевле (по размеру) было бы заменить (4 байта, 4 цикла)


sbi    0x0b, 0
sbi    0x0b, 1

а не: (6 байтов, но 3 цикла)


in    r24, 0x0b
ori    r24, 0x03
out    0x0b, r24 
Тут может быть гораздо более неприятный момент: две интсрукции sbi атомарны (каждая по отдельности) и не чуствительны к изменению в процессе выполнения других битов регистра из какого-нибудь прерывания. Чего нельзя сказать о конструкции in — ori — out. Именно в подобных случаях и приходится периодически контролировать код, генерируемый компилятором.

Так и есть при включенной оптимизации (результаты моей проверки):


PORTD = 0x01;

-O0


  ae:    8b e2           ldi    r24, 0x2B    ; 43
  b0:    90 e0           ldi    r25, 0x00    ; 0
  b2:    21 e0           ldi    r18, 0x01    ; 1
  b4:    fc 01           movw    r30, r24
  b6:    20 83           st    Z, r18

Скрытый текст

-Os


  a6:    81 e0           ldi    r24, 0x01    ; 1
  a8:    8b b9           out    0x0b, r24    ; 11

PORTD |= 0x03;

-O0


  ae:    8b e2           ldi    r24, 0x2B    ; 43
  b0:    90 e0           ldi    r25, 0x00    ; 0
  b2:    2b e2           ldi    r18, 0x2B    ; 43
  b4:    30 e0           ldi    r19, 0x00    ; 0
  b6:    f9 01           movw    r30, r18
  b8:    20 81           ld    r18, Z
  ba:    23 60           ori    r18, 0x03    ; 3
  bc:    fc 01           movw    r30, r24
  be:    20 83           st    Z, r18

-Os


  a6:    8b b1           in    r24, 0x0b    ; 11
  a8:    83 60           ori    r24, 0x03    ; 3
  aa:    8b b9           out    0x0b, r24    ; 11

PORTD |= 0x01;

-O0


  ae:    8b e2           ldi    r24, 0x2B    ; 43
  b0:    90 e0           ldi    r25, 0x00    ; 0
  b2:    2b e2           ldi    r18, 0x2B    ; 43
  b4:    30 e0           ldi    r19, 0x00    ; 0
  b6:    f9 01           movw    r30, r18
  b8:    20 81           ld    r18, Z
  ba:    21 60           ori    r18, 0x01    ; 1
  bc:    fc 01           movw    r30, r24
  be:    20 83           st    Z, r18

-Os


  a6:    58 9a           sbi    0x0b, 0    ; 11
Проверил, заменил макроподстановки на прямой ассемблерный код, время цикла изменилось незначительно: вместо 213 стало 195 мС.
Как то давно для своих проектов использовал такие макросы
#define digitalWriteC(pin,val)\
 if (val) { *((volatile uint8_t *) port_to_output_PGM[digital_pin_to_port_PGM[pin]]) |= (digital_pin_to_bit_mask_PGM[pin]);}\
 else {*((volatile uint8_t *) port_to_output_PGM[digital_pin_to_port_PGM[pin]]) &= ~(digital_pin_to_bit_mask_PGM[pin]);}

#define pinModeC(pin,mode)\
  if (mode == INPUT) { \
    *((volatile uint8_t *) port_to_mode_PGM[digital_pin_to_port_PGM[pin]]) &= ~(digital_pin_to_bit_mask_PGM[pin]);\
    *((volatile uint8_t *) port_to_output_PGM[digital_pin_to_port_PGM[pin]]) &= ~(digital_pin_to_bit_mask_PGM[pin]);\
  } else if (mode == INPUT_PULLUP) {\
    *((volatile uint8_t *) port_to_mode_PGM[digital_pin_to_port_PGM[pin]]) &= ~(digital_pin_to_bit_mask_PGM[pin]);\
    *((volatile uint8_t *) port_to_output_PGM[digital_pin_to_port_PGM[pin]]) |= (digital_pin_to_bit_mask_PGM[pin]);\
  } else {\
    *((volatile uint8_t *) port_to_mode_PGM[digital_pin_to_port_PGM[pin]]) |= (digital_pin_to_bit_mask_PGM[pin]);\
  };

inline uint8_t digitalReadC (uint8_t pin) __attribute__((always_inline));
uint8_t digitalReadC (uint8_t pin)
 {
   if (*((volatile uint8_t *) port_to_input_PGM[digital_pin_to_port_PGM[pin]]) & (digital_pin_to_bit_mask_PGM[pin])) {return HIGH;} else {return LOW;};  
 };


Работают корректно только если номера пинов и портов — константы. GCC всегда транслирует в sbi и cbi если это возможно. При этом Изменений в программе минимум. Всего то нужно использовать digitalWriteC вместо digitalWrite.

Забавно, что SBI/CBI таки не всегда быстрее, хотя и компактнее.

У Константина Чижова есть восхитительный шаблонный класс в его библиотеке mcucpp, который генерит минимально возможную последовательность записи данных в произвольный набор ножек.
Вот ещё один человек скоро прозреет от ардуиновых либ и окажется что все фокусы и приемы на ардуине дают 2-3 прирост а нормальный подход сразу дал бы увеличение скорости раз так в 30 не менее :)
Иногда таки полезно знать ассемблерные команды которые в один такт делают то что адруновая либа детает за примерно полторы тысячи тактов а результат то тот же.
Вы делаете по сути программную эмуляцию готовой апаратной функции а как все знают программная эмуляция всегда впринципе медленнее апаратной а тут ещё и ардуино который и в программных эмуляциях лидер по тормозам :)
Поясните свою мысль: хотите ли Вы сказать, что команда PORTD=data транслируется в полторы тысячи тактов из-за ардуиновых либ? Я пока уверен, что эта команда транслируется в одну ассемблерную :)
НЛО прилетело и опубликовало эту надпись здесь
Ну раз Вы отвечаете вопросом на вопрос — и я поступлю так же: Вы конечно же проанализировали уже, во что превращается команда PORTD=data и готовы показать обществу результат своих изысканий?
НЛО прилетело и опубликовало эту надпись здесь

Как показала проверка, фантазии компилятору не занимать :-) — в одном случае он использует доступ к портам через общее адресное пространство (смещение на 0x20), а в другом — как к порта ввода-вывода.

digitalwrite транслируется в ту самую тыщу тактов :)
а portD=data явно не то что нужно! мы же не всем портом разом рулить хотим а только одним битом так? значить сначала надо отключить прерывания(в них ведь могут дергать порты и биты портов так?) потом считать кудато состояние порта на сейчас, потом применить битовую маску разную для установки или снятия бита, потом записать полученный байт назад в порт и снова разрешить прерывания… видите как не просто даже простое изменение бита? cbi sbi делает это все за один такт. Ещё вопросы?
Вы статью читали или просто по мотивам выступаете? :)

Я рулю всем байтом разом одной командой. Статья именно об этом: как на Нано использовать весь порт D одновременно.

Так понятно?
совсем не ясно Зачем вы накладываете на себя ограничения в виде полноценного порта?
ну нет порта: -возьмите полпорта В
ну будет две команды:
PORTD=dataD & maskD;
PORTB=dataB & maskB;
зато без «огорода»
Я захотел сделать байтовый вывод максимально быстро — т.е. одной командой. Вывод в два полубайта — две команды и они выполняются в два раза дольше.
Если Вы хотели сделать вывод максимально быстро, то не следует перед выводом каждого байта подтверждать состояние управляющей ноги.
Я не понял Вас. ПОясните. О какой управляющей ноге Вы пишете?
D11_High; в SendDat()
Эта нога управляет режимом Data\Command при управлении дисплеем.
Обычно за байтом команды идут несколько байт данных.
Действительно, наверно можно оптимизировать и выставлять ногу один раз.
Подумаю на досуге :)
Проверил, перенес манипуляции D11 в SendCom:
3.1 раза перешло в 3.6 раза.
дада дольше на 1 микросекунду
стоит ли однамиллионная секунды того огорода что вы нагородили?
стоит ли она того времени, что вы затратили на отладку мультиплексирования?
зачем усложнять? (возможно это вызов? я например для увлекательности, при решении, понижаю себе IQ, возможно без мультиплексирования не было бы этой статьи)
Это просто развлечение техногика. Исследование возможностей железки. Мне это доставляет удовольствие.
Если же подходить к вопросу с точки зрения здравого смысла, то ни использованный процессор, ни дисплей не оптимальны. Сейчас использовать Ардуино Нано практического смысла нет — его прекрасно заменяет Blue Pill на STM32F103, у которого цена меньше, размер незначительно больше, процессор существенно мощнее.

Даже без эксперимента понятно, что вывод байта ассемблерным кодом будет работать быстрее, чем побитовый ногодрыг на С++ :)

«Практика — критерий истины» (с) В.И.Ленин ;)
Вот кто-кто, а В.И.Ленин уж лучше бы ограничился теоретическими измышлениями, а не проверял их на практике.
Спасибо, интересно, как раз думал поэкспериментировать с таким дисплеем…
А MQ-7 не используйте для таких целей, на что он реагирует и что измеряет это большой вопрос.)) Из всей этой серии как-то приспособил MQ2 под контроль утечки баллонного газа, помог найти утечку в подводных шлангах. А для таких серьезных вещей как СО\СО2 использую Winsen-овские датчики, они конечно подороже, но хоть понятно что измеряют.
Про свойства MQ7 я в курсе — некогда провел достаточно подробный цикл измерений в лаборатории газов с эталонными газами. ЕГо конечно нельзя использовать в качестве измерителя концентрации конкретного газа, но как показометр общего загрязнения — вполне можно.
Для контроля СО2 использую датчики К-30 на NDIR технологии.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории