Pull to refresh

Comments 131

Дополню, если конфигурацию первого режима организовать следующим образом:
if (phase==3) phase=0; mask=(1<<phase++);

то F (max) = 54 кГц.
Это чтобы отпали вопросы по дальнейшей оптимизации.
Естественно первый и третий режимы можно тоже переварить в инлайновый ассемблер, но здесь прирост будет не таким сказочным (4-5 кГц). Хотя судя по достоверным источникам, это близко к аппаратному ШИМ на
AVR ATmega -48 -88 -168
1<<phase++

Сильно смахивает на UB… Да и даже если не оно, все равно небольшая неоднозначность остается
Запись постфиксная, приоритет в сдвиге. Можно для страстных и разнести, но сути — снижение барьера частоты на 20 кГц, это не поможет.
Каюсь, не прав:
00000019  CPI      R16,0x03		Compare with immediate 
0000001A  BRNE      PC+0x02		Branch if not equal 

0000001B  LDI     R16,0x00		Load immediate 
0000001C  MOV     R18,R16    		Copy register 
0000001D  LDI     R24,0x01		Load immediate 
0000001E  ADD     R24,R16		Add without carry 
0000001F  MOV     R16,R24	        Copy register 
00000020  LDI     R24,0x01		Load immediate 
00000021  LDI     R25,0x00		Load immediate 
00000022  RJMP    PC+0x0003		Relative jump 
00000023  LSL     R24		        Logical Shift Left 
00000024  ROL     R25		        Rotate Left Through Carry 
00000025  DEC     R18		        Decrement 
00000026  BRPL    PC-0x03	        Branch if plus 
00000027  MOV     R14,R24	        Copy register


Приоритет ++ — всегда выше, хотя пример работает чудным образом :)
о чем и речь. лучше так не писать, если это не поделка для себя на пять минут, а то потом внезапные косяки вылезают…
Выражение как раз относилось к поделке для проверки на 5 минут, так что ничего страшного, но спасибо за внимательность.
Вас с толку сбили, нету там никакого UB, код эквивалентен (что даже видно по ассемблеру):
int t = phase;
++phase;
mask=(1<<t);
UB было бы в случае присвоения результата переменной «phase», чего здесь нет.

Каких-то неоднозначностей тоже нет, приоритеты всё чётко определяют.
Неоднозначность в том, что ожидалось так:
int t = phase;
mask=(1<<t);
++phase;

так что MaximChistov, абсолютно прав.
Здесь нет неоднозначности, это эквивалентные вычисления. Даже если написать их в такой развёрнутой форме, в ассемблере они могут быть в другом порядке, компилятор волен переставлять инструкции, если это никак не влияет на результат.
Так в том то и дело, что результат отличен от предполагаемого!
для:
++phase; mask=(1<<t); маски будут такими: 001, 010, 011,100

а тут:
 mask=(1<<t);++phase; маски будут такими: 000, 001, 010,011

Запутался сам :)

в первом варианте:
010, 100,…
во втором:
001,010,100…
Я тоже уже запутался. Смысл пост-операций:
  1. Скопировать
  2. Изменить оригинал
  3. Вернуть для использования в выражении копию

Т.е. при «mask = 0», «1<<phase++» === «1<<0» (а «phase» стало 1) и результат первой операции «001», как и должно быть. Обратите внимание на копирование старого значения.
у "++" приоритет выше чем у "<<"
Да.
То есть «1<<0»!=«1<<1»
Да. НО, если «phase» == 0, то «1 << phase++», это «1 << 0» (см.), такова семантика данного выражения, инкремент «phase» является побочным эффектом, который не участвует в выражении.
Так вот же доказательство:
0000001B  LDI     R16,0x00		Load immediate 
0000001C  MOV     R18,R16    		Copy register 
0000001D  LDI     R24,0x01		Load immediate 
0000001E  ADD     R24,R16		Add without carry 
0000001F  MOV     R16,R24	        Copy register 
Не уверен, что всё правильно написал (не знаю этот ассемблер), но вроде очевидно, что используется старое значение переменной:
00000019  CPI      R16,0x03		if (phase == 3)
0000001A  BRNE      PC+0x02		goto A

0000001B  LDI     R16,0x00		phase = 0
0000001C  MOV     R18,R16       	A: int t = phase
0000001D  LDI     R24,0x01		int c = 1
0000001E  ADD     R24,R16		c += phase
0000001F  MOV     R16,R24   	        phase = c
00000020  LDI     R24,0x01		int tmp_mask = 1
00000021  LDI     R25,0x00		int k = 0
00000022  RJMP    PC+0x0003		goto B
00000023  LSL     R24		        C: tmp_mask <<= 1
00000024  ROL     R25		        tmp_mask += <carry bit>
00000025  DEC     R18		        B: --t
00000026  BRPL    PC-0x03	        if (t >= 0) goto C
00000027  MOV     R14,R24	        mask = mask
эээм, опять запутался, но сдвиг не выполняется тогда :)
Как был кошмар программиста-читателя, так и остался:

Что будет, если перед входом phase=3;

Инкремент, phase=4;
Усливие, истина, phase=0;
Сдвиг на 0-1=-1 БАМ! Получите UB.

Обфускация кода ради красивых комментариев, которые должны повышать ясность кода… даже не знаю, как это назвать.
Вы не правы, если условие истина, то сдвиг не выполняется ;)
Я не предлагаю примеров, которые не проверил. Извините, кредо.
Как так может быть? Вы хотите сказать, что в данном случае, UB выражается просто в отсутствии сдвига?

Так писать на C нельзя. И заменять && на & в условиях «просто так» нельзя, потому как & — просто операция, а && — еще и точка следования.
Не будьте голословны, приводите примеры!
Повторюсь, это не повод холиваров, если программист уверен в своем коде, и ему так удобно, это только его проблемы (он делает это на свой страх и риск).
Я же стараюсь следовать классике, хоть это старомодно.
void main()
{
    int phase=4;
    if (++phase>3) phase=0;
    printf("%d\n",phase);
}

gcc test.c -O2

./a.out
0

ISO 9899:1999 6.5.7 Bit-wise shift operators §3
If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

Если величина правого операнда отрицательна или превышает ширину левого оперенда, поведение неопределено.
Это же очевидно, как и выдержка из стандарта. Мыслите логически почему — не определено!
А по поводу примеров, я имел ввиду приведите пример когда && не равно &.
Только и я Вас загоню при этом в рамки:
Любые использованные переменные должны быть целочисленными, без знаковыми и выровнены разрядности процессора.
Код на C не должен добираться до неопределенного поведения и использовать его.

По второму вопросу — если в коде вида:
if(connect(...) || die(...))
{
}

заменить оператор || на |, его порядок работы будет не определен, и die() может быть вызвано раньше connect().
Не правильно! Хоть вы и не учли мои требования, я Вам отвечу по поводу Вашего примера:
Результатом выражения в условии будет побитовая операция над результатами функций,
т.е. хоть порядок и не определен, результатом будет сложение, и в случае если результат отвечает моим требованиям (см предыдущий пост), то условие выполнится при любом результате этого сложения отличным от нуля (сложение побитовое конечно).

Когда при использовании ||, значение результата функции die(), для истинности условия не важно, при истинности результата connect().

Приведите хороший пример ;)
Обратите внимание на мой код. В нем операнды дизъюнкции должны вызываться строго слева направо, и как только будет получен 0, вызовы должны прекратиться.

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

То есть в вашем конкретном случае, побитовая проверка — легитимна. Но про эту оговорку нужно помнить.
Согласен с Вами, именно поэтому и говорю — плохой пример ;)
По поводу скорости выполнения — отдельный разговор. Если мы будем использовать || и вперед вынесем тот вариант условия, который возникает чаще, код будет выполнятся быстрее, так как остаток выражения даже не будет рассчитываться.

Итого, выбор нужного оператора должен происходить с учетом всех этих факторов, а не потому, что | — якобы быстрее. На «большом» процессоре с конвейером, предвыборкой и спекулятивным исполнением, можно хорошо выиграть, если доступно написать системе, что мы от нее хотим.
Про это и речь, только если Вы внимательно читали мою статью, я как раз говорю наоборот:
Результат будет один и тот же, а вот выполняться второй пример будет ЗНАЧИТЕЛЬНО дольше по времени, и ЗНАЧИТЕЛЬНО раздует код.


Предположу, что Вы допустили опечататку.
Мне кажется, что вы что-то выдумываете. Различие между "||" и "|" в том, что "||" это логическая операция сравнения ((оп1) || (оп2)) в которой оп1 и оп2 рассматриваются как true/false, а "|" — это побитовая операция ((оп1) | (оп2)) между операндами оп1 и оп2, результат которой уже оценивается как true/false.
Собственно это 2 совершенно разных выражения, а не варианты написания условий. И компилируются они совсем по разному.

if(...)
{
mask+=phase;
}
else
{
mask=1;
phase=0;
}

if((phase == 1) || (mask < 3))
00002A 2F10 MOV R17,R16
00002C 3001 CPI R16,0x01
00002E F019 BREQ 0x36
000030 8100 LD R16,Z
000032 3003 CPI R16,0x03
000034 F420 BRCC 0x3E
mask+=phase;
000036 8100 LD R16,Z
000038 0F01 ADD R16,R17
00003A 8300 ST Z,R16
00003C C004 RJMP 0x046
mask=1;
00003E E001 LDI R16,0x01
000040 8300 ST Z,R16
phase=0;
000042 E000 LDI R16,0x00
000044 8301 STD Z+1,R16
}

if((phase == 1) | (mask < 3))
00002A 2F10 MOV R17,R16
00002C 3001 CPI R16,0x01
00002E F041 BREQ 0x40
000030 8100 LD R16,Z
000032 3003 CPI R16,0x03
000034 F028 BRCS 0x40
mask=1;
000036 E001 LDI R16,0x01
000038 8300 ST Z,R16
phase=0;
00003A E000 LDI R16,0x00
00003C 8301 STD Z+1,R16
00003E C003 RJMP 0x046
mask+=phase;
000040 8100 LD R16,Z
000042 0F01 ADD R16,R17
000044 8300 ST Z,R16
}
И вновь с Вами согласен, но обратите внимание на Ваш код! :)
А что не так с моим кодом?
Он идентичен в обоих случаях по времени выполнения и размеру.
Загвоздка в том, что результаты обоих сравнений, одного типа.
Но вы уже близко к ответу. :)
Это не главное :) Главное то, что это принципиально разные операции. тыц. Обратите внимание на «3.7 Logical Operators» и «3.9 Bitwise Logical Operators»

А для условного перехода это принципиально разные вещи. Как вы видите в моем примере выше если используется || то получив в первой части true не имеет смыслы выполнять вторую, потому что при любом ее значение результат ветвления уже определен. Тогда как при побитовой операции | возможны варианты.
Это и означает — точка следования.
Это я все к тому, что gbg писал

заменить оператор || на |, его порядок работы будет не определен, и die() может быть вызвано раньше connect().


Тут нету никакого неопределенного порядка. А в самой терминологии, «точка следования» это или нет, я не спорю.
Как так нет, когда стандарт не определяет, в каком порядке считать члены выражения или аргументы функции, если они не отделены точками следования или приоритетами операций?
Вы привели код, замена операторов в котором не очень то и корректна с точки зрения алгоритма.

Вот вам пример, где вы получите принципиально разное поведение программы.

if(connect(...) && die(...))
{
}

Если connect(...) возвращает 1, а die(...) возвращает 2.

в случае "&&" у вас будет и там и там «true», а если заменить на & то побитовая операция 1 & 2 (неважно в каком порядке они выполняться) будет равна 0 (false).
Замечание интересное, но код приводился для демонстрации того, что в таком случае можно использовать только логический оператор, который является точкой следования.

Для ясности нужно было конечно же показать, что connect() возвращает int со значениями строго 1 или 0, а die() всегда возвращает int(0).
Вы как-бы пытаетесь подменить причину и следствие. При использовании разных операторов («логическое или» и «побитовое или») вы принципиально меняете алгоритм работы программы. Если у вас «логическое или» то вариант
if(connect(...) || die(...))
{
оп_xz();
}

превращается в
if(connect(...))
{
оп_xz();
}
else
{
    if(die(...))
    {
    оп_xz();
    }
}

А вариант
if(connect(...) | die(...))
{
оп_xz();
}

в
int tmp = connect(...) | die(...);

if(tmp != 0)
{
оп_xz();
}
Так, все стоп! Разобрались же уже! )
Ниже читайте.
Я и пытаюсь показать, что замена I на II не эквивалентна.
Мне не дают покоя ваши слова
Код на C не должен добираться до неопределенного поведения и использовать его.


В примерах
if (A==0 || B==1 || C==2) doSomething();
if (A==0 | B==1 | C==2) doSomething();

Поведение вполне определенное и даже в вашем примере операция "|" скорее всего будет происходить слева на право.
Так мои слова про UB, они вот про это:
if (++phase>3) phase=0; mask=(1<<(phase-1));

Тут phase становится 0, после чего из него вычитают 1 и выполняют сдвиг. Это форменное UB.
phase объявлена как uint8, беззнаковое размерность 8 бит. 0 — 1 в таком случае будет 0xFF? Вроде ничего страшного не случится :)
В таком случае, будет int(-1). Даже если там будет 255, в стандарте написано (выдержку из которого я привел выше), что сдвигать на величину, большую чем ширина сдвигаемой переменной в битах — нельзя.
При отрицательном сдвиг не выполняется, да читайте Вы код в конце концов:
00000023  LSL     R24		        Logical Shift Left 
00000024  ROL     R25		        Rotate Left Through Carry 
00000025  DEC     R18		        Decrement 
00000026  BRPL    PC-0x03	        Branch if plus 
00000027  MOV     R14,R24	        Copy register
Это в данном конкретном случае, данным конкретным компилятором.

Невозможно даже гарантировать, что при дописывании нового кода к этой программе компилятор это переведет в тот же машинный код.

Невозможно гарантировать, что при использовании этого кода на другом контроллере компилятор не выдаст там что-то ужасное.

Невозможно гарантировать, что при смене настройки оптимизации этот код не выродится.

UB просто не допустимо.
Если код который вы привели — из проекта, то это crap. Писать в одну строку выражение, означающее
if (++phase>3) {
  phase=0;
}
mask=(1<<(phase-1));
— это не просто плохо, это повод к применению линейки.
И? Мы ж вроде в ключе одного проекта обсуждаем. Там phase объявлена как uint8.
У автора в комментариях на скриншоте есть ещё более прекрасное:
if (++phase<3) mask-=phase; else {mask+=phase; phase=0;}


Похоже, это фирменный стиль автора.
Давайте обойдемся без подобных заявлений, Вы только что обозначились как не читавший статью.
Ви пr'авда считаете, что меня это сколь-нибудь волнует?

Ваше замечание про «условия в одну строку» я видел. Что ж вы всю программу в одну строчку не запихнули, если уже используете по два выражения в одной строке (условие с его веткой, плюс безусловное выражение)? Можно было ещё отступы выкинуть — столько места съедают…

В общем, ваши нападки про чтение статьи ничуть не оправдывают говнокод, который вы дали в комментарии выше.
Нападки? Да Ви пг'осто себя пе'геоцениваете :)
Это код не из проекта, это пример.
Про линейку вкурсе :)
Давайте не будем выдумывать, и рассмотрим такой вариант:
B=0b10;
if ((A==1) && B ) {} else {}
if ((A==1) & B ) {} else {}

думаю так проще для понимания.
Предлагаю Вам самому понять почему.
Возможно ли было избежать ассемблерной вставки на месте обработчика, путем тонкой настройки флагов и компилятора?
Ни одного флага, или аттрибута, которые бы заставили компилятор генерировать короткий пролог у обработчика прерывания я не вижу.
__attribute__((interrupt)) или вообще __attribute__((naked))
Так-то да, но их как раз у автора в исходнике нету, вместо них — кусок на ассемблере.
Там используется макрос ISR(), который ставит __attribute__((signal)) по умолчанию (отличается от interrupt тем, что не разрешает прерывания в прологе). И простой или критичный по времени обработчик может иметь смысл написать на асме, как в данном случае, если хочется выжать всё из контроллера.
Тут надо конкретный случай смотреть, сможет ли компилятор выдать код того же размера/скорости, или не сможет.
С этим сложно поспорить, сильно зависит от компилятора. Я по этому поводу соглашусь с Кнутом: «premature optimization is the root of all evil». Сначала померить/посмотреть objdump, потом уже оптимизировать, если надо.
Ради интереса написал, отличается от стандартного, как я и говорил ниже, на пару слов, и как Вы сказали — cli и sei
Максимум для первого и третьего режима получилось 85666 Гц, с учетом сдвига маски через перенос (3 слова). Но и это — уже перебор.
в первом варианте:
ISR (TIM0_COMPA_vect, ISR_NOBLOCK)
+2 байта кода,
во втором:
ISR (TIM0_COMPA_vect, ISR_NAKED)
-4
naked — способ сделать c-compatible символ, пролога-то нет, от слова совсем.
Прироста производительности — тоже.
А можно код на С, который «плохо» компилировался?
Он содержится в комментарии к исходным текстам. А первый вариант не сохранился к сожалению. но он не намного хуже того что приведен.
Папа замечаний
1. Утверждение о том, что
if (A==0 || B==1 || C==2) doSomething();
if ((A==0) | (B==1) | (C==2)) doSomething();
дают один и тот же результат, на мой взгляд, спорное. Я не нестолько знаю стандарт описания С, чтобы утверждать, что результат логической операции приводится к одной константе и так будет во всех реализациях, а если принять что всякий не 0 — истина, то последнее выражение может и не дать истины при истинных составляющих.
2. Если фрагмент
 if (phase!=6)
 { mask = ((phase==1) || (phase==3)) ? (mask+phase+1) : phase; }
else (phase=0; mask=1; }
phase++;
заменить на что-то вроде :
 mask=pphase*;
 if (pphase==pphaseend) pphase=pphasestart else pphase++;
и создать соответствующий массив и указатель на него, то, скорее всего, никакие оптимизации кода не потребуются и быстродействие существенно поднимется.
Ну и так, между прочим, ассемблерный код в приведенном окне эквивалентен несколько иному коду на С, хотя функционал и идентичен, просто не следует терять точность.
И вновь:
дают один и тот же результат, на мой взгляд, спорное.

Приведенный Вами пример не соответствует тому примеру на который указал я.
При этом я не сторонник таких трюков, но иногда к ним прибегаю при необходимости.
И присоединяйтесь к обсуждению, оно чуть выше в комментариях :)
Ну, вообще-то, я взял пример как раз из Вашей статьи.
Наверное, подразумевалось, что Вы на него не указывали? ))

Ваш код при размещении массива в регистрах (для экономии памяти), для второго режима дал прирост используемой памяти в 120 байт.
120 байт для хранения 6 элеметов типа целое?
У Вас по дефолту размер слова 20 байт?
Мы про Tiny говорим?
Мне понравилась ТАКАЯ экономия памяти.
При размещении массива в памяти дополнительный размер не должен превысить 6*(1-2-4) =6-12-24 байта.
Вы невнимательны, массив в регистрах, память не используется.
120 — прирост кода при реализации обработки масок с чтением из массива.
То есть при загонах компилятора с присвоением значений через временные регистры и тп.
Так вот я и задаю вопрос — какую память мы экономим?
Насколько я помню, у Tiny есть возможность считывать данные из FLASH памяти через индексный регистр Z.
Значит должна быть и возможность держать массив констант (а это именно массив констант) в памяти программ и не тратить лишнего.
И в этом случае Вы получаете как минимум две операции на загрузку значения из памяти в регистр,
когда из массива регистров, это делается за 1 цикл на значение.
mov R16, R20 (21,22,23,24,25,26,27)

В Вашем случае, компилятор сделает загрузку из памяти во временный регистр, потом копирование в R16. А это еще расходы по коду и скорости.

Да Вы попробуйте, а потом расскажете ))
Я это уже прошел.
Попробовал, рассказываю.
Я уже лет 5 не писал для Tiny, поэтому мог где то накосячить, но вот что получилось
Ваш вариант:
cpi r16,0x06  1
breq newp     1/2
cpi r16,0x01  1
breq nextp    1/2
cpi r16,0x03  1
breq nextp    1/2
mov r14,r16   1
rjmp nextr    2
newp:
clr r14       1
clr 16        1
nextp:
add r14,r16   1
inc r14       1
nextr:
inc r16       1
Итак, считаем — длина модуля 13 слов, количество используемых регистров — 2, время исполнения для 6 — 8 тактов, для 1 — 8 тактов, для 3 — 10 тактов, для остальных — 10 тактов, среднее время 56/6 = 9 тактов (8-10).
Предлагаемый вариант
lpm r14,z+     3
cpi zl,dataend 1
brne nextr     1/2
sbiw z,datalen 2
Считаем еще раз — длина модуля 4 слова + 6 слов массива = 10 слов, количество испльзуемых регистров — 2, время исполнения для 1-5 — 6 тактов, для 6 — 7 тактов среднее время 37/6 = 6 тактов (6-7).
Вывод — модуль получается компактнее и быстрее. У него есть 1 недостаток — изменение времени исполнения для разных вариантов, что можно устранить вариантом:
lpm r14,z+        3
sbiw zl,datalen   1
cpse zl,datastart 1/3
adiw z,datalen+1  3
но он чуть длиннее — 8 тактов всегда.
Единственно, что смущает — не помню, можно ли использовать регистр z в арифметических операциях. Если нельзя, то придется добавить
movw r15,zl и 
movw zl,r15
и проводить операции с парой (r15,r16), что увеличивает длину модуля на 2 и время исполнения на 2 такта, тогда варианты практически сравниваются по параметрам.
про пример я имел ввиду выражение:
а если принять что всякий не 0 — истина, то последнее выражение может и не дать истины при истинных составляющих.
За исключением вопроса о точках следования и раннем выходе из условия, второй код — легитимный. В C определено, что операции <, >, == и так далее выдают int(0) и int(1) соответственно.
C11(ISO/IEC 9899:201x) §6.5.8 Relational operators
Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.107) The result has type int.

Каждый из операторов [...] должен давать 1 в случае истины, и 0 в случае лжи. Тип возвращаемого значения — int
Прошу прощения за придирку, но в приведенной цитате нет оператора ==.
Спасибо. Про них написано в 6.5.9 то же самое:
Each of the operators yields 1 if the specified relation is true and 0 if it is false. The result has type int.
Вот теперь в точку!
Результат типа int ;)
Подведете итог? Без точек следования, потому что с этим разобрались сразу — логика работает быстрее.
Конечно, логика будет НЕ МЕДЛЕННЕЕ, поскольку отсутствует преобразование результата термов в тип (1,0), которое необходимо во втором варианте.
Да я в этом и с самого начала не сомневался.
Поэтому второй вариант изначально выглядел несколько вычурно и я никогда бы его не применил, даже если бы он работал быстрее, чего не наблюдается.
Битовые операции имеют вполне конкретное назначение и рассматривать их как альтернативу логическим методологически неверно.
А вот насчет совпадения результатов — разработчики стандарта все предусмотрели, респект и уважуха и им и тем, кто стандарт читает.
Кстати, интересно, а почему сравнение отдельно описано, а равенство отдельно? Это фича такая?
Вероятно из-за приоритетов. В AVR ASM тоже рекомендуют не использовать breq, а вычленять бит регистра статуса, хотя по времени — одинаково (не помню где читал, но на англицком).
Честно говоря, проект под Attiny13, из которой хочется выжать всё, проще с нуля написать на ассемблере, чем изучать поведение компилятора. Последнее очень полезно в академических целях, но не практических.
Ассемблер AVR прост как бейсик, день кодинга — и начнёшь на нём разговаривать.
Так с любым ассемблером.
Я знаком с разными архитектурами под которые писал на ассемблере, но когда проект выходит за рамки нескольких сот байт, Си становится удобней.
А конкретно про эту задачу — да, в академических целях ее можно реализовать полностью на ассемблере и это будет удобней.
Здесь же я преследовал другую идею — получить быстрый ШИМ с заданной конфигурацией, и оставить место для реализации законченного проекта, который замечу, требует много ресурсов. Но это уже не суть данной статьи.

Я надеюсь, что те кому пригодится мой пример в их разработках, выберут для себя нужный режим, удалят комментарии,
правильно оформят нужное условие, и с пользой применят в своих разработках, доведя их до состояния завершенности.
При этом не испытывая трудностей в нехватке памяти и быстродействия.
А благодарностью мне, послужит полученное ими удовольствие.
Может я не правильно понял условия, но
1. я бы включил таймер в режим CTC (сброс при совпадении с OCRA), если он есть в тини, конечно. Ну или хотя бы сбрасывал таймер в начале, иначе буде ждиттер.
2. Маску рассчитал бы заранее и хранил в ПЗУ.
Если это перевести в асм, ИМХО, будет быстрее:
const uint8_t mask[] = {0, 1, 2, 3, 4, 5};

ISR(TIM0_COMPA_vect)
{
	static phase = 6;
	PORTB = mask[--phase];
	if(!phase) phase = 6;
}
1. У тини нет CTC, таймер сбрасывается в начале и в конце каждой фазы.
2. Доступ к ПЗУ медленнее чем к регистрам и к SRAM, а чтение из массива раздувает код.
Ну тогда из ОЗУ. Я сварщик не настоящий, но, похоже, там для доступа к ОЗУ одна инструкция на два такта: LD Rd, — X // Load Indirect and Pre-Dec. Затем проверить младшую часть пары X на ноль (команда сравнения + перехода), еще тактов 3-6.
Вот пример:

volatile uint8_t* current_mask; 
current_mask = (uint8_t *)0x0A; current_mask[0]=0; // R10 
current_mask[1]=0b001;current_mask[2]=0b011;current_mask[3]=0b010;current_mask[4]=0b110;current_mask[5]=0b100;current_mask[6]=0b101;


Массив в регистрах с R10 по R16
UFO just landed and posted this here
Бесспорно, но это не мигать светодиодами :)
Сказанное в статье на эту тему, было попыткой не отталкивать новичков.
Извиняюсь что не по теме, но вижу тут много хороших специалистов, которые, возможно, увидят мой пост и смогут помочь…

Я несколько лет работаю с микроконтроллерами AtMega и заметил, что при продолжительной работе их начинает «глючить». Либо что-то я не правильно делаю.
Вот такой пример. Пару месяцев назад собрал реле времени для бойлера нагрева воды (микроконтролер AtMega 16). Включает бойлер на 2 часа в 5 часов вечера и 5 утра.

cs622028.vk.me/v622028863/b429/4pqbA4ND30g.jpg
cs622028.vk.me/v622028863/b417/E34uBXfpNeg.jpg

2 кнопки сбоку- установка времени, большая красная кнопка- дополнительно включить бойлер на 2 часа. Ну не важно. Устройство работает нормально 2 дня, неделю, 2 недели (вроде бы), а если более (через месяц) его начинает глючить. Время показывает правильно, но устройство перестает реагировать на кнопки. (обработка кнопок- самый простой вариант в цикле, через _delayms() (без прерываний, и не хотел дополн. переменные писать).

Проверял с большой красной кнопкой (должен загореться желтый светодиод и замыкается реле (которое бойлер запустит) к тому же на экран выводится дополнительная строка таймера обратного отсчета (сколько осталось до выключения)). Бывает нажмешь ее раз в месяц- реле включается, светодиод горит, но на экран строка не выводится. Бойлер через 2 часа не выключается. При повторном ее нажатии- должно отключить дополнительное время и вернутся в нормальное состояние- не реагирует. Или вчера нажимаю- выводит на экран отсчет времени не 2 часа, а 7 часов, n минут…

Как только перезапускаю устройство- работает нормально…

Переполнений памяти в принципе быть не может…

На всякий случай о питании…
Блок питания- импульсный (от DVD проигрывателя, +5в, +12в) +стабилизатор на 5в от линии 5в (хотя там 6в).
Питание реле- от 12 вольт этого БП. Запускаю через транзистор кт972а.

Код (если нужен) pastebin.com/Qb2t7fwH

В чем может быть проблема? В коде- вряд ли. Несколько дней работает без проблем, потом глючит…
Проверьте питание в первую очередь. Стабилизатор на 5v линейный? Или buck-boost? И в том, и в другом случае надо смотреть допустимые параметры работы стабилизатора (минимальное напряжение/падение на стабилизаторе), допустимые температуры.

Также стоит посмотреть, хватает ли вам тиков, не проскакиваете ли вы seconds == 0.

Кроме того, можно настроить wdt, чтобы перезапускать контроллер автоматически.
… и вообще это называется «отладка», самая затратная по времени часть, которую заочно провести не получится. Это как лечить по телефону.
Со временем нету проблем, идут хорошо.

Стабилизатор вроде как линейный. Получает 6.2в, выдает 5в и дальше на конденсатор…
Реле висит на другой линии питания…
Ищите даташит на стабилизатор и смотрите параметры. Если он не LDO (обычный 7805, например), то с питанием уже печаль. Обычное падение на линейных стабилизаторах от 1.2 до 1.7 v.
Ни одна из переменных, которые задействованы в таймере (обработчике прерываний) не объявлена volatile. Возможны любые глюки.
Такое поведение очень похоже на утечку памяти. Поскольку логика программы довольно жесткая и линейная, большое количество сбоев с переполнением стека или порчей переменных в памяти остается незамеченной. Тем проблема и трудна к обнаружению. Тут потенциально помог бы отладчик, хотябы выяснить где крутится программа во время сбоя, состояние стека и всех переменных… но ведь это накладно держать контроллер под отладчиком месяцами.
Месяцами не обязательно, у автора включен делитель таймера на 1024, можно ускорить.
Это нарушит чистоту эксперимента, вдруг проблема возникает только при заданном делителе? Конечно, проблему в самом прерывании так можно выявить, но для полной уверенности в отсутствии проблем отладку проводить надо в поле при боевых настройках.
Устройство работает месяцами и вовремя включает и отключает бойлер. С этим проблем нету. Сбоев времени не было ни разу. Странно что потом на кнопки криво реагирует.
Подебажить было бы интересно конечно…
Это отлично, но судя по этому:
	if(StartTime > EndTime)	{
		CalcTime = (86400 - StartTime) + EndTime;
	}

И Вашим словам:
Включает бойлер на 2 часа в 5 часов вечера и 5 утра.

Где из этого:
	int StartTime= (StartH*3600)+(StartM*60)+StartS;
	int EndTime= (EndH*3600)+(EndM*60)+EndS;

Следует, что 17*3600=61200 = (int)-28443, а 86400 = (int) -20865
После чего Вы полученные значения преобразуете в отрицательные часы, минуты и секунды,
а потом вызываете вывод их на дисплей, честно говоря — не знаю как себя поведет весь остальной код.
ЗЫ: возможно в переводе на единицу ошибся, тем не менее, попробуйте проверить тут.
Да я как-то нарушил последовательность извлечения регистров из стека при выходе из прерывания — год никаких проблем не было, пока не взял этот кусок кода для другого проекта и заметил очевидное несоответствие.
тут ведь понимаешь, работает теория вероятности по принципу «куда бог пошлёт». Вот видимо на ячейки где отсчитывается текущее время он пока еще не посылал…
Не нравится мне вот этот момент:
	int CalcTime;
	if(StartTime > EndTime)
	{
		CalcTime = (86400 - StartTime) + EndTime;
	}
	else
	{
		CalcTime = EndTime - StartTime;
	}

Не помню зачем я это делал, но видимо вместо того, чтобы сделать проверку if(Hours>23)Hours-=23 я написал такое) (я вроде как отказался от этого, но почему-то тут оставил)

Вы выделили этот кусок кода, но упустили самый интересный:

int StartTime= (StartH*3600)+(StartM*60)+StartS;
int EndTime= (EndH*3600)+(EndM*60)+EndS;
int CalcTime;


Помойму это очень грубо.
А теперь прикиньте, какая разрядность у Ваших операций, где Вы используете int (16bit max=65535)
сплю почти, не успел: INT — знаковый -32767...+32767
То есть ошибка, скорей всего — погрешности Ваших вычислений времени.
Используйте другие типы для этих переменных.
Ну и советы выше — не навредят.
Если совсем будет бесить эта глюконутость и будет время- возможно перенесу на ПЛИС. На каком-то сраном Max2 сделаю)

Пару месяцев написал AVR микроконтроллер на плисе. Правда программу пришлось на ассемблере писать. Если бы эту программу на ассемблер переписать (сложнее всего будет с драйвером экранчика) и сравнить работу меги и моего мк) Правда памяти не хватит на эту программу… Нужно плис менять… (про внешнюю память не напоминать)).
Прошу прощения за возможно глупый вопрос, но я вставил этот код как он есть ( с раскомментированной строкой) в arduino ide, он скомпилировался и загрузился в тиньку, но все порты в единичку и ничего не происходит. Почему так? АВРСтудии под рукой просто нет :\
А как он туда загрузился без загрузчика? и какой, собственно, код?
Код который в 3x_Soft_PWM.cpp в конце статьи. Загружается посредством программатора USBasp
Ещё такой момент — таймеры по переполнению не срабатывают. Попробовал AVR Студию, зашивал через AVRDUDEPROG — тоже самое (фьюзы dl.dropboxusercontent.com/u/13249224/2015-12-31%20%284%29.png)
Разве может быть такое что в МК работает все кроме таймера по переполнению?
Что такое таймер по переполнению? неработать он может только если забыли включить обработку этого переполнения, неправильно прописать вектор прерывания(при компиляции указали не тот камень в который прошивали) или вообще забыть разрешить прерывания глобально.
Прошу прощения — про таймеры можно временно забыть — я ещё икспириминтирую :)
И по коду статьи — никаких действий она не производит, хотя должно быть «настольной мигалкой».
Не понял какая строка раскомментирована, та что с задержкой для тестов?
Частота слишком высокая, попробуйте испытать в «протеусе», с картинкой на осцилографе.

Не уверен что в ардуинке заведется, впрочем не проверял.
от ардуины только ide. в аврстудии тоже самое. А вот это из протеуса. И это неизменно. Только желтая линия по миллиметру в год сползает вниз, но не в стороны.
Вновь не понял, сформулируйте чего вы хотите достичь, судя по картинке шота из протеуса, код работает так же как у меня. Вы хотите изменение скважности? Ну то есть чтобы можно было увеличивать-уменьшать яркость например?
Если так то это не тот проект.
В смысле, опишите задачу, я Вам помогу с исходником, который Вам нужен.
В данном случае хотел поглазеть на моргалку описанную в PS для новичков и потыкать код. Но ничего не выходит — все как в протеусе. Проверял в железе, с разной оптимизацией и без оной.
Ну и да, хотелось бы немного понять про изменение яркости.
Я понял, изменю исходник и скину сюда в комменты, немного подождать, скажем — до завтра ;)
У вас канал А настроен на переменное напряжение, потому и сползает вниз — включена развязка входа по постоянному току.
Sign up to leave a comment.

Articles