I2C tastatur
Denne side handler om konstruktionen af et I2C tastatur der fungerer som I2C slave og kan tilsluttes sammen med andre I2C Moduler til en I2C master som vist i følgende skitse.
Struktur for I2C-kommunikation med I2C tastaturet
Princippet i I2C kommunikation er beskrevet under I2C.
Alle resourcer til I2C tastaturet er Zippet sammen i en ZIP-fil.
I2C tastature kan f.x. anvendes til en I2C Lommeregner sammen med en Arduino og et I2C Display.
Princippet i I2C tastaturet
I2C tastaturet er opbygget som en selvstændig enhed der kan opfange de indtastninger der er foretaget på tastaturet, og disse indtastninger kan hentes via I2C ud fra den dokumenterede kommunikation.
Man kan anvende I2C tastaturet ved at opbygge hardwaren og lægge softwaren på som beskrevet i Atmel udviklingsmiljø. Herefter kan man kommunikere med tastaturet via I2C som dokumenteret i kommunikationen.
Ideen i at lave et selvstændigt tastatur er, at man ikke hele tiden skal servicere et tastatur, men at det kan fungere autonomt, så det selv opsamler indtastninger, og man kan så hente dem når ens kode har tid til det - her ved hjælp af I2C kommunikation.
Strukturen i I2C tastaturet
I2C tastaturet er opbygget omkring en microcontroller af typen ATTiny45 som vist i følgende blokdiagram:
Blokdiagram over I2C tastaturet
På tastaturet sidder et stik til at programmere microcontrolleren mens den sidder i printet. Det er fysisk det samme stik som anvendes til I2C kommunikationen.
Når man anvender stikket til I2C kommunikation, så skal man være opmærksom på at de to ekstra ben der anvendes til programmring ikke må forbindes, da det ene kan resette mikrocontrolleren og det andet anvendes til at angive I2C adressen på I2C tastaturet.
Dette valg af adresse sidder som en jumper hvor man kan vælge mellem en lige og en ulige adresse på I2C tastaturet.
Så er der selvfølgelig det vigtigste, nemlig selve tastaturet, der er opbygget ud fra ideen til AD-tastaturet konstrueret til PIC-mikrocontrollere. Princippet er nærmere beskrevet under beskrivelsen af AD tastaturet.
Endelig er der en lydgiver, som giver muligheden for at få en auditiv respons fra tastaturet når man indtaster.
I2C tastatur hardware
I2C tastaturet er lagt ud ved hjælpe af Eagle og layoutet med en PDF til at lave print efter kan hentes i denne ZIP-fil.
Det samlede diagram ser ud som følger:
Total-diagram over I2C tastatur
Printet kan monteres efter følgende layout:
Komponent-layout over I2C tastatur
Komponentlisten til I2C tastaturet er som følger:
Komponent | Type | Værdi |
---|---|---|
U1 | MikroController | ATTiny 45 (vendes rigtigt - i sokkel) |
U1 | IC Sokkel | 8 bens IC-sokkel (vendes rigtigt) |
SP1 | Lydgiver | AL60P 5V lydgiver (vendes rigtigt) |
D1 | Småsignal diode | 1N4148 (vendes rigtigt) |
LED1 | Lysdiode | 5mm LED Grøn (vendes rigtigt) |
S1 - S16 | Trykknap | 16 stk trykknapper |
SV1 | Jumper stik | 2 polet pin-række med Jumper |
PR1 | Molex stik | 2x3 polet pin-række til Moles fladkabel-stik |
R1 | Modstand | 10k ohm |
R2 | Modstand | 680 ohm |
R3 - R18 | Modstande | 16 stk. 1k ohm |
R19 | Modstand | 220k ohm |
R21 | Modstand | 4k7 ohm |
R24 | Modstand | 220k ohm |
C1 | Polyester kondensator | 100nF |
C2 | Elektrolyt kondensator | 10uF (vendes rigtigt) |
H1 - H4 | Monteringshul | 10mm M3 gevindstag med M3 skrue |
AD tast
Tastatur modellen brugt i I2C tastaturet er taget fra skolens wiki side om AD-tast.
Modulet består af 16 ens modstande sat i en serie forbindelse hvor 16 knapper vælger en af spændingerne.
Spændingerne vil være jævnt fordelt fra 1/16 af forsyningen op til selve forsyningen, så man på enkel vis kan beregne sig frem til tastenummeret ud fra måling af spændingen.
Hver modstand er 1 kilo ohm, så en belastning af spændingsdeleren ikke vil trække den skævt.
Hvis der ikke er trykket på en tast, så vil indgangen på mikrocontrolleren i princippet svæve, så for at man har styr på hvad der måles når der ikke er trykket en tast nede, så er der en pull-down modstand til stel. På I2C tastaturet er den valgt til 220k.
Hvis man regner på pull-down modstanden, så er det mest kritisk ved den midterste tast, hvor pull-down modstanden har størst mulighed for at trække spændingsdeleren skævt (der er størst modstand mod stel og størst modstand mod forsyningen). I dette tilfælde må pull-down ikke trække mere skævt end halvdelen af et trin ubelastet. Grænsen for dette er mellem 56k og 68 k, så valget af en 200k er ret konservativt.
Kommunikationsporten til I2C
Som vist på diagrammet indeholder kommunikationsporten PR1 til I2C flere ting, da portbenene anvendes til flere forskellige funktioner.
Stikket er 6-polet selvom det kun er de 4 ben der anvendes til I2C-kommunikationen.
Til I2C har porten følgende funktion:
Ben nr | Signal-navn | Specielle forhold |
---|---|---|
1 | MISO | Anvendes ikke til I2C - skal svæve ved reset |
2 | Vcc (+ 5V) | Forsyning til I2C modulet |
3 | SCK | Serial Clock til I2C |
4 | SDA | Serial Data til I2C |
5 | Reset | Mikrocontrollerens Reset - skal svæve når modulet er i funktion |
6 | GND | Stel-forbindelse til I2C modulet |
PR1 porten kan også anvendes til at programmere Mikrocontrolleren igennem, hvor porten får følgende funktion:
Ben nr | Signal-navn | Specielle forhold |
---|---|---|
1 | MISO | En del af ICSP programmeringen |
2 | Vcc (+ 5V) | Forsyning til I2C modulet |
3 | SCK | En del af ICSP programmeringen |
4 | MOSI | En del af ICSP programmeringen |
5 | Reset | Mikrocontrollerens Reset - Anvendes til at initiere ICSP |
6 | GND | Stel-forbindelse til I2C modulet |
Adresse på I2C tastaturet med Jumper
Ud over dette anvendes ben 1 i PR1 til at angive om det er en lige eller en ulige adresse I2C-tastaturet skal reagere på. Dette angives ved hjælp af jumperen SV1, hvor den er trukket høj af R24 når jumperen ikke er monteret og bliver trukket lav gennem R21 når man sætter jumperen på. Niveauet læses ved reset af mikrocontrolleren.
Som det kan ses på billederne til højre, så er det blot en jumper der kan sættes på for at skifte adressen. Når jumperen ikke er på, så bliver adressen (0x22) en højere (0x23), end hvis den er på.
Ideen med dette er at man kan have to ens tastaturer på samme I2C, og endda have samme program liggende i dem, men at man kan henvende sig til hvert af dem, uden det giver konflikt.
Yderligere hardware på I2C tastaturet
Der er yderligere komponeter på I2C tastaturet som har en mindre rolle at spille.
SP1 er en lydgiver, så man kan få tastaturet til at give små bip hver gang man taster - det kan være en funktion man ønsker at anvende for at give brugere en bedre fornemmelse af at der er tastet. Denne lydfunktion kan slås til og fra via I2C kommunikationen.
C1 og C2 er blot monteret for at fastholde forsyningen, hvis I2C modulet bliver forsynet gennem et længere kabel. Det er for at gøre modulet miondre støjfølsomt.
R1 og D1 er en del af reset-kredsløbet, som ellers håndteres internt i Mikrocontrolleren.
R2 og LED1 er blot en power-indikation der kan være praktisk at anvende i opstillinger hvor man kobler tingene sammen med kabler og løse ledninger. Hvis man ønsker at spare strøm i sin opstilling, så kan man spare 5 mA ved at undlade at montere disse to komponenter.
Taste overlæg til tastaturet
Når man anvender tastaturet, så kan det være en god ide at have en tolkning af hvad knappernes funktion er.
Til dette formål kan man anvende et layout som vist her.
Layoutet til knapperne kan hentes som en PhotoShop fil eller PDF-fil.
I2C Kommandoer
Alle kommandoer skal selvfølgelig sendes til den adresse I2C tastaturet har.
Efter adressen sendes en byte der bestemmer hvilken kommando man vil udføre, og alt efter hvilken kommando det er skal der sendes en byte mere, modtages en byte eller evt. modtages to byte - det afhænger naturligvis af hvilken funktion kommandoen skal udføre.
I2C tastaturet har følgende kommandoer:
Funktion | Kommando nr. | Byte til tastatur | Byte 1 fra tastatur | Byte 2 fra tastatur |
---|---|---|---|---|
Hent tast i buffer | 1 | - | tast (0-15) / 255 | - |
Hent antal taster i buffer | 2 | - | antal i bufferen | - |
Slet indholdet i bufferen | 3 | - | - | - |
Sæt Beep mode (lyd) | 4 | 0 / 1 | - | - |
Sæt Repeat mode | 5 | 0 / 1 | - | - |
Sæt tid før godkendelse | 6 | antal 10ms perioder | - | - |
Sæt tid før repeat starter | 7 | antal 10ms perioder | - | - |
Sæt tid mellem hver tast i repeat | 8 | antal 10ms perioder | - | - |
Hent nedtrykket tast | 0 | - | tast (0-15) / 255 | - |
Hent timer0 værdi | 9 | - | TCNT0 | - |
Hent pins in Port B (PINB) | 10 | - | decimal value | - |
Hent Buffer start og end | 11 | - | Buffer start pointer | Buffer end pointer |
Disse kommandoer bliver implementeret ved hjælp af den efterfølgende software.
I2C tastatur software
Koden til I2C tastaturet inde i ATTiny45'en ligger i denne ZIP-fil. Koden som den er kan brændes ned i en ATTiny45 ved at man redigerer den makefile-fil der ligger i mappen til den COM-port som Atmel udviklingsmiljøet skal bruge for at brænde HEX-filen ned i mikrocontrolleren. Hvis det er en ny ATTiny der ikke har fået brændt fuses, så skal man også huske at brænde dem ned i den.
Softwaren til tastaturet er skrevet i C++ til AVR og kan compileres med AVR Compileren. Koden er opbygget som de fleste andre koder til Mikrocontrollere med en initialiseringsdel hvor det hele sættes op så det er klart og en udførselsdel, hvor Mikrocontolleren tjekker alle ting løbende. Dette princip følger ideen i polling, hvor man dybest set looper så hurtigt rundt som muligt og tjekker om der skal laves noget på de forskellige ting.
Filer til softwaren
Koden er modulariseret i forskellige C++ (.cpp) filer med tilhørende include-filer (.h).
Den grundlæggende kode ligger i mail.cpp hvor initialiseringen foretages og det uendelige loop til polling ligger.
I main.cpp håndteres timingen og der kaldes ud til AD-aflæsning og I2C håndtering. Ud fra AD-aflæsningen beregnes tasteværdierne og når der registreres en indtastning placeres indtastningen i bufferen. Desuden håndteres lydgiveren.
AD-konverteringen er lavet som et simpelt modul adc.cpp der kan aflæse AD-konverteren ud fra en valg om hvilken kanal der skal aflæses og som venter på at konverteringen sker.
I2C kommunikationen løses på første niveau med i2c.cpp, som angiver hvordan I2C kommunikationen skal opføre sig på det overliggende plan, altså hvilke muligheder man stiller til rådighed for masteren
Forløbet af softwaren
Som illustreret i det viste flowchart, så sker der en initialisering af softwaren hvorefter der loopes med prioritering af I2C-kommunikationen. For hver 10. millisekund bliver tastaturet tjekket om der er sket nyt.
Flowchart over I2C-tastaturets software
Flowchartet illustrer ikke alle detaljer i hvordan tastaturet aflæses, da der både er indbygget lyd og repeat-funktioner i aflæsningen.
Koden til softwaren
Forklaringen til koden opdeles i de forskellige funktioner der løses, så tingene kan forklares samlet.
AD aflæsning
Selv om der kun anvendes en kanal til I2C tastaturet, så anvendes et generel modul til dette formål.
Modulet includes i main.cpp med:
#include "adc.h"
I starten af main-funktionen initialiseres AD-konverteren med følgende kald:
adc_init();
Initialiseringen foregår på følgende måde:
// Initializing AD module
void adc_init() {
// The AD converter is set to single conversions
ADCSRA = 0;
ADCSRA |= (1<<ADPS2) | (1<<ADPS1); // Set prescaler to div64 --> 125kHz ADC clock
ADCSRA |= (1<<ADEN); // Enable the AD-converter
}
Det gør at AD-værdien kan aflæses på følgende måde:
sensorValue = adc_read(2); // Read AD input of ADC2 (pin PB4)
Det retrunerede tal har en værdi fra 0 til 1023 repræsenterende 0-5V
Selve konverteringen sker på følgende måde:
// Routine for all AD-channels
int adc_read(uint8_t channel) {
int res;
ADMUX = ADC_REF | channel; // Select the channel for conversion
switch (channel) { // Set the selected channel as input
case 0: set_input(DDRB, PB5);
break;
case 1: set_input(DDRB, PB2);
break;
case 2: set_input(DDRB, PB4);
break;
case 3: set_input(DDRB, PB3);
break;
}
//start a conversion
ADCSRA |= (1<<ADSC);
//wait for end of conversion
while (ADCSRA & (1<<ADSC)) {
//do nothing
}
res = ADCL; // Read the converted value from two bytes
res |= ADCH << 8;
return res; // Return it from the routine
}
Bemærk at koden venter på at konverteringen sker
Styring af tiden i softwaren
Da koden har forskellig gennemløbstid alt efter hvad der sker i koden, så vælges der her at sætte en fast timing op, så det er til at regne med hvilke tider der gælder for tasterne. Til dette formål vælges at arbejde med 10 ms enheder, så alle de tidsmæssige indstillinger der laves er i denne enhed (altså fra 10 ms op til 2.55 sekund, da det rummes i en byte).
De tider tastaturet arbejder med er skitseret herunder:
Tidsdiagram der illustrerer indtastninger på tastaturet
De svagt grå tids-enheder svarer til 10ms inddelinger.
Der er to tastetryk illustreret i tidsdiagrammet, med følgende noter tilknyttet:
1) Første tast nedtrykkes.
2) Her registreres første tast, og den lagres i bufferen. Samtidigt starter et 50 ms beep.
3) Her slippes den første tast.
4) Anden tast nedtrykkes.
5) Her registreres anden tast, og den lagres i bufferen. Samtidigt starter et 50 ms beep.
6) Her starter repeat af anden tast, den lagres i bufferen og der beepes.
7) Anden tast repeteres igen, den lagres i bufferen og der beepes.
8) Anden tast repeteres for tredje gang, den lagres i bufferen og der beepes. Det kan fortsætte så længe tasten holdes nede eller bufferen fyldes.
9) Anden tast slippes igen.
For at kunne opnå denne 10 ms opdeling anvendes Timer0, der i sin fulde udstrækning er beskrevet i databladet[1] side 65-82, hvor register-indholdet er beskrevet side 77-82.
Timeren indstilles til at fungere i CTC-mode, hvor timeren TCNT0 tæller fra 0 til indholdet i OCR0A og resetter. Der indstilles en prescaler til at dele system-clocken på 8MHz med 1024, og ved at skrive 78 i OCR0A, så nulstilles hvert 9.984 ms, hvilket er fint acceptabelt som 10 ms i denne sammenhæng.
Disse instillinger laves i starten af main-funktionen i main.cpp:
TCCR0A = 0x02; // Limit to OCR0A
TCCR0B = 0x05; // 1024 prescaler
OCR0A = 78; // time period of 9.984 ms
For at fange denne nulstilling hver gang, så testes på interrupt-flaget, uden at lave en interrupt-rutine. Det gøres ved følgende test:
if ((TIFR & (1 << OCF0A)) > 0) { // Timer overflow - comes about every 10 ms
TIFR = 1 << OCF0A; // Clear timer overflow
Når man finder interruptet findes, så slettes det med sætningen efter testen.
Hele keyboard-aflæsningen og håndteringen af buzzeren sker inde i denne if-sætning, altså hvert 10. ms.
Registrering af en tast
Når AD-værdien bliver aflæst hvert 10. ms, så testes først om der er holdt en tast nede ud fra at AD-værdien skal være over halvdelen af værdien for den mindste tastespænding. Det sker ved følgende test:
if (sensorValue > (resolution / (antalTaster * 2))) { // Test for any key
Ud fra den aflæse AD-værdi beregnes tastenummeret fra 0 til 15 ved at trække et halvt taste-interval fra og dividere med taste-intervallet som vist her:
// Calculate what key seems to be pressed
calcTast = (sensorValue - (resolution / (antalTaster * 2))) / (resolution / antalTaster);
Hvis det ikke er den samme om sidste aflæsning, så registreres det at der er den nye tasteværdi, og at den er læst 0 gange ind til videre, samt at den ikke skal være i taste-repeat.
if (calcTast != tast) { // Is it a new key that are pressed
antalRead = 0; // Start to check for a new key
tast = calcTast;
repeat = false;
Hvis vi har samme aflæsning som sidst tjekkes først om vi har læst så mange gange at vi skal begynde at repetere tasten, og hvis det ikke er tilfældet, så registreres at den samme tast er læst en gang til:
} else {
if (antalRead < repStart) { // Are we below the start of repeating
antalRead += 1;
// The same key has been read a number of times, so it is accepted
Hvis vi så har læst den samme tast 5 gange (defaultværdi i anatalGodkend) så accepteres tasten (det svarer til at tasten accepteres efter 50 ms, hvilket brugeren ikke registrerer).
Hvis det er en godkendt tast, så tjekkes lige om der er plads i bufferen, og hvis der er det, så tændes buzzeren forudsat vi er i beep-mode (det er vi som default), og tiden sættes op til at buzzeren skal lyde i 50 ms.
Når det er gjort lagres den registrerede tast i bufferen og start-pointeren til bufferen flyttes en frem.
På denne måde kommer bufferen til at fungere som en FIFO-buffer, ved at aflæsningen af tastaturet med I2C sker ved at tasteværdierne trækkes ud ved end-pointeren.
if (antalRead == antalGodkend) {
if (((bufStart + 1) % bufSize) != bufEnd) { // Is the buffer not full
if (beepMode) { // If the beep-mode is set
beepTime = 5; // Set the beep-time
output_high(PORTB, PB3); // Turn on the buzzer
}
taster[bufStart] = tast; // Save the key that has been accepted
bufStart++; // Adjust the start of the buffer
bufStart %= bufSize;
}
Stadigvæk under godkendelsen af tasten kigges på om vi er kommet til at repetere, for så sættes tiden til den næste tast til at være repAntal, der som default er 20 (giver 200 ms mellem repetationerne).
if (repeat) { // If the repeat has been started
antalRead -= repAntal; // Set the time to the next key-event
}
}
Når vi når til starten af ventetiden før tasten skal repetere, så kan der ske to ting. Hvis vi ikke er i repeat-mode (hvilket ellers er default), så vil der ikke ske mere - tiden tælles ikke længere frem, og der vil ikke ske noget før der kommer en anden taste-aflæsning. Er vi i repeat-mode, så vil tiden sættes således at der vil går 200 ms inden den næste tast kommer.
} else { // When the start of repeating is reached
if (repeatMode) { // Is repeating allowed
repeat = true; // Start the repeat
antalRead = antalGodkend - repAntal; // and set the time to the next key
}
}
}
Hvis AD-aflæsningen indikerer at der ikke er trykket på nogen taster, så sættes tastværdien til 255.
} else { // If no key is pressed
tast = 255; // Indicate that no key is pressed
}
Til slut i den if-sætning der afvikles hvert 10. ms håndteres tiden for buzzeren, ved at den tælles ned hvis den er over 0, og når den rammer 0, så slukkes der for buzzeren.
if (beepTime > 0) { // If the buzzer is ON
beepTime--;
if (beepTime == 0) { // Beep till the time has run out
output_low(PORTB, PB3);
}
}
Håndtering af I2C kommunikationen
For at adskille koden, så er selve håndteringen af I2C kommunikationen lagt ud i en fil for sig, i2c.cpp som includes i starten af main.cpp:
#include "i2c.h"
Lige som AD-modulet, så kaldes der en initialiserings-rutine fra main.cpp:
i2c_init(); // Initialization of external modules
Denne initialisering indeholder følgende kode, der sætter interrupt, så I2C hardwaremn kan fungere, input og output sættes op, og der indstilles I2C tastaturets adresse ud fra den jumper der kan placeres på SV1. Til sidst kaldes usiTwiSlaveInit, der er en initialisering af det grundlæggende I2C-modul:
// Initializing the I2C module
void i2c_init() {
sei(); // Set the interrupt
set_output(DDRB, PB3); // Buzzer as output
output_low(PORTB, PB3); // Buzzer silent
set_input(DDRB, PB1); // Input for read address for odd or even
set_input(DDRB, PB4); // AD - key analog input
if ((PINB && 0x02) > 0) { // Tjeck for address setting
I2C_SLAVE_ADDR++;
}
usiTwiSlaveInit(I2C_SLAVE_ADDR); // init I2C Slave mode in the usiTwiSlave module
}
I main.cpp der kaldes I2C taste-tjekkes hele tiden (for hver gang i loopet), så I2C kan reagere hurtigst muligt:
i2c_tast(); // Tjeck for the I2C communication for every loop
Rutinen tjekker hver gang om masteren har henvendt sig, hvilket sker på følgende måde:
// Routine that can access the keyboard variables to get and set keyboard information via I2C
void i2c_tast() {
uint8_t byteRcvd;
if (usiTwiDataInReceiveBuffer()){ // got I2C input!
byteRcvd = usiTwiReceiveByte(); // get the command byte from master
De rutiner der anvendes til at etablere I2C-kommunikationen er hentet i et generelt modul usiTwiSlave[2], der indstiller I2C porten til at reagere på den angivne I2C adresse ved hjælp af usiTwiSlaveInit(I2C_SLAVE_ADDR), og samle bytes op når de modtages og kan sende dem i det rigtige format, så når det modul er initialiseret.
Modultet fungerer interrupt-baseret, så man ikke hele tiden skal tjekke indgangene - opsamlingen udnytter at Atmel mikroconterollere har indbygget hardware til at håndtere en I2C-kommunikation, så kan hele kommunikationen forløbe ved hjælp af følgende 3 rutiner:
usiTwiDataInReceiveBuffer() der kan angive om der er modtaget data.
usiTwiReceiveByte() der henter en byte data, hvis der er modtaget noget.
usiTwiTransmitByte(tast); der sender en byte (her variablen tast)
Den første byte der modtages tolkes i dette tilfælde som en kommando, der siger hvordan resten af kommunikationen skal forløbe for den kommando. Det er stillet op i en længere case-struktur, hvor starten er angivet her:
switch (byteRcvd) { // All the different commands
case 0: // The actual tast value - will be 255 if no key is pressed - normally for testpurpose
usiTwiTransmitByte(tast);
break;
case 1: // Get a key from the buffer
if (bufEnd == bufStart) {
usiTwiTransmitByte(255); // Will return 255 meaning no byte, if the buffer is empty
} else {
usiTwiTransmitByte(taster[bufEnd]); // Transmit the first in the buffer
bufEnd++; // Move buffer pointers to "clear" the transmitted key
bufEnd %= bufSize;
}
break;
case 2: // Number of keys in buffer
if (bufStart >= bufEnd) {
usiTwiTransmitByte(bufStart - bufEnd);
} else {
usiTwiTransmitByte(bufStart + bufSize - bufEnd);
}
break;
.
.
.
Kommando 0 er blot til test, hvor man kan læse hvilken tastværdi tatstaturet rent faktisk tolker.
Kommando 1 kan faktisk anvendes til standard aflæsning af tastaturet, hvis man er opmærksom på at der svares 255, hvis der ikke er taster i bufferen.
Kommando 2 kan anvendes til at se om der ligger taster i bufferen, så man ikke behøves at tolke det man læser med kommando 1.
Der er defineret 12 forskellige kommandoer i resten af koden der følger samme struktur, dels til at aflæse tastaturet med, og dels til at stille på tastaturets egenskaber. Koden er ikke vist her, men kan ses i ZIP-filen med koden.
Alle kommandoer er gennemgået under I2C kommandoer.
Test af I2C tastaturet
Tastaturet er blevet testet løbende under udviklingen, og der er brugt den viste testopstilling.
Den ene arduino med et AVR-programmer Shield på er til at brænde kode ned i ATTiny45 mikrocontrolleren med.
Den anden arduino er til at teste I2C kommunikationen med, her anvendes også et Shield til I2C kommunikation, blot fordi det er nemmest med de 6 polede Molex stil på et fladkabel.
Fra denne arduino kan man hente information i I2C tastaturet og præsentere det på PC'en skærm. Princippet er at man programmerer arduinoen til at fungere som Master i I2C kommunikationen, og løbende tilretter den til at afteste de funktioner man implementerer i ATTinyen.
Test af tidsmæssige forhold i I2C kommunikationen
Når man henter informationer i I2C tastaturet, så kan der være problemer med at kommunikationen låser.
I første omgang løstes dette ved blot at smide nogle små delays på 1 ms ind mange steder i Masterens kode.
Dette tolkes ikke som en holdbar løsning, så der oprettedes en ny master-kode til Arduinoen, hvor der blev tilføjet to debug-output, så man kan måle på arduinoen samtidigt med at man måler på I2C kommunikationen. Det blev gjort med den viste testopstillen, hvor man anvender en logik-analysator til at måle med.
Først måles hvordan signalerne ser ud når man måler på kommunikationen når den fungerer:
Måling af en fungerende kommunikation, hvor antal i bufferen hentes og den ønskede tast hentes.
Det man specielt skal lægge mærke til her er den lyslilla visning lige efter 05 (taste-nummeret) i den fortolkede I2C kommunikation. Det er en NAK der skal afslutte de hentede data, lige som den ses i slutningen af den første blok hvor antallet 01 hentes.
Man skal også lægge mærke til den ekstra lille puls i slutningen af Digital 2, der er et debug-output som viser forløbet af koden i arduinoen.
Herefter måles hvad der sker når kommunikationen fejler:
Måling af en fejl kommunikation, hvor den ønskede tast ikke bliver hentet.
Her er der zoomet ind på hentningen af tasten, da det er her der fejles. Det der sker er at der hvor tasten hentes, der svares der med 00 efterfulgt af en ACK, og ikke en NAK som den skulle. Dette gør at masteren ikke kan hente den ønskede taste-værdi og som det ses, så afspejles det også ved at der ikke er den lille ekstra puls på Digital 2.
Årsagen til dette er at ATTiny'en der laver I2C tastaturet ikke kan nå at lægge pakken klar til afhentning i alle situationer. Løsningen på dette er at lægge en delay ind på 100 mikrosekunder ind mellem den Wire.endTransmission hvor man sender kommandoen og den Wire.requestFrom som beder om en byte fra I2C tastaturet.
Dette gør at koden til getTastBuf skal se ud som følger:
void getTastBuf() {
Wire.beginTransmission(I2C_Address);
Wire.write(2);
error = Wire.endTransmission();
if (error == 0)
{
delayMicroseconds(100);
Wire.requestFrom(I2C_Address, 1);
if (Wire.available() == 1) {
error = Wire.read();
if (error >= 1) {
Serial.print("Num in buffer: ");
Serial.print(error);
Serial.print("\tKey reading: ");
Wire.beginTransmission(I2C_Address);
Wire.write(1);
error = Wire.endTransmission();
if (error == 0) {
delayMicroseconds(100);
Wire.requestFrom(I2C_Address, 1);
if (Wire.available() == 1) {
error = Wire.read();
Serial.println(error);
}
}
}
}
} else {
Serial.print(error);
Serial.println(" Fail");
}
}
Arduino Master software
For at kunne anvende I2C tastaturet til noget fornuftigt, så skal der skrives en I2C master der trækker det ud af slaven (tastaturet) som er relevant - her er det indtastninger på tastaturet.
Denne Arduino master er skrevet for at illustrere de forskellige funktioner i I2C tastaturet som man kan få fat i via I2C kommunikationen.
Koden til I2C-masteren kan hentes i denne ZIP-fil.
For at kunne opbygge en I2C master skal modulet wire includeres, og der defineres en adresse man vil kommunikere med:
#include <Wire.h>
#define I2C_Address 0x23
Adressen er her sat til 0x23, hvilket svarer til at der ikke er en jumper på SV1 - hvis der placeres en jumper på SV1, så bliver adressen til 0x22 (læses kun ved reset af tastaturet).
setup()
Initialiseringen af wire-modulet foretages som følger:
void setup()
{
Wire.begin();
Serial.begin(9600);
delay(100);
help();
}
Ud over dette indstilles den serielle port, så man i test-programmet kan se hvad der sker, og der udskrives en hjælpeskærm, så man får vist hvilke muligheder der kan afprøves.
loop()
I loopet startes med at kigge efter en seriel karakter, så man kan bestemme hvad man skal kommunikere til modulet:
void loop()
{
if (Serial.available() > 0) {
ch = Serial.read();
if ((ch == 'h') | (ch == 'H')) {
help();
}
if ((ch == 'b') | (ch == 'B')) {
error = Serial.parseInt();
setBeep(error);
}
if ((ch == 'a') | (ch == 'A')) {
getTast();
}
if ((ch == 'r') | (ch == 'R')) {
error = Serial.parseInt();
setRepeat(error);
}
.
.
.
Der er en del flere muligheder, som det kan ses af følgende hjælpeskærm:
Demo program for I2C AD keyboard -------------------------------- H Displays this help-screen B n Set Beep mode to n M Flips the mode where the keys are presented A Gets the actually read keyvalue G Tries to read from the key-buffer C Clears the buffer R n Repeat set to n N Number in buffer S Start and End pointers to the buffer K n Time before a key is accepted as valid U n Time before starting repeat V n Time between repeating keys P Port B as input (decimal) T Timer value
Hvis test-softwaren er sat til getMode (default), så kaldes rutinen gerTastBuf() der forsøger at hente en tast i bufferen. Dette mode kan ændres ved at taste M.
if (getMode) {
getTastBuf();
}
Læsning af en tast på tastaturet
getTastBuf er defineret som følger:
void getTastBuf() {
Wire.beginTransmission(I2C_Address);
Wire.write(2); // Kommando der spørger om antallet af taster i bufferen
error = Wire.endTransmission();
if (error == 0)
{
delay(1);
Wire.requestFrom(I2C_Address, 1); // Læs svar fra tastaturet
delay(1);
if (Wire.available() == 1) {
error = Wire.read(); // Læs antallet af taster der er klar i tastaturet
if (error >= 1) {
Serial.print("Num in buffer: ");
Serial.print(error); // Udskriv resultater
Serial.print("\tKey reading: ");
Wire.beginTransmission(I2C_Address);
Wire.write(1);
error = Wire.endTransmission();
if (error == 0) {
delay(1);
Wire.requestFrom(I2C_Address, 1); // Læs svar fra tastaturet
delay(1);
if (Wire.available() == 1) {
error = Wire.read(); // Læs hvilken tast det er
Serial.println(error); // og skriv den ud
}
}
}
}
} else {
Serial.print(error); // Visning af fejl-kommunikation
Serial.println(" Fail");
}
}
Det ser sker i koden er at der sendes et 2-tal til tastaturet, og hvis det går godt, så hentes der en byte, der angiver hvor mange taster der ligger i bufferen.
Hvis det tal er over 0 (at der ligger taster i bufferen), så udskrives antallet sammen med noget tekst, og der sendes et 1-tal til tastaturet, og går det godt, så hentes den karakter der ligger i bufferen, og den skrives ud.
Dette er en lidt avanceret måde at hente tasterne på.
Man kan også gøre det mere simpelt som vist i getTastRaw():
void getTastRaw() {
Wire.beginTransmission(I2C_Address);
Wire.write(1);
error = Wire.endTransmission();
if (error == 0)
{
Wire.requestFrom(I2C_Address, 1);
delay(1);
if (Wire.available() == 1) {
error = Wire.read();
Serial.print("Key reading: ");
Serial.println(error);
}
} else {
Serial.print(error);
Serial.println(" Fail");
}
}
Her hentes tasten simpelthen og udskrives. Det man vil se er at hvis der ikke er tastet noget, så svarer tastaturet med 255. Det vil sige at man på denne måde bare kan hente tasten, og hvis den er 255, så betyder det at der ikke er kommet en tast siden man tjekkede sidst.
Det kan lade sig gøre at indstille forskellige ting i tastaturet ved at sende forskellige kommandoer. Dette eksempel viser hvordan man kan slå Beep funktionen til og fra.
Sender man 0 efter kommandoen bliver beep-funktionen slået fra.
Sender man 1 efter kommandoen bliver beep-funktionen slået til.
void setBeep(byte mode) { // Rutine der kan slå lydgiveren til og fra
Wire.beginTransmission(I2C_Address);
Wire.write(4); // Angiv at det er kommandoen til lydgiveren
Wire.write(mode); // Angiv om den skal såes til eller fra
error = Wire.endTransmission();
if (error == 0) {
Serial.print("Beep mode set to: ");
Serial.println(mode); // Vis resultatet hvis det lykkedes
}
}
Der ligger også forskellige test-kommandoer, hvor man f.x. kan læse hvordan start og end pointer stå. Denne funktion har ikke nogen værdi i forbindelse med anvendelsen af tastaturet, men kan være nyttig hvis man er i tvivl om hvordan tastaturet fungerer.
void getStartEnd() { // Rutine der henter de to buffer-pointere
Wire.beginTransmission(I2C_Address);
Wire.write(11); // Angiv kommando til at hente buffer-pointere med
error = Wire.endTransmission();
if (error == 0)
{
delay(1);
Wire.requestFrom(I2C_Address, 2);
delay(1);
if (Wire.available() == 2) { // Tjek om der der er kommet 2 byte
error = Wire.read(); // læs start-pointer
Serial.print("Start: ");
Serial.print(error); // læs end-pointer
Serial.print("\tEnd: ");
error = Wire.read();
Serial.println(error); // Det hele skrives ud på terminalen
}
}
else
{
Serial.print(error);
Serial.println(" Fail");
}
}
Referencer