I2C-RF-modtager

Fra Holstebro HTX Wiki
Skift til: navigering, søgning
Modul til RF modtagelse af variable

I2C-RF-modtager modulet er baseret på det ene af to billige moduler der kan købes sammen i Kina f.x. ved denne nethandel[1]

Modulet kan anvendes i forskellige sammenhænge hvor men skal modtage informationer trådløst, og det kan så kobles op i en sammenhæng med andre I2C-moduler som skitseret her:
Struktur for I2C kommunikation hvor der indgår et I2C-RF-modtager modul
Struktur for I2C kommunikation hvor der indgår et I2C-RF-modtager modul

Modulet skal arbejde sammen med en enhed der sender efter den specificerede protokol, hvor man kan overføre en række forskellige variabler af ønskede længder.

Som standard er modulet udviklet til at kunne håndtere 16 variabler af maksimalt 4 bytes hver.

Teori for RF-link modulerne

Modul til sending af 433 MHz radiosignal
Modul til modtagelse af 433 MHz radiosignal


Der er ikke så meget teori for RF-link modulet, da det princip der udnyttes blot er at transmittermodulet kan styres til at lave en frekvens på 433 MHz som den sender, eller der kan slukkes for den, så den ikke sender noget. Modtagermodulet giver så et højt signal ud når den modtager 433 MHz og et lavt når den ikke modtager noget.

Den indledende teori er beskrevet i RF-link.

Tidsmæssig definitioner i pakken

For at modulet skal fungere bedst muligt, så skal man gerne skifte mellem sending/passivt eller høj/lav hele tiden for at modtageren bedst kan indstille sig på niveauet for høj/lav.

For at modtageren kan vide hvornår den skal starte med at læse en pakke, så sendes der i starten en marker som sammensættes af en høj i 500us og en lav i 300 us. Dette vil ikke kunne forekomme inde i en pakke, så hvis man ser denne kombination efter hinanden, så er det starten af en pakke. Starten af en pakke ser ud som følger:

Starten af en pakke med 500us høj og 300us lav, efterfulgt af en række bit
Starten af en pakke med 500us høj og 300us lav, efterfulgt af en række bit

For opnå flest muligt skift, så kodes et sendt 0 som høj i 100us og lav i 100us, og for at der skal være en rimelig forskel kodes 1 som høj i 100 us og lav i 200 us. På denne måde kan modtageren skelne mellem 0 og 1 i modtagelsen. Dette kan ses på følgende måling taget midt i en pakke:

modtagelse af bit høj og lav
Modtagelse af bit med betydning 1 og 0 - hver bit består både høj og lav

Da pakkerne kan have forskellige længder, så skal der også være noget der indikerer afslutningen af en pakke. Alle de høje niveauer gennem pakken har været på 100us, så afslutningen kan markeres ved at sende i 300us. Dette ser ud som følger:

Afslutningen af en pakke med en 300us høj puls
Afslutningen af en pakke med et par bit og til slut en 300us høj puls

En total pakke består som forklaret af en starte og en stop, og så er der en hel serie af data inde i pakken. Dette ser ud som følger:

En hel pakke med en to-bytes variabel
En hel pakke med en to-bytes variabel

Når der ikke modtages noget

Når der ikke sendes noget, så vil modtageren skrue op for følsomheden, og vil på et tidspunkt modtage støj, som er skiftende høje og lave signaler, her vist hvor der er pulser fra 8us op over 1ms.

Måling af modtaget støj med højst skiftende tider i pulser og pauser
Måling af modtaget støj med højst skiftende tider i pulser og pauser

I denne støj ville den kunne modtage noget der kunne ses som starten af en pakke. Derfor kontrolleres gennem hele pakken om det modtagne ligger inden for rimelige grænser af hvad der kan accepteres som 0 og 1. Modtages der noget tidsmæssigt afvigende, så forkastens pakken. Derfor kan man ikke stole på at alle pakker kommer igennem til modtageren.

Specielt i starten af pakken, så kan modtageren tro at den modtager signal, hvor der ikke sendes noget. Når der så sendes 500 us, så kan den modtagne puls være noget længere, men ikke særligt meget kortere, derfor ledes efter en puls der er over 400us. Når modtageren så har indstillet sig på niveauet, så vil den pause der skal ledes efter være mellem 250us og 350us. Hvis begge dele kommer i træk så startes der med at modtages en pakke.

Indhold i pakken

Pakkerne er designet til at kunne indeholde en variabel, som kan være af typen byte, int eller long, altså hhv. 1, 2 eller 4 byte lang.

Ud over dette lægges der en adresse på modtageren (en byte) og der angives hvilken variabel det er (en byte 0-15), og for at man ikke kommer til at overskrive en variabel med noget forkert, så lægges der en del kontrol ind i pakken, som skal stemme overens med hinanden for at man kan acceptere pakkens indhold.

En pakke der indeholder en int (2 databyte) kan illustreres på følgende måde:

|  start  |  adr  |  /adr  |  nr  |  /nr  |  low  |  /low  |  high  |  /high  |  CS  |  slut  |

Start er de 500us høj og 300us lav.

Alle de efterfølgende bytes sendes to gange, hvor den anden gang er det bitmæssigt inverterede indhold (indikeret med / foran). Dette kan kontrolleres ved modtagelsen, og hvis der ikke er modtaget det samme inverteret, så kasseres pakken.

adr er den adresse modulet har, hvis den ikke er korrekt, så ignoreres pakken.

nr er det nummer variablen har, så protokollen kan håndtere forskellige variabler.

low og high er selve data-indholdet af pakken.

CS er en sammentælling af adr, nr og data-indholdet i en byte (overflower). Hvis sammentællingen ikke passer med den modtagne CS, så kasseres pakken.

Slut er den 300us høj puls der modtages.

En total pakke ser ud som følger:

En hel pakke med en to-bytes variabel
En hel pakke med en to-bytes variabel

Princip for RF-modtager

Modulet er opbygget ved at bruge et simpelt 433 MHz modtager modul, der giver høj ud når det modtager 433 MHz og lav når det ikke modtager det.

Det digitale signal behandles af en microcontroller af typen ATTiny45, der forholder sig til de tider det modtager med og tolker om det modtagne overholder det pakkeformat der er beskrevet under de tidsmæssige definitioner af pakken. De pakker der modtages og godkendes lagres i microcontrolleren.

Microcontrolleren er programmeret til at fungere som en I2C enhed der kan svare på en bestemt I2C adresse, hvor man kan indstille forskellige ting i modtageren, og man kan hente indholdet af de pakker der er modtaget.

I det efterfølgende er både hardware og software i I2C modulet beskrevet.

Strukturen i I2C RF modtageren

Blokdiagram over I2C RF Modtager
Blokdiagram over I2C RF Modtager

Blokken med 433 MHz modtageren er et færdigt købt modul der placeres i et stik på modtageren, og giver et digitalt signal ud.

Microcontrolleren er en 8 bens ATTiny, der kan håndtere I2C på 2 ben, på de samme ben kan den programmeres ved også at tilgå reset-benet og endnu et ben - det er sådan at I2C kommunikationen og ICSP-programmeringen kan laves i samme 6 polede stik, efter samme standard som Arduinoens ISCP-stik.

Det sidste der er på RF-modtageren er en indikator-lysdiode, der anvendes til at give en indikation for om der er modtaget en pakke.

I2C RF Modtager hardware

Monteret I2C RF Modtager med modtager

I2C tastaturet er lagt ud ved hjælpe af Eagle og layoutet med en PDF til at lave print efter kan hentes i denne ZIP-fil.

Det samlede diagram ser ud som følger:

Total-diagram over I2C RF Modtager
Total-diagram over I2C tastatur

Printet kan monteres efter følgende layout:

Komponent-layout over I2C RF Modtager   Monteret I2C RF Modtager
Komponent-layout over I2C tastatur og et monteret print

Komponentlisten til I2C RF Modtager er som følger:

Komponent Type Værdi
U1 MikroController ATTiny 45 (vendes rigtigt - i sokkel)
U1 IC Sokkel 8 bens IC-sokkel (vendes rigtigt)
D1 Småsignal diode 1N4148 (vendes rigtigt)
LED1 Lysdiode 5mm LED Grøn (vendes rigtigt)
LED2 Lysdiode 5mm LED Gul (vendes rigtigt)
SV1 Jumper stik 2 polet pin-række med Jumper
SV2 Pin-header Hun 4 polet hun stik i stribe, der klippes af 20 poles stribe
PR1 Molex stik 2x3 polet pin-række til Moles fladkabel-stik
R1 Modstand 10k ohm
R2 Modstand 680 ohm
R3 Modstand 680 ohm
R4 Modstand 220k ohm
R5 Modstand 2k2 ohm
R24 Modstand 220k ohm
C1 Polyester kondensator 100nF
C2 Elektrolyt kondensator 10uF (vendes rigtigt)
H1 - H4 Monteringshul 10mm M3 gevindstag med M3 skrue
Kommunikationsport til I2C

Kommunikationsporten til I2C

Som vist på diagrammet indeholder kommunikationsporten PR1 til I2C flere ting, da portbenene anvendes til flere forskellige funktioner.

Stikket er 6-polet selvom det kun er de 4 ben der anvendes til I2C-kommunikationen.

Til I2C har porten følgende funktion:

Ben nr Signal-navn Specielle forhold
1 MISO Anvendes ikke til I2C - skal svæve ved reset
2 Vcc (+ 5V) Forsyning til I2C modulet
3 SCK Serial Clock til I2C
4 SDA Serial Data til I2C
5 Reset Mikrocontrollerens Reset - skal svæve når modulet er i funktion
6 GND Stel-forbindelse til I2C modulet

PR1 porten kan også anvendes til at programmere Mikrocontrolleren igennem, hvor porten får følgende funktion:

Ben nr Signal-navn Specielle forhold
1 MISO En del af ICSP programmeringen
2 Vcc (+ 5V) Forsyning til I2C modulet
3 SCK En del af ICSP programmeringen
4 MOSI En del af ICSP programmeringen
5 Reset Mikrocontrollerens Reset - Anvendes til at initiere ICSP
6 GND Stel-forbindelse til I2C modulet
Adresse-jumper I2C RF Modtager

Adresse på I2C tastaturet med Jumper

Ud over dette anvendes ben 1 i PR1 til at angive om det er en lige eller en ulige adresse I2C-RF-modtageren skal reagere på. Dette angives ved hjælp af jumperen SV1, hvor den er trukket høj af R4 når jumperen ikke er monteret og bliver trukket lav gennem R5 når man sætter jumperen på. Niveauet læses ved reset af mikrocontrolleren.

Som det kan ses på billederne til højre, så er det blot en jumper der kan sættes på for at skifte adressen. Når jumperen ikke er på, så bliver adressen (0x28) en højere (0x29), end hvis den er på.

Ideen med dette er at man kan have to ens RF-modtagere på samme I2C, og endda have samme program liggende i dem, men at man kan henvende sig til hvert af dem, uden det giver konflikt.

Yderligere hardware på RF-modtageren

Yderligere hardware på I2C RF Modtager

Der er yderligere komponeter på I2C RF-modtageren som har en mindre rolle at spille.

C1 og C2 er blot monteret for at fastholde forsyningen, hvis I2C modulet bliver forsynet gennem et længere kabel. Det er for at gøre modulet mindre støjfølsomt.

R1 og D1 er en del af reset-kredsløbet, som ellers håndteres internt i Mikrocontrolleren.

R2 og LED1 er blot en power-indikation der kan være praktisk at anvende i opstillinger hvor man kobler tingene sammen med kabler og løse ledninger. Hvis man ønsker at spare strøm i sin opstilling, så kan man spare 5 mA ved at undlade at montere disse to komponenter.

R3 og LED2 er indikation til hvornår modulet modtager pakker. Det kan være en praktisk indikation, hvis man ikke har en ide om hvad der sker i modulet. Hvis man ønsker at spare strøm i sin opstilling, så kan man spare 5 mA ved at undlade at montere disse to komponenter.

Elektronikken på det færdige RF Modtager-modul

Modtagermodulet består af en diskret opbygget modtager omkring to transistorer og en svingningskreds hvor der indgår en trimmekondensator som er afstemt til den ønskede frekvens. I forstærkningen udnyttes en AGC-teknik der tilpasser forstærkning til styrken af det modtagne signal bedst muligt. Dette betyder at modtagren vil forsøge at genkende et signal i det modtagne, så man uanset styrken næsten altid vil modtage et signal - også selvom der ikke sendes til modtageren. Herefter bliver signalet ensrettet og sammenlignet med et niveau, så modulet giver høj ud når det modtager noget på den ønskede frekvens, og det giver lavt niveau ud når der ikke modtages noget. AGC-kredsløbet betyder at der også vil modtages skift selvom der ikke sendes et signal - dette skal man tage høje for i modtagelsen.

Sammenligningen foretages med en LM358 comperator inde i modulet.

Specifikationer for RF-link Modtager modul

Specifikationerne er sakset fra de hjemmesider det har været muligt at finde, som dokumenterer funktionen.

Modtager modul[2]
Parameter Data
Forsynings spænding 3V - 8V DC (nom. 5V)
Krævet forsynings-strøm < 3mA @ 5V
Modtage frekvens 315 / 433 MHz (433 MHz i vores version)
Frekvens område 260 - 440 MHz justerbart (undlad dette)
Modulationsform ASK / OOK
RF følsomhed -105 dBm (50 Ω)
Data rate < 5 kbps (315 MHz, -95 dBm)
Temperatur område -20 til 70 grader C
Data output TTL niveau (0/5V)
Antenne længde 24 cm (315 MHz), 18 cm (433,92 MHz)

Det samlede RF-Link

Til RF-linket anvendes to små hardware-moduler: et sendermodul med en tilhørende software og et modtagermodul med en tilhørende software. De to moduler skal være indstillet til samme frekvens, hvor de viste moduler arbejder på 433 MHz.

Ud over dette skal der etableres en protokol, så de to moduler kan "snakke sammen", og der kan overføres en information fra en enhed til en anden.

Sender-modul

Sender-modul på Arduino til at lave et RF-link med

Opbygningen af sendermodulet er en færdig opbygget oscillator, der arbejder på 433 MHz, hvor man styrer ind på en transistor i modulet, og på den måde styrer om oscillatoren skal være tændt eller slukket. Styringen kan gøres f.x. fra en Arduino og skal ske ved hjælp af et databene, hvor det skal have 5V for at sende og 0V for at være slukket.

Ben Navn Betydning Arduino-ben
1 GND Stel GND
2 Vcc +5V forsyning +5V eller en højere spænding op til 12 V
3 Data Sende signal - når den er høj, sendes 433 MHz Valgfrit digitalt signal 0-13

Software til RF-link sender modul

Denne side handler om modtageren, og der angives kun det mest nødvendige om senderen for at belyse hvordan modtagelsen foregår.

Den nærmere opbygning af sender-software er lavet i RF-sender.

Afsending ved hjælp af RF-modulet

For at kunne foretage en transmission hvor man sender nogle forskellige værdier hvor RF-send-demo1.ino illustrerer denne anvendelse sammen med RF-I2C-modt-demo1.ino

Modulerne udnytter modulet RF-I2C-link for at etablere

void loop() {
  RF_send(0,tal_b);
  tal_b++;
  delay(5);
  RF_send(1,tal_i);
  tal_i++;
  delay(5);
  RF_send(2,tal_l);
  tal_l++;
  delay(50);
}

Koden er lavet så den sender de 3 variabler som er lavet i forskellig størrelse, hhv. en byte, en int (2 byte) og en long (4 byte), så de forskellige muligheder testes af.

Denne simple sending anvendes til at afteste de forskellige principper for modtagelse.

Første test modtagelse

Testopstilling til modtagelse af pakker

For at få styr på om der kan modtages noget fornuftigt, så sættes RF-modtageren på en Arduino, hvor den bare kobles på pin 2-5, så pin 2 giver stel og pin 5 giver forsyning. Signalet kan i princippet læses både på pin 3 og 4, men læses bare på pin 3. Opstillingen kan ses her til højre.

Modtagelsen skal ske ved hjælp af tid, og for at kunne få det til at fungere nede ved så korte tider anvendes den indbyggede funktion micros(), der kan aflæse tiden i mikrosekunder siden start af Arduinoen.

I først omgang baseres koden bare på at der loopes hele tiden og at der ikke kaves andet, så man ikke misser noget af en bit.

Da modtageren er sat på en række I/O så skal den forsynes og signalet skal læses på en række pins. Det bliver defineret som følger. Der sættes desuden op til at kommunikere serielt, så vi kan se hvilke resultater vi får ud af det.:

const int ground = 2;
const int rfInput = 3;
const int supply = 5;

void setup() {
  Serial.begin(9600);
  Serial.write("Init RF receiver");
  pinMode(ground, OUTPUT);
  pinMode(supply, OUTPUT);
  pinMode(rfInput, INPUT);
  digitalWrite(ground, LOW);
  digitalWrite(supply, HIGH);
}

Til modtagelsen anvendes en række variable som vist her:

byte buffer[16];
byte mode = 0;
byte inputVal;
byte bitPtr = 0;
byte bytePtr = 0;
long last;
long tid;

Modtagelsen bases på mode variablen, der fungerer som en simpel state-machine.

Forklaring af den state-machine der anvendes i første test

State-machinen kan illustreres med følgende state-diagram, der indikerer hvordan de forskellige skift sker hen gennem modtagelsen af pakken.

Statediagram der illustrerer modtagelsen i første version
Statediagram der illustrerer modtagelsen i første version

I mode 0 ventes på den høje marker (starten af pakken).

I mode 1 og 2 kontrolleres at den høje og hhv. den lave marker har den korrekte tid. Dette er etableret med følgende kode:

void loop(){
  switch (mode) {
    case 0:
      if (digitalRead(rfInput)) {
        last = micros();
        mode = 1;
      }
      break;
    case 1:
      if (!digitalRead(rfInput)) {
        tid = micros() - last;
        last = micros();
        if (tid > 450) {
          mode = 2;
        } else {
          mode = 0;
        }
      }
      break;
    case 2:
      if (digitalRead(rfInput)) {
        tid = micros() - last;
        last = micros();
        if ((tid > 250) && (tid < 350)) {
          bitPtr = 0;
          bytePtr = 0;
          mode = 3;
        } else {
          mode = 1;
        }
      }
      break;

Mode 3 håndterer den høje del af en bit-modtagelse, hvor den tester om det er ca. 100 us, er der det skiftes til mode 4.

Det mest komplicerede er hvis tiden ligger på ca. 300 us. Det indikerer at pakken er slut. Her testes først om der lige er afsluttet en byte, og hvis der er det, så analyseres bufferen igennem, så alle parametre for om modtagelsen er gået godt passer. Undervejs i analysen sendes serielt ud de vigtige data i modtagelsen, eller hvis noget går galt, så sendes en fejlkode.

Hvis der modtages andre tider, så sættes mode tilbage til 0, så søgningen på en ny pakke kan begynde.

    case 3:
      if (!digitalRead(rfInput)) {
        tid = micros() - last;
        last = micros();
        if ((tid > 250) && (tid < 350)) {
          if (bitPtr == 0) {
            byte n = 0;
            byte CS = 0;
            byte fejl = 0;
            do {
              CS += buffer[n];
              n++;
              byte inv = ~buffer[n];
              if (buffer[n-1] != inv) {
                fejl = n;
              } else {
                Serial.print(buffer[n-1]);
                Serial.print(" ");
              }
              n++;
            } while (n < bytePtr - 1);
            if (CS != buffer[n]) {
              fejl = 99;
            }
            Serial.println(fejl);
          } else {
            Serial.print(bitPtr);
          }
          mode = 0;
        } else if ((tid > 50) && (tid < 150)) {
          mode = 4;
        } else {
          Serial.println(50);
          mode = 0;
        }
      }
      break;

I mode 4 modtages den lave del af en bit, og afhængigt af tiden registreres om det er en 0 eller 1 bit der modtages. Dette samles op i inputVal. Når der er samlet 8 bit op i en byte, så gemmes den i bufferen og der gøres klar til at modtage en ny byte. Hvis den modtagne tid ikke passer med enten 100 us eller 200 us, så skiftes også her tilbage til mode 0, så der søges efter en ny start på pakken.

    case 4:
      if (digitalRead(rfInput)) {
        tid = micros() - last;
        last = micros();
        bitPtr++;
        if ((tid > 150) && (tid < 250)) {
          inputVal <<= 1;
          inputVal++;
          mode = 3;
        } else if ((tid > 50) && (tid < 150)) {
          inputVal <<= 1;
          mode = 3;
        } else {
          mode = 1;
        }
        if (bitPtr == 8) {
          bitPtr = 0;
          buffer[bytePtr] = inputVal;
          bytePtr++;
        }
      }
      break;
  }      
}

Test af den første version af modtagelse

På den serielle monitor kan man se hvordan modtagelsen af pakker forløber. En visning kan se ud som følger:

40 0 162 0
40 1 138 164 0
40 2 226 226 15 0 0
40 0 163 0
40 1 139 164 0
40 0 164 0
40 2 228 226 15 0 0
50
50
40 2 229 226 15 0 0
40 0 166 0
40 1 142 164 0
40 2 230 226 15 0 0
40 0 167 0
40 1 143 164 0
40 2 231 226 15 0 0

Umiddelbart var det svært at få modtagelsen til at fejle, men ved at forstyrre med anden transmission ind over (en bilnøgle), så kunne man forstyrre så der kom en fejl - her indikeret med fejl 50 to gange i træk.

De fejltyper der kan detekteres kommer ud som følgende tal:

  • 0 Ingen fejl
  • 1-11 En fejl hvor den modtagne byte ikke modtages som den inverterede
  • 21-27 Afslutning af en pakke, hvor det ikke passer med et helt antal bytes (sidste byte er ikke 8 bit)
  • 40 Forkert tid i lav (ikke 100 us eller 200 us)
  • 50 Forkert tid i høj (ikke 100 us)
  • 99 Checksum passer ikke med summering af bytes

Der udskrives ikke fejl, hvis det er tidsfejl i starten af pakken. Disse fejl vil blot blive ignoreret.

Man vil kunne observere at nogle pakker fejler, hvilket er en del af konceptet, da der blot sendes uden nogen form for feedback, og derfor heller ikke nogen mulighed for fejlkontrol eller korrektion af fejl, så kommunikationen kan ikke anvendes i en kommunikation hvor der skal sikres data-overførsel for alle data der sendes.

Det bør altså være opbygget således at man sender sine variabler gentagene gange, og så bruger man dem kun når de er korrekt modtaget.

Refleksioner over første version af modtageren

Den første version af modtageren illustrerer at det sendte signal kan modtages med ret stor sikkerhed (over korte afstande), men er ikke testet yderliger.

Softwaren er lavet, så den måler tiden i hoved-loopet, så det vil være meget tidkritisk at bygge den sammen med andre aktiviteter der skal afvikles i softwaren. Tidsmæssige variationer ned omkring bit-tiden på 100 us vil kunne forstyrre modtagelsen lavet ud fra dette princip.

Anden test-modtagelse

Til den anden test anvendes stadig den samme hardware som i den første test.

Forskellen er at der anvendes kanttrigget interrupt til at fange inputtet med, og hele tolkningen og opsamlingen af pakken ligger i interrupt, mens udskriften ligger i loopet uden for interruptet.

Lige som sidst laves nogenlunde den samme initialisering i setup():

const int ground = 2;
const int rxPin =  3;      // the number of the RF RX-pin
const int supply = 5;
const int highTime = 100;
const int lowZeroTime = 100;
const int lowOneTime = 200;

void setup() {
  Serial.begin(9600);
  Serial.write("Init RF receiver");
  pinMode(ground, OUTPUT);
  pinMode(supply, OUTPUT);
  pinMode(rxPin, INPUT);
  digitalWrite(ground, LOW);
  digitalWrite(supply, HIGH);
  attachInterrupt(1, kant, CHANGE);
}

Den store forskel er at der sættes en interrupt-rutine der hedder kant op med attachInterrupt til pin 3 (interrupt 1) og det skal reagere på skift i niveauet (CHANGE).

Desuden angives tiderne som konstanter.

Fordi det skal fungere i interrupt anvendes en del flere variabler, og der anvendes et lidt andet princip.

byte CS;
byte tal = 0;
byte inByte = 0;
byte inCount = 0;
byte byteCount = 1;
byte inMirror = 0;
byte add;
byte address;
byte inData[4];
byte inNr;
boolean inPackage;
boolean packageOK = false;
long lastMicros;
long thisMicros;
int tid;

Forklaring af interruptrutinen

Det centrale i modtagelsen af pakken her er interrupt-rutinen, og da den altid kaldes efter et skift af signalet, så ved vi at vi kan registrere hvor lang tid der er gået siden sidste kant - det gøres først i rutinen:

void kant() {
  thisMicros = micros();
  tid = thisMicros - lastMicros;
  lastMicros = thisMicros;

Herefter forholder vi os til om vi er begyndt at modtage pakken, og hvis vi ikke er det, så kigger vi efter de to specielle tider - en høj på mere end 450 us og umiddelbart derefter en lav på ca. 300 us.

Dette kan betragtes på følgende måde, hvor vi bliver uden for modtagelsen af pakken, indtil vi har modtaget de korrekte tider.

Tilstandene, når vi ikke er i gang med at modtage en pakke
Tilstandene, når vi ikke er i gang med at modtage en pakke

Det gøres ved at byteCount står på 1 når vi ikke er inde i pakken, og hvis vi modtager en høj puls der er lang nok, så sættes byteCount til 0, så vi ved den lige har været der, modtages der så en lav på ca. 300 us, så startes modtagelsen af selve pakkens data, ellers sættes byteCount tilbage til 1.

  if (! inPackage) {
    if (digitalRead(rxPin)) {
      if ((tid > 250) && (tid < 350) && (byteCount == 0)) {
        inPackage = true;
        packageOK = false;
        inCount = 0;
        CS = 0;
      } else {
        byteCount = 1;
      }
    } else {
      if (tid > 450) {
        byteCount = 0;
      }
    }

Når vi er i gang med at modtage en pakke, så kan selve modtagelsen betragtes på følgende måde - der ligger selvfølgelig mere i det, men den grundlæggende tankegang er som illustreret her.

Illustration af skiftene når vi modtager en pakke
Illustration af skiftene når vi modtager en pakke

Hvis vi er i gang med at modtage pakken, så behandles først de lave signaler (der angiver om det er 0 eller 1 der modtages).

Der indikeres først at det er en forkert tid, og hvis ikke tiden enten er ca. 100 us (svarer til en 0-bit) eller 200 us (svarer til en 1-bit), så vil der registreres en fejl, og vi starter forfra med at vi ikke har modtaget noget, så vi kan lede efter starten af en pakke igen.

  } else {   // Vi er i gang med at modtage en pakke
    if (digitalRead(rxPin)) {
      add = 2;
      if (tid > (lowZeroTime - 40) && tid < (lowZeroTime + 40)) {
        add = 0;
      } else if (tid > (lowOneTime - 40) && tid < (lowOneTime + 40)) {
        add = 1;
      }
      if (add == 2) {
        inPackage = false;
        byteCount = 1;

Hvis modtagelsen af en bit gik godt, så skelnes imellem om der er talt under 8 eller 16 bit, hvor den modtagen bit lægges i hhv. inByte eller inMirror, hvor inMirror gerne skulle være den inverterede byte af inByte.

      } else {
        if (inCount < 8) {
          inByte <<= 1;
          inCount++;
          inByte += add;
        } else {
          inMirror <<= 1;
          inCount++;
          inMirror += add;

Når begge bytes er modtaget, så inverteres (bit-mæssigt) den modtagne byte der er sendt inverteret, så de to inverteringer skulle gerne give den oprindelige byte, som er modtaget som inByte, det testes der for, og hvis en af dem er modtaget forkert, så vil de være forskellige, og der indikeres at det modtagne er forkert, ved at der ledes efter en ny pakke.

Hvis byten er OK, så vil den først modtagne byte være adressen, den næste nummeret og resten vil være data, hvor det lagres i de respektive variabler.

          if (inCount == 16) {
            inMirror = ~inMirror;
            if (inByte == inMirror) {
              if (byteCount == 0) {
                address = inByte;
              } else if (byteCount == 1) {
                inNr = inByte;
              } else if (byteCount >= 2) {
                inData[byteCount - 2] = inByte;
              } 
              CS += inByte;
              byteCount += 1;
            } else {
              inPackage = false;
              byteCount = 1;
            }
            inCount = 0;
          }
        }
      }

Hvis det ikke er den lave periode af bitten der modtages, så må det være den høje, som ved alle databit skal være ca. 100 us. Hvis den i stedet er ca. 300 us, så betegner det afslutningen af en pakke, dog forudsat at der er modtaget netop 8 bit, som er lagret i inByte. Denne modtagne byte skal svare til den simple sammentælling af modtagne bytes der er foretaget i CS, og hvis det er tilfældet, så godkendes hele pakken ved at packageOK sættes true. Uanset om pakken er godkendt eller ej, så vil en lang høj puls stoppe modtagelsen af pakken.

    } else {
      if (tid > (highTime - 50) && tid < (highTime + 50)) {
      // pause
      } else if ((tid > 250) && (tid < 350)) {
        if (inCount == 8) {
          if (inByte == CS) {
            packageOK = true;
          }
        } else {
        }
        inPackage = false;
        byteCount = 1;

Hvis tiden er andet en det forventede, så stoppes modtagelsen også, og hermed er kant-interruptet behandlet.

      } else {
        inPackage = false;
        byteCount = 1;
      }
    }
  }
}

Hvis alle tjek på den modtagne pakke kommer gennem nåleøjet skulle variablen packageOK gerne være true.

Main-loopet i softwaren, der viser resultatet

I loopet laver vi ikke andet end at kigge efter denne variabel, og når den bliver true, så printes indholdet af pakken som vist her:

void loop(){
  if (packageOK) {
    Serial.print(address);
    Serial.print(" ");
    Serial.print(inNr);
    if (inNr == 0) inByte = 1;
    if (inNr == 1) inByte = 2;
    if (inNr == 2) inByte = 4;
    Serial.print(" ");
    for (tal = 0; tal < inByte; tal++) {
      Serial.print(inData[tal]);
      Serial.print(" ");
    }
    Serial.println(CS);
    packageOK = false;
  }
  delay(5);  // Større delay giver fejl
}

Hvis denne software skal anvendes, så er det vigtigt at der tjekke mindst lige så hurtigt som der sendes pakker, ellers kan man risikere at miste nogen - det kan man faktisk alligevel, hvis der er en fejl i modtagelsen.

Test af anden version af modtagelsen

Ved testen af modtagelsen viste det sig at langt de fleste pakker kom fint igennem som vist her:

40 0 88 128
40 1 64 121 226
40 2 152 183 15 0 136
40 0 89 129
40 1 65 121 227
40 2 153 183 15 0 137
40 0 90 130
40 1 66 121 228
40 2 154 183 15 0 138
40 0 91 131
40 1 67 121 229
40 2 155 183 15 0 139
40 0 92 132
40 1 68 121 230
40 2 156 183 15 0 140

Refleksioner over anden version af modtageren

Den anden version af modtageren illustrerer at det sendte signal kan modtages med ret stor sikkerhed (over korte afstande), men er ikke testet yderligere.

Softwaren er lavet, så den måler tiden i en interrupt-rutine, så det er mindre tidkritisk end den første version. Det vil dog stadig stille krav til softwaren at bygge den sammen med andre aktiviteter der skal afvikles. Tidsmæssige variationer vil kunne forstyrre modtagelsen lavet ud fra dette princip, men det bliver afhængigt af hvor tit senderen fyrer pakker afsted. Man skal her sikre sig at der bliver kigget på packageOK i hvert fald hurtigere end længden af pausen mellem de sendte pakker, ellers vil man både kunne miste pakker, men det vil også kunne virke forstyrrende på modtagelsen.

Hvis man kunne leve med disse krav, så ville dette princip kunne anvendes, og modtageren kunne lægges i et software modul, så det på enkelt vis kunne anvendes sammen med anden software. Dette er ikke realiseret, da det der er formålet her er at lave et selvstændigt hardware-modul med endnu større fleksibilitet.

I2C-modulets software

Der kommunikeres med I2C modtagermodulet på adressen 0x28, og modulet er som standard sat op til at kunne modtage 16 variabler med hver 2 byte indhold, så hvis man ikke ændrer på disse størrelser så kan modulet hente ud fra følgende kommandostruktur:

Kommando antal pakker modtag 1 modtag 2
0 antal pakker med variabel 0 siden sidst mindst betydende byte i variabel 0 mest betydende byte i variabel 0
1 antal pakker med variabel 1 siden sidst mindst betydende byte i variabel 1 mest betydende byte i variabel 1
2 antal pakker med variabel 2 siden sidst mindst betydende byte i variabel 2 mest betydende byte i variabel 2
. . . .
. . . .
15 antal pakker med variabel 15 siden sidst mindst betydende byte i variabel 15 mest betydende byte i variabel 15

Alle variabler i modulet kan indstilles til at modtage forskelligt antal bytes via I2C kommunikationen. Som standard er alle variabler sat til 2 bytes, men kan valgfrit sættes til 1, 2 eller 4. Variabelnummeret i I2C kommunikationen tillægges 128 og der sendes antallet, så man har følgende kommandoer til at indstille antal med:

Kommando antal
128 antal byte i variabel 0
129 antal byte i variabel 1
130 antal byte i variabel 2
. .
. .
143 antal byte i variabel 15

Dette antal gemmes i EEprom, og læses når I2C-RF modtageren startes. Dette gør at masteren ikke hele tiden skal sikre sig at antallet er korrekt, hvor I2C-RF modtageren har genstartet.

Referencer