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.
STM 32 har hårdvaruimplementerade i2c-bussändtagare. Det kan finnas 2 eller 3 sådana moduler i en MK. För att konfigurera dem används speciella register, som beskrivs i referensen för den MK som används.

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.
Låt oss först kolla om bussen är ledig, det finns en funktion för detta I2Cn_Is_Idle(); returnerar 1 om bussen är ledig och 0 om det finns byte på den.

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.
För att läsa data från slaven, använd funktionen:

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.
Låt oss försöka koppla något till vår MK. Till att börja med: den utbredda PCF8574(A)-mikrokretsen, som är en utvidgning av in-/utgångsportar som styrs via i2c-bussen. Detta chip innehåller endast ett internt register, vilket är dess fysiska I/O-port. Det vill säga, om du skickar en byte till henne kommer den omedelbart att exponeras för hennes slutsatser. Om du räknar en byte från den (sänd START adress med läsflagga, signal RESTERT, läsa data och slutligen generera en signal SLUTA) så kommer den att återspegla de logiska tillstånden på sina utgångar. Låt oss ansluta vår mikrokrets i enlighet med databladet:


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
Eftersom vi kommer att behöva arbeta med den här indikatorn ganska ofta kommer vi att skapa ett plugin-bibliotek "i2c_lcd.h" . För detta ändamål i Projektledare Header-filer och välj Lägg till ny fil . Låt oss skapa vår rubrikfil.

#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 Låt oss nu ansluta det nyskapade biblioteket till filen med vår huvudfunktion:

#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!




Topp