Комментарии 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;
....
Нет, не совсем тоже самое.
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 меняют конкретные биты порта, совсем не трогая остальные.
Спасибо, интересно посмотреть, как g++ будет вести себя в этом случае в различных режимах оптимизации.
#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 минут раньше.
Я контроллерами AVR редко пользуюсь, поэтому долго искал, где же у меня лежит среда разработки для них. А ведь еще параллельно и работать приходится.
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
Так и есть при включенной оптимизации (результаты моей проверки):
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
#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 таки не всегда быстрее, хотя и компактнее.
Иногда таки полезно знать ассемблерные команды которые в один такт делают то что адруновая либа детает за примерно полторы тысячи тактов а результат то тот же.
Вы делаете по сути программную эмуляцию готовой апаратной функции а как все знают программная эмуляция всегда впринципе медленнее апаратной а тут ещё и ардуино который и в программных эмуляциях лидер по тормозам :)
Как показала проверка, фантазии компилятору не занимать :-) — в одном случае он использует доступ к портам через общее адресное пространство (смещение на 0x20), а в другом — как к порта ввода-вывода.
а portD=data явно не то что нужно! мы же не всем портом разом рулить хотим а только одним битом так? значить сначала надо отключить прерывания(в них ведь могут дергать порты и биты портов так?) потом считать кудато состояние порта на сейчас, потом применить битовую маску разную для установки или снятия бита, потом записать полученный байт назад в порт и снова разрешить прерывания… видите как не просто даже простое изменение бита? cbi sbi делает это все за один такт. Ещё вопросы?
ну нет порта: -возьмите полпорта В
ну будет две команды:
PORTD=dataD & maskD;
PORTB=dataB & maskB;
зато без «огорода»
стоит ли однамиллионная секунды того огорода что вы нагородили?
стоит ли она того времени, что вы затратили на отладку мультиплексирования?
зачем усложнять? (возможно это вызов? я например для увлекательности, при решении, понижаю себе IQ, возможно без мультиплексирования не было бы этой статьи)
Если же подходить к вопросу с точки зрения здравого смысла, то ни использованный процессор, ни дисплей не оптимальны. Сейчас использовать Ардуино Нано практического смысла нет — его прекрасно заменяет Blue Pill на STM32F103, у которого цена меньше, размер незначительно больше, процессор существенно мощнее.
А MQ-7 не используйте для таких целей, на что он реагирует и что измеряет это большой вопрос.)) Из всей этой серии как-то приспособил MQ2 под контроль утечки баллонного газа, помог найти утечку в подводных шлангах. А для таких серьезных вещей как СО\СО2 использую Winsen-овские датчики, они конечно подороже, но хоть понятно что измеряют.
Для контроля СО2 использую датчики К-30 на NDIR технологии.
Мультиплексирование вывода данных на дисплей с параллельным портом