I2C-enheten - Timslag
ATmega328P.pdf ( Register summary on p 428 )

Antag att din antika klockas timslag har "gett upp" och att mekanismen verkar omöjlig att reparera!
Detta kan ge oss en förevändning att studera AVR-processorernas I2C-enhet (TWI Two Wire Interface) som kan användas till många olika periferikretsar, bland annat RTC-moduler (Real Time Clock).
RTC-moduler används för att hålla reda på datum och tid. Allt detta går naturligtvis att programmera med vanliga mikroprocessorer, men det kan då vara svårt att uppnå lika låg strömförbrukning som med en dedikerad RTC-krets. Med batteribackup kan en sådan krets ge korrekt tid många år i följd även om det skulle förekomma långa avbrott på spänningsmatningen.
DS1307 Tiny RTC, med uppladdningsbart batteri LIR2450

Vårt enkla exempel använder inte RTC-modulens år och datum, utan vi nöjer oss med att läsa av tiden (timmar och minuter) för att låta AVR-processorn dra en elektromagnet (dragmagnet) som kan slå klockans timslag.

I2C ("Inter-Integrated Circuit") är en tvåtrådars seriell kommunikationsförbindelse mellan kretsar.
Det kan vara många olika kretsar som är anslutna samtidigt, och dom skiljs åt genom att ha olika "adresser".
Den enhet som styr kommunikationen kallas för Master och andra enheter som styrs kallas för Slave,
i princip kan valfri enhet vara Master.
Vi kommer att begränsa oss till att enbart använda AVR-processorn som Master,
och RTC-kretsen kan bara vara Slave.
Figuren visar att de två ledningarna måste vara anslutna till matningsspänningen med pullup-resistorer (10 kOhm). De resistorerna finns monterade på RTC-kretskortet, men bygger man en egen krets måste man se till att inkludera dessa.
De två I2C-ledningarna kallas för SCL (klocka) och SDA (data) och det är Mastern som ansvarar för att generera klockpulserna. Två unika kombinationer av SCL och SDA definierar "start" respektive "stopp" för dataöverföringen.

AVR-processorn har en inbyggd enhet för I2C kommunikation som kallas för TWI (Two Wire Interface), som kan generera start-vilkoret och stopp-vilkoret samt styra dataöverföring av byte's (8 bitar) åt gången.

RTC kretsen DS1307 har I2C-skrivadressen 0xD0 och I2C-läsadressen 0xD1.
Den har 8 interna Byteregister som innehåller tid och datum packade som BCD tal samt några enskilda bitar som styr klockans arbetssätt.
RTC DS1307 registers 0x00: "Seconds register" Format: "stop/!run sss ssss" 00-59 0x01: "Minutes register" Format: "0 mmm mmmm" 00-59 0x02: "Hours register" Format: "0 12/!24 !a/p h hhhh" 00-12 a-p ; 00-23 0x03: "Day register" Format: "0000 0 ddd" 1-7 0x04: "Date register" Format: "00 DD DDDD" 01-28/29/30/31 0x05: "Month register" Format: "000 M MMMM" 01-12 0x06: "Year register" Format: "yyyy yyyy" 00-99 0x07: "Control register" Format: "OUT 00 SQWE 00 RS1 RS2" 1, 4096, 8192, 32768 Hz
Sekundregistrets åttonde bit stannar klockan om "1" så den måste alltid sättas till "0".
Timregistrets sjunde bit avgör om det är en 12-timmars eller 24-timmars klocka.
Är det en 12-timmarsklocka så används den sjätte biten för att skilja mellan "at morning" eller "past morning".
I alla registren lagras tiderna som BCD siffror (0-9). Man kan vid programmeringen i stället skriva hexadecimala tal eftersom de tecken som skiljer (A-F) aldrig kommer att förekomma!
Om man behöver göra om ett byte BCD-tal i ett register till ett vanligt binärt tal kan man göra så här: bin = (bcd & 0xF) + 10*( bcd >> 4);.
AVR-processorns TWI-enhet styrs med registren TWDR, TWBR, TWAR, TWSR, TWCR. Man behöver skriva funktioner för start, stopp och byteöverföring som använder dessa register.
TWI-enheten är Master och den måste därför generera klockpulserna. RTC-kretsen som vi använder kan högst arbeta vid 100 kHz så detta får bestämma klockfrekvensen. Med registren TWSR och TWBR ställer man in klockfrekvensen enligt formeln:

//set SCL frequency to < 100kHz the max freq of DS1307
// Bits in TWSR register: "TWS7 TWS6 TWS5 TWS4 TWS3 - TWPS1 TWPS0"
TWSR = 0x00; // "xxxxx-00" prescaler set to divide by one
// TWBR register: frequency division byte
TWBR = 73; // SCLfreq = CPUfreq / (16+2*TWBR*Presc) = 16*10^6 / (16+2*73*1) = 99 kHz
//enable TWI
// Bits in TWCR register: "TWINT TWEA TWSTA TWSTO TWWC TWEN - TWIE"
TWCR = (1 << TWEN); // "-----1--"
För att generera start-kombinationen så behöver biten TWSTA sättas,
tillsammans med bitarna TWEN och TWINT (i TWCR registret).
Man behöver sedan vänta ut att startförloppet är avklarat ( TWINT ) innan man går vidare.
// Bits in TWCR register: "TWINT TWEA TWSTA TWSTO TWWC TWEN - TWIE" TWCR = (1 << TWINT)|(1 << TWSTA)|(1 << TWEN); // "1-1--1--" while ((TWCR & (1 << TWINT)) == 0); // "?-------" wait for done
För att generera stopp-kombinationen så behöver biten TWSTO sättas, tillsammans med bitarna TWEN och TWINT (i TWCR registret).
// Bits in TWCR register: "TWINT TWEA TWSTA TWSTO TWWC TWEN - TWIE" TWCR = (1 << TWINT)|(1 << TWSTO)|(1 << TWEN); // "1--1-1--"
För att skriva data/adresser till slaven så placeras det i TWDR-registret.
Därefter sätts bitarna TWEN och TWINT (i TWCR registret).
Man behöver sedan vänta ut skrivförloppet ( TWINT ).
Slave kommer att "kvittera" det motagna tecknet med att sända en ACK-bit ("1"). (Man kan kontrollera om det sker med funktionen I2C_GetStatus(), men det är inget som behövs om allt fungerar.
TWDR = data; // Bits in TWCR register: "TWINT TWEA TWSTA TWSTO TWWC TWEN - TWIE" TWCR = (1 << TWINT)|(1 << TWEN); // "1----1--" while ((TWCR & (1 << TWINT)) == 0); // "?-------" wait for done
Överföring av data från Slave till Master sker byte för byte.
Vill man läsa flera bytes i följd så använder man funktionen I2C_ReadACK().
Den avslutar läsningen med att sända ut en ACK-bit ("1") på slutet.
Man behöver vänta ut hela läsförloppet ( TWINT ).
// Bits in TWCR register: "TWINT TWEA TWSTA TWSTO TWWC TWEN - TWIE" // with ACK set TWCR = (1 << TWINT)|(1 << TWEN)|(1 << TWEA); // "11---1--" while ((TWCR & (1 << TWINT)) == 0); // "?-------" wait for done return TWDR;
Vill man bara läsa en enstaka byte från Slave, eller när det gäller den sista önskade byten,
använder man funktionen I2C_ReadNACK().
Den avslutar i stället läsningen med att sända ut en NACK-bit ("0") på slutet.
Man behöver vänta ut hela läsförloppet ( TWINT ).
// Bits in TWCR register: "TWINT TWEA TWSTA TWSTO TWWC TWEN - TWIE" // no acknowledge bit set, NOT ACK TWCR = (1 << TWINT)|(1 << TWEN); // "1----1--" while ((TWCR & (1 << TWINT)) == 0); // "?-------" wait for done return TWDR;
Under utprovning av utrustningen och under avlusningen av programkod är en funktion I2C_GetStatus() användbar. Den raporterar statuskoder från TWI-enheten.
// Bits in TWSR register: "TWS7 TWS6 TWS5 TWS4 TWS3 - TWPS1 TWSP0" Status = TWSR & 0xF8; // "-----000" mask status return Status; // TWI masked status code for debug
Som felsökningshjälpmedel har vi gjort en funktion printf_byte_uart() som är liknande printf(), men mycket mindre och starkt förenklad. Den kan skriva ut ledtexter och byte-varibler som decimala (%u, %d), hexadecimala (%x), binära (%b) tal, eller som bokstäver (%c).
Viktigast är att skriva till sekundregistret (register 0). Konstanten SECONDS är i programmet angiven som ett hexadecimalt tal för att kunna passa direkt som BCD-tal. Sekundregistrets 8:e bit är en start/stopp bit för klockan och den måste sättas "0". När rtc-kretsen levereras kan den biten råka ha vilket värde som helst.
/* Set RTC "Seconds register", (0x01) with "stop/!run" bit */ /* Format: "stop/!run sss ssss" 00-59 */ I2C_Start(); I2C_Write( RTC_WR_ADDRESS); I2C_Write( 0x00 ); // address pointer for "Seconds register" with stop/!run bit I2C_Write( SECONDS & 0x7F ); // mask seconds, and force !run/stop to run (run = 0) I2C_Stop();
På liknande sätt ställer man sedan in minutregistret och timregistret - se programkoden.
Man kan inte ha ett program som ställer in klockan på nytt med samma konstanta tid varje gång programmet startar. RTC_setup programmet ska köras en gång, därefter tar man bort RTC-modulen och låter den gå själv med backup-batteriet.
RTC_setup.txt
(for Arduino store code as: RTC_setup\RTC_setup.ino)Vi behöver läsa av timmar och minuter. Vid läsning skriver man först enhetens skrivadress, därefter skriver man som data registeradressen (1 för minutregistret). Sedan gör man omstart och skriver enhetens läsadress. Eftersom vi endast läser en byte använder man sedan funktionen I2C_ReadNACK().
/* Read minutes register */ I2C_Start(); I2C_Write( RTC_WR_ADDRESS); I2C_Write( 0x01 ); // dummy write "Minutes register" to set address pointer I2C_Start(); // repeat start I2C_Write( RTC_RD_ADDRESS); mn = (I2C_ReadNACK()) & 0x7F; // the last and only data byte to read (NACK) I2C_Stop();
Läsning av timmar sker på liknande sätt - se programkoden.
Programmet hämtar tiden från RTC varje sekund.
Om minuterna var "59" senast vi hämtade tiden, och minuterna nu är "00", så ska klockan slå "timmar" antal slag.
RTC_bell.txt
(for Arduino store code as: RTC_bell\RTC_bell.ino)
William Sandqvist willsandqvist@gmail.com