Занимательная микроэлектроника — страница 88 из 117

Листинг 16.6

:=======инициализация адреса flash

   clr ZH  ;старший EEPROM

   ldi ZL,EaddrL  ;младший EEPROM

   rcall ReadEEP

       mov AddrL,temp

       ldi ZL,EaddrH

       rcall ReadEEP

       mov AddrH,temp ;теперь в AddrH:AddrL адрес из EEPROM

ldi temp,0xFF ;если все FF, то память была пуста

    ср AddrL,temp

       ldi temp,0xFF

       cpc AddrH, temp

       brne cont_1

       clr AddrH  ;если пуста, то присваиваем адрес = 0

  clr AddrL

       clr ZH  ;старший EEPROM

ldi ZL,EaddrL  ;младший EEPROM

  mov temp,AddrL

       rcall WriteEEP  ;и записываем его опять в EEPROM

  inc ZL

       mov temp,AddrH

       rcall WriteEEP

cont_1:  ;теперь проверку на последний адрес $7FFF

  ldi temp,0xFF

       cp AddrL,temp

       ldi temp,0x7F

       cpc AddrH, temp

       brne cont_2

       sbr Flag,4  ;4 бит регистра Flag = конец памяти

cont_2:  ;загрузка байта разрешения записи flash

  clr ZH  ;старший EEPROM

    ldi ZL,FEnEE

       rcall ReadEEP

       ldi ZH,1  ;старший RAM

   ldi ZL,FEnRAM  ;младший RAM

    st Z,temp  ;сохраняем значение флага


Отдельный бит «конец памяти» в регистре Flag (бит 2, т. е. устанавливается он командой sbr Flag, 4, см. главу 13) нам понадобится позднее, для того, чтобы можно было временно запретить запись во flash внешней командой, не сбрасывая значения адреса и независимо от того, достигнут конец памяти или нет.

Теперь в секции прерываний заменим reti на rjmp TIM1_COMPA в строке для прерывания Timer1 Compare А (шестое сверху, не считая RESET), и напишем его обработчик (листинг 16.7).

Листинг 16.7

TIM1_COMPA:  ;15 секунд

;проверять разрешение записи во flash

ldi ZH,1  ;старший RAM

ldi ZL,FEnRAM

          ld temp,Z

          cpi temp,$FF

          breq flag_WF

          reti  ;если запрещено, то выходим из прерывания

flag_WF:

          ldi ZL,Thex  ;адрес значения в SRAM

    ld DATA,Z+  ;старший T

rcall WriteFlash  ;пишем во flash

adiw AddrL,1

          ld DATA,Z+  ;младший T

  rcall WriteFlash

          adiw AddrL,1

          ld DATA,Z+  ;старший Р

    rcall WriteFlash

          adiw AddrL,1

          ld DATA,Z+  ;младший P

     rcall WriteFlash

;проверяем адрес на 7FFF

   ldi temp,0xFF

          cp AddrL,temp

          ldi temp,0x7F

          cpc AddrH, temp

          breq clr_FE ;если равен, на clr_FE

   adiw AddrL,1  ; иначе сохраняем след, адрес:

   clr ZH

          ldi ZL,EaddrL  ;в EEPROM

   mov temp,AddrL

          rcall WriteEEP

          inc ZL

          mov temp,AddrH

          rcall WriteEEP

reti  ;выход из прерывания

clr_FE  ;если конец памяти:

clr temp

          ldi ZH,1  ;старший RAM

  ldi ZL,FEnRAM

          st Z,temp  ;сбрасываем разрешение записи

clr ZH  ;старший EEPR

  ldi ZL,FEnEE

          rcall WriteEEP

          sbr Flag,4  ;бит «конец памяти»

   clr temp

          out TCCR1A,temp  ;отмена переключающего режима для вывода PD5-OC1A

reti  ;на выход из прерывания


Как мы видим, здесь каждые 15 с идет запись в EEPROM текущего адреса (того, по которому должна производиться следующая запись), т. е. если в какой-то момент питание пропадет, то при следующей загрузке запись все равно начнется с текущего адреса. При достижении конца памяти отключится переключающий режим для вывода OC1A, и светодиод перестанет мигать, сигнализируя о конце памяти.

Отметим, что запись идет через каждые четыре байта, т. е. для заполнения 32 кбайтов внешней памяти придется 8192 раза обновить содержимое ячеек. Таким образом, для достижения теоретического предела по количеству циклов записи в EEPROM (100 тыс.) нужно как минимум двенадцать раз заполнить внешнюю память. На самом же деле число допустимых циклов еще намного больше (автор специально запускал запись каждые три секунды на пару месяцев, но сбоев так и не добился), потому можно не опасаться, что мы исчерпаем ресурс встроенной EEPROM.

Заметки на полях

Обратите внимание, что в процедурах I2С имеются псевдонимы: DATA и ClkA есть те же регистры, что и temp1 и temp2 в основной программе. Я предостерегал вас от такой практики, но в данном случае ничего страшного не произойдет, т. к. использование этих регистров разнесено во времени — обращение к I2С никогда не сможет произойти во время расчетов, где задействованы temp1 и temp2. В дальнейшем эти регистры нам где-нибудь еще пригодятся.


Чтение данных из памяти через UART

Теперь займемся процедурами чтения содержимого внешней памяти и осуществления установок разрешения-запрещения записи. Естественно, это придется делать через компьютер (а куда еще читать?), и мы используем уже инициализированный нами UART. Нам потребуется реализовать четыре процедуры:

• Запретить запись во flash.

• Разрешить запись во flash.

• Прочесть содержимое flash.

• Обнулить адрес flash, чтобы начать запись с начала.

Добавим в текст программы, в основной цикл (по метке Gcykle, обязательно после команды rcall in_com) следующие строки:

cpi temp,0xF0  ;запись во flash разрешить breq proc_F0

cpi temp,0xF1  ;запись во flash запретить breq proc_F1

cpi temp,0xF2  ;читать flash breq proc_F2

cpi temp,0xF8  ;запись во flash с начала, обнулить адрес

breq proc_F8


Посылая соответствующие команды ($F0 и т. д.) с компьютера, мы будем вызывать соответствующую процедуру. Проще всего оформить процедуры разрешения и запрещения так, как в листинге 16.8.

Листинг 16.8

proc_F0:  ;F0 запись flash разрешить

   rcall EnableFlash

rjmp Gcykle

proc_F1:  ;F1 запись flash запретить

   rcall DisFlash

rjmp Gcykle

EnableFlash:

cli

;сначала проверяем бит «конец памяти»

sbrc Flag,2

        rjmp exit_FE

;проверяем байт разрешения, если нет, пишем его в память

  ldi ZH,1  ;старший RAM

  ldi ZL,FEnRAM

        ld temp,Z

        cpi temp,$FF

        breq exit_FA

        ldi temp,$FF

        st Z,temp

        clr ZH  ;старший EEPR

  ldi ZL,FEnEE

        rcall WriteEEP

        ldi temp,$AA  ;все Ok

rcall out_com

sei

ret

exi t_FA:

       ldi temp,$FA  ;ответ в комп. — уже разрешен

rcall out_com

sei

ret

exit_FE:

       ldi temp,$FE  ;ответ в комп. — конец памяти

  rcall out_com

sei

ret


DisFlash:

cli

       clr temp

       ldi ZH,1  ;старший RAM

    ldi ZL,FEnRAM

       st Z,temp

       clr ZH  ;старший EEPR

  ldi ZL,FEnEE

       rcall WriteEEP

       ldi temp,$AA  ;ответ в комп. — все Ok

rcall out_com

sei

ret


Мы используем запрещение прерываний, потому что процедуры достаточно долгие и запросто можно «испортить» temp по ходу дела, а здесь это уже недопустимо. Кроме того, они включают запись в EEPROM, во время которой прерывания надо все равно запрещать. Из этих процедур мы также видим, зачем нам понадобился отдельный флаг «конец памяти» — если он установлен, то разрешить запись будет нельзя. Это можно будет сделать только одновременно со сбросом адреса, что необратимо, и данные после этого уже прочесть будет нельзя. Потому мы сначала займемся их чтением (листинг 16.9).

Листинг 16.9

proc_F2:  ;F2 читать flash

  rcall ReadFullFlash

rjmp Gcykle

ReadFullFlash:

cli

        mov YH,AddrH  ;сохраняем текущий адрес в Y

mov YL,AddrL

        clr AddrL  ;чтение начнем с начала памяти

clr AddrH

loopRF:

        cp AddrL, YL  ;не дошли ли до текущего

  срс AddrH, YH

        breq end_RF  ;если дошли, то конец чтения

     rcall ReadFlash  ;собственно чтение

  mov temp,DATA  ;данные из DATA в temp

rcall out_com  ;передаем наружу

adiw AddrL,1  ;следующий адрес

  rjmp loopRF

end_RF:

        mov AddrH,YH  ;восстанавливаем текущий адрес

mov AddrL,YL

sei

ret


Процедура эта будет долгой, если записан сколько-нибудь существенный кусок в памяти (для передачи 32 кбайт со скоростью 9600 потребуется порядка полминуты, да еще и чтение по I