Среднечастотный частотомер на AVR. Часть 2, статическая индикация.

  В первой части статьи рассматривался вывод на дисплей с динамической индикацией. В этой будет рассмотрен вывод на дисплеи со статической индикацией. Как правило это достигается применением внешнего контроллера дисплея.

  Для кода применялся тот же включаемый файл FQMacros.inc, что и в первой части. Код регенерации динамической индикации из программы удален.

Статическая индикация на дисплее с торговой маркой TIC (TIC71, TIC265).

  Индикаторы TIC представляют собой сборки СoG (чип на стекле) состоящие из многоразрядного семисегментного статического LCD индикатора и управляющего им одного или двух контроллеров ML1001. ML1001 это 40-разрядный сдвигающий CMOS регистр с защелками на выходах разрядов. Ближайший функциональный аналог — 74HC595. Кроме того он содержит встроенную схему регенерации LCD индикатора со встроенным генератором (обычно 125-2000Hz). Частота регенерации задается при изготовлении индикатора. Исполнение бескорпусное, после соединения индикатора и контроллера, последний заливается каплей компаунда. Выпускаются на заказ тайваньской фирмой Ampire, встречаются и в продаже.
  Управление стандартное для SPI, входы данных (DIN), синхроимпульсов (DCLK) и импульс переноса данных на выходы защелок (LOAD). Некоторые индикаторы имеют дополнительные вход LCLK и/или выход DOUT. И если с DOUT все понятно, это выход сдвигового регистра для каскадирования, со входом LCLK интереснее. Это вход от внешней схемы регенерации LCD дисплея. Казалось бы зачем, весь контроллер уже содержит внутреннюю схему регенерации? Это сделано для снижения мощности токопотребления дисплея. Индикатор использующий внутреннюю схему может потреблять 5..50mkA. Зависит от размера и кол-ва символов, частоты регенерации и напряжения питания. Если используется внешняя (15-30Hz), энергопотребление может быть значительно снижено (1mkA и менее).
  Синхроимпульсы (DCLK) могут следовать с частотой до 500kHz, системный таймер программы должен ограничить частоту тактирования этим значением. Учитывая что мы имеем дело со статическим индикатором, и вывод на него производится только один раз за измерение, логика работы системного таймера немного изменена по сравнению с динамической индикацией.
  Теперь системный таймер основное время цикла работает с прескалером 1024 на частоте 8MHz/1024/256=~30,5Hz и занимается опросом кнопок. Как только программа подготовила результат измерения для вывода на индикатор, прескалер таймера закорачивается и выдача результата на индикатор происходит на частоте 8MHz/256=31,25kHz. После вывода таймер опять перезапускается с прескалером 1024 до следующей готовности результата измерений.
  Это был первый вариант, он и остался окончательным. Я пробовал увеличивать частоту системного таймера, т.е. загружать его делителем в прерывании. Выигрыш минимальный, а прерывание усложняется, нет смысла.
  Вывод результата на индикацию побитно по интерфейсу SPI производится флаговой подпрограммой. Ее особенностью является настраиваемый темп вывода. С какой периодичностью будет выставляться флаг MRK_SPI, с той же периодичностью будут выводиться символы по SPI. Периодичность флага определяет системный таймер.

;------ Таблица прерываний -----------------------------------------------------;
; . . . . . . . . . . . . . . . . . ; пропущенная часть
.ORG OVF0addr
    IN    Safe, SREG                ;
    SBRC  BITx1, MRK_Disp           ; Проверить флаг готовности результатов
    SBR   BITx1, (1<<MRK_SPI)       ; Установить флаг вывода бита
    SBRS  BITx1, MRK_Disp           ; Проверить отсутствие флага готовности результатов
    SBR   BITx1, (1<<MRK_Key)       ; Установить флаг опроса клавиатуры
    OUT   SREG, Safe                ;
    RETI                            ;
.ORG INT_VECTORS_SIZE
;
.DEF    NumMask     = R2	    ; Номер выводимой битовой маски
.DEF    NumBit      = R3	    ; Номер выводимого бита 
.DEF    BitMask     = R4	    ; Динамичная битовая маска
;
;== Вывод одного бита на индикатор, MRK_Dyn = 1 ================================;
    SBRS   BITx1, MRK_SPI           ; Проверить флаг готовности вывода
    RJMP   locMP_09                 ; Если не готов, пропуск
; ------------------------------------------------------------------------------;
    SBRC  BitMask, 0                ; проверка нулевого бита
    RJMP  locMP_05                  ; 
    CBI   PORTD, DCLK               ; Установить пин в 0
    CBI   PORTB, DOUT               ; -//-
    SBI   PORTD, DCLK               ; -//-
    RJMP  locMP_06                  ;
locMP_05:                           ;
    CBI   PORTD, DCLK               ; Установить пин в 1
    SBI   PORTB, DOUT               ; -//-
    SBI   PORTD, DCLK               ; -//-
locMP_06:                           ;
    LSR   BitMask                   ; Сдвинуть маску вправо (с младшего бита)
    DEC   NumBit                    ; Уменьшить номер выводимого бита
    BRPL  locMP_08                  ; Вывод маски не закончен
    LDIL  NumBit, 7                 ;
;
    DEC   NumMask                   ; Уменьшить номер выводимой битовой маски
    BRPL  locMP_07                  ; Вывод числа не закончен, новая маска
    ; завершение вывода	
    CBI   PORTD, LOAD               ; Записывающий импульс
    CBI   PORTB, DOUT               ; -//-
    SBI   PORTD, LOAD               ; -//-
    CBR   BITx1, (1<<MRK_Disp)      ; сбрасываем флаг вывода результата
    OUTI  TCCR0, (1<<CS02)|(1<<CS00); перезапустить таймер 0, прескалер 1024
    RJMP  locMP_08                  
locMP_07:
    LD    BitMask, X+               ; Новая битовая маска
locMP_08:	
    CBR   BITx1, (1<<MRK_SPI)       ; сбрасываем флаг вывода одного бита
locMP_09:

Перед запуском подпрограммы требуется предварительная подготовка, установить параметры ее работы, перезапустить системный таймер.

SetDisplayData:                     ; установка параметров работы SPI
    LDIL  NumMask, BufSize-1        ; кол-во символов - 1
    LDIL  NumBit, 7                 ; 8 бит в маске
    LDIDX X, RAM_Symbol             ; адрес буфера символов
    LD    BitMask, X+               ; первая битовая маска
    RET
;
;== Вывод результата на индикацию, MRK_Good = 1 ================================;
    SBRS  BITx1, MRK_Rdy            ; если нет  флага начала обработки, на выход
    RJMP  locMP_03                  ;
    OUTI  TCCR0, (1<<CS00)          ; перезапустить таймер 0, прескалер 0
    RCALL  SetDisplayData           ; установка параметров работы
    SBR   BITx1, (1<<MRK_Disp)      ; разрешить вывод битов по SPI
    CBR   BITx1, (1<<MRK_Rdy)       ; сбросить флаг текущего шага 
locMP_03: ;=====================================================================;

  Применение отдельной подпрограммы SetDisplayData вызвано тем, что выводить данные c одинаковыми параметрами требуется в двух местах. Сначала при инициализации выводится номер версии, в последующем результат измерений.
  К первому обращению подпрограмма вывода должна уже иметь в переменной BitMask первый символ из буфера (битовую маску) и указатель на следующий символ. После передачи первого символа, остальные она подгружает сама руководствуясь их кол-вом NumMask. Такое построение алгоритма впоследствии позволит адаптировать подпрограмму к другим контроллерам.

  Рассмотрим в Proteus работу программы в течении цикла. Вывод производится на 9-разрядный дисплей TIC71, TIC265. Они различаются только наличием (TIC71) или отсутствием (TIC265) вывода LCLK. Ниже график работы программы со второй по третью секунду. Первые три сигнала DOUT, DCLK, LOAD это управление индикатором, LCLK — регенерация дисплея, вызывается из подпрограммы опроса кнопок.




;== Опрос клавиатуры, MRK_Key=1 ================================================;
    SBRS  BITx1, MRK_Key            ; если нет  флага начала обработки, на выход
    RJMP  locMP_Kb
//
    IN    Temp, PortB               ;
    LDI   Cycler, (1<<LCLK)         ;
    EOR   Temp, Cycler              ; инвертируем бит LCLK
    OUT   PortB, Temp               ;
//
; Обработка кнопок, не реализовано
//
    CBR   BITx1, (1<<MRK_Key)       ; сбросить флаг текущего шага
locMP_Kb:

  Развернем экран на интересующую нас область. Первые четыре сигнала те же, а Tst0, Tst1, Tst2 это выполнение подпрограмм BINtoPackBCD, PackBCDtoUnpackBCD, UnpackBCDtoSymbolsCode соответственно. При желании курсорами можно измерить время исполнения любой части программы. В первой части уже измерялось время исполнения подпрограммы BINtoPackBCD — 192 mks, оно не изменилось, измерим теперь время вывода на дисплей 9 разрядов по SPI — 2,28 ms, чуть больше 0,2% времени выполнения цикла.



Статическая индикация на сдвиговых регистрах 74HC595.

  Такая индикация для LED индикаторов неоднократно описана в литературе, легко находится поиском в инете, вот один из вариантов — Статический светодиодный цифровой индикатор, управляемый по SPI.
  Сама программа почти не отличается от программы управления индикаторами TIC, ведь по управлению они аналогичны. Различие в том, что регистры 74HC595 имеют двухтактные выходы "Push-Pull" и могут управлять LED индикаторами как с общим катодом, так и с общим анодом. Токи выходных каскадов и для низкого и для высокого уровней составляют 35 mA, что более чем достаточно. В программу заложена возможность управления обоими типами индикаторов.
  Достигнуто это с помощью средств условной компиляции препроцессора ассемблера. В программе определен дефайн, его применение просто инвертирует битовые маски семисегментных символов.

;#define Common_Anode_Display    ; дисплей с ОА, инверсное управление
;
;==== Битовые маски для дисплея ================================================;
;            a
;            --
;          f|  |b
;           g--
;          e|  |c
;            -- .h
;            d
;
.EQU    Seg_A       = 0b10000000 
.EQU    Seg_B       = 0b01000000
.EQU    Seg_C       = 0b00100000 
.EQU    Seg_D       = 0b00010000 
.EQU    Seg_E       = 0b00001000 
.EQU    Seg_F       = 0b00000100
.EQU    Seg_G       = 0b00000010  
.EQU    Seg_H       = 0b00000001 
;
.SET    CH_0        = Seg_A | Seg_B | Seg_C | Seg_D | Seg_E | Seg_F
.SET    CH_1        = Seg_B | Seg_C
.SET    CH_2        = Seg_A | Seg_B | Seg_D | Seg_E | Seg_G
.SET    CH_3        = Seg_A | Seg_B | Seg_C | Seg_D | Seg_G
.SET    CH_4        = Seg_B | Seg_C | Seg_F | Seg_G
.SET    CH_5        = Seg_A | Seg_C | Seg_D | Seg_F | Seg_G
.SET    CH_6        = Seg_A | Seg_C | Seg_D | Seg_E | Seg_F | Seg_G
.SET    CH_7        = Seg_A | Seg_B | Seg_C
.SET    CH_8        = Seg_A | Seg_B | Seg_C | Seg_D | Seg_E | Seg_F | Seg_G
.SET    CH_9        = Seg_A | Seg_B | Seg_C | Seg_D | Seg_F | Seg_G
;
.SET    CH_v        = Seg_C | Seg_D | Seg_E
.SET    CH_IDENT    = CH_1 | Seg_H  ; major номер версии для идентификатора
.SET    CH_NULL     = 0b00000000    ; символ гашения знакоместа
;
#ifdef Common_Anode_Display
    .SET    CH_0        = ~CH_0
    .SET    CH_1        = ~CH_1
    .SET    CH_2        = ~CH_2
    .SET    CH_3        = ~CH_3
    .SET    CH_4        = ~CH_4
    .SET    CH_5        = ~CH_5
    .SET    CH_6        = ~CH_6
    .SET    CH_7        = ~CH_7
    .SET    CH_8        = ~CH_8
    .SET    CH_9        = ~CH_9
;
    .SET    CH_v        = ~CH_v     ; символ версии v
    .SET    CH_IDENT    = ~CH_IDENT ; major номер версии для идентификатора 	 
    .SET    CH_NULL     = ~CH_NULL  ; символ гашения знакоместа
endif
;
TBL_DIGITS:    
    .DB     CH_0, CH_1, CH_2, CH_3, CH_4, CH_5, CH_6, CH_7, CH_8, CH_9  ; 0-9
;-------------------------------------------------------------------------------;

Видно что для индикаторов с ОК и ОА используются разные символы гашения знакоместа. Если для индикатора с ОК это все нули, для индикатора с ОА это все единицы.

;== Преобразование Symbols code <- UnpackBCD с гашением 0, MRK_Symb = 1 ========;
    SBRS  BITx1, MRK_Symb         ; если нет  флага начала обработки, на выход
    RJMP  locMP_02                ;
//
    CLT                           ; очистить Т
    LDIDX X, RAM_UnBCD+BufSize    ;
    LDIDX Y, RAM_Symbol+BufSize   ;
    LDI   Cycler, BufSize         ;
locSm0:
    DEC   Cycler                  ; Z=1 - последняя цифра
    LDIZ  TBL_DIGITS              ; Загрузить адрес таблицы кодов символов
    LD    Temp, -X                ; загрузить неупакованный BCD 
    BRTS  locSm2                  ; если Т установлен
    BREQ  locSm2                  ; если последняя цифра
    TST   Temp                    ;
    BRNE  locSm1                  ; если не ноль
    LDI   Temp, CH_NULL           ; гашение знакоместа индикатора
    RJMP  locSm3                  ;
locSm1:
    SET                           ; установить Т
locSm2:
    ADD   ZL, Temp                ; Найти
    CLR   Temp                    ; нужный 
    ADC   ZH, Temp                ; символ
    LPM   Temp, Z                 ; загрузить код символа
locSm3:
    ST    -Y, Temp                ; сохранить код символа
    TST   Cycler                  ;
    BRNE  locSm0                  ;
//
    SBR   BITx1, (1<<MRK_Rdy)     ; установить флаг следующего шага
    CBR   BITx1, (1<<MRK_Symb)    ; сбросить флаг текущего шага 
locMP_02: ;=====================================================================;


Статическая индикация на дисплее с контроллером MAX7219.

  MAX7219 — компактный драйвер дисплея для 7-сегментных цифровых светодиодных индикаторов с общим катодом до 8 цифр или 64 отдельных светодиодов, совместим с микропроцессорным интерфейсом. Именно этот контроллер применен в статье, послужившей катализатором для написания этой (см. оригинал).
   Особенности применения:
  • Индивидуальный контроль и управление сегментами
  • Выбор BCD декодирование/отсутствие декодирования разрядов
  • Режим Shutdown с низким энергопотреблением (150mkA)
  • Цифровое и аналоговое управление яркостью
  • Дисплей погашен при включении питания
  • Предназначен для светодиодных дисплеев с общим катодом
  • SPI последовательный интерфейс до 10 МГц
  • 24-контактные DIP и SO корпуса
  Во время работы над программой сделал свой вариант перевода даташита. Оформил в виде файла 7219help.chm и приложу к статье (в ZIP архиве), может кому пригодится. Если коротко, контроллер содержит восемь регистров для хранения кодов отображаемых символов и пять регистров управления. Обращение к ним производится по 16-разрядному SPI. В первом байте передается номер регистра, во втором или код символа, или код операции для регистров управления. Причем код символа может передаваться как в символьном виде (каждый бит соответствует своему сегменту), так и напрямую в BCD коде (имеются встроенные дешифраторы). Т.к. первые две секунды отображаются сведения о версии, они выводятся в символьном виде, затем включаются дешифраторы и BCD коды выводятся напрямую. Благодаря этому отпадает операция перекодирования Symbol code < — BCD.
  Максимальная измеряемая частота частотомера 3.200.000 Hz и занимает не более 7 знакомест. Хотя контроллер имеет регистр диаппазона сканирования (Scan Limit, адрес B) и его можно программно настроить на отображение от 1 до 8 знакомест, в программе это не реализовано. Всегда передаются 8 цифр, незначащие нули заменяются на символ гашения 0x0F.
  Также контроллер имеет регистр яркости (Intensity, адрес А), но в программе яркость установлена на максимум, и ее изменение в данном варианте не реализовано.
Заданные константы команд:

;--- Константы MAX7219 ---------------------------------------------------------;
.EQU    NoDecode    = 0b00000000    ; Дешифраторы отключены, адрес 9
.EQU    EnDecode    = 0b11111111    ; Дешифраторы включены, адрес 9
.EQU    Intensity   = 0b00001111    ; Максимальная яркость, адрес A
.EQU    DispCount   = 0b00000111    ; 8 знакомест, адрес B
.EQU    Shutdown    = 0b00000001    ; Режим отображения, адрес C

  В программе используется уже рассмотренная ранее подпрограмма вывода бита по SPI с одним отличием, число выдвигается старшими разрядами вперед. Вспомним, что подпрограмма требует перед запуском занести в переменную BitMask первый символ, и передает NumMask кол-во символов. Нам же требуется сформировать 16-битный пакет, 8 бит адреса и 8 бит символьный код. Поэтому для первого символа заносим в BitMask адрес, передаем указатель на символ в буфере, в NumMask указываем передать один символ. Для остальных символов заносим в BitMask только адрес, в NumMask указываем передать один символ, автоинкремент указателя произведет подпрограмма вывода бита.

;== Вывод результата на индикацию, MRK_Disp = 1 ================================;
    SBRS  BITx1, MRK_Disp           ; если нет  флага начала обработки, на выход
    RJMP  locMP_03                  ;
    OUTI  TCCR0, (1<<CS00)          ; перезапустить таймер 0, прескалер 0
    SBRC  BITx1, MRK_SPI            ; если вывод слова не закончен, на выход
    RJMP  locMP_03
    LDIL  NumMask, 1                ; 1 байт за раз
    LDIL  NumBit, 7                 ; 8 бит в маске
    TST   Cycler                    ; Достигли конца вывода?
    BRNE  locMP_02                  ; Если нет, выводим	следующее слово
    CBR   BITx1, (1<<MRK_Disp)      ; сбросить флаг текущего шага 
    OUTI  TCCR0, (1<<CS02)|(1<<CS00); перезапустить таймер 0, прескалер 1024
    RJMP  locMP_03                  ; Выйти
locMP_02:
    MOV   BitMask, Cycler           ; Номер регистра в первый байт
    DEC   Cycler                    ; Изменим номер регистра
    SBR   BITx1, (1<<MRK_SPI)       ; разрешить слово по SPI
locMP_03: ;=====================================================================;

Предварительно необходимо занести в Cycler кол-во передаваемых символов и указатель на адрес буфера символов. Это сделано в конце предыдущей подпрограммы:

.DEF    Mask_4      = R20           ; маска ниббла
.DEF    PackBCD     = R21           ; упакованный BCD
.DEF    CopyBCD     = R22           ; копия -//-
;
;== Преобразование UnpackBCD <- PackBCD с гашением 0, MRK_B2B = 1 ==============;
;
.EQU    HiNibble    = 0b11110000    ; старший ниббл
.EQU    LoNibble    = 0b00001111    ; младший ниббл
;
    SBRS  BITx1, MRK_B2B            ; если нет  флага начала обработки, на выход
    RJMP  locMP_01                  ;
//
    CLT                             ; очистить Т
    LDIDX X, RAM_UnBCD+BufSize      ; с верхних адресов неупакованных BCD
    LDIDX Y, RAM_BCD_0+SizePackBCD  ; с верхних адресов упакованных BCD
    LDI   Cycler, SizePackBCD       ; кол-во байт упакованных BCD
locSYMB:
    LD    PackBCD, -Y               ; чтение упакованного BCD
    MOV   CopyBCD, PackBCD          ; копия
    ANDI  PackBCD, HiNibble         ; старший ниббл
    BRTS  loc0S1                    ; если Т установлен
    BREQ  loc0S0                    ; если 0
    SET                             ; установить Т
    RJMP  loc0S1                    ;
loc0S0:
    ORI   PackBCD, HiNibble         ; символ гашения MAX7219
loc0S1:
    SWAP  PackBCD                   ; перевернем старший ниббл
    ST    -X, PackBCD               ; в буфер неупакованных BCD
    ANDI  CopyBCD, LoNibble         ; младший ниббл
    BRTS  loc0S3                    ; если Т установлен
    BRNE  loc0S2                    ; если не 0
    CPI   Cycler, 1                 ; самый правый
    BREQ  loc0S3                    ; ноль оставляем
    ORI   CopyBCD, LoNibble         ; символ гашения MAX7219
    RJMP  loc0S3                    ;
loc0S2:
    SET                             ; установить Т
loc0S3:
    ST    -X, CopyBCD               ; в буфер неупакованных BCD
    DEC   Cycler                    ; 
    BRNE  locSYMB                   ;
//
    LDI   Cycler, BufSize           ; кол-во символов на индикацию
    LDIDX X, RAM_UnBCD              ; на начало буфера
//
    SBRS  BITx1, MRK_Beg            ; если нет  флага начальной загрузки, на вывод буфера
    RJMP  locComm                   ;
    LDI   Temp, EnDecode            ; Включить дешифраторы
    STS   RAM_BCD_3, Temp           ; перенести в буфер
    INC   Cycler                    ; добавлена команда
    SUBI  XL, 1                     ; перейти на 
    SBCI  XH, 0                     ; адрес RAM_BCD_3
    CBR   BITx1, (1<<MRK_Beg)       ; сбросить флаг начальной загрузки 
locComm:
//
    SBR   BITx1, (1<<MRK_Disp)      ; установить флаг следующего шага
    CBR   BITx1, (1<<MRK_B2B)       ; сбросить флаг текущего шага 
locMP_01: ;=====================================================================;
;
.UNDEF    Mask_4                    ; R20 
.UNDEF    PackBCD                   ; R21
.UNDEF    CopyBCD                   ; R22

Кроме этого подпрограмма PackBCDtoUnpackBCD заменяет незначащие нули на символ гашения MAX7219 (0x0F), а также проверяется флаг начальной загрузки. Если флаг установлен, к буферу добавляется команда включения дешифраторов. Если точнее буфер RAM_UnBCD расширен за счет старшего байта RAM_BCD_3. Так числа мы уже преобразовали, его порча значения не имеет.
Т.к. вместо 8-разрядного здесь применен 16-разрядный SPI, интересно будет посмотреть как изменилась длительность вывода 8 разрядов на дисплей:



Как и ожидалось, она выросла примерно в два раза, и занимает ~0,4% цикла сканирования.

Статическая индикация на дисплее с контроллером HD44780.

  HD44780 это полноценный контроллер матричного LCD дисплея с собственной системой команд, знакогенератором кодов символов ASCII. Вторая часть таблицы может содержать национальные символы. Восемь символов могут быть запрограммированы пользователем. Хорошо и коротко об этом рассказано здесь Алфавитно-цифровые индицирующие ЖК-модули на основе контроллера HD44780.
  Поэтому управление им кардинально отличается от ранее описанного. Программа написана для четырехпроводного подключения (для данных, еще два вывода — управление), без чтения бита BUSY, т.е. на задержках. Задержки расчитывались под частоту основного контроллера 8 MHz и дисплейного 250-270 kHz.
  Обращение на простых задержках сделано для упрощения, т.к. никаких отрицательных последствий для программы это не несет. При необходимости цикл задержки легко будет прерван коротким прерыванием, а небольшое увеличение времени задержки ничему не мешает. Теперь системному таймеру нет необходимости формировать дисплейные задержки и он постоянно работает с прескалером 1024 для сканирования клавиатуры. Длительности задержек были взяты из даташитов, подобраны и проверены в Proteus.

;----- Константы задержек для контроллера дисплея (частота ATmega 8 MHz) ------;
.EQU    Del40mc     = 0xF9B0        ; 40 mc
.EQU    Del15mc     = 0x5DA2        ; 15 mc
.EQU    Del4_1mc    = 0x199F        ; 4,1 mc
.EQU    Del1_52mc   = 0x096D        ; 1,52 mc
.EQU    Del100mkc   = 0x009C        ; 100 mkc
.EQU    Del37mkc    = 0x0037        ; 37 mkc

Код реализующий задержки:

;------ Подпрограмма задержек контроллера дисплея -----------------------------;
;
.DEF    LDelay       = R20          ; Младший байт 
.DEF    HDelay       = R21          ; Старший байт 
;
LCD_Waiting:                        ; циклическая задержка
    CBI   PortC, LCDEN              ;
locLoop:
    NOP                             ;
    SUBI  LDelay, 1                 ;
    SBCI  HDelay, 0                 ;
    BRCC  locLoop                   ;
    RET                             ;
;
; Отсылка байта команды, на входе:
; HDelay, LDelay - задержка
; Temp - код команды
LCD_SendCmdByte:                    ;
    OUT   PORTC, Temp               ;
    RCALL LCD_Waiting               ; Задержка
    RET                             ;
;
.DEF    Tmp          = R22          ;  Временный регистр 
;
; Отсылка байта данных, на входе:
; HDelay, LDelay - задержка
; Temp - код данных
LCD_SendTetraByte:                  ;
    MOV   Tmp, Temp                 ;
    LSR   Temp                      ; старшую половину 
    LSR   Temp                      ; в середину
    ORI   Temp, LCD_SET             ; сформировать полный байт
    LDIW  HDelay, LDelay, Del37mkc  ; Задержка 37 mkc
    OUT   PORTC, Temp               ;
    RCALL LCD_Waiting               ; Задержка
    LSL   Tmp                       ; младшую половину 
    LSL   Tmp                       ; в середину
    ORI   Tmp, LCD_SET              ; сформировать полный байт
    LDIW  HDelay, LDelay, Del37mkc  ; Задержка 37 mkc
    OUT   PORTC, Tmp                ;
    RCALL LCD_Waiting               ; Задержка
    OUTI  PORTC, LCD_BUSY           ;
    RET                             ;
;
.UNDEF  Tmp                         ; R22
;
LCD_SetStrAddr:                     ; установка на строку
    LDIW  HDelay, LDelay, Del37mkc  ; Задержка 37 mkc
    RCALL LCD_SendCmdByte           ;
    LDI   Temp, LCD_Zero            ; Очистить младший байт
    LDIW  HDelay, LDelay, Del37mkc  ; Задержка 37 mkc
    RCALL LCD_SendCmdByte           ;
    RET                             ;

Отправка байта команды или данных требует предварительного занесения в рабочие регистры слова величины задержки и передаваемого байта. Удобнее это оформить макросами:

;------ MACROS -----------------------------------------------------------------;
.INCLUDE "FQMacros.inc"             ; Макросы
;
; LDIW @0 (регистр R16..R31), @1 (регистр R16..R31), @2 (слово)
.MACRO  LDIW                        ; загрузка константы в два регистра
    LDI @0, High(@2)
    LDI @1, Low(@2)
.ENDM
;
; SendCmd @0 (команда (константа 8 бит)), @1 (задержка (константа 16 бит))
.MACRO  SendCmd                     ; отсылка команды в HD44780
    LDI   Temp, @0                  ; Выставить код
    LDIW  HDelay, LDelay, @1        ; Задержка
    RCALL LCD_SendCmdByte           ;
.ENDM
;
; SendDat @0 (данные (константа 8 бит)), @1 (задержка (константа 16 бит))
.MACRO  SendDat                     ; отсылка данных в HD44780
    LDI   Temp, @0                  ; выставить данные
    LDIW  HDelay, LDelay, @1        ; Задержка
    RCALL LCD_SendTetraByte         ;
.ENDM

Применение этих макросов позволяет оформить отсылку команды/данных в стиле ЯВУ:

    SendCmd LCD_INIT, Del4_1mc      ; Выставить код инициализации

Контроллер HD44780 принимает данные в виде стандартных кодов ASCII, поэтому хранить в программе таблицу перекодировки нет надобности, хотя необходимость перекодировки сохраняется.

;== Преобразование Symbols code <- UnpackBCD, гашение 0, MRK_Symb = 1 ==========;
    SBRS  BITx1, MRK_Symb           ; если нет  флага начала обработки, на выход
    RJMP  locMP_02                  ;
//
    CLT                             ; очистить Т
    LDIDX X, RAM_UnBCD+BufSize      ;
    LDIDX Y, RAM_Symbol             ;
    LDI   Cycler, BufSize           ;
locSm0:
    DEC   Cycler                    ; Z=1 - последняя цифра
    LD    Temp, -X                  ; загрузить неупакованный BCD 
    BRTS  locSm2                    ; если Т установлен
    BREQ  locSm2                    ; если последняя цифра
    TST   Temp                      ;
    BRNE  locSm1                    ; если не ноль
    LDI   Temp, ' '                 ; пробел
    RJMP  locSm3                    ;
locSm1:
    SET                             ; установить Т
locSm2:
    SUBI  Temp, -'0'                ; ASCII смещение
locSm3:
    ST    Y+, Temp                  ; сохранить код символа
    TST   Cycler                    ;
    BRNE  locSm0                    ;
//
    SBR   BITx1, (1<<MRK_Disp)      ; установить флаг следующего шага
    CBR   BITx1, (1<<MRK_Symb)      ; сбросить флаг текущего шага 
locMP_02: ;=====================================================================;

  Существуют клоны контроллера HD44780 с той же системой команд, но немного отличающиеся процедурой и таймингами инициализации, например ST7066U (кликабельно).



  В программе заложена возможность работы с обоими видами контроллеров, если компилируется для ST7066U необходимо раскоментировать соответствующий дефайн.

;#define ST7066U                    ; дисплей с контроллером ST7066U

  В качестве примера реализована возможность загрузки двух символов пользователя. Это символы положительного (адрес 0х00) и отрицательного импульса (адрес 0х01). В программе они не используются.
  Т.к. коды команд дисплейного контроллера зараннее известны, лучше при определении сразу привязать их к символьным определениям выводов дисплея. Тогда при переопределении выводов, коды констант команд автоматом пересчитаются.

; подключение HD44780, биты должны принадлежать одному порту, D4-D7 последовательно, по возрастающей
.EQU    LCDRS       = PortC0        ; бит 0, LCD Register Select
.EQU    LCDEN       = PortC1        ; бит 1, LCD Enable
.EQU    LCDD4       = PortC2        ; бит 2, LCD D4 bus line
.EQU    LCDD5       = PortC3        ; бит 3, LCD D5 bus line
.EQU    LCDD6       = PortC4        ; бит 4, LCD D6 bus line
.EQU    LCDD7       = PortC5        ; бит 5, LCD D7 bus line
; шаблоны, дествительны те же биты
.EQU    LCD_INIT    = (0<<LCDD7)|(0<<LCDD6)|(1<<LCDD5)|(1<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Шаблон начальной инициализации
.EQU    LCD_4bitHi  = (0<<LCDD7)|(0<<LCDD6)|(1<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Шаблон инициализации 4 bit Hi
.EQU    LCD_4bitLo  = (1<<LCDD7)|(0<<LCDD6)|(0<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Шаблон инициализации 4 bit Lo
.EQU    LCD_EMSet   = (0<<LCDD7)|(0<<LCDD6)|(1<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Шаблон установки режима
.EQU    LCD_Enabl   = (1<<LCDD7)|(1<<LCDD6)|(0<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Blink=0, Cursor=0, Display=1 
.EQU    LCD_BUSY    = (1<<LCDD7)|(1<<LCDD6)|(1<<LCDD5)|(1<<LCDD4)|(0<<LCDEN)|(0<<LCDRS) ; Шаблон ожидания
.EQU    LCD_SET     = (0<<LCDD7)|(0<<LCDD6)|(0<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(1<<LCDRS) ; Шаблон данных
.EQU    LCD_Zero    = (0<<LCDD7)|(0<<LCDD6)|(0<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Нулевой полубайт дисплея
.EQU    LCD_Clear   = (0<<LCDD7)|(0<<LCDD6)|(0<<LCDD5)|(1<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Шаблон очистки дисплея
.EQU    LCD_1Str    = (1<<LCDD7)|(0<<LCDD6)|(0<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Адрес первой строки
.EQU    LCD_2Str    = (1<<LCDD7)|(1<<LCDD6)|(0<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Адрес второй строки
.EQU    LCD_Smbl    = (0<<LCDD7)|(1<<LCDD6)|(0<<LCDD5)|(0<<LCDD4)|(1<<LCDEN)|(0<<LCDRS) ; Адрес CGRAM

  Вывод данных на контроллер очень прост, командой выставляется адрес строки, затем последовательно передаются данные. Автоинкремент адреса контроллер производит сам.

;== Вывод результата на индикацию, MRK_Disp = 1 ================================;
    SBRS  BITx1, MRK_Disp           ; если нет  флага начала обработки, на выход
    RJMP  locMP_03                  ;
//
    LDI   Temp, LCD_2Str            ;
    RCALL LCD_SetStrAddr            ; на 2 строку
    LDIDX X, RAM_Symbol             ; адрес буфера
    LDI   Cycler, BufSize           ; размер буфера
locDs0:
    LD    Temp, X+                  ; загрузить символ цифры
    RCALL LCD_SendTetraByte         ; отправить в дисплей
    DEC   Cycler                    ;
    BRNE  locDs0                    ;
//
    CBR   BITx1, (1<<MRK_Disp)      ; сбросить флаг текущего шага 
locMP_03: ;=====================================================================;

Теперь посмотрим сколько времени займет вывод на дисплей:



Вместе с командой установки строки вывод 7 цифр занимает 603 мкс, т.е. менее 0,1% времени цикла сканирования.

P.S.

  VGA предоставил свой код для быстрого вывода на дисплей TIC. Я его протестировал и с его разрешения решил включить этот вариант в статью.
  Т.к. теперь нет нужды использовать системный таймер для вывода результата измерений на индикатор, будем использовать его только для опроса клавиатуры.

;------ Настроить таймер 0, режим Normal ---------------------------------------;
    OUTI  TCCR0, (1<<CS02)|(1<<CS00); прескалер 1024

Соответственно и по прерыванию будет устанавливаться только флаг опроса клавиатуры.

;------ Таблица прерываний -----------------------------------------------------;
; пропущенная часть
;.....................
.ORG OVF0addr
    IN    Safe, SREG                ;
    SBR   BITx1, (1<<MRK_Key)       ; Установить флаг опроса клавиатуры
    OUT   SREG, Safe                ;
    RETI                            ;
.ORG INT_VECTORS_SIZE


Немного видоизмененный код вывода результата по SPI от VGA, старую подпрограмму побитного вывода можно убрать.

;== Вывод результата на индикацию, MRK_Rdy = 1 =================================;
    SBRS  BITx1, MRK_Rdy            ; если нет  флага начала обработки, на выход
    RJMP  locMP_03                  ;
//
SPISend:
    TST   Cycler                    ;
    BREQ  SPISend_Exit              ;
    LD    Temp, X+                  ;
    DEC   Cycler                    ;
    SEC                             ;
SPISend_SendBit:
    ROR   Temp                      ;
    BREQ  SPISend                   ;
    CBI   PORTD, DCLK               ;
    BRCC  SPISend_Zero              ;
    SBI   PORTB, DOUT               ;
    RJMP  SPISend_EndBit            ;
SPISend_Zero:
    CBI   PORTB, DOUT               ;
SPISend_EndBit:
    CLC                             ;
    SBI   PORTD, DCLK               ;
    RJMP  SPISend_SendBit           ;
SPISend_Exit:
    CBI   PORTD, LOAD               ;
    CBI   PORTB, DOUT               ;
    SBI   PORTD, LOAD               ;
//
    CBR   BITx1, (1<<MRK_Rdy)       ; сбросить флаг текущего шага 
locMP_03: ;=====================================================================;

перед вызовом этой подпрограммы необходимо занести в Cycler кол-во выводимых разрядов, а в регистр Х поместить адрес буфера символов.

    LDI   Cycler, BufSize           ;
    LDIDX X, RAM_Symbol             ;
    SBR   BITx1, (1<<MRK_Rdy)       ; разрешить вывод битов по SPI

Делается это в двух местах, в конце инициализации для вывода номера версии и в конце предыдущей подпрограммы UnpackBCDtoSymbolsCode.
Посмотрим сколько займет вывод на 9-разрядный дисплей:



  Для вывода 9 разрядов потребовалось 131 мкс, это в 17 раз быстрее чем предыдущая подпрограмма. Это лучше или хуже? Мне кажется что все равно. Предыдущая подпрограмма вывода по таймеру после передачи каждого бита выходит в главный цикл, а этот вариант до вывода всех битов управление не отдает. Возможно где то это и может иметь принципиальное значение, но данной программе все равно. Первое прерывание по переполнению счетного таймера 1 и входной частоте 3,2МГц может возникнуть через 65536/3200000=0,02048сек или ~20,5 мс от начала цикла. А за это время успеют отработать и первый и второй варианты SPI. Так что используйте тот вариант, который вам больше нравится. Этот же код подойдет без переделки для управления статическим индикатором на сдвиговых регистрах 74НС595. А вот адаптировать этот вариант для вывода на дисплей MAX7219 будет сложнее.
Возможно у этого варианта SPI есть другое ограничение, частота следования импульсов DCLK:



Как видно на графике один период синхроимпульсов занимает 1,63 мкс, что соответствует частоте ~613 кГц. У меня сохранился старый даташит на ML1001, в нем частота DCLK ограничена 500 кГц. Сейчас вроде подняли эту частоту до 1000 кГц, не знаю как правильно.

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

.EQU    Div0cnt     = 128           ; делитель таймера 0
.DEF    DivX        = R0            ; Регистр хранения делителя таймера 0
;
;------ Очистка регистров ------------------------------------------------------;
    LDIL  DivX, Div0cnt             ;

И в прерывании:

;------ Таблица прерываний -----------------------------------------------------;
; пропущенная часть
;.....................
.ORG OVF0addr
    IN    Safe, SREG                ;
    SBRC  BITx1, MRK_Disp           ; Проверить флаг готовности результатов
    SBR   BITx1, (1<<MRK_SPI)       ; Установить флаг вывода бита
    SBRS  BITx1, MRK_Disp           ; Проверить отсутствие флага готовности результатов
    SBR   BITx1, (1<<MRK_Key)       ; Установить флаг опроса клавиатуры
    OUT   SREG, Safe                ;
    OUT   TCNT0, DivX               ; загрузить делитель
    RETI                            ;
.ORG INT_VECTORS_SIZE

С делителем 128 время вывода результата измерений на индикацию сократится в два раза. Конечно соответственно вырастет и частота опроса клавиатуры.

В приложенном архиве FreqStat.zip проекты AVR Studio и Proteus. Подробнее файл Readme внутри.
В архиве 7219help.zip файл 7219help.chm русскоязычной помощи по MAX7219.
  • ?
  • 09 января 2018, 19:42
  • anakost
  • 2
Файлы в топике: 7219help.zip, FreqStat.zip

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

RSS свернуть / развернуть
У ATMEGA8 есть аппаратный SPI, который ты не используешь. Нафига городить софтовый?
CBI PORTD, DCLK; Установить пин в 0
CBI PORTB, DOUT; -//-
SBI PORTD, DCLK; -//-
При тактовой частоте МК 8МГц длительность импульса tL составит 250нс, тогда как даташит на ML1001 требует не менее 400нс. Надо перед SBI воткнуть пару нопов.
В принципе, пока выполняется код выборки следующего бита, необходимое время tH тоже пройдет, так что синхронизировать SPI по таймеру совершенно незачем. Ну если только из соображений «не морозить главный цикл на сотню микросекунд».

Также видно, что автора не смущает потупить 2-4мс (16-32к тактов) в процедуре вывода на SPI-дисплей, 1.5к тактов на перекодировку BIN->BCD его тоже не смущает, а вот 1к тактов на перемешивание битиков для MT1638 — это пиздец как долго и абсолютно неприемлемо, да-да.
+1
  • avatar
  • Vga
  • 10 января 2018, 03:55
А что мне стоит проверить?

Как видно длительность синхроимпульса 503нс и в даташит укладывается.
А второе, операция BCD<-BIN является необходимой для BCD преобразования, и должна быть выполнена в любом случае.
Перемешивание битиков для MT1638 — это криворукость проектировщиков чипа и обойтись без нее было несложно.
0
А, SBI и CBI занимают по два такта. А требования, чтобы между установкой DATA и фронтом CLOCK было 400нс в даташите нету. Тогда действительно, проходит, хотя по прежнему неясно, зачем вешать это на таймер.
А второе, операция BCD<-BIN является необходимой для BCD преобразования, и должна быть выполнена в любом случае.
Ну так и операция перекодирования является необходимой для подключения ОА индикаторов через MAX7219 или MT1638.
Перемешивание битиков для MT1638 — это криворукость проектировщиков чипа и обойтись без нее было несложно.
Если включить мозг, то становится очевидна причина такой «криворукости» — они содрали MAX7219 и задокументировали подключение ОА как штатный режим. «Несложно» — скорее всего в точности наоборот, как к вопросу ни подойди — требуется число транзисторов, сравнимое с уже имеющимся.
А вот перемешать биты — действительно несложно, это займет 30-40 байт флеша и сотню микросекунд времени. То есть «менее 0.1% цикла сканирования».
0
Никакого особого транзисторного бютжета для переключения ОК/ОА не требуется. Надо просто инвертировать выходной сигнал, как выше я сделал для 74НС595.
Кстати контроллеры LCD, в частности ML1001 делают то же самое. При регенерации они постоянно инвертируют сигналы сегментов относительно СОМ при регенерации.
Для инвертирования выхода при необходимости, достаточно элемента XOR.
А то как сделано в MT1638 иначе чем криворукостью не назовешь.
0
Никакого особого транзисторного бютжета для переключения ОК/ОА не требуется. Надо просто инвертировать выходной сигнал, как выше я сделал для 74НС595.
Ты забываешь о том, что в 74HC595 — просто двухтактный выходной ключ на малый ток, а в MAX7219/MT1638 — сильноточные ключи разрядов и стабилизаторы тока сегментов. Как минимум придется удваивать число ключей (а это крупные и потому дорогие транзисторы) и стабилизаторов тока.
Кстати контроллеры LCD, в частности ML1001 делают то же самое. При регенерации они постоянно инвертируют сигналы сегментов относительно СОМ при регенерации.
Ну ты сравнил — обычные пуш-пул ключи на 3.5 микроампера с источниками тока и ключами на пару сотен миллиампер.
А то как сделано в MT1638 иначе чем криворукостью не назовешь.
Что характерно, ребята из крупной и уважаемой конторы MAXIM сделали абсолютно так же. Они, однако, благоразумно промолчали, что к их драйверу можно подключить ОА индикаторы — потому что умный сам догадается и будет тем горд, а глупый обосрет чип.
0
Ребята из «Dallas Semiconductor» не занимались ранее сопряжением аналоговых и цифровых схем. Так что это первый блин, достаточно удачный.
Чуть поднаберутся опыта, сконструируют не хуже именинитых бредов. А то что написано MAXIM, они только купили Dallas.
0
Даллас не менее старая и уважаемая контора. Я скорее в твоей компетентности усомнюсь, чем в их.
0
Отдельный вопрос — с чего ты вообще взял, что это далласовский чип? Мало того, что оно префиксом имеет MAX, а не DS, так еще и даташит (C) Maxim Integrated, 1997, тогда как DS они купили в 2001.
А сомневаться, что Maxim умеет сочетать цифру и аналог как-то не приходится. У них много и цифровых, и аналоговых, и цифроаналоговых продуктов. Куда вероятнее, что драйверы сегментов/разрядов занимают 2/3 кристалла и дублировать их — делать чип вдвое дороже.
0
Куда вероятнее, что драйверы сегментов/разрядов занимают 2/3 кристалла
А кстати именно так и есть, у них же картинка чипа в даташите. Драйверы занимают примерно 60-65% кристалла.
0
Мне вот интересно, не успею слова сказать, VGA тут же и против. Везде успел, и знает что там внутри Dallas|MAXIM, и какой у них транзисторный бытжет. А что это светило делает на русском форуме?
Не прокомментируете?
0
А по делу-то есть что сказать?
0
Так статьей по делу высказался, есть комментарий по статье?
0
Есть, в корне ветки.
0
В корне ветки…
Если вы о том, что я не использую аппаратный SPI?
Пробовал, не получается передать 9 байт с последующим LOAD, и 16-байтный SPI тоже не получился.
0
С чем именно возникли проблемы?
0
Именно с тем, что аппаратный SPI 8-разрядный. Чтобы получить 9 или 2 байтный надо обвешать код маячками и постоянно отслеживать. Смысла нет.
Мне проще было написать программный SPI.
0
Не вижу в статье ни одного дисплея, использующего 9-битный SPI. Сигнал LOAD для всех этих индикаторов не проблема формировать в прерывании SPI, которое в любом случае нужно для загрузки очередного байта.
0
В статье нет ни одного дисплея использующего 9-битный SPI.
ML1001 требуется 72-битный, 74НС595 — 64-битный, MAX7219 — 16-битный.
0
CS даже у «нормального» SPI нередко формируют программно. А здесь — и подавно. При этом железо возьмет на себя наиболее затратную часть — выталкивание байтиков на пин и генерацию клока.
0
Ошибся, т.к. выводим 7 разрядов, 74НС595 — 56-битный
0
Не буду утверждать, что на аппаратном SPI получилось бы хуже. У меня не удалось, возможно недопонял. Сделал на программном, мне так показалось удобнее.
0
Вариант софтового SPI для TICxx:
; FCPU: 8MHz
; Input:
; X: data to send
; Count: Data length, Rx
; Temp:
; Data, Rx
SPISend:
    TST Count
    BREQ SPISend_Exit
    LD Data, X+
    DEC Count
    SEC
SPISend_SendBit:
    ROR Data
    BREQ SPISend
    CBI PORTSPI, SPICLK
    BRCC SPISend_Zero
    SBI PORTSPI, SPIDO
    RJMP SPISend_EndBit
SPISend_Zero:    
    CBI PORTSPI, SPIDO
SPISend_EndBit:    
    CLC
    SBI PORTSPI, SPICLK
    RJMP SPISend_SendBit
SPISend_Exit:
    CBI PORTSPI, SPILD
    CBI PORTSPI, SPIDO
    SBI PORTSPI, SPILD
    RET
0
  • avatar
  • Vga
  • 14 января 2018, 14:43
Понятно, я попробую, мне не нравится что код не настраивается на темп вывода. Но попробую…
0
Я не вижу смысла в том, чтобы растягивать скважность клока до over9000 при длительности импульса на нижней грани требований даташита.
0
Дополнил статью твоим кодом, спасибо.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.