Динамическая индикация - поток сознания :)

Как я понял, нефильтрованый поток чистого сознания есть основа большиства блогов, а чем я хуже :)
И так, здраствуйте люди местные, сами мы не еще пока чайники, посему не бейте сильно.
Сподвигло к написанию сего опуса тема ув. OSS про дин. индикацию.
Для начала — Динамическая индикация – это метод отображения целостной картины через быстрое последовательное отображение отдельных элементов этой картины. Причем, «целостность» восприятия получается благодаря инерционности человеческого зрения.
А то, что использовал OSS есть суть статическая. Дальше будет попытка реализовать его ТЗ :)
Для начала, я считаю, надо разделить вывод на экран и просчет положения точки. Вывод на экран должен быть быстрым, 25 кадров на каждый столбец, и того 8 столбцов — 200 Гц. Можно все это конечно реализовать на задержках, но я, с тех пор, как освоил таймер, люблю таймеры и всякие флаги.
Сначала напишу концепцию, потом будет листинг готового кода.
И так, запускаем таймер0, с делителем 1024 и срабатыванием по сравнению, где-то раз в 5 мсек.
При срабатывании таймера ставится флаг, который потом обрабатывается в теле програмы вместе с вторичными счетчиками-таймерами.
в основной проге, при установке флага таймера, идет проверка на вторичный таймер на 1 сек. (200 срабатываний таймера 0).
Первичный таймер будет управлять собственно динамической индикацией, вторичный — менять данные о положении точек.
Т.к. вывод на экран динамический, я считаю, что прозрачнее создать буфер экрана, который содержит фактически слепок экрана, сканер экрана будет по нему циклически пробегать, считывая данные, а в другом месте проги будет процедура, которая периодически меняет значение этого буфера.
Дальше код, как это реализовано.

.include "m32def.inc"   ; Используем ATMega32

.Macro  Init_Main_Timer_0

            LDI Temp1,78                    ;78  тактов - где-то 5 мсек. до сравнения.
            OUT OCR0,Temp1

            LDI Temp1,(1<<CS02)|(1<<CS00)|(1<<WGM01) ; Делитель на 1024, режим 2 (сброс по сравнению)
            OUT TCCR0,Temp1                   

            IN Temp1,TIMSK
            ORI Temp1,(1<<TOIE0)|(1<<OCIE0)            ; прерывание по сравнению
            OUT TIMSK,Temp1

            IN  Temp1,SFIOR
            ORI Temp1,1<<PSR10   ; Reset Timer0/1 prescaler
            OUT SFIOR,Temp1




.EndMacro

; Макрос, сдвигающий точку по одной из координат
; Т.к. сдвигаем точку в пределах 0-7, то старший бит переменной будет содержать информацию
; о направлении сдвига, т.е. увеличиваем или уменьшаем переменную
.Macro    Mover 
            LDS Temp1,@0
            ANDI Temp1,0b01111111
            LDS Temp2,@0
            ANDI Temp2,0b10000000
; На этом этапе разделили саму переменную, которая отражает положение точки на экране
; И флаг направления ее движения

; Осуществляем манипуляции с точкой в соответствии с флагом
            SBRC Temp2,7
                INC Temp1
            SBRS Temp2,7
                DEC Temp1
; Проверяем достигла ли точка края экрана, если да - меняем фла направления движения
            CPI Temp1,7
            BRNE M1
                ANDI Temp2,0        
    M1:    
            CPI Temp1,0
            BRNE M2
                ORI Temp2, 0b10000000
    M2:    
; Склеиваем координаты точки и флаг и кладем назад
            OR Temp1,Temp2
            STS @0,Temp1
.EndMacro

; Макрос, формирующий в буфере точку
.Macro Store_LED_Out
; Сначала в соответствии с адресом точки по вертикали получаем ее графическое положение
; Т.е. "1" в месте, где точка по вертикали расположена
            LDS Temp1,@1
            ANDI Temp1,0b01111111
            LSL Temp1                        ; Умножаем У на 2 (путем сдвига влево)
            LDI Temp2,0
            LDI    ZL, Low(LED_Decoder*2)    ; Загружаем адрес нашей таблицы. Компилятор сам посчитает
            LDI    ZH, High(LED_Decoder*2)    ; Умножение и запишет уже результат.Старший и младший байты.
            ADD    ZL, Temp1                    ; Добавляем смещение равное фактически отображаемому адресу*2
            ADC    ZH, Temp2
            LPM    Temp3,Z+                    ; Т.к. область программы адресуется словами, то считываем старший байт слова
; В Темп3 теперь байт, содержащий "1" в месте положения точки по вертикали
; Считываем из буфера соответствующий переменной Х столбик, и накладываем на этот столбик 
; Байт с нашей точкой, и сохраняем столбик снова в буфер
        LDS Temp1,@0
        ANDI Temp1,0b01111111
        LDI ZL,Low(LED_Out)     ; Загружаем в Z начало переменных 
        LDI ZH,High(LED_Out)     ; в которых хранятся значения столбцов
        ADD ZL,Temp1                 ; Добавляем смещение соответствующее
        LDI Temp2,0                     ; метке столбца
        ADC ZH,Temp2
        LD Temp1,Z                    ; Накладываем на столбец из памяти наш столбец с точкой
        OR Temp1,Temp3
        ST Z,Temp1                     ; Сохраняем склееный столбец
            


.EndMacro


; RAM =====================================================
        .DSEG            ; Сегмент ОЗУ
c1sec_Counter:                .byte   1 ; Переменная-счетчик для вторичного таймера 1 сек.

X1:                            .byte    1
Y1:                            .byte    1
X2:                            .byte    1
Y2:                            .byte    1
LED_Out:                    .byte    8

; END RAM ================================================= 
; FLASH ===================================================
         .CSEG
         .ORG $000        ; (RESET) 
         RJMP   Reset
         .ORG INT0addr        ; (INT0) External Interrupt Request 0
         RETI             
         .ORG INT1addr        ; (INT1) External Interrupt Request 1
         RETI             
         .ORG INT2addr        ; (INT2) External Interrupt Request 2
         RETI              
         .ORG OC2addr        ; (TIMER2 COMP) Timer/Counter2 Compare Match
         RETI             
         .ORG OVF2addr        ; (TIMER2 OVF) Timer/Counter2 Overflow
         RETI              
         .ORG ICP1addr        ; (TIMER1 CAPT) Timer/Counter1 Capture Event
         RETI             
         .ORG OC1Aaddr        ; (TIMER1 COMPA) Timer/Counter1 Compare Match A
         RETI           
         .ORG OC1Baddr        ; (TIMER1 COMPB) Timer/Counter1 Compare Match B
         RETI             
         .ORG OVF1addr        ; (TIMER1 OVF) Timer/Counter1 Overflow
         RETI
         .ORG OC0addr        ; (Timer/Counter0) Compare Match
         RJMP Timer0_Compare
         .ORG OVF0addr        ; (Timer/Counter0) Overflow
         RETI
         .ORG SPIaddr        ; (SPI, STC) Serial Transfer Complete
         RETI             
         .ORG URXCaddr        ; (USART, RXC) USART, Rx Complete
         RETI             
         .ORG UDREaddr        ; (USART, UDRE) USART Data Register Empty
         RETI              
         .ORG UTXCaddr        ; (USART, TXC) USART, Tx Complete
         RETI             
         .ORG ADCCaddr        ; (ADC) ADC Conversion Complete
         RETI             
         .ORG ERDYaddr        ; (EE_RDY) EEPROM Ready
         RETI             
         .ORG ACIaddr        ; (ANA_COMP) Analog Comparator
         RETI             
         .ORG TWIaddr        ; (TWI) Two-wire Serial Interface
         RETI             
         .ORG SPMRaddr        ; (SPM_RDY) Store Program Memory Ready
         RETI             
 
     .ORG   INT_VECTORS_SIZE          ; Конец таблицы прерываний
 
; Interrupts ==========================================================

; По срабатыванию таймера - ставим флаг
Timer0_Compare:
            Push Temp1       
            IN Temp1,SREG    ; Сохраняем значение SREG на всяк случай
            Push Temp1
                
                ORI Timer_Flags_Register,0b00000001
            Pop Temp1
            OUT SREG,Temp1  ; Восстанавливаем значение SREG
            Pop Temp1

        RETI
        
; END Interrupts =====================================================

; Переменные, сохраненные в FLASH ===========================================
; Массив, преобразующий число в сдвиг
LED_Decoder: .dw 0b00000001,0b00000010
             .dw 0b00000100,0b00001000
             .dw 0b00010000,0b00100000
             .dw 0b01000000,0b10000000

; Конец блока переменных в FLASH ========================================


; Тут будут процедуры =====================================
; процедура очистки экрана
; Вызывается каждый раз перед расчетом нового положения точек
CLR_Led:
            LDI Temp1,0
            STS LED_Out,Temp1
            STS LED_Out+1,Temp1
            STS LED_Out+2,Temp1
            STS LED_Out+3,Temp1
            STS LED_Out+4,Temp1
            STS LED_Out+5,Temp1
            STS LED_Out+6,Temp1
            STS LED_Out+7,Temp1
RET





; Конец блока процедур ====================================


Reset:


.DEF Temp1=R16
.DEF Temp2=R17
.DEF Temp3=R18
.DEF Temp4=R19
.DEF Timer_Flags_Register=R20
;Флаги: 0 - Timer0 overflow - 5 мсек.
;        1 - 1 сек. счетчик

.DEF LED_Pointer=R21


    LDI R16,Low(RAMEND)            ; Инициализация стека
    OUT SPL,R16        
     LDI R16,High(RAMEND)
    OUT SPH,R16                    ; Конец инициализации

    LDI    ZL,30       ; +-----------------------+
    CLR    ZH            ; |                          |
    DEC    ZL            ; | Очистка РОН (R00-R31) |
    ST    Z,ZH        ; |                            |
    BRNE PC-2        ; +-----------------------+

RAM_Flush:    LDI    ZL,Low(SRAM_START)    ;======= Очистка RAM
            LDI    ZH,High(SRAM_START)    ; Помещаем адрес начала RAM в Z
            CLR    R16                    ; Очищаем R16
            ST     Z+,R16                ; Сохраняем 0 в ячейку памяти
            CPI    ZH,High(RAMEND)        ; Достигли конца оперативки?
            BRNE    PC-2            ; Нет? Крутимся дальше!
 
            CPI    ZL,Low(RAMEND+1)    ; А младший байт достиг конца?
            BRNE    PC-4            ; Честно слизана у Di Halt-а :)
            


            Init_Main_Timer_0

              ldi Temp1,0b11111111                     ;настройка портов
              out ddrb,Temp1

              ldi Temp1,0b11111111
              out ddra,Temp1

            LDI LED_Pointer,0 ; Начальная установка метки сканирования столбцов экрана
    
 ; Загрузка начальных положений точек
            LDI Temp1,0b00000010
            STS X1,Temp1

            LDI Temp1,0b00000011
            STS Y1,Temp1

            LDI Temp1,0b10000101
            STS X2,Temp1

            LDI Temp1,0b10000011
            STS Y2,Temp1



    SEI                        ; Прерывания глобально вкл.

; Конец начальной инициализации =========================================

Main:

            SBRS Timer_Flags_Register,0        ;Проверка флага переполнения нулевого таймера
            JMP Skip_Timed_Events
;========== Обработчик по флагу нулевого таймера
    ;====== Блок обработки вторичных счетчиков 1 сек.
            LDS Temp1,c1sec_Counter    ;Загрузка счетчика 1 сек.
            INC Temp1
            STS c1sec_Counter,Temp1
            CPI Temp1,200            ; Проверка на достижение интервала в 1 сек.
            BRNE End_Secondary_Timers_Settings
                LDI Temp1,0            ;Обнуление счетчика 1мсек.
                STS c1sec_Counter,Temp1
                ORI Timer_Flags_Register,0b00000010 ; Установка флага 1 сек.

End_Secondary_Timers_Settings:
    ;====== Конец блока обработки вторичных счетчиков



            SBRS Timer_Flags_Register,0
            JMP l5msec_skip
    ;====== Обработка событий по 5 мсек.
    ; В блоке по 5 мсек будет обновление индикатора, циклическое
            INC LED_Pointer
            CPI LED_Pointer,8
            BRNE Skip_LED_Pointer_Reset
                LDI LED_Pointer,0
Skip_LED_Pointer_Reset:
    ; Выводим бегущую единицу на общие катоды
            MOV Temp1,LED_Pointer
            LSL Temp1                        ; Умножаем LCD_Pointer на 2 (путем сдвига влево)
            LDI Temp2,0
            LDI    ZL, Low(LED_Decoder*2)    ; Загружаем адрес нашей таблицы. Компилятор сам посчитает
            LDI    ZH, High(LED_Decoder*2)    ; Умножение и запишет уже результат.Старший и младший байты.
            ADD    ZL, Temp1                    ; Добавляем смещение равное фактически отображаемой цифре*2
            ADC    ZH, Temp2
            LPM    Temp1,Z+                    ; Т.к. область программы адресуется словами, то считываем старший байт слова
            OUT PortB,Temp1
                
    ; Выводим из памяти столбец, соответствующий указтелю            
        LDI ZL,Low(LED_Out)     ; Загружаем в Z начало переменных 
        LDI ZH,High(LED_Out)     ; в которых хранятся значения разрядов
        ADD ZL,LED_Pointer                 ; Добавляем смещение соответствующее
        LDI Temp2,0                     ; метке столбца
        ADC ZH,Temp2
        LD Temp1,Z                     ; Грузим в Темп1 значение столбца
            COM Temp1
            OUT PortA,Temp1

                ANDI Timer_Flags_Register,~0b00000001 ;Сброс флага 5 мсек.
    ;====== Конец обработки событий по 5 мсек.            
l5msec_skip:



            SBRS Timer_Flags_Register,1
            JMP l1sec_skip

    ;====== Обработка событий по 1 сек.
                NOP
            Call CLR_Led
            Mover X1
            Mover Y1



            Mover X2
            Mover Y2
            
            Store_LED_Out X1,Y1

            Store_LED_Out X2,Y2




                ANDI Timer_Flags_Register,~0b00000010    ;Сброс флага 1 сек.
    ;====== Конец обработки событий по 1 сек.
l1sec_skip:
                

;========== Конец обработчика по флагу нулевого таймера
Skip_Timed_Events:        
            NOP
            NOP
            NOP


            
            
JMP Main
; EEPROM ==================================================
        .ESEG            ; Сегмент EEPROM



Вот так несколько сумбурно оно выглядит :)
На сим поток сознания иссяк :)
ссылка на проект и файл протеуса, чтобы не тупить в протеус часами стоит поменять константу сравнения вторичного таймера с 200 на 20
194.187.50.224/led-matrix.rar
2 OSS — у меня Мега32, у них с Мега 16 помоему не совпадают адреса векторов прерываний, надо смотреть ДШ
  • +1
  • 07 апреля 2011, 12:34
  • Kitaro

Комментарии (9)

RSS свернуть / развернуть
блин круто
спасибо
а ты бы на конкурс выставил статейку
0
  • avatar
  • oss
  • 07 апреля 2011, 18:55
да как-то не тянет сей опус на конкурс :)
0
хе работает )))))
изменил CPI Temp1,200 на CPI Temp1,10
щас вроде похоже стало на то что пиксели бегают
надо еще поковыряться )))))
0
Хм… наверное я забыл уточнить, что у меня Мега на 16МГц, и в таком случает Таймер0 срабатывает раз в 5 мсек, 200 раз по 5 — это 1 секунда, точки будут бегать медленно и печально. Имеет смысл наверное пересчитать константы и делитель таймера для другого кварца.
0
ну это не так и страшно
главное работает хыы как я и хотел ))))))
0
ну почему. может осцилл выиграешь?
0
Какая то сильно сложная программа получилась, а что там в результате должно быть видно на матричном экране? В свое время делал бегущую строку на матричном экране 5х7, прикольно получается, у меня как то проще было.
0
Во даже алгоритм нашел. Вся идея кстати заключалась, в размещении таблицы в памяти, а разворачивать букву по столбцам, а потом по таймеру смещать адрес начала таблицы на один, получалась бегущая строка. Вообще в статьях сильно не хватает алгоритмов, весь код читать лень, а взглянул на алгоритм и все понятно сразу.
0
тут два пикселя бегают по матрице 8х8
от одного угла к другому
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.