STM32, seriellt I2C-gränssnitt. STM32, seriellt gränssnitt I2C Stm32 gränssnitt i2c beskrivning fortsättning
Vissa gillar pajer, andra inte.
i2c-gränssnittet är mycket utbrett och används. I stm32f4 finns det så många som tre moduler som implementerar detta protokoll.
Naturligtvis med fullt stöd hela den här grejen.
Att arbeta med modulen är i allmänhet detsamma som i andra styrenheter: du ger den kommandon, den kör dem och rapporterar resultatet:
I> Vi gick START.
S> Ok, jag skickade det.
Jag> Coolt, skicka adressen nu. Så här: 0xxxx.
S> Ok, jag skickade den. Jag fick höra att ACK. Låt oss gå vidare.
I> Fortfarande vid liv, bra. Här är registernumret: 0xYY, - låt oss gå.
S> Skickade, fick ACK.
I> Skicka nu data till honom, här är byten: 0xZZ.
S> Skickat går han med på mer: ACK.
Jag> Knulla honom, inte än. De gick STOPP.
S> Okej.
Och allt är ungefär i denna anda.
I denna styrenhet i2c-stiften är utspridda över portarna så här:
PB6: I2C1_SCL
PB7: I2C1_SDA
PB8: I2C1_SCL
PB9: I2C1_SDA
PB10: I2C2_SCL
PB11: I2C2_SDA
PA8: I2C3_SCL
PC9: I2C3_SDA
Generellt sett är det praktiskt att titta på stiftet på kringutrustningen på sidan 59.
Överraskande nog, för att arbeta med i2c behöver du alla dess register, lyckligtvis finns det få av dem:
I2C_CR1- kommandon till modulen för att skicka kommandon/tillstånd och välja driftslägen;
I2C_CR2- ställa in DMA och indikera modulens driftfrekvens (2-42 MHz);
I2C_OAR1- ställa in enhetens adress (för slav), adressstorlek (7 eller 10 bitar);
I2C_OAR2- ställa in enhetens adress (om det finns två adresser);
I2C_DR- dataregister;
I2C_SR1- modulstatusregister;
I2C_SR2- statusregister (slav, måste läsas om ADDR- eller STOPF-flaggor är satta i SR1);
I2C_CCR- ställa in gränssnittshastigheten;
I2C_TRISE- ställa in tidpunkter för kanter.
Men hälften av dem är av typen "skriv ner och glöm det".
STM32F4-Discovery-kortet har redan en I2C-enhet som du kan öva med: CS43L22, ljud-DAC. Den är ansluten till stift PB6/PB9. Det viktigaste är att inte glömma att tillämpa en hög nivå på stift PD4 (~RESET sitter där), annars kommer inte DAC:n att svara.
Inställningsproceduren är ungefär som följer:
1
. Tillåt klockning av portar och själva modulen.
Vi behöver stift PB6/PB9, så vi måste ställa in bit 1 (GPIOBEN) i RCC_AHB1ENR-registret för att aktivera porten.
Och ställ in bit 21 (I2C1EN) i RCC_APB1ENR-registret för att aktivera I2C-modulen. För den andra och tredje modulen är bitnumren 22 respektive 23.
2
. Därefter konfigureras stiften: Oped Drain-utgång (GPIO->OTYPER), alternativt funktionsläge (GPIO->MODER) och alternativt funktionsnummer (GPIO->AFR).
Om så önskas kan du konfigurera en pull-up (GPIO->PUPDR), om den inte finns på kortet (och en pull-up till strömförsörjningen för båda linjerna krävs i någon form). Numret för I2C är alltid detsamma: 4. Det är trevligt att det finns ett separat nummer för varje typ av kringutrustning.
3
. Den aktuella klockfrekvensen för den perifera Fpclk1-enheten (uttryckt i MHz) indikeras i CR2-registret. Som jag förstår det behövs detta för att beräkna olika protokolltider.
Det bör förresten vara minst två för normalt läge och minst fyra för snabbläge. Och om du behöver en full hastighet på 400 kHz, så måste den också delas med 10 (10, 20, 30, 40 MHz).
Högsta tillåtna klockfrekvens: 42 MHz.
4
. Gränssnittshastigheten konfigureras i CCR-registret och läget väljs (normal/snabb).
Betydelsen är: Tsck = CCR * 2 * Tpckl1, dvs. SCK-perioden är proportionell mot CCR (för det snabba läget är allt lite mer knepigt, men det beskrivs i RM).
5
. Den maximala tiden för stigande kant i TRISE-registret justeras. För standardläge är denna tid 1 µs. I registret måste du skriva antalet busscykler som passar in i denna tid, plus en:
om Tpclk1-cykeln varar 125 ns, skriv sedan (1000 ns / 125 ns) + 1 = 8 + 1 = 9.
6
. Generering av avbrottssignaler (fel, status och data) är valfritt aktiverat;
7
. Modulen slås på: PE-flaggan i CR1-registret är inställd på 1.
Då fungerar modulen som den ska. Du behöver bara implementera rätt ordning på kommandon och kontrollera resultaten. Till exempel en registerpost:
1
. Först måste du skicka START genom att sätta en flagga med samma namn i CR1-registret. Om allt är ok kommer efter en tid SB-flaggan att sättas i SR1-registret.
Jag skulle vilja notera en punkt - om det inte finns någon pull-up på linjen (och de är på 0), så kan den här flaggan inte vänta alls.
2
. Om flaggan tas emot skickar vi adressen. För en sju-bitars adress skriver vi den helt enkelt i DR precis som den kommer att vara på linjen (7 adressbitar + riktningsbit). För tio-bitars, en mer komplex algoritm.
Om enheten svarar på adressen med ACK, kommer ADDR-flaggan att visas i SR1-registret. Om inte, kommer AF-flaggan (Acknowledge failure) att visas.
Om ADDR visas måste du läsa SR2-registret. Du behöver inte titta på någonting där, bara sekventiell läsning av SR1 och SR2 återställer denna flagga. Och medan flaggan är inställd hålls SCL lågt av mastern, vilket är användbart om du behöver be fjärrenheten att vänta innan data skickas.
Om allt är ok, kommer modulen sedan att växla till läget för att ta emot eller överföra data, beroende på den minst signifikanta biten av den skickade adressen. För att skriva måste det vara noll, för att läsa måste det vara ett.
men vi tittar på posten, så vi kommer att anta att det fanns en nolla där.
3
. Därefter skickar vi adressen till det register som intresserar oss. På samma sätt skriver man ner det i DR. Efter överföringen sätts TXE (överföringsbufferten är tom) och BTF (överföringen slutförd) flaggorna.
4
. Därefter kommer data som kan skickas medan enheten svarar med ett ACK. Om svaret är NACK kommer dessa flaggor inte att ställas in.
5
. Vid slutförande av överföringen (eller i händelse av ett oväntat tillstånd) skickar vi STOP: flaggan med samma namn sätts i CR1-registret.
När man läser är allt sig likt. Ändringar först efter att man skrivit registeradress.
Istället för att skriva data skickas START igen (omstart) och adressen skickas med minst signifikanta bituppsättning (lästecken).
Modulen väntar på data från enheten. För att uppmuntra den att skicka nästa byte, måste du ställa in ACK-flaggan i CR1 innan du tar emot (så att modulen efter mottagandet skickar samma ACK).
När du tröttnar på det, ta bort flaggan, enheten kommer att se NACK och tystna. Därefter skickar vi STOP på vanligt sätt och gläds över mottagna data.
Här är samma sak i kodform:
// Initiera modulen void i2c_Init(void) ( uint32_t Clock = 16000000UL; // Module klockfrekvens (system_stm32f4xx.c används inte) uint32_t Speed = 100000UL; // 100 kHz-port 100 kHz /-B clocking RCCOB1 |= RCC_AHB1ENR_GPIOBEN; // Sätt upp stift PB6, PB9 // Öppna dränering! GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9; // Uppdraget är externt, så det kan inte konfigureras här! // om det behövs, se GPIOB->PUPDR-registret // Nummer för den alternativa GPIOB-funktionen ->AFR &= ~(0x0FUL<< (6 * 4)); // 6 очистим
GPIOB->AFR |= (0x04UL<< (6 * 4)); // В 6 запишем 4
GPIOB->AFR &= ~(0x0FUL<< ((9 - 8) * 4)); // 9 очистим
GPIOB->AFR |= (0x04UL<< ((9 - 8) * 4)); // В 9 запишем 4
// Режим: альтернативная функция
GPIOB->MODER &= ~((0x03UL<< (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим
GPIOB->MODER |= ((0x02UL<< (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2
// Включить тактирование модуля I2C1
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Vid denna tidpunkt bör I2C stängas av // Återställ allt (SWRST == 1, återställ) I2C1->CR1 = I2C_CR1_SWRST; // PE == 0, detta är huvudsaken I2C1->CR1 = 0; // Vi antar att vi kör från RC (16 MHz) // Det finns inga förskalare i klocksystemet (alla 1) // På ett vänskapligt sätt bör vi beräkna allt detta från // modulens faktiska klockfrekvens I2C1->CR2 = Klocka / 1000000UL; // 16 MHz // Justera frekvensen ( // Tclk = (1 / Fperiph); // Thigh = Tclk * CCR; // Tlow = Thigh; // Fi2c = 1 / CCR * 2; // CCR = Fperiph / ( Fi2c * 2); uint16_t Värde = (uint16_t)(Klocka / (Hastighet * 2)); // Minsta värde: 4 if(Värde< 4) Value = 4;
I2C1->CCR = Värde; ) // Ställ in gränsen för stigtid // I standardläge är denna tid 1000 ns // Vi lägger helt enkelt till en till frekvensen uttryckt i MHz (se RM s. 604). I2C1->TRISE = (Klocka / 1000000UL) + 1; // Aktivera modul I2C1->CR1 |= (I2C_CR1_PE); // Nu kan du göra något ) // Skicka en byte bool i2c_SendByte(uint8_t Address, uint8_t Register, uint8_t Data) ( if(!i2c_SendStart()) return false; // Chipadress if(!i2c_SendAddress(Address)) return i2c_SendStop (); // Registrera adress if(!i2c_SendData(Register)) return i2c_SendStop(); // Data if(!i2c_SendData(Data)) return i2c_SendStop(); // Stop! i2c_SendStop(); return true; ) // Ta emot byte bool i2c_ReceiveByte(uint8_t Address, uint8_t Register, uint8_t * Data) ( if(!i2c_SendStart()) return false; // Chipadress if(!i2c_SendAddress(Address)) return i2c_SendStop(); // Registrera adress if(! i2c_SendData(Register)) returnerar i2c_SendStop(); // Starta om if(!i2c_SendStart()) returnerar falskt; // Chipadress (läs) if(!i2c_SendAddress(Adress | 1)) returnerar i2c_SendStop(); // Ta emot en byte if(!i2c_ReceiveData(Data)) return i2c_SendStop(); // Stop! i2c_SendStop(); return true; ) Användning: ( uint8_t ID = 0; i2c_Init(); // Vi antar att PD4 är satt till en hög nivå och DAC:n fungerar (detta måste göras på något sätt) // Skicka en byte till enheten med adress 0x94, för att registrera 0x00 med värdet 0x00. i2c_SendByte(0x94, 0x00, 0x00); // Ta emot en byte från enheten med adress 0x94 från register 0x01 (ID) till den variabla bufferten i2c_ReceiveByte(0x94, 0x01, &ID); )
Naturligtvis kan du inte göra detta annat än i ett träningsexempel. Väntan på att åtgärden ska slutföras är för lång för en så snabb kontroller.
(Utvecklarguide för HCS08-familjens mikrokontroller)
För att styra I2C-modulen används 6 specialfunktionsregister:
- IICC - första kontrollregistret för I2C-modulen;
- IICC2 - andra styrregister för I2C-modulen;
- IICS - I2C-modulstatusregister;
- IICF - I2C-modulens baudhastighetsregister;
- IICA - I2C-moduladressregister;
- IICD är I2C-modulens dataregister.
QE-seriens MCU innehåller 2 I2C-moduler och följaktligen två styrregister av varje typ. Till exempel är det första statusregistret IIC1S och det andra statusregistret är IIC2S.
11.2.8.1. IICC kontrollregister
För MK serie AC. AW, Dx, EL, GB, GT, JM, LC, QE. Q.G. SG, SH, SL
Registrera | Läge | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICC | Läsning | IICEN | IICIE | MST | TX | TXAK | 0 | 0 | 0 |
Spela in | RSTA | — | — | ||||||
Återställa | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Bits beskrivning:
Bitnamn | Beskrivning | Symbol på C-språk |
IICEN | I2C-modulaktiveringsbit: 0 — I2C-styrenheten är inaktiverad; 1 - I2C-styrenheten är aktiverad. |
bIICEN |
IICIE | Modulavbrottsaktiveringsbit från I2C: 0 - I2C-begäran avbrott är inaktiverade; 1 - I2C-begäran avbrott är aktiverade. |
bHCIE |
MST | I2C-styrenhetsvalsbit: 0 - I2C-styrenheten arbetar i slavläge; 1 - I2C-styrenheten arbetar i masterläge. När denna bit ändras från 0 till 1 genereras ett starttillstånd. Omvänt, när en bit ändras från 1 till 0, genereras ett stoppvillkor. |
bMST |
TX | Sändningsriktningsvalbit på SDA-datalinjen: 0 — linjen fungerar för inmatning; 1 - rad fungerar för utgång. |
bTX |
TXAK | Kvittensbit i mottagningsläge: 0 - en bekräftelsebit genereras efter mottagande av en byte; 1 - en okvitterad bitgrupp genereras. Denna bit styr genereringen av en bekräftelsebit efter att ha mottagit en databyte, oavsett om det är en slav eller en master. |
bTXAK |
RSTA | Om I2C-modulen arbetar i masterläge, kommer att skriva en 1 till den här biten göra att Start - "Omstart"-tillståndet återskapas. | bRSTA |
11.2.8.2. IICS Status Register
För MK-serien AC, AW, Dx, EL, GB, GT, JM, LC, QE, QG, SG, SH, SL
Registrera | Läge | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICS | Läsning | TCF | IAAS | UPPTAGEN | ARBL | 0 | SRW | IICIF | RXAK |
Spela in | — | — | — | — | — | ||||
Återställa | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Bits beskrivning:
Bitnamn | Beskrivning | Symbol på C-språk |
TCF | Byt ut kompletteringsbiten. Ställ in efter att byte av en byte är klar: 0 — utbyte ej genomfört; 1 - utbyte genomfört. TCF-flaggan rensas till 0 när IICD-dataregistret läses (i mottagningsläge) eller när IICD-dataregistret skrivs (i sändningsläge). |
bTCF |
IAAS | Slavadressflagga. Ställ in om enheten arbetar i slavläge och adressen som sänds i mastermeddelandet är lika med slavadressen, som lagras i IICA-adressregistret. Flaggan rensas när man skriver till IICC-registret. |
bIAAS |
UPPTAGEN | Upptagen linje flagga. Denna flagga sätts om I2C-modulen har identifierat startbiten på linjen. Flaggan rensas när modulen detekterar en stoppbit på linjen. 0 — I2C-bussen är gratis; 1 - I2C-bussen är upptagen. |
bUPPTAGT |
ARBL | Skiljedomsförlustflagga: 0 - inga överträdelser i driften av I2C-bussen; 1 – det finns en förlust av arbitrage. I2C-modulen måste vänta ett tag och sedan påbörja överföringen igen. |
bARBL |
SRW | Slavsändningsriktningsbit. Denna bit indikerar tillståndet för R/W-biten i adressfältet: 0 - slav accepterar. Ledaren sänder till slaven; 1 - slav sänder. Ledaren får av slaven. |
bSRW |
IICIF | Flagga för obetjänade avbrottsbegäranden för I2C-modulen. Sätt till 1 om en av flaggorna är inställd: TCF, IAAS eller ARBL. 0 - inga oservade avbrott; 1 - det finns oservade avbrott. Flaggan återställs genom att skriva 1 till den. |
bIICIF |
RXAK | Master acknowledge bit: 0—slav bekräftad mottagning av data; 1 — slaven bekräftade inte datamottagning. Denna bit återspeglar tillståndet för ASK-fältet på tidsdiagrammet för utbytet. |
bRXAK |
11.2.8.3. IICA Adressregister
Registrera | Läge | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICA | Läsning | ADDR | |||||||
Spela in | |||||||||
Återställa | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Detta register lagrar den 7-bitars slavadress som utvecklaren tilldelade denna apparat när man utvecklar systemet. Denna adress jämförs automatiskt med adresskoden som slaven fick i adressfältet på I2C-bussen. Om adresserna matchar ställs IAAS-biten i IICS-statusregistret in.
11.2.8.4. IICF Baud Rate Register
För MK-serien AC, AW, Dx, EL,GB, GT, JM, LC, QE, QG, SG, SH, SL
Registrera | Läge | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICF | Läsning | MULTI | ICR | ||||||
Spela in | |||||||||
Återställa | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Bits beskrivning:
Värden på koefficienterna SCL_DIV och SDA_HV
ISR | SCL_DIV | SDA_HV | ISR | SCL_DIV | SDA_HV | |
0x00 | 20 | 7 | 0x20 | 160 | 17 | |
0x01 | 22 | 7 | 0x21 | 192 | 17 | |
0x02 | 24 | 8 | 0x22 | 224 | 33 | |
0x03 | 26 | 8 | 0x23 | 256 | 33 | |
0x04 | 28 | 9 | 0x24 | 288 | 49 | |
0x05 | 30 | 9 | 0x25 | 320 | 49 | |
0x06 | 34 | 10 | 0x26 | 384 | 65 | |
0x07 | 40 | 10 | 0x27 | 480 | 65 | |
0x08 | 28 | 7 | 0x28 | 320 | 33 | |
0x09 | 32 | 7 | 0x29 | 384 | 33 | |
0x0A | 36 | 9 | 0x2A | 448 | 65 | |
0x0B | 40 | 9 | 0x2B | 512 | 65 | |
0x0C | 44 | 11 | 0x2C | 576 | 97 | |
0x0D | 48 | 11 | 0x2D | 640 | 97 | |
0x0E | 56 | 13 | 0x2E | 768 | 129 | |
0x0F | 68 | 13 | 0x2F | 960 | 129 | |
0x10 | 48 | 9 | 0x30 | 640 | 65 | |
0x11 | 56 | 9 | 0x31 | 768 | 65 | |
0x12 | 64 | 13 | 0x32 | 896 | 129 | |
0x13 | 72 | 13 | 0x33 | 1024 | 129 | |
0x14 | 80 | 17 | 0x34 | 1152 | 193 | |
0x15 | 88 | 17 | 0x35 | 1280 | 193 | |
0x16 | 104 | 21 | 0x36 | 1536 | 257 | |
0x17 | 128 | 21 | 0x37 | 1920 | 257 | |
0x18 | 80 | 9 | 0x38 | 1280 | 129 | |
0x19 | 96 | 9 | 0x39 | 1536 | 129 | |
0x1A | 112 | 17 | 0x3A | 1792 | 257 | |
0x1B | 128 | 17 | 0x3B | 2048 | 257 | |
0x1C | 144 | 25 | 0x3C | 2304 | 385 | |
0x1D | 160 | 25 | 0x3D | 2560 | 385 | |
0x1E | 192 | 33 | 0x3E | 3072 | 513 | |
0x1F | 240 | 33 | 0x3F | 3840 | 513 |
Detta register lagrar två bitfält som bestämmer hastigheten och tidsparametrarna för I2C-utbyte. Frekvensen för synkroniseringssignaler bestäms av formeln:
SDA_hold_time-datainställningstiden på I2C-bussen är tidsintervallet mellan det ögonblick då SCL-signalen sätts till 0 och data på SDA-linjen ändras. Tilldelas av parametern SDA_HV (SDA_Hold_Value) från tabellen för ICR-faktorn för baudhastighetsregistret:
.
11.2.8.5. IICD dataregister
För MK-serien AC, AW, Dx, EL,GB, GT, JM, LC, QE, QG, SG, SH, SL
Registrera | Läge | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICD | Läsning | I2C DATA | |||||||
Spela in | |||||||||
Återställa | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Om I2C-modulen arbetar i mastermode, initierar en skrivoperation till detta register I2C-kommunikation (men endast om kommunikationsriktningsbiten i IICC-styrregistret är korrekt inställd, dvs. TX = 1). Den första byten efter starttillståndet, som programmet skriver till dataregistret, tolkas av slavarna som enhetens adress. Därför måste programmet korrekt bilda innehållet i den första byten. Registerläsoperationen returnerar den senast mottagna byten via I2C. Registerläsoperationen initierar också starten av att ta emot nästa byte, men endast om kommunikationsriktningsbiten i IICC-styrregistret är korrekt inställd, dvs. vid TX = 0! Med TX = 1 kommer en registerläsoperation inte att orsaka att en ny byte tas emot via I2C från slaven.
Om I2C-modulen arbetar i slavläge, kommer data som skrivs till detta register att överföras till SDA-linjen på I2C-bussen när masterenheten utför en mottagningscykel från denna slav. Registerläsoperationen returnerar den senast mottagna byten från mastern.
11.2.8.6. Kontrollregister IICC2
För MK serie AC, Dx, EL, JM, QE, SG, SH, SL
Registrera | Läge | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICC | Läsning | GCAEN | ADEXT | 0 | 0 | 0 | AD10 | AD9 | AD8 |
Spela in | — | — | — | ||||||
Återställa | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Första stegen med STM32 och mikroC-kompilator för ARM-arkitektur - Del 4 - I2C, pcf8574 och HD4478 baserad LCD-anslutning
Jag skulle vilja ägna nästa artikel åt att arbeta med det gemensamma i2c-gränssnittet, som ofta används i olika mikrokretsar kopplade till en mikrokontroller.
I2C är en buss som fungerar över två fysiska anslutningar (utöver den gemensamma ledningen). Det skrivs ganska mycket om det på Internet, det finns bra artiklar på Wikipedia. Dessutom är bussdriftsalgoritmen mycket tydligt beskriven. Kort sagt är bussen en tvåtrådig synkron buss. Upp till 127 enheter kan vara på bussen samtidigt (enhetsadressen är 7-bitars, vi återkommer till detta senare). Nedan finns ett typiskt diagram för att ansluta enheter till i2c-bussen, med MK som huvudenhet.
För i2c använder alla enheter (både master och slavar) öppna dräneringsutgångar. Enkelt uttryckt kan de ENDAST locka däcket till marken. Den höga bussnivån säkerställs av pull-up-motstånd. Värdet på dessa motstånd väljs vanligtvis i intervallet från 4,7 till 10 kOhm. i2c är ganska känslig för de fysiska linjer som ansluter enheter, så om en anslutning med stor kapacitans används (till exempel en lång tunn eller skärmad kabel), kan påverkan av denna kapacitans "suddar ut" signalkanterna och störa normal drift däck. Ju mindre pull-up-motståndet är, desto mindre inflytande har denna kapacitans på egenskaperna hos signalkanterna, men desto STÖRRE BELASTNING på utgångstransistorerna på i2c-gränssnitten. Värdet på dessa motstånd väljs för varje specifik implementering, men de bör inte vara mindre än 2,2 kOhm, annars kan du helt enkelt bränna utgångstransistorerna i enheter som fungerar med bussen.
Bussen består av två linjer: SDA (datalinje) och SCL (klocksignal). Klockar bussens masterenhet, vanligtvis vår MK. När SCL är hög läses information från databussen. SDA-tillståndet kan endast ändras när klocksignalen är låg.. När SCL är hög ändras signalen på SDA när signaler genereras START (när SCL är hög ändras signalen på SDA från hög till låg) och SLUTA - när SCL-nivån är hög ändras signalen på SDA från låg till hög).
Separat bör det sägas att i i2c anges adressen som ett 7-bitars nummer. 8 - minst signifikant bit indikerar riktningen för dataöverföring 0 - betyder att slaven kommer att sända data, 1 - ta emot.. Kortfattat är algoritmen för att arbeta med i2c följande:
- Hög nivå på SDA och SCL– Bussen är gratis, du kan börja jobba
- Master hissar SCL till 1 och ändrar tillstånd S.D.A. från 1 till 0 - lockar den till marken - en signal bildas START
- Mastern sänder en 7-bitars slavadress med en riktningsbit (data på S.D.A. ställs ut när SCL dras till marken och läses av slaven när den släpps). Om slaven inte har tid att "gripa" den föregående biten, lockar den SCL till marken, vilket gör det klart för befälhavaren att databussens tillstånd inte behöver ändras: "Jag läser fortfarande den föregående." Efter att befälhavaren har släppt däcket kollar han lät slaven henne gå?.
- Efter att ha sänt 8 bitar av adressen genererar mastern den 9:e klockcykeln och släpper databussen. Om slaven hörde sin adress och tog emot den, då han kommer att trycka S.D.A. till marken. Det är så signalen bildas FRÅGA- accepterat, allt är OK. Om slaven inte förstår någonting, eller om han helt enkelt inte är där, kommer det inte att finnas någon som trycker på däcket. befälhavaren kommer att vänta på en timeout och förstå att han inte blev förstådd.
- Efter att ha överfört adressen, om vi har ställt in riktningen från herre till slav(8 bitar av adressen är lika med 1), sedan överför mastern data till slaven, utan att glömma att kontrollera närvaron av FRÅGA från slaven och väntar på att slavenheten ska bearbeta den inkommande informationen.
- När mastern tar emot data från slaven genererar mastern själv en signal FRÅGA efter att ha tagit emot varje byte, och slaven styr sin närvaro. Befälhavaren kanske inte specifikt skickar FRÅGA innan du skickar kommandot SLUTA, vilket vanligtvis gör det klart för slaven att det inte finns något behov av att tillhandahålla mer data.
- Om det, efter att ha skickat data av mastern (skrivläge), är nödvändigt att läsa data från slaven, sedan genererar mastern signalen igen START , skickar slavadressen med en läsflagga. (om före kommandot STARTöverfördes inte SLUTA sedan bildas ett lag OMSTART). Detta används för att ändra riktningen för master-slav-kommunikation. Till exempel skickar vi registeradressen till slaven och läser sedan data från den.)
- Efter avslutat arbete med slaven genererar mastern en signal SLUTA- på en hög nivå av klocksignalen bildar den en databussövergång från 0 till 1.
I MicroC, innan du använder i2c (liksom någon kringutrustning), måste den initieras ordentligt. För att göra detta använder vi följande funktion (initiering som master):
I2Cn_Init_Advanced(unsigned long: I2C_ClockSpeed, const Module_Struct *module);
- n- numret på den använda modulen, till exempel I2C1 eller I2C2.
- I2C_ClockSpeed- busshastighet, 100 000 (100 kbs, standardläge) eller 400 000 (400 kbs, snabbt läge). Den andra är fyra gånger snabbare, men inte alla enheter stöder den
- *modul- pekare till en perifer modul, till exempel &_GPIO_MODULE_I2C1_PB67, låt oss inte glömma det här Kodassistent (ctrl-mellanslag ) hjälper mycket.
I2Cn_Start();
Var n- nummer på den använda i2c-modulen på vår mikrokontroller. Funktionen returnerar 0 om det är fel på bussen och 1 om allt är OK.
För att överföra data till slaven använder vi funktionen:
I2Cn_Write(osignerad char slave_address, osignerad char *buf, unsigned long count, unsigned long END_mode);
- n- numret på den använda modulen
- slave_adress- 7-bitars slavadress.
- *buff- en pekare till våra data - en byte eller byte array.
- räkna- antalet överförda databytes.
- END_läge- vad man ska göra efter att ha överfört data till slaven, END_MODE_STOPP - sända en signal SLUTA, eller END_MODE_RESTART skicka igen START, genererar en signal OMSTART och göra klart för avdelningen att sessionen med honom inte är över och data kommer nu att läsas av honom.
I2Cn_Read(char slave_address, char *ptrdata, unsigned long count, unsigned long END_mode);
- n- numret på den använda modulen
- slave_adress- 7-bitars slavadress.
- *buff- en pekare till en variabel eller array i vilken vi tar emot data, skriv char eller short int
- räkna- antal mottagna databytes.
- END_läge- vad man ska göra efter att ha tagit emot data från slaven - END_MODE_STOPP - sända en signal SLUTA, eller END_MODE_RESTART skicka en signal OMSTART.
Mikrokretsadressen bildas av stiftens tillstånd A0, A1, A2. För mikrokretsar PCF8574 adressen blir: 0100A0A1A2. (Vi har till exempel A0, A1, A2 på en hög nivå, så adressen till vår mikrokrets kommer att vara 0b0100 111 = 0x27). För PCF8574A - 0111A0A1A2, som med vårt kopplingsschema kommer att ge adressen 0b0111 111 = 0x3F. Om, säg, A2 är ansluten till jord, då adressen för PCF8574A kommer 0x3B. Totalt kan 16 mikrokretsar monteras samtidigt på en i2c-buss, 8 PCF8574A och PCF8574 vardera.
Låt oss försöka överföra något, initiera i2c-bussen och överföra något till vår PCF8574.
#define PCF8574A_ADDR 0x3F //Adressen till vår PCF8574 void I2C_PCF8574_WriteReg(osignerad char wData) ( I2C1_Start(); // Generera START-signalen I2C1_Write(PCF8574A_ADDR,&w data av /ENDa_ADDR,1,STte av /ENDa_ADDR,&w); generera STOPP signal ) char PCF8574A_reg ; // variabeln som vi skriver i PCF8574 void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Starta I2C delay_ms(25); // Vänta lite PCF8574A_reg.b0 = 0; /_/8 den första lysdioden PCF4A = 1; // stäng av den andra lysdioden medan (1) ( delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // invertera tillståndet för lysdioderna I2C4_PCFrite57_PCF85(774A_reg.b0) ; // överför data till vår PCF8574))
Vi kompilerar och kör vårt program och ser att våra lysdioder blinkar växelvis.
Jag kopplade LED-katoden till vår PCF8574 av en anledning. Saken är att när en logisk 0 matas till utgången drar mikrokretsen ärligt sin utgång till marken, men när en logisk 1 appliceras ansluter den den till + ström genom en strömkälla på 100 μA. Det vill säga, du kan inte få en "ärlig" logisk 1 vid utgången. Och du kan inte tända en LED med 100 µA. Detta gjordes för att konfigurera PCF8574-utgången till ingången utan ytterligare register. Vi skriver helt enkelt till utgångsregister 1 (ställer i huvudsak pintillstånden till Vdd) och kan helt enkelt kortsluta det till jord. Den aktuella källan kommer inte att tillåta slutsteget på vår I/O-expander att "bränna ut". Om benet dras till marken så är jordpotentialen på det och logisk 0 läses. Om benet dras till + läses logisk 1. Å ena sidan är det enkelt, men å andra sidan du bör alltid komma ihåg detta när du arbetar med dessa mikrokretsar.
Låt oss försöka läsa tillståndet för stiften på vårt expanderchip.
#define PCF8574A_ADDR 0x3F //Adressen till vår PCF8574 void I2C_PCF8574_WriteReg(osignerad char wData) ( I2C1_Start(); // Generera START-signalen I2C1_Write(PCF8574A_ADDR, 1,ST och ENDA_ADDR, 1,ST och ENDO data); generera STOPP signal ) void I2C_PCF8574_ReadReg (osignerad char rData) ( I2C1_Start(); // Generera START-signalen I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Läs 1 byte med data och generera ST_FOP-signalen 4 PCFOP; //variabel som vi skriver till PCF8574 char PCF8574A_out; // variabeln vi läser in och PCF8574 char lad_state; //vår lysdiod är på eller av void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Start I2C delay_ms(25); // Vänta lite PCF8574A_reg.b0 = 0; // tänd den första lysdioden PCF85 1; / / stäng av den andra LED PCF8574A_reg.b6 = 1; // Dra stift 6 och 7 till ström. PCF8574A_reg.b7 = 1; while (1) ( delay_ms(100); I2C_PCF8574_WriteReg (PCF8574A_reg.b7_reg); till PCF8574 I2C_PCF8574_ReadReg (PCF85 74A_out ); // läs från PCF8574 if (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // Om 1-knappen för 7 trycks ner från 0, trycks 1-knappen från 7:e från PCF slå på/av vår LED) om (~PCF8574A_out .b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // liknande för 2 knappar och 2 lysdioder ) )
Nu genom att trycka på knapparna slår vi på eller av vår LED. Mikrokretsen har en annan utgång INT. En puls genereras på den varje gång tillståndet för stiften på vår I/O-expander ändras. Genom att ansluta den till den externa avbrottsingången på vår MK (jag kommer att berätta hur du konfigurerar externa avbrott och hur du arbetar med dem i en av följande artiklar).
Låt oss använda vår portexpander för att ansluta en teckenskärm genom den. Det finns väldigt många av dessa, men nästan alla är byggda på basis av ett kontrollerchip HD44780 och hans kloner. Till exempel använde jag en LCD2004-skärm.
Databladet för den och HD44780-styrenheten kan lätt hittas på Internet. Låt oss ansluta vår skärm till PCF8574, respektive hennes, till vår STM32.
HD44780 använder ett parallellt gated gränssnitt. Data överförs med 8 (i en klockcykel) eller 4 (i 2 klockcykler) grindpulser vid utgången E. (läses av displaykontrollen på en fallande kant, övergång från 1 till 0). Slutsats R.S. indikerar om vi skickar data till vår display ( RS = 1) (tecken som den ska visa är faktiskt ASCII-koder) eller kommandot ( RS = 0). RW indikerar riktningen för dataöverföring, skrivning eller läsning. Vanligtvis skriver vi data till displayen, så ( RW=0). Resistor R6 styr displayens kontrast. Du kan inte bara ansluta kontrastjusteringsingången till jord eller ström, annars ser du ingenting.. VT1 används för att slå på och av displayens bakgrundsbelysning enligt MK-kommandon. MicroC har ett bibliotek för att arbeta med sådana skärmar via ett parallellt gränssnitt, men vanligtvis är det dyrt att spendera 8 ben på en skärm, så jag använder nästan alltid PCF8574 för att arbeta med sådana skärmar. (Om någon är intresserad kommer jag att skriva en artikel om att arbeta med HD44780-baserade skärmar inbyggda i MicroC via ett parallellt gränssnitt.) Utbytesprotokollet är inte speciellt komplicerat (vi kommer att använda 4 datalinjer och överföra information i 2 klockcykler), det visas tydligt av följande tidsdiagram:
Innan data överförs till vår display måste den initieras genom att skicka servicekommandon. (beskrivs i databladet, här presenterar vi bara de mest använda)
- 0x28- kommunikation med indikatorn via 4 linjer
- 0x0C- aktivera bildutmatning, inaktivera markörvisning
- 0x0E- aktivera bildutmatning, aktivera markörvisning
- 0x01- rensa indikatorn
- 0x08- inaktivera bildutmatning
- 0x06- efter att symbolen visas flyttas markören 1 välbekant plats
#define PCF8574A_ADDR 0x3F //Adressen till vår PCF8574 #define DB4 b4 // Korrespondens mellan PCF8574-stiften och indikatorn #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 BL b0 // bakgrundsbelysningskontroll #define displenth 20 // antal tecken i vår displayrad statisk osignerad char BL_status; // variabel som lagrar bakgrundsbelysningens tillstånd (på/av) void lcd_I2C_Init(void); // Display och PCF8574 initieringsfunktion void lcd_I2C_txt(char *pnt); // Visar en textrad, parametern är en pekare till denna rad void lcd_I2C_int(int pnt); // Visar värdet på en heltalsvariabel, parametern är utdatavärdet void lcd_I2C_Goto(osignerad kort rad, osignerad kort kol); // flyttar markören till angiven position, parametrar rad - linje (från 1 till 2 eller 4 beroende på displayen) och kol - (från 1 till displenth)) void lcd_I2C_cls(); // Rensar skärmens tomrum lcd_I2C_backlight (osignerad kort int-tillstånd); // Aktiverar (vid sändning 1 och inaktiverar - vid sändning 0 displayens bakgrundsbelysning)
Låt oss nu beskriva våra funktioner, återigen går vi till Projektledare högerklicka på mappen Källor
och välj Lägg till ny fil
. Skapa en fil "i2c_lcd.с"
.
#include "i2c_lcd.h" //inkludera vår rubrikfil char lcd_reg; //register för temporär lagring av data som skickas till PCF8574 void I2C_PCF8574_WriteReg(osignerad char wData) //funktion för att skicka data via i2c till PCF8574-chippet ( I2C1_Start(); I2C1_Write(PCF8574_END_ADM, LCD, vODE, END_ADM, LCD, 1; OCH ( char com) //funktionen för att skicka ett kommando till vår display (lcd_reg = 0; //skriv 0 till det tillfälliga registret lcd_reg.BL = BL_status.b0; //ställ in bakgrundsbelysningsstiftet i enlighet med värdet på variabeln som lagrar bakgrundsbelysningstillstånd lcd_reg.DB4 = com.b4; // ställ in de 4 mest signifikanta bitarna i vårt kommando till indikatordatabussen lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; lcd_reg.SV = 1; //ställ strobeutgången till 1 I2C_PCF8574_WriteReg ( lcd_reg); //skriv till PCF8574-registret, skickar faktiskt data till indikatorn delay_us (300); //vänta på timeout lcd_reg.EN = 0; /återställ strobepulsen till 0, indikatorn läser data I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.DB4 = minst/samma för the 4; signifikanta bitar lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.SV = 1; I2C_PCF8574_WriteReg(lcd_reg); delay_us(300); lcd_reg.SV = 0; I2C_PCF8574_WriteReg(lcd_reg); delay_us(300); ) void LCD_CHAR (osignerad char com) //sänder data (ASCII-teckenkod) till indikatorn ( lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; //sänder ett tecken skiljer sig från att skicka kommandon genom att ställa in RS-biten till 1 lcd_reg.DB4 = com.b4; //ställ in de 4 mest signifikanta bitarna vid ingångarna lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; I2C_PCF8574_WriteReg (lcd_reg) ; delay_us (300); lcd_reg.EN = 0; // återställ strobepulsen till 0, indikatorn läser data I2C_PCF8574_WriteReg (lcd_us_reg;lcd_reg (lcd_reg;lcd_reg3); .BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; lcd_reg.DB4 = com.b0; //ställ in de 4 minst signifikanta bitarna vid ingångarna lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; I2C_PCF8574_WriteReg ( lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300) IcdInn_ (300) _Advanced(400000, &_GPIO_MODULE_I2C1_PB67 ); //initiera vår I2c-modul för MK delay_ms(200) ;lcd_Command(0x28); // Visa i 4 bitar per klockläge delay_ms (5); lcd_Command(0x08); //Inaktivera datautmatning till displayen delay_ms (5); lcd_Command(0x01); //Rensa displayen delay_ms (5); lcd_Command(0x06); //Aktivera automatisk markörförskjutning efter visning av symbolen delay_ms (5); lcd_Command(0x0C); //Slå på att visa information utan att visa markören delay_ms (25); ) void lcd_I2C_txt(char *pnt) //Mata ut en sträng med tecken till displayen (osignerad kort int i; //temporary character array index variabel char tmp_str; //temporary array of characters, längd 1 större än längden på skärmen rad, eftersom raden måste avslutas сiv med ett NULL ASCII-tecken 0x00 strncpy(tmp_str, pnt, displenth); //kopiera inte mer än displenth-tecken av den ursprungliga strängen till vår temporära sträng för (i=0; i
#include "i2c_lcd.h" //inkludera vår rubrikfil osignerad int i; //temporary variabel counter void main() ( lcd_I2C_Init(); //initiera displayen lcd_I2C_backlight (1); //slå på bakgrundsbelysningen lcd_I2C_txt ("Hej habrahabr"); //visa raden medan (1) ( delay_ms( 1000); lcd_I2C_Goto (2,1); //gå till tecken 1 på rad 2 lcd_i2c_int (i); //visa värdet i++; //öka upp räknaren ) )
Om allt är korrekt monterat bör vi se text på indikatorn och en räknare som ökar varje sekund. I allmänhet inget komplicerat :)
I nästa artikel kommer vi att fortsätta att förstå i2c-protokollet och enheter som fungerar med det. Låt oss överväga att arbeta med EEPROM 24XX-minne och MPU6050 accelerometer/gyroskop.
Idag finns det en ny gäst på vårt operationsbord, detta är en Microchip-produkt, portexpandern MCP23008-E. Denna sak är avsedd (som namnet antyder) att öka antalet I/O-ben på mikrokontrollern om de plötsligt blir otillräckliga. Naturligtvis, om vi behöver benen och utgångarna, då kan vi ta det och inte oroa oss för det. Om du behöver inmatningsben, så finns det en lösning baserad på strikt logik. Behöver vi både in- och utgångar och även en kontrollerad pull-up för ingångarna så är en portexpander kanske den mest normala lösningen. När det gäller priset på enheten är det mycket blygsamt - ungefär en dollar. I den här artikeln kommer jag att försöka beskriva i detalj hur man styr denna mikrokrets med hjälp av AVR-mikrokontrollern.
Först lite om egenskaperna:
- 8 oberoende portstift
- Gränssnitt för kommunikation med omvärlden - I2C (frekvens upp till 1,7 MHz)
- Anpassningsbar pull-up för entréer
- Kan rycka i benet när tillståndet för vissa ingångar ändras
- Tre ingångar för att ställa in adressen till mikrokretsen (du kan hänga 8 enheter på en buss)
- Driftspänning från 1,8 till 5,5 volt
- Låg strömförbrukning
- Stort urval av kapslingar (PDIP/SOIC/SSOP/QFN)
Jag föredrar att använda gränssnittet I2C, det lockar mig med ett litet antal kablar :-) men om du behöver mycket snabb hastighet (upp till 10 MHz), måste du använda SPI-gränssnittet som finns i MCP23S08. Skillnaden mellan MCP23S08 och MCP23008, som jag förstår det, ligger bara i gränssnittet och i antalet ben för att ställa in adressen till chippet. Vem gillar något kortare? Mikruhis pinout finns i databladet, det är inte intressant på något sätt och kommer inte att behandlas här. Låt oss därför omedelbart gå vidare till hur man börjar arbeta med den här enheten. Allt arbete handlar om att skriva och läsa vissa data från mikrokretsens register. Till min glädje fanns det inte många register alls - bara elva. Att skriva och läsa data från register är väldigt enkelt, inlägg om det. Det är sant att vi inte pratar om register, men principen är densamma. Nu återstår bara att lista ut vilka register man ska läsa vad ur och vilka register man ska skriva vad man ska. Naturligtvis kommer databladet att hjälpa oss med detta. Från databladet får vi veta att mikrokretsen har följande register:
IODIR register
Anger i vilken riktning data flödar. Om biten som motsvarar ett visst ben är satt till ett, så är benet en ingång. Om nollställs, avsluta. Kort sagt, detta register är analogt med DDRx i AVR (endast i AVR är 1 en utgång och 0 är en ingång).
IPOL-register
Om en registerbit är inställd, är ingångsinvertering aktiverad för motsvarande ben. Det betyder att om du applicerar en stock på benet. noll då från GPIO-registret anses vara en och vice versa. Användbarheten av denna funktion är mycket tveksam.
GPINTEN register
Varje bit i detta register motsvarar ett specifikt portstift. Om biten är inställd kan motsvarande portstift som är konfigurerat som en ingång orsaka ett avbrott. Om biten återställs kommer det inte att bli något avbrott, oavsett vad du gör med babordsbenet. Avbrottsvillkoren ställs in av följande två register.
DEFVAL register
Det aktuella värdet på stiften som är konfigurerade för ingång jämförs konstant med detta register. Om det aktuella värdet plötsligt börjar skilja sig från det som finns i detta register, uppstår ett avbrott. Enkelt uttryckt, om biten är inställd, kommer avbrottet att inträffa när nivån ändras från hög till låg på motsvarande ben. Om biten återställs sker avbrottet på en stigande kant.
INTCON register
Varje bit i detta register motsvarar ett specifikt portstift. Om biten är klar, orsakar varje förändring i den logiska nivån till den motsatta ett avbrott. Om biten är inställd påverkas förekomsten av avbrottet av DEFVAL-registret (annars ignoreras det helt).
IOCON register
Detta är inställningsregistret. Den består av fyra bitar:
SEQOP— bitkontroller automatisk ökning av adress. Om den är installerad är automatisk ökning avaktiverad, annars är den aktiverad. Om vi stänger av det kan vi läsa värdet på samma register mycket snabbt på grund av att vi inte behöver skicka dess adress varje gång. Om du snabbt behöver läsa alla 11 register i tur och ordning måste autoökning vara aktiverad. Varje gång en byte läses från registret kommer själva adressen att öka med en och behöver inte överföras.
DISSLW– vem vet vad det här är. Hur jag än vrider på det så fungerar allt ändå. Jag skulle bli glad om någon förklarade.
HAEN— Inställningsbit för INT-utgång. Om den är inställd är stiftet konfigurerat som ett öppet avlopp, om biten återställs, bestämmer den aktiva nivån på INT-benet INTPOL-biten
INTPOL— bestämmer den aktiva nivån på INT-benet. Om den är inställd är den aktiva nivån ett, annars noll.
Det finns fortfarande lite HAEN men det används inte i detta chip (slår på/stänger av hårdvaruadresseringsstift i MCP23S08)
GPPU-register
Styr input pull-up. Om biten är inställd, kommer en pull-up till strömförsörjningen genom ett 100 kOhm motstånd att visas på motsvarande stift.
INTF-register
Avbryt flaggregistret. Om biten är inställd betyder det att motsvarande portben orsakade ett avbrott. Naturligtvis måste avbrott för de nödvändiga benen vara aktiverade i GPINTEN-registret
INTCAP-register
När ett avbrott inträffar läses hela porten in i detta register. Om det blir fler avbrott efter detta kommer inte innehållet i detta register att skrivas över av det nya värdet förrän vi läser det eller GPIO.
GPIO-register
När vi läser data från registret läser vi de logiska nivåerna på portens stift. När vi registrerar data sätter vi logiska nivåer. När du skriver till detta register skrivs samma data automatiskt till OLAT-registret
OLAT-registret
Genom att skriva data till detta register matar vi ut data till porten. Om du läser data därifrån kommer du att läsa vad som skrevs ner, och inte vad som faktiskt finns vid portingångarna. För att läsa ingångar använder vi endast GPIO.
Beskrivningen av registren är enligt min mening ganska normal och borde inte förvirra någon. Nu, bara för skojs skull, låt oss skriva en liten demo som kommer att fråga 4 knappar anslutna till portexpandern, och beroende på deras tillstånd, tända eller släcka 4 motsvarande lysdioder som är anslutna till samma expander. Exemplet kommer att vara så enkelt som möjligt, utan några avbrott. Vi läser helt enkelt konstant knapparnas tillstånd och visar detta tillstånd på lysdioderna. Diagrammet för vår enhet kommer att se ut så här:
Avbrottsstiftet behöver inte vara anslutet, vi behöver det inte här, men uppdraget för återställningsbenet krävs! Jag tillbringade mycket tid tills jag insåg att min portexpander periodvis återställdes på grund av bristande åtdragning. Låt oss nu komma till koden. Självklart kommer vi att skriva på . Enkel kod:
Program rashiritel; const AdrR=%01000001; //Adressen till chippet med läsbiten const AdrW=%01000000; //Adressen till chippet med bitposten var r:byte; ///Proceduren skriver data från Dat-variabeln till registret på adressen Adr Procedure WriteReg(Dat,Adr:byte); Börja TWI_Start(); TWI_Write(AdrW); TWI_Write(Adr); TWI_Write(Dat); TWI_Stop(); Slutet; ///Funktionen returnerar registervärdet på adressen Adr Funktion ReadReg(Adr:byte):byte; var a:byte; Börja TWI_Start(); TWI_Write(AdrW); TWI_Write(Adr); TWI_Start(); TWI_Write(AdrR); a:=TWI_Read(0); TWI_Stop(); resultat:=a; Slutet; börja TWI_INIT(200000); ///Initialisering av i2c WriteReg(%00001111,0x00); //De lägsta 4 bitarna är ingångar och de återstående 4 bitarna är utgångar WriteReg(%00001111,0x06); //På pull-up för 4 ingångar Medan TRUE börjar r:=ReadReg(0x09); //Läs tillståndet för ingångarna r:= INTE r; //Det är nödvändigt att invertera bitarna, annars tänds lysdioderna när knappen släpps r:= r shl 4; //Skift 4 bitar till vänster... WriteReg(r,0x0A); //Visa status för knapparna slut; slutet.
Publicerad 2016-10-26
I den tidigare artikeln tittade vi på driften av STM32 med I 2 C-bussen som Master. Det vill säga, han var ledare och förhörde sensorn. Låt oss nu göra STM32 till en slav och svara på förfrågningar, det vill säga att den själv fungerar som en sensor. Vi kommer att allokera 255 byte minne för register med adresser från 0 till 0xFF, och låta Mastern skriva/läsa dem. Och för att göra exemplet inte så enkelt, låt oss göra vår STM32 till en analog-till-digital-omvandlare med ett I 2 C-gränssnitt. ADC kommer att bearbeta 8 kanaler. Regulatorn kommer att ge resultatet av transformationerna till Mastern när den läser från register. Eftersom resultatet av ADC-konverteringen är 12 bitar behöver vi 2 register (2 byte) för varje ADC-kanal.
i2c_slave.h innehåller inställningar:
I2CSLAVE_ADDR– adressen till vår enhet;
ADC_ADDR_START– startadressen för de register som är ansvariga för resultaten av ADC-konverteringar.
I fil i2c_slave.c vi är mest intresserade av funktioner get_i2c1_ram Och set_i2c1_ram. Fungera get_i2c1_ram ansvarar för att läsa data från register. Den returnerar data från den angivna adressen, som ges till befälhavaren. I vårt fall läses data från arrayen i2c1_ram men om mastern frågar efter registeradresser från intervallet som tilldelats för ADC-resultat, så skickas ADC-konverteringsdata.
get_i2c1_ram:
Uint8_t get_i2c1_ram(uint8_t adr) ( //ADC data if ((ADC_ADDR_START<= adr) & (adr < ADC_ADDR_START + ADC_CHANNELS*2)) { return ADCBuffer; } else { // Other addresses return i2c1_ram; } }
Fungera set_i2c1_ram– skriver data mottagna från Mastern till register med angiven adress. I vårt fall skrivs data helt enkelt till en array i2c1_ram. Men detta är valfritt. Du kan till exempel lägga till en check och, när ett visst nummer kommer till en viss adress, utföra några åtgärder. På så sätt kan du skicka olika kommandon till mikrokontrollern.
set_i2c1_ram:
Void set_i2c1_ram(uint8_t adr, uint8_t val) ( i2c1_ram = val; retur; )
Initiering är ganska enkel:
Int main(void) ( SetSysClockTo72(); ADC_DMA_init(); I2C1_Slave_init(); while(1) ( ) )
Först ställer vi in regulatorns maximala driftfrekvens. Maximal hastighet krävs när eventuella förseningar på I 2 C-bussen ska undvikas. Sedan startar vi ADC-driften med DMA. HANDLA OM . HANDLA OM . Och slutligen initierar vi I 2 C-bussen som Slav. Som du kan se, inget komplicerat.
Låt oss nu ansluta vår STM32-modul till Raspberry Pi. Låt oss ansluta potentiometrar till ADC-kanalerna. Och vi kommer att läsa ADC-indikatorer från vår kontroller. Glöm inte att för att I 2 C-bussen ska fungera måste du installera pull-up-motstånd på varje linje av bussen.
I Raspberry-konsolen, låt oss kontrollera om vår enhet är synlig på I 2 C-bussen (om det):
I2cdetect -y 1
Som du kan se, enhetens adress 0x27, även om vi angav 0x4E. När du har tid, fundera på varför detta hände.
För att läsa från registren för I 2 C-Slave-enheten, kör kommandot:
I2cget -y 1 0x27 0x00
Var:
0x27– enhetsadress,
0x00– registeradress (0x00…0xFF).
För att skriva till registren för I 2 C-Slave-enheten, kör kommandot:
I2cset -y 1 0x27 0xA0 0xDD
De:
0x27– enhetsadress,
0xA0– registeradress
0xDD-8-bitars data (0x00…0xFF)
Det föregående kommandot skrev numret 0xDD till registret 0xA0(du kan skriva till de första 16 registren, men det är ingen mening, utan de är reserverade för ADC). Låt oss nu läsa:
I2cget -y 1 0x27 0xA0
För att förenkla processen att läsa ADC-kanaldata skrev jag ett skript:
#!/usr/bin/env python import smbus import time bus = smbus.SMBus(1) adress = 0x27 medan (1): ADC = (); för i inom intervallet(0, 8): LBS = bus.read_byte_data(adress, 0x00+i*2) MBS = bus.read_byte_data(adress, 0x00+i*2+1) ADC[i] = MBS*256 + LBS skriv ut ADC time.sleep(0.2)
Den pollar och visar resultaten av alla 8 ADC-kanaler till konsolen.
På liknande sätt kan du kombinera flera mikrokontroller. En av dem ska vara Master(), den andra Slave.
Jag önskar er framgång!