Stiskněte "Enter" pro přeskočení obsahu

Komunikace s čidlem přes I2C bez použití knihovny

5

Pokud používáte Arduino máte k dispozici nepřeberné množství knihoven na konkrétní senzory. Knihovna k displeji, knihovna k termistoru, knihovna k tlačítku, … Přitom se někdy jedná o poměrně jednoduché kusy kódu. Výhoda použití specializovaných a jednoúčelových knihoven je jejich snadné používání – naimportuje se knihovna a pak např. pomocí jednoho řádku kódu čtete požadované parametry z čidla. Problém nastane, pokud máte třeba Arduino Micro, Nano, apod., které má menší paměť. Knihovna musí (měla by být) „blbuvzdorná“, může obsahovat funkce, které nepotřebujete apod. A onen „blbuvzdorný“ a „nadbytečný“ kód samozřejmě zabírá část paměti.

Výhodou může potom také být, že přesně víte jak program funguje. Může se stát, že importujete několik knihoven – každá samostatně funguje, ale pokud je použijete zároveň, některá z nich nemusí fungovat. A nelze jednoduše zjistit proč.

V tomto článku bych chtěl pouze za pomoci knihovny Wire.h (knihovna pro komunikaci I2C) vyčíst data z čidla BH1750, což je čidlo intenzity osvětlení komunikující po sběrnici. Výstup z čidla je přímo intenzita osvětlení v Luxech.

Takto může vypadat načítání z čidel přes sběrnici I2C – čas a datum, teplota, vlhkost, intenzita osvětlení

Fyzická vrstva

Díky použití knihovny Wire.h není nutná přesná znalost sběrnice I2C. Jen v krátkosti vysvětlím, že se jedná o dvoudrátovou multi-masterovou sběrnici. Na vodičích SDA (data) a SCL (hodiny) je v klidovém stavu kladné napětí udržované pull-up rezistory. Oba vodiče jsou připojeny k procesoru, který hlídá napěťovou úroveň na vodiči a v případě potřeby (vysílání) je schopný vodič přes tranzistor vyzkratovat proti zemi a tím snížit jeho napětí na 0 V. V případě modulů pro Arduino jsou již potřebné rezistory integrovány přímo na modulech. To by mohl být problém při připojení více modulů ke sběrnici. Mohlo by docházet k přetěžování tranzistoru – obvod by nebyl schopný napěťovou úroveň snížit. Při použití více modulů je tedy možné vypájet kromě jednoho ze všech modulů pull-up rezistory.

Na internetu jsem narazil na tento článek, kde jde přehledně vidět tvar signálu v závislosti na odporu pull-up rezistorů. Minimální hodnota rezistoru by měla být cca R = (Vcc – 0,4) / 0,003. Pro pětivoltový systém pak vychází rezistor cca 1k5. Záleží však i na délce vedení, zarušení atd… Všeobecně sběrnice I2C není vhodná na komunikaci na velké vzdálenosti, ale spíše v řádech max. desítek centimetrů.

Schéma sběrnice I2C
Zapojení I/O sběrnice I2C

Zařízení na sběrnici jsou adresovaná. Takže master zahájí komunikaci vysláním adresy a potřebných dat. Když se v síti nachází zařízení s požadovanou adresou, tak zařízení odpoví. Adresa zařízení je udaná v datasheetu (někdy si lze zvolit mezi dvěma adresami). Na sběrnici může být až 127 zařízení, některé adresy jsou vyhrazeny pro speciální účely.

Hardware

K otestování čidla bude potřeba Arduino (je v podstatě jedno jaké) a čidlo BH1750 (doporučuji si projít datasheet), jehož cena je cca $1. Arduino s čidlem propojíme pomocí vodičů Vcc (napájení 3 až 5 V; deska obsahuje stabilizátor na 3,3 V), GND (napájení -; ke GND se vztahuje potenciál na datových vodičích), SDA a SCK. Datové vodiče se nekříží! Pull-up rezistory není třeba osazovat, modul je má již integrované – na fotce níže jsou to ty dva 10k (nápis 103).

Software

Ke komunikaci pomocí sběrnice je nutné importovat knihovnu Wire.h. Je to základní knihovna, takže by s ní neměl být problém. Knihovnu je nutné inicializovat. Dále si přidám globální proměnnou (je ji možné použít kdekoliv v programu), do které se bude ukládat intenzita osvětlení v Luxech. Vytvořím si definici pro adresu zařízení.

Adresa zařízení by měla být v datasheetu. U tohoto konkrétního čidla lze adresu změnit přivedením log. 1, nebo log. 0 na vstup ADDR.

Adresace čidla
//import knihovny pro komunikaci I2C
#include <Wire.h>
#define BH1750_ADDRESS 0x23 //adresa zařízení 23hex = 35dec (možné je použít 0x5C, nebo jiné podle datasheetu)
uint16_t lightLevel; //intenzita osvětlení v Luxech
void setup(void) {
//Inicializace knihovny
//Pokud se zadá s parametrem, je zařízení přidělena adresa
Wire.begin();
}

Dále se vytvoří jedna funkce, jejíž návratová hodnota bude intenzita osvětlení. V té bude nutné začít komunikovat na zadané adrese. Čidlu se odešle konfigurace, počká se, než dojde ke změření intenzity osvětlení. Jak je vidět na obrázku níže, lze čidlo konfigurovat několika příkazy. Lze si vyžádat různé náměry (podle přesnosti se mění doba měření) a lze změnit i čas měření. Instrukce jsou ve dvojkové soustavě, pro přehlednost je dobré si je převést na hexadecimální tvar (lze k tomu použít kalkulačku ve Windows, přepnutou do programátorského režimu). Kód pro jedno přečtení informace v H-Res Mode2 by byl 0×21.

Po změření intenzity osvětlení je nutné si data stáhnout. Čidlo odpovídá dvěma bajty podle obrázku níže.

Je tedy nutné:

  1. Zahájit komunikaci na zvolené adrese (u tohoto čidla 0×23)
  2. Odeslat konfiguraci (zde 0×21 pro přečtení kóduv H-Res mode2)
  3. Ukončit komunikaci
  4. Počkat, dokud nebudou k dispozici data. Měření trvá cca 120 ms, max. 180 ms (čidlo odpovídá dvěma 8b bajty)
  5. Přečíst data a převést je na vhodný formát
  6. Vrátit načtenou intenzitu osvětlení

Ve funkci se definuje šestnáctibitová proměnná result. Ta má ve výchozím nastavení hodnotu 00000000 00000000 (pro přehlednost rozdělena mezerou). Prvním přečtením Wire.read(); se do proměnné uloží první, horní bajt do dolní poloviny. Řekněme, že se načtou data 11001101. Proměnná result bude mít tedy hodnotu 00000000 11001101. Poté následuje bitový posun o 8 pozic vlevo. Výsledek bude vypadat takto: 11001101 00000000. Horní bajt je v horní polovině. Je možné tedy přečíst dolní bajt. Bude přečteno třeba 11100011. Výsledek v proměnné result bude tedy 11001101 11100011. hodnota proměnné je 52707. Ta se nakonec ještě vydělí číslem 1,2 a výsledek bude 43922 (proměnná je typu integer, neukládá se desetinná část). Samotná funkce na čtení z čidla by tedy mohla vypadat takto:

uint16_t GetLightLevel() {
 uint16_t result; //interní proměnná pro měření 16b proměnná
Wire.beginTransmission(BH1750_ADDRESS); //zahájení komunikace na požadované adrese
Wire.write(0x21); //odeslání příkazu k měření H-Res Mode2
Wire.endTransmission(); //ukončení komunikace
Wire.requestFrom(BH1750_ADDRESS, 2); //požadavek na dva bajty od zařízení s adresou BH1750_ADDRESS
uint32_t timeout = millis() + 180; //proměnná pro timeout - max. 180 ms
while (Wire.available() < 2) {
//dokud nejsou přijata data opakuj
if ((millis() - timeout) > 0) {
//pokud vypršel časový limit ukonči funkci a vrať číslo 0
return 0;
}
}
result = Wire.read(); //přečtení dat (horní bajt)
result <<= 8; //bitový posun o 8 bitů vlevo
result += Wire.read(); //přičtení dat (dolní bajt)
result /= 1.2; //vydělení výsledku 1,2
return result; //navrácení výsledku
}

Funkce je hotová. Lze ji tedy kdykoliv zavolat. Lze si samozřejmě měnit adresu a parametr načítání. Ale v praxi ani není potřeba ho často měnit. Tím se ušetří část kódu, který musí v knihovně být. Program pro načítání dat z čidla a jeho odesílání na sériový port by mohl vypadat takto..

//import knihovny pro komunikaci I2C
#include <Wire.h>
#define BH1750_ADDRESS 0x23 //adresa zařízení 23hex = 35dec (možné je použít 0x5C, nebo jiné podle datasheetu)
uint16_t lightLevel; //intenzita osvětlení v Luxech
void setup() {
//Inicializace knihovny
//Pokud se zadá s parametrem, je zařízení přidělena adresa
Wire.begin();
//komunikace po sériovém portu
Serial.begin(9600);
}
void loop(){
lightLevel = GetLightLevel(); //načtení hodnoty
Serial.print(lightLevel); //vypsání na sériový port
delay(5000); //zpoždění 5 sekund před dalším načtením
}
uint16_t GetLightLevel() {
//načtení intenzity osvětlení z čidla BH1750 pouze s knihovnou Wire.h
uint16_t result; //interní proměnná pro měření 16b proměnná
Wire.beginTransmission(BH1750_ADDRESS); //zahájení komunikace na požadované adrese
Wire.write(0x21); //odeslání příkazu k měření H-Res Mode2
Wire.endTransmission(); //ukončení komunikace
Wire.requestFrom(BH1750_ADDRESS, 2); //požadavek na dva bajty od zařízení s adresou BH1750_ADDRESS
uint32_t timeout = millis() + 180; //proměnná pro timeout - max. 180 ms
while (Wire.available() < 2) {
//dokud nejsou přijata data opakuj
if ((millis() - timeout) > 0) {
//pokud vypršel časový limit ukonči funkci a vrať číslo 0
return 0;
}
}
result = Wire.read(); //přečtení dat (horní bajt)
result <<= 8; //bitový posun o 8 bitů vlevo
result += Wire.read(); //přičtení dat (dolní bajt)
result /= 1.2; //vydělení výsledku 1,2
return result; //navrácení výsledku
}

Komunikace s jinými čidly je v podstatě stejná. Podle datasheetu se zjistí jaké příkazy je nutné odesílat a v jakém formátu data chodí. Pokud jde o větší čísla, nebo čísla s desetinnými místy je nutné je převést. Doporučuji si projít článek o proměnných, abyste věděli jaké číslo lze do jaké proměnné vložit. Také bude dobré si přečíst článek o bitovém posunu.

  1. Jarda Jarda

    Nedaří se mi komunikovat bez knihovny se senzorem: V manuálu je:
    I2C Communication (Only Slave Mode Operation)
    Internal pull up resister
    Slave Address: 0×31, Slave Address Byte: Slave Address(0×31) 7 Bit + R/W 1 Bit
    R/W Bit : Read = 1/Write = 0
    When reading the data, Slave Address Byte is 0×63, When writing the data, Slave Address Byte is0×62.
    Block Diagram
    Transmission Sequence in Master

    1. I2C Start Condition
    2. Write Command(Slave Address + R/W Bit(0) = 0×62) Transmission and Check Acknowledge
    3. Write Command(ASCII ‘R’ : 0×52) Transmission and Check Acknowledge
    4. I2C Stop Command
    5. I2C Start Command
    6. Read Command(Slave Address + R/W Bit(1) = 0×63) Transmission and Check Acknowledge
    7. Read 7 Byte Receiving Data from Module and Send Acknowledge

    (Delay at least 1ms for reading each byte)

    muj program je:
    #include <Wire.h>

    int hodnota;
    byte mb=1;
    byte mb1=1;
    byte mb2=1;
    byte mb3=1;
    byte mb4=1;
    byte mb5=1;
    byte mb6=1;
    byte error;

    void setup(){
    Wire.begin();
    Serial.begin(115200);
    }

    void loop(){
    Wire.beginTran­smission(0×31);
    // error = Wire.endTransmis­sion();
    Wire.write(0×62);
    Wire.write(0×52);
    Wire.endTransmis­sion(); //ukončení komunikace
    Wire.beginTran­smission(0×31);
    Wire.requestFrom(0×63,7­);
    while(Wire.ava­ilable() > 0){
    mb = Wire.read(); //nižší byte
    mb1 = Wire.read(); //vyšší byte
    mb2 = Wire.read();
    mb3 = Wire.read();
    mb4 = Wire.read();
    mb5 = Wire.read();
    mb6 = Wire.read();
    //nyní hodnoty opět spojíme dohromady
    hodnota = mb + mb1;
     }

    Serial.println(mb);
    Serial.println(mb1);
    Serial.println(mb2);
    Serial.println(mb3);
    Serial.println(mb4);
    Serial.println(mb5);
    Serial.println(mb6);
    Serial.println(hod­nota);
    delay(5000);

    }

  2. Peťan: Napiš na Arduino fórum.
  3. Jarda Jarda

    [1] Jarda: Na debilní otázky nelze odpovědět.

  4. Peťan: To ne, ale tohle není fórum k řešení takových dotazů (nepodporuje vkládání zdrojových kódu, citování atd). Když napíšeš na fórum dotaz s kódem a s tím, co ti to dělá/nedělá tak tam denně chodí daleko víc lidí, kteří dokážou odpovědět, nebo o tom podebatovat. Napiš dotaz sem.
    Jinak, ze všeho nejdřív bych si vyzkoušel nějakej program na hledání I2C zařízení, jestli máš vůbec dobrou adresu (jestli zařízení odpovídá). Co že je to za zařízení?
  5. Jarda Jarda

    [2] Jarda: Rád se přihlásím na diskuzi, ale na otázky takovéhoto charakteru nereaguji:
    Jaký dopravní prostředek se dá nalézt ve spojení „moko loko“?:

    Jinak Scanner funguje a adresu 0×31 vpoho najde.

  6. Peťan: Ber to tak, že je to ochrana proti botům. Jinak jsou tyhle fóra zaspamovaný, že jsou k ničemu. Takže je to jen v tobě – buď chceš, nebo ne…
  7. Kvetak Kvetak

    Zdravím, krásný článek a určitě užitečny. Jen mám dotaz, jak autor zjistil že čidlo odpovídá dvěma 8b bajty? Kde to vyčku? A co znamenají ty poměry (15:8, 7:0 apod) u těch bajtu/bitu? Rád bych se to naučil, ale když si otevřu jakýkoliv jiný datasheet není to tam takhle krásně. Zkoušel jsem například čidlo tlaku BMP280, tak sem něco málo zjistil, ale stejně dostávám odpovědi které určitě nejsou správné (například samé jedničky) apod.

    Peťan: Záleží na datasheetu jak je tam komunikace popsaná. Zrovna v tomto případě se dá vyčíst délka odpovědi např. z obrázku „measurement sequence example“. Je tam znázorněn dotaz s jedním bajtem (master > slave) a odpověď dvěma bajty (slave > master) + nějaký ty Acky a podobně (to odchytí knihovna Wire). Jednotlivé adresy jsou v tabulce „Instruction set“. Ty „poměry“ jsou jednotlivé bity 7:0 (= nultý až sedmý bit). Někdy data můžou jít pozpátku.
    Čidlo BMP280 jsem takhle přímo nezkoušel. Ale v datasheetu je tabulka adres v části 5.2.2 návod jak číst přes I2C. Potom je ještě nutné hodnoty překódovat. Můžu doporučit nahlédnout např. do knihovny Adafruit_BMP280, kde se dá okouknout kód.

    • Kvetak Kvetak

      Už jsem trochu pokročil. Není to prdel číst to takhle nahrubo. Ale když si chci postavit např meteostanici, která bude logovat na sd kartu a ještě přístupná přez wi-fi tak už tam mám pět knihoven a jen načtení všech hodnot z čidel a rtc hodin a jejich uložení na sd kartu je dost náročná procedura. Ještě tam není wi-fi. Nechci se vyhnout všem knihovnám a dělat vše po svém, ale zas bych rád věděl jak to funguje a ne že když napíšu getTemperature() , knihovna „nějak vykouzlí“ teplotu. Zatim čtu ty první tři kalibrační hodnoty (3.11.2 tab 17) pak ty bity co vracejí nějakou hodnotu teploty (4.2 tab 18) a počítám podle vzorečku v odstavci 3.12 ale dostávám nesmysly jako třeba 0.01 st. C… Koukám že ještě chvíli budu muset zústat u knihoven.
      Ale kurňik řekl bych že sem blízko 😀

Napsat komentář: Jarda Zrušit odpověď na komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *