Разработка электроники,

Систем автоматики,

Программного обеспечения

ООО "Антех ПСБ", Санкт-Петербург

+79811865082

anteh@bk.ru
Телеграм: собака antehru

ООО Антех ПСБ примеры

8 bit AVR TWI/I2C RTC DS1307 DS1338. Драйвер DS13xx на AVR Assembler. Демонстрация работы мультизадачного драйвера для DS13xx

26.10.2015 https://anteh.ru

Речь об использовании аппаратного TWI/I2C интерфейса 8 битных AVR контроллеров для управления RTC DS1307 (VCC 4.5-5.5V) или его более современного аналога DS1338 (DS1338-18 1.71-5.5V, DS1338-3 2.7-5.5V, DS1338-33 3.0-5.5V).
Тема избитая, для бытового применения RTC+LCD или USART и пр. Для промышленного решения, когда RTC является одним из винтиков в общей массе используемых узлов, доступного решения не нашлось. Код должен быть универсальным, компактным, с минимальными задержками на выполнение, учитывать возможные нюансы работы RTC. По корыстным причинам "вылизанный" код, удовлетворяющий всем перечисленным требованиям, не приводится. Приведён полностью рабочий код AVR Assembler, который позволит быстро разобраться, что к чему и при желании довести до своего совершенства.
Рассматриваются режимы работы TWI Master transmitter(MT), Master receiver(MR). Режимы Slave transmitter(ST), Slave receiver(SR) не рассматриваются.

Фото первого макета разрабатываемого контроллера на котором отрабатывалась работа с DS1338-33:контроллер ds1338 1307


При первоначальной подаче питания на DS13xx с подключённой 3V батареей резервного питания её регистры находятся в произвольном состоянии. Нужно их инициализировать. По крайней мере, первые 8 отвечающие за работу и настройки RTC. Описание регистров есть в многочисленной документации и сети. Документацию лучше искать на DS1307

Пишем драйвер
Инициализация TWI:
Пины SCL SDA AVR контроллера настраиваем в третье состояние. В коем они по умолчанию и находятся. TWI выход DS13xx, это открытый сток или коллектор, нужно подтянуть к VCC. Можно использовать встроенные подтягивающие резисторы AVR контроллера. Но их номинал для больших скоростей обмена велик 20k-100k и подойдёт для работы TWI/I2C на малых частотах с небольшим количеством устройств на шине. Номинал резистора зависит от рабочей частоты, от количества устройств на шине, емкости шины. Есть рекомендация 1k-4.7k при +5V. Документ AVR1320 рекомендует 2k для частоты 400kHz. Если устройств на шине несколько, то для уверенности в надёжности обмена данными, нужно воспользоваться осциллографом, импульсы на SCL SDA должны быть похожи на прямоугольные. Ниже будет осциллограмма для 1.2k pull-up резисторов, AtMega128A, 320kHz и одно устройство на шине DS1338-33.
Для AtMega128A настройка SCL SDA в третье состояние:

                ;PD0    SCL I2C ThreeState
                ;PD1    SDA I2C ThreeState
                ;PD2
                ;PD3
                ;PD4
                ;PD5
                ;PD6
                ;PD7
                ldi     r16, 0b00000000
                out     DDRD, r16
                ldi     r16, 0b00000000
                out     PORTD, r16

Настройка TWI регистров:

              ldi     r27, high(TWBR) ;400kHz на кварце 16.0MHz
                ldi     r26, low(TWBR)
                ldi     r16, 12
                st              X+, r16
                ldi     r16, 0b00000000 ;TWSR
                st              X, r16          

                call    DS1338_I ;Инициализация RTC. Если первая подача напряжения, 
                                                 ;то полная инициализация в том числе и дефолтным 
                                                 ;временем. Если не первая, то инициализация 
                                                 ;частичная/"пожарная"

Другие TWI регистры не трогаем.

Добавляем буфер для работы с DS13xx:

.dseg
        StartRAM:
                …
                DS1338_buf: .BYTE 67    ;Буфер DS1338 для обмена данными c DS13xx
                …
        EndRAM:

Область между StartRAM EndRAM обнуляется при инициализации контроллера:

              ldi     r29, high(StartRAM)             ;* Обнуляем память данных
                ldi     r28, low(StartRAM)              ;*
                ldi     r27, high(EndRAM-StartRAM)      ;*
                ldi     r26, low(EndRAM-StartRAM)       ;*
        nxzr34:                                                         ;*
                st              Y+, r15                                 ;*
                ld              r16, -X                                 ;*
                or              r27, r27                                ;*
                brne    nxzr34                                  ;*
                or              r26, r26                                ;*
                brne    nxzr34                                  ;*

                clr             r25             ;GIR0 регистр глобальных флагов
                clr             r15             ;Нулевой регистр

В памяти программ создаём 2 посылки инициализации DS13xx

.cseg 
                ;Инициализирующая посылка DS1338
                ; 16.10.2015 пятница(5) 21:15:33
                ;                LENG  SADR  adr   00h         01h   02h         03h   04h   05h   06h   07h         выравнивающий
                DS1338_init: .DB 0x0a, 0xd0, 0x00, 0b00110011, 0x15, 0b00100001, 0x05, 0x16, 0x10, 0x15, 0b00000000, 0x00
                ;                                  33          15    21          5     16    10    15
                ;                    LENG  SADR  adr   07h   
                DS1338_sub_init: .DB 0x03, 0xd0, 0x07, 0x00
                ;LENG -количество байт в посылке к передаче

DS1338_init -последовательность байт записываемая при инициализации DS13xx при первом включении, когда DS1338_first_init_flag !=0xA5
DS1338_sub_init -последовательность байт записываемая при каждом перезапуске контроллера. По непроверенным слухам 07h конфигурационный регистр DS13xx может слетать. Поэтому пока предусмотрена процедура его инициализации при запуске контроллера. Сюда же можно добавить проверку корректности CH бита -в чём смысла не вижу, ибо если он действительно слетает, то вместе с ним нужно и время подправлять. Если SQW/OUT не используется, то "слетане" 07h ни на что не повлияет. Нужно уделить внимание стабильности 32.768kHz кварца.

Функция копирования из CSEG в DSEG:

;X -DSEG
;Z -CSEG
CSEG_to_DSEG_with_start_LEN:
                add             r30, r30
                adc             r31, r31
                lpm             r17, Z+
                st              X+, r17
        CSEG_to_DSEG_nxrd:
                lpm             r16, Z+
                st              X+, r16
                dec             r17
                brne    CSEG_to_DSEG_nxrd
ret

В ESEG размещаем:

eseg
                DS1338_first_init_flag: .DB 0x00 ;Флаг инициализации DS1338 при 
                                                                                 ;первоначальной подаче питания. 
                                                                                 ;Если =0xA5 то инициализация была

Для работы с DS13xx достаточно 2х функций записи и чтения регистров:
(1)
;Функция используемые регистры не сохраняет. В прерываниях не запускать
;Z -Передаваемая последовательность байт в DS1338 из DSEG
;Формат посылки записи данных в регистры DS13xx:
;DS1338_buf = [N][SLA+W][adr][data0][data1]...[data63]
;[N] -количество байт передаваемых в DS13xx без учёта самого байта длины
;[SLA+W] =0xD0 статический адрес SLAVE DS13xx и флаг записи данных
;[adr] -адрес регистра начала записи в DS13xx
;[data0]-[data63] -значения регистров DS13xx любое количество байт, в том числе и =0 т.е. данных может не быть, нужно для настройки на чтение данных с произвольного регистра DS13xx
Z_to_DS1338:

ret
(2)
;Чтение данных из DS13xx
;Для настройки на нужный регистр для чтения, нужно произвести запись в DS13xx байта статического адреса и байта адреса регистра. Затем произвести операцию чтения, в DS1338_to_Z это реализовано в функции автоматически
;Формат последовательности байт чтения из DS13xx:
;В функцию DS1338_to_Z передаётся: DS1338_buf = [N][adr]
;[N] -количество байт читаемых из DS13xx
;[adr] -адрес регистра начала чтения из DS13xx
;Из функции DS1338_to_Z возвращается DS1338_buf = [N][data0][data1]..[dataN]
;[N] -количество байт прочтённое из DS13xx
;[data0][data1]..[dataN] -прочитанные данные
DS1338_to_Z:

ret

Обмен данными сводится к заполнению DS1338_buf посылкой к чтению или записи в приведённом выше формате и запуску функции Z_to_DS1338(запись) или DS1338_to_Z(чтение).

Функция инициализации DS13xx с проверкой, была ли произведена полная инициализация. И если была, то запуск частичной инициализации:

;Инициализация DS1338
DS1338_I:
                ldi             r27, high(DS1338_first_init_flag) ;Чтение флага произведённой первоначальной инициализации
                ldi             r26, low(DS1338_first_init_flag)
                call    EEPROM_X_to_r16_read
                cpi     r16, 0xa5
                breq    DS1338_I_NoNeedTimeInit

                ldi     r31, high(DS1338_init) ;Копирование полной инициализирующей посылки в DS1338_buf
                ldi     r30, low(DS1338_init)
                ldi             r27, high(DS1338_buf)
                ldi             r26, low(DS1338_buf)            
                call    CSEG_to_DSEG_with_start_LEN
        DS1338_I_repeat:

                clt                                     ;T -флаг ошибки, если T=1 то обмен данными завершился ошибкой
                call    Z_to_DS1338
                brts    DS1338_I_Error
                
                ldi             r16, 0xa5       ;Установка флага инициализации DS1338 при первоначальной подаче напряжения
                ldi             r27, high(DS1338_first_init_flag)
                ldi             r26, low(DS1338_first_init_flag)
                call    EEPROM_r16_to_X_write
        DS1338_I_Error:
ret
        DS1338_I_NoNeedTimeInit:
                ldi     r31, high(DS1338_sub_init) ;Запись 07h байта в DS1338 на всякий пожарный по слухам может слететь
                ldi     r30, low(DS1338_sub_init)
                ldi             r27, high(DS1338_buf)
                ldi             r26, low(DS1338_buf)            
                call    CSEG_to_DSEG_with_start_LEN             
                rjmp    DS1338_I_repeat

И теперь собственно сами загадочные и волшебные Z_to_DS1338 и DS1338_to_Z:

;Функция используемые регистры не сохраняет. В прерываниях не запускать
;Z -Передаваемая последовательность байт в DS1338 из DSEG
;Формат посылки записи данных в DS13xx:
;[N][SLA+W][adr][data0][data1]...[data63]
;[N] -количество байт передаваемых в DS13xx без учёта самого байта длины
;[SLA+W] =0xD0 статический адрес SLAVE DS13xx и флаг записи
;[adr] -адрес регистра начала записи в DS13xx
;[data0]-[data63] -значения регистров DS13xx любое количество байт, в том числе и =0
Z_to_DS1338:
                ldi     r31, high(DS1338_buf)
                ldi     r30, low(DS1338_buf)
                ld              r17, Z+         ;Количество байт к записи в DS1338

                ldi     r27, high(TWCR)
                ldi     r26, low(TWCR)
                ldi     r29, high(TWSR) ;Статусный регистр
                ldi     r28, low(TWSR)

                ;[S] START
                ldi             r16, (1<<TWINT)|(1<<TWSTA)|(1<<TWEN) ;Возможная очистка TWINT, установка TWSTA бита передачи стартового признака и разрешение работы TWI
        st              X, r16
                ;[Wait]
                call    r16_to_DS1338_wait  ;Ожидание выполнения операции
                ;[Check status]
                ld              r16, Y                          ;Проверка статуса операции
                andi    r16, 0b11111000
                cpi             r16, 0x08                       ;A START condition has been transmitted
                brne    r16_to_DS1338_ERROR

                ;[Send address to DS1338]
                ld              r16, Z+         ;Байт адреса
                st              -X, r16         ;TWDR загрузка в передающий регистр
                ldi     r16, (1<<TWINT) | (1<<TWEN)
                adiw    r26, 1
                st              X, r16          ;TWCR
                ;[Wait]
                call    r16_to_DS1338_wait  ;Ожидание выполнения операции
                ;[Check status]
                ld              r16, Y                          ;Проверка статуса операции
                andi    r16, 0b11111000         
                cpi             r16, 0x18                       ;SLA+W has been transmitted ACK has been received
                brne    r16_to_DS1338_ERROR

                dec             r17     ;Байт адреса передан

                ;[Send register address and data to DS1338]
        DS1338_I_nxbr:          
                ld              r16, Z+
                st              -X, r16         ;TWDR загрузка в передающий регистр
                ldi     r16, (1<<TWINT) | (1<<TWEN)
                adiw    r26, 1
                st              X, r16          ;TWCR
                ;[Wait]
                call    r16_to_DS1338_wait  ;Ожидание выполнения операции
                ;[Check status]
                ld              r16, Y                          ;Проверка статуса операции
                andi    r16, 0b11111000
                cpi             r16, 0x28                       ;Data byte has been transmitted ACK has been received 
                brne    r16_to_DS1338_ERROR

                dec             r17
                brne    DS1338_I_nxbr

                ;[P] STOP
                ldi     r16, (1<<TWINT)|(1<<TWEN)|(1<<TWSTO)
                st              X, r16  ;STOP signal to TWCR
ret
        r16_to_DS1338_ERROR:
                st              X, r15 ;TWCR = 0b00000000
                set             ;T=1 установка признака ошибки TWI обмена
ret

r16_to_DS1338_wait: ;Ожидание выполнения операции
        ld              r19, X
            sbrs        r19, TWINT
                rjmp    r16_to_DS1338_wait
ret
;Чтение из DS13xx
;Для настройки на нужный регистр для чтения нужно произвести запись в DS13xx байта статического адреса и байта адреса регистра
;Затем произвести операцию чтения, в DS1338_to_Z это реализовано автоматически
;Формат последовательности байт чтения из DS13xx:
;В функцию DS1338_to_Z передаётся: DS1338_buf = [N][adr]
;[N] -количество байт читаемых из DS13xx
;[adr] -адрес регистра начала чтения из DS13xx
;Из функции DS1338_to_Z возвращается S1338_buf = [N][data0][data1]..[dataN]
;[N] -количество байт прочтённое из DS13xx
;[data0][data1]..[dataN] -прочитанные данные
DS1338_to_Z:
                ;Настраиваемся на нужный адрес
                ldi     r31, high(DS1338_buf) ;Настройка буфера
                ldi     r30, low(DS1338_buf)
                ld              r16, Z+ ;Сохранение количества байт к чтению
                push    r16
                ld              r17, Z  ;Адрес начала чтения            
                ldi     r16, 2          ;Количество байт записываемых в DS13xx
                st              -Z, r16
                ld              r16, Z+         ;Настройка на нужный байт
                ldi     r16, 0xd0       ;Адрес DS13xx с признаком запись
                st              Z+, r16
                st              Z, r17          ;Адрес начала чтения
                call    Z_to_DS1338 ;Настраиваем указатель DS13xx на нужный регистр

                ;Производим чтение заданного количества байт в DS1338_buf, начиная с указанного адреса
                ldi     r31, high(DS1338_buf)
                ldi     r30, low(DS1338_buf)
                pop             r17                     ;Восстанавливаем количество байт к чтению
                st              Z+, r17


                ldi     r27, high(TWCR)
                ldi     r26, low(TWCR)
                ldi     r29, high(TWSR) ;Статусный регистр
                ldi     r28, low(TWSR)

                ;[S] START
                ldi             r16, (1<<TWINT)|(1<<TWSTA)|(1<<TWEN) ;Возможная очистка TWINT, установка TWSTA бита передачи стартового признака и разрешение работы TWI
        st              X, r16
                ;[Wait]
                call    r16_to_DS1338_wait  ;Ожидание выполнения операции
                ;[Check status]
                ld              r16, Y                          ;Проверка статуса операции
                andi    r16, 0b11111000
                cpi             r16, 0x08                       ;A START condition has been transmitted
                brne    r16_to_DS1338_ERROR


                ;[Send address to DS1338 with R]
                ldi             r16, 0xd1       ;Байт адреса с признаком чтения
                st              -X, r16         ;TWDR загрузка в передающий регистр
                ldi     r16, (1<<TWINT) | (1<<TWEN)
                adiw    r26, 1
                st              X, r16          ;TWCR
                ;[Wait]
                call    r16_to_DS1338_wait  ;Ожидание выполнения операции
                ;[Check status]
                ld              r16, Y                          ;Проверка статуса операции
                andi    r16, 0b11111000         
                cpi             r16, 0x40                       ;SLA+R has been transmitted ACK has been received
                brne    r16_to_DS1338_ERROR

                ;Производим чтение r17 байт из DS13xx
        ReadNextBytes45:
                dec             r17
                brne    ReadNoLastByte          
                ldi     r16, (1<<TWINT|1<<TWEN) ;Чтение последнего байта
                rjmp    dg5yks89dwm46
        ReadNoLastByte:
                ldi     r16, (1<<TWINT|1<<TWEN|1<<TWEA) ;чтение не последнего байта
        dg5yks89dwm46:

                st              X, r16 ;TWCR
                ;[Wait]
                call    r16_to_DS1338_wait  ;Ожидание выполнения операции
                ;[Check status]
                ld              r16, Y                          ;Проверка статуса операции
                andi    r16, 0b11111000         
                or              r17, r17
                brne    NoLastByteStatusCheck
                cpi             r16, 0x58                       ;Data byte has been received NOT ACK has been returned
                brne    r16_to_DS1338_ERROR
                rjmp    dfe4hbse46
        NoLastByteStatusCheck:
                cpi             r16, 0x50                       ;Data byte has been received ACK has been returned
                brne    r16_to_DS1338_ERROR
        dfe4hbse46:
                ;[Safe received data]
                ld              r16, -X ;Чтение TWDR
                st              Z+, r16
                ld              r16, X+ ;Возврат на TWCR

                or              r17, r17
                brne    ReadNextBytes45

                ;[P] STOP
                ldi     r16, (1<<TWINT)|(1<<TWEN)|(1<<TWSTO)
                st              X, r16  ;STOP signal to TWCR
ret

Из функций чтения записи можно убрать проверку статуса операции, что значительно сократит количество кода. Проверка использовалась для отладки.
Оптимально убрать из функций задержки на ожидание выполнения TWI операций. Для этого нужно организовать независимое/мульти задачное/параллельное выполнение кода обмена данными с RTC таймером, это позволит читать время из RTC на любой частоте не оказывая влияния на работу остального кода. Влияние будет в несколько десятков тактов, запрет прерываний в коде обмена с DS13xx не используется. Ниже приведена видео демонстрация работы такого кода.

Чтение данных состоит из 2х операций:

1. 2 байта, записываются в DS13xx настраивая указатель на регистр, с которого будет производиться чтение. Это TWI адрес DS13xx и адрес регистра, с которого начнётся чтение данных.

2. Чтение 7 байт даты и времени. Последний, конфигурационный байт не читается, не нужен.чтение даты времени из ds1307 ds1338

Осциллограммы pull-up=1.2k Ftwi=320kHz DS1338-33ds1307 ds1308 read data oscilloscope

Более подробно:осциллограмма чтения данных из ds1338 ds1307 anteh.ru

Питание DS1338-33: VCC=5V.

На осциллограммах чётко просматривается короткий импульс после восьмого бита при записи данных в DS13xx и после 9го при чтении полагаю, это связано с ACK.

Если кому интересно: DS1338-33 Rpull-up=1.2k, постоянный опрос без задержек или опрос с периодом 2ms, не работает на Ftwi=770kHz, TWBR=1. На Ftwi=715kHz работает TWBR=2.

Проверил, для DS1338-33 на Ftwi=715kHz с TWBR=2 работает отлично с Rpull-up=1.2k и =4.7k с 4.7k фронты конечно "покруглее", но работает чётко. Для максимально заявленной в документации на DS1338-33 Ftwi=400kHz pull-up можно и побольше поставить. А если выбрать частоту ещё меньше, то и встроенных pull-up контроллера будет достаточно. Минимальная частота для кварца контроллера 14.7456MHz Ftwi=444 Hz. Проверено, DS1338-33 VCC=5V минимум работает на 444Hz-740kHz частотах TWI Rpull-up=4.7k.

Качество видео, демонстрирующего мульти задачный обмен данными с DS13xx вышло не очень, но смысл передаёт. Видео демонстрирует, реализацию вышеприведённого подхода. Обмен с DS1338-33 на Ftwi=444 Hz, кварц AtMega128A=14.7456MHz, TWBR=255 и TWSP=11 пред делитель TWI =64. И циклическая передача посылки данных между USART0 USART1 2xRS485 на 115200. Посылка состоит из 2х частей первая статическая около 30 байт, она формируется только один раз при инициализации контроллера и далее циркулирует от USART0 к USART1 и обратно в бесконечном цикле. Т.е. если произойдут какие-либо сбои или ошибки, то посылка будет испорчена и это сразу будет видно по выводу снифера. Вторая часть этой же посылки динамическая -это дата и время читаемое с DS1338-33, эта часть посылки формируется с заданным интервалом -270ms и длится 210ms. Напоминаю Ftwi=444 Hz кварц AtMega128A=14.7456MHz настройки TWI на минимальную частоту чтобы показать, что TWI и 2 USARTа, настроенные на максимальную частоту, друг другу не мешают.

(1) По X 100 мкс в клетке. Частота Ftwi=740kHz USART0 и USART1 =115200. Опрос DS1338 постоянный. Желтый верхний канал SCL, синий средний SDA, сиреневый нижний USARTx RS485 дифференциальный выход. Видео демонстрирует отсутствие каких либо задержек при смежной работе 2хUSART и TWI и ещё ряда функций:

(2) Частоты TWI и USART те же. Но добавлен период опроса DS1338 несколько десятков миллисекунд. Демонстрируется небольшое влияние на TWI работы USARTов и остального кода прошивки, это выражено в псевдо случайном увеличении периодов между байтами данных TWI:

Задержки можно уменьшить, если у используемого контроллера TWI регистры будут с прямым доступом а также, для хранения промежуточных переменных использовать свободные регистры, а не память.

(3) Частота Ftwi=440 Hz. Частота RS485 USART 115200. Опрос постоянный. TWI также не вносит никаких задержек и сам не теряется:

(4) Тоже, что и (3) но опрос DS13xx с периодом несколько десятков миллисекунд, что позволяет детально разглядеть обмен чтения даты времени по TWI:

За дорого, по безналичному расчёту могу предложить выше расхваливаемую мультизадачную функцию, точнее несколько функций управления DS13xx RTC таймером. AVR Assembler. Помимо всего можно удобно читать и записывать все регистры DS1338 DS1307. Рассматривается вопрос разработки подобного кода под Ваши устройства для самых разнообразных датчиков и контролируемых узлов

Copyright ©Новиков Алексей Александрович,

2025 Санкт-Петербург, 197372, ООО "Антех ПСБ",

anteh собака bk.ru, телеграм: собака antehru