Если ваш вопрос не влез ни в одну из вышеперечисленных тем, вам сюда.
Ответить

Re: Хитрые, необычные алгоритмы и код

Вс сен 08, 2013 17:03:09

*Trigger*, способ получить ещё одно внешнее прерывание, конечно, не "прямой" :) , но интересный.
Однако, в вашем случае больше ни для чего нельзя использовать таймер. Что может быть весьма неудобно.
Я бы сказал, есть путь "прямее" :) и, к тому же, таймер можно использовать для чего-то другого.
Цитата из спека на мегу8:
Bit 6 – ICES1: Input Capture Edge Select
This bit selects which edge on the Input Capture Pin (ICP1) that is used to trigger a capture
event. When the ICES1 bit is written to zero, a falling (negative) edge is used as trigger, and
when the ICES1 bit is written to one, a rising (positive) edge will trigger the capture.
When a capture is triggered according to the ICES1 setting, the counter value is copied into the
Input Capture Register (ICR1). The event will also set the Input Capture Flag (ICF1), and this
can be used to cause an Input Capture Interrupt, if this interrupt is enabled.

Т.е. на основе Input Capture можно получить ещё одно внешнее прерывание с сохранением, так сказать, таймера.
Прерывание будет Timer/Counter1 Capture Event (TIMER1 CAPT).

Re: Хитрые, необычные алгоритмы и код

Вс сен 08, 2013 17:11:56

Да, действительно. Что-то я про захват забыл. Но можно использовать их одновременно, получив ещё два прерывания.

Re: Хитрые, необычные алгоритмы и код

Сб окт 12, 2013 19:36:30

Месяц прошёл - можно поднять тему :wink: На неделе понадобился большой-пребольшой линейный счётчик с выводом на индикатор. Как нельзя лучше подошёл алгоритм ILYAUL приведённый в этой теме. Может ещё кому понадобится.
count_bcd.asm

Re: Хитрые, необычные алгоритмы и код

Ср фев 12, 2014 03:18:18

Здравствуйте. Недавно, на соседнем форуме был задан вопрос о быстром умножении 24-разрядных чисел. Было предложено использование алгоритма Дональда Кнута. В результате появился код, который выполняет
Умножение 24р*24р=48р выполняется за 75 тактов и занимает 65 слов
Умножение 32р*32р=64р выполняется за 134 такта и занимает 117 слов
Формат представления чисел старший-младший
MULT_KNUTH.zip

Re: Хитрые, необычные алгоритмы и код

Пт фев 14, 2014 12:18:45

Задумался я о выводе текста на графическом дисплее (например, WS0010).
Например, нарисовали шрифты 5×8 точек (5 байт на символ) — 32 буквы (без "ё") или 31 (без "ё" и "ъ", но с пробелом) чтобы уложиться в 5 бит.
Или нарисовать весь алфавит, цифры и символы — всего 64 знака, т.е. 6 бит кодируют символ. Куда применить остальные 2 или 3 бита?

Ответ прост: «архивировать» (кодировать) ещё один символ! Но не любой, а только 3 или 7 наиболее часто встречающихся. В русском языке (согласно http://ru.wikipedia.org/wiki/Частотность ) это (в порядке убывания частоты) «О», «Е», «А», «И», «Н», «Т», «С».
Например, сделаем в таблице шрифтов (32 символа, т.е. 5 информационных бит и 3 дополнительных) так, чтобы у буквы «О» было условное смещение в таблице = 1, у «Е» = 2 и т.д., т.е. вынесем их в начало таблицы. Тогда слово «НЕТ» в памяти программ без «архивации» будет занимать 3 байта:
0b00000101 0b00000010 0b00000110,
а с данным алгоритмом — всего 2:
0b01000101 0b00000110,
здесь буква «Е» переехала в старшие 2 бита буквы «Н». Экономия места в памяти программ (ПП)!
Причём сама функция вывода усложнилась совсем чуть-чуть. Если раньше было (5+3 бит)
Код:
1. считали байт из ПП
2. вызвали процедуру печати символа
3. уменьшили (декремент) счётчик
4. переход к п.1, если не ноль

, то стало
Код:
1. считали байт из ПП
2. скопировали его
3. маскируем его (лог. «И») с 0b00011111
4. вызвали процедуру печати символа
5. проверили копию на наличие ещё одного символа
// если все 3 старшие биты нулевые — второго символа в данном байте нет
// проще всего организовать через сравнение с константой 0b00011111
6. переход к п.10, если второго символа нет
7. иначе переместили старшие 3 бита в младшие
// например, через обмен тетрадами и сдвиг вправо
8. маскировали его (лог. «И») с 0b00000111
9. вызвали процедуру печати символа
10. уменьшили (декремент) счётчик
11. переход к п.1, если не ноль

Re: Хитрые, необычные алгоритмы и код

Сб май 03, 2014 17:44:19

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



P.S. Код ещё будет дорабатываться.

Re: Хитрые, необычные алгоритмы и код

Сб авг 16, 2014 18:27:47

Небольшое исхищрение на тему экономии регистров МК.

Идея возникла в связи вот с какой задачей. Есть внешний цикл на 12 итераций (12 = 0x0C). Счётчик итераций умещается в пол-байта. И есть внутренний цикл на чуть более, чем 256 итераций (в моём случае 484 = 0x01E4). Счётчик итераций внутреннего цикла в байт ну никак не умещается, а второй регистр заводить для него жаба давит. Какой выход? Правильно! Уместить неуместившиеся разряды внутреннего счётчика в свободные разряды внешнего счётчика.

Сначала вариант "цивилизованного" кода, использующего все 3 регистра для счётчиков:
Код:
;               {...}
;               {делаем что-нибудь}
;               {...}
                ldi     r20,    0x0C    ;   Инициализация внешнего счётчика
outer_loop:
;               {...}
;               {содержимое внешнего цикла}
;               {...}
                ldi     r18,    0xE4    ;   Инициализация внутреннего счётчика
                ldi     r19,    0x01    ;   То, что не поместилось в байт
inner_loop:
;               {...}
;               {содержимое внутреннего цикла}
;               {...}
                dec     r18             ;   Уменьшаем внутренний счётчик
                brne    inner_loop
                dec     r19             ;   Тоже внутренний счётчик
                brne    inner_loop
                dec     r20             ;   Уменьшаем внешний счётчик
                brne    outer_loop
;               {...}
;               {делаем ещё что-нибудь}
;               {...}


А теперь экономный вариант с использованием всего 2-х регистров:
Код:
;               {...}
;               {делаем что-нибудь}
;               {...}
                ldi     r19,    0x0C    ;   Инициализация внешнего счётчика
outer_loop:
;               {...}
;               {содержимое внешнего цикла}
;               {...}
                ldi     r18,    0xE4    ;   Инициализация внутреннего счётчика
                ori     r19,    0xE0    ;   То, что не поместилось в байт
                                        ;   Величина, помещаемая в старший полубайт
                                        ;   вычисляется по формуле y = 0xF - x
                                        ;   В моём случае x=0x1, поэтому y=0xE
inner_loop:
;               {...}
;               {содержимое внутреннего цикла}
;               {...}
                dec     r18             ;   Уменьшаем внутренний счётчик
                brne    inner_loop
                subi    r19,    0xF0    ;   Тоже внутренний счётчик
                                        ;   Выполнение этой команды увеличивает
                                        ;   старший полубайт регистра r16 на 0x1,
                                        ;   при этом флаг переноса уставлен, кроме случая,
                                        ;   когда старший полубайт оказывается равным 0x0
                brcs    inner_loop
                dec     r19             ;   Уменьшаем внешний счётчик
                brne    outer_loop
;               {...}
;               {делаем ещё что-нибудь}
;               {...}

Как можно заметить, количество инструкций не увеличилось, за то уменьшилось число используемых разрядов. Правда, достигнуто это ценой усложнения программы для понимания. Однако, есть и другая, эстетическая сторона: разнообразился состав используемых инструкций МК. То была связка ldi / dec / brne, а теперь к ним добавились команды ori / subi / brcs.

Это ухищрение обладает достаточно большой гибкостью. Если бы количество итераций внешнего цикла умещалось бы в 3 бита, то целых 13 бит можно было бы использовать для счётчика внутреннего цикла. С другой стороны, если когда для внутреннего цикла требуется 9 бит как в моём случае, то для счётчика внешнего цикла можно использовать целых 7 бит сдвоенного регистра. Модификация требует всего лишь правильно задать значения констант.

Более того, в случае 7/9 (как у меня) можно сэкономить ещё и на одной инструкции МК — на инициализации самого старшего бита внутреннего счётчика, так как его инвертированное значение на входе во внутренний цикл всегда равно нулю, что и требуется. Я же в своей программе этого делать не стал, так как у меня может возникнуть необходимость увеличить число итераций внутреннего цикла.

Вот ещё один, чуть более человечный вариант:

Интересно, а компилятор Си умеет делать такую оптимизацию?

Re: Хитрые, необычные алгоритмы и код

Пн авг 25, 2014 00:38:03

внутренний цикл на чуть более, чем 256 итераций (в моём случае 484 = 0x01E4).

Расположить две итерации внутреннего цикла друг за другом, тем более что у Вас количество чётное. Счетчик станет 242.

Re: Хитрые, необычные алгоритмы и код

Пн авг 25, 2014 04:23:13

Когда нужно вернуться из процедуры или прерывания в другую точку программы,
можно сделать так:

Код:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; STACK HACK FOR AVR                                      ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.include "m8def.inc"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.dseg
.org         (RAMEND - 1)
st_ret_h:    .byte     1
st_ret_l:    .byte     1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.cseg
.org 0x0000
            rjmp    RESET

.org         INT_VECTORS_SIZE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RESET:        ldi        r16,            Low(RAMEND)
            ldi        r17,            High(RAMEND)
            out        SPL,            r16
            out        SPH,            r17

            rcall    TEST

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LX:            rjmp    LX

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LOOP:        rjmp    LOOP

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TEST:        ldi     r16,             Low(LOOP)
            sts     st_ret_l,         r16
            ldi        r16,            High(LOOP)
            sts        st_ret_h,        r16
            ret



После возвращения из процедуры TEST, мы должны попасть в цикл LX, если не шаманить со стеком.
Но мы попадаем в цикл LOOP, так как переписали в стеке адрес возврата напрямую.
То есть, без использования инструкций PUSH / POP. Единственное, что не надо забывать при таком
приеме, это вытащить предварительно все данные, вручную помещенные в стек.

Re: Хитрые, необычные алгоритмы и код

Пн авг 25, 2014 07:24:49

наверное, все знают, но рискну высказаться: АЦП отлично может функционировать в качестве дополнительного таймера с фиксированным периодом счета...

Re: Хитрые, необычные алгоритмы и код

Пн авг 25, 2014 08:01:40

...причем в случае с AVR можно задать 7 различных делителей тактовой для АЦП и получить "нестандартные" для обычных таймеров (кроме режимов CTC) периоды в 13, 26, 52, 104, 208, 416, 832 и 1664 такта.

Re: Хитрые, необычные алгоритмы и код

Вт авг 26, 2014 10:34:32

ARV писал(а):АЦП отлично может функционировать в качестве дополнительного таймера с фиксированным периодом счета...

Хм... А какой-нить УАРТ/И²С/СПИ тоже ведь так можно настроить?

Re: Хитрые, необычные алгоритмы и код

Вт авг 26, 2014 10:39:11

Gudd-Head писал(а):
ARV писал(а):АЦП отлично может функционировать в качестве дополнительного таймера с фиксированным периодом счета...

Хм... А какой-нить УАРТ/И²С/СПИ тоже ведь так можно настроить?
:))) можно, но...
...это будет "однократный" таймер, который в обработчике прерываний придется перезапускать
...этот "таймер" будет гадить на внешних линиях ввода-вывода

Re: Хитрые, необычные алгоритмы и код

Вт авг 26, 2014 10:50:00

ARV писал(а):...это будет "однократный" таймер, который в обработчике прерываний придется перезапускать

Ну, 1-2 команды для запуска, думаю, не критично.
ARV писал(а):...этот "таймер" будет гадить на внешних линиях ввода-вывода

Это да. А вот у АЦП, если мультиплексором выбрать такую комбинацию бит, которым ничего не соответствует в ДШ будет нормально работать? И не задействует никакую ногу? (напр., MUX3:0 = 1000...1101 для 8-й меги). Если так, то это преимущество использования АЦП.

Зато УАРТ более гибкий в плане временного периода прерывания: ≈ Fтакт/(16*UBRR), где UBRR 12 бит у 8-й меги, т.е. можно получить частоту Fтакт/32 000.

Re: Хитрые, необычные алгоритмы и код

Вт авг 26, 2014 11:13:36

Gudd-Head писал(а):А вот у АЦП, если мультиплексором выбрать такую комбинацию бит, которым ничего не соответствует в ДШ будет нормально работать? И не задействует никакую ногу?

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

Re: Хитрые, необычные алгоритмы и код

Вт авг 26, 2014 11:23:17

с АЦП все просто, как трусы: мультиплексор может быть скоммутирован как угодно! вы ведь не используете результат преобразования, поэтому вам абсолютно все равно, что именно вы там будете оцифровывать. любая ножка, которая как бы вход АЦП, при этом может быть нормальным выходом и выполнять свои задачи в соответствии с вашей схемой.

Re: Хитрые, необычные алгоритмы и код

Пт сен 05, 2014 17:08:59

Еще один прием.
Допустим, у нас есть 100 команд и 100 действий.
Команды идут подряд (от нуля до 99)
К примеру приняли пакет по UART с адресом, командой, данными и CRC.
Команду надо как-то обработать. Длинный swith в Си или cpi r16, CMDx не канает, так как ест много тактов и памяти.
Но есть другое решение.

Вариант на Си:
Код:


#include <avr\io.h>
#include <util\delay.h>
#include <avr\pgmspace.h>

typedef int(*myFunc)(void);

int func1(void);
int func2(void);
int func3(void);

myFunc fArr[3] = {func1, func2, func3};

//----------
int main()
    {
        while(1)
        {
            for(int i = 0; i < 3; i++)
            {
                fArr[i]();
            }
        }
    }
//----------
int func1(void)
{
    PORTB=0x01;
    return 0;
}

int func2(void)
{
    PORTB=0x02;
    return 0;
}

int func3(void)
{
    PORTB=0x04;
    return 0;
}
 


В данном примере вызываются три функции по очереди. Если вместо индекса i
подставить значение команды, то вызовется соответствующая функция.

То же самое на АСМе: (выдран из рабочего проекта)

Код:
;---------- Адреса процедур
HANDLERS:
.dw MPT, RSTCFG, OPNCFG, MENUCFG, LIGHT_CFG, US_OV, STAT_CFG, SENS_CFG, CLR_CFG, MASTERCFG


;---------- конфигурации
CONFIG:
rcall         KEYREQ ;Опрос клавиатуры
cpi           temp0,            _NO_BT ; ничего не нажато (0xFF)
breq          CONFIG
cpi           temp0,            _CLR ;нажата кнопка "Сброс" (0x0F)
breq          CFGEND
cpi           temp0,            _A ; нажаты кнопки от А до Enter (всего 16 кнопок. 0...9, A, B, C, D, Enter, CLR) = (0x00...0x0F)
brsh          CFGERR
ldi           ZL,               Low(HANDLERS*2) ; нажаты кнопки от 0 до 9
ldi           ZH,               High(HANDLERS*2) ; соответственно, выбран один из пунктов меню. Загружаем адрес начала массива адресов процедур
lsl           temp0 ; Из-за типичной для AVR организации памяти, приходится домножать номер процедуры на два простым сдвигом влево.
clr           temp1
add           ZL,               temp0 ;смещаемся до нужного элемента массива
adc           ZH,               temp1
lpm           dx2,              Z ; грузим адрес из массива
adiw          Z,                0x01
lpm           dx3,              Z
mov           ZL,               dx2
mov           ZH,               dx3
icall ; вызываем процедуру
rjmp          CONFIG
CFGERR:
rcall         ERR ; индикация ошибки
rjmp          CONFIG
;---------- Выход из меню
CFGEND:
rcall         INIT
ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;----------
MPT:
;процедура номер ноль
ret

;----------
RSTCFG:
;процедура номер раз
ret

;----------
OPNCFG:
;процедура номер два
ret

;----------
MENUCFG:
; процедура номер три
ret

;----------
LIGHT_CFG:
;процедура номер четыре
ret

;----------
US_OV:
;процедура номер пять
ret

;----------
STAT_CFG:
;процедура номер шесть
ret

;----------
SENS_CFG:
;процедура номер семь
ret

;----------
CLR_CFG:
;процедура номер восемь
ret

;----------
MASTERCFG:
;процедура номер девять
ret



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

Данные приемы экономят как память, так и такты.

Re: Хитрые, необычные алгоритмы и код

Пт сен 05, 2014 17:53:37

Скорее, предыдущий совет - это то, как не надо делать. Точнее так. Использования обработчиков и таблицы переходов само по себе не плохо, но не в случае оптимизации switch, потому что:
1. Мы экономим на семечках. В подавляющем большинстве случаев обработка команд происходит в фоне и выигрыш в несколько тактов не стоит усложнения кода. Иначе стоит задуматься об архитектуре приложения, а не заниматься бесполезной оптимизацией.
2. Конструкция switch с более-менее равномерным диапазоном значений с большой долей вероятности преобразуется компилятором в ту же самую таблицу переходов. Зачем делать за компилятор то, что он может сделать лучше?

Re: Хитрые, необычные алгоритмы и код

Пт сен 05, 2014 20:35:07

Намного проще и правильней выглядит вот такой вариант:
Код:
void Handle(Command cmd)
{
    case Command1: Handle1(); break;
    case Command2: Handle2(); break;
    case Command3: Handle3(); break;
    case Command4: Handle4(); break;
    case Command5: Handle5(); break;
    case Command6: Handle6(); break;
    case Command7: Handle7(); break;
    case Command8: Handle8(); break;
    case Command9: Handle9(); break;
    case CommandA: HandleA(); break;
    case CommandB: HandleB(); break;
    case CommandC: HandleC(); break;
    case CommandD: HandleD(); break;
    case CommandE: HandleE(); break;
    case CommandF: HandleF(); break;

    default: Handle0(); break;
}


Весь код из switch так же выносится в обработчики. Ориентироваться в таком switch очень легко, даже если он длинный. Не надо смотреть в таблицу, чтобы понять какая команда какому обработчику соответствует. Не надо шерстить таблицу, чтобы заменить один на другой, удалить, или добавить обработчик. Легко реализуется обработчик по умолчанию. Нет проблем, если команды имеют большие номера. Нет проблем, если номера команд стоят далеко друг от друга. Компилятор оперирует на уровне отдельных инструкций, у него гораздо больше возможностей оптимизировать всё это.

Если что, предлагаю писать в личку, чтобы не засорять форум посторонними беседами.

Re: Хитрые, необычные алгоритмы и код

Сб сен 06, 2014 22:17:19

mezonda ладно. Пусть пример на Си останется для тех, кто плохо понимает ассемблер или только начал изучать его (но уже был знаком с Си).
То есть, пример на Си описывает принцип работы примера на ассемблере.
Но на АСМе оно пригодится. У меня так в одном девайсе клавиатуная менюшка реализована. По примеру охранных устройств от компаний DSC и Cerber.
У крупных ППКОП от DSC количество пунктов в главном меню достигает нескольких сотен, а в качестве "мозга" стоит PIC средней паршивости. (на некоторых моделях видел даже Z80)
Там наверняка такая реализация.
Ответить