Forum: Mikrocontroller und Digitale Elektronik RFM01 Empfänger mit Interrupt


von ArduStemmi (Gast)


Lesenswert?

Ich beschäftige mich schon geraume Zeit mit der Thematik, wie ich die 
Daten aus dem RFM01 per Interrupt auslesen kann. Leider komme ich nicht 
recht voran!

Ich habe mir eine Lösung für den RFM12 angeschaut, dort wird nach in der 
ISR der Befehl 0xB0 zum RFM12 geschickt und danach ein Byte aus dem FIFO 
ausgelesen! Daraus schlussfolgere ich, dass jedes einzelne Byte einen 
Interrupt auslösen muss! Hierbei werden keine Statusbytes gelesen und 
verarbeitet.

Im Vergleich dazu wird in der Polling Prozedur aus Benedikts Source Code 
nachdem das SDO Pin Low ist 16 Bit (Statusregister ausgelesen und 
verworfen, danach wird ein Byte gelesen und verarbeitet! Wenn die 
Paketgröße also 16 Byte beträgt, wird die Statusbytes 16mal gelesen und 
verworfen.

Woher kommt dieser Unterschied? Warum muss ich beim RFM12 erst noch 
einen Befehl hinschicken, und beim RFM01 erst Statusdaten lesen?

Bei der Polling Struktur ist natürlich immer klar, wann ein Paket zu 
Ende ist, das ist bei der Interruptstruktur nur mit Hilfe einer 
statischen Hilfsvariable möglich. Das heißt, dass Risiko ist hoch, dass 
ein Byte verlorengeht und dann keine sinnvolle Information zu Stande 
kommt.

Kann mir jemand erklären, wie man es richtig macht?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

ArduStemmi schrieb:
> Warum muss ich beim RFM12 erst noch
> einen Befehl hinschicken, und beim RFM01 erst Statusdaten lesen?

Der RFM12 ist ein Transceiver, d.h. er kann senden und empfangen. Der 
RFM01 ist ein reiner Empfänger und deswegen muss man ihm nicht 
mitteilen, das er empfangen soll.

ArduStemmi schrieb:
> Das heißt, dass Risiko ist hoch, dass
> ein Byte verlorengeht und dann keine sinnvolle Information zu Stande
> kommt.

Gerade bei der Interuptsteuerung geht sehr wenig verloren, weil die 
Prioritäten hoch sind.
Leider ist der Grossteil meines IRQ gesteuerten RFM Moduls verloren 
gegangen, aber das Prinzip geht so:
* Pinchange Interrupt für den Pin, an dem nIRQ vom Modul dran ist, 
einrichten, aber noch nicht freigeben.
* (an den RFM sende ich dazu : 0xc0a2, 0xce88, 0xce8b)
* Empfänger initialisieren und Fifo anschalten. Datenpointer 
vorbereiten.
* ( RFM bekommt 0xc0a3)
* Pinchange Interrupt freigeben.

Im Interrupt:
Daten zum RFM01 dauerhaft lo setzen.
CS des RFM auf aktiv setzen, also RFM anwählen.
Jetzt 16 bit vom RFM Datenpin lesen, dabei mit der Clk weitertakten, 
also immer high, dann low, dann lesen. Das ist das Statusword. Wenn 
dadrin das FIFO_IT Bit gesetzt ist (das oberste  Bit15), das Datenbyte 
einlesen, sonst den Empfang abbrechen.
Byte einlesen mit Clk Hi, einige µs warten, Datenbit lesen, Clk low und 
8 mal wiederholen, dann das Byte im Buffer speichern.

Ganz am Ende dann CS wieder deaktivieren. Das wars.

im Hauptprogramm den Datenpointer checken. Wenn die Paketlänge komplett 
ist, Fifo resetten und wieder von vorne.
*

von Christian K. (the_kirsch)


Lesenswert?

Ich kann dir ja mal ein paar Tipps wie ich es gelöst hatte.

du must die zwei Interrupt Routinen
ISR (INTx_vect){
}

und


ISR (SPI_STC_vect) {
}

aus programmieren.

zusätzlich brauchst du hilf Funktionen die du aus deinem Hauptprogramm 
aufrufst.

1. Es werden immer 16 Bit per SPI übertragen. Der AVR-SPI-Kontroller 
arbeitet aber nur mit 8 Bits, daher musst du ihn immer  zweimal 
'anwerfen'.

ich mach das immer so:
1
volatile uint8_t busyFlag_ISR;
2
volatile uint8_t rxBuf_ISR;
3
volatile uint8_t mode16Flag_ISR;
4
volatile uint8_t rxBufHi_ISR;
5
volatile uint8_t txBuf_ISR;
6
7
8
void Spi_init() {
9
  SPSR = 0x01;
10
  SPCR = 0xD1;
11
}
12
13
ISR (SPI_STC_vect)
14
{
15
  if ( mode16Flag_ISR ) {
16
    mode16Flag_ISR = 0;
17
    rxBufHi_ISR = SPDR;
18
    SPDR = txBuf_ISR;
19
  } else {
20
    rxBuf_ISR = SPDR;
21
    /* SS to Hi */
22
    SPI_NSEL_PORT |= ( 1 << SPI_NSEL_PIN );
23
    busyFlag_ISR = 0;
24
    rfm_code_here();
25
  }
26
}
27
28
uint8_t Spi_16( uint16_t data ) {
29
  uint8_t result;
30
  cli();
31
  result = Spi_16_ISR( data );
32
  sei();
33
  return result;
34
}
35
36
uint8_t Spi_16_ISR( uint16_t data ) {
37
  if ( busyFlag_ISR ) {
38
    return 0;
39
  }
40
  mode16Flag_ISR = 1;
41
  busyFlag_ISR = 1;
42
  txBuf_ISR = data;
43
  //SS to Low
44
  SPI_NSEL_PORT &= ~( 1 << SPI_NSEL_PIN );
45
  SPDR = ( data >> 8 );
46
  return 1;
47
}
48
49
void Spi_16W( uint16_t data ) {
50
  while ( 1 ) {
51
    cli();
52
    if ( busyFlag_ISR ) {
53
      sei();
54
      continue;
55
    }
56
    break;
57
  }
58
  Spi_16_ISR( data );
59
  sei();
60
}
61
62
uint8_t Spi_8( uint8_t data ) {
63
  uint8_t result;
64
  cli();
65
  result = Spi_8_ISR( data );
66
  sei();
67
  return result;
68
}
69
70
uint8_t Spi_8_ISR( uint8_t data ) {
71
  if ( busyFlag_ISR ) {
72
    return 0;
73
  }
74
  busyFlag_ISR = 1;
75
  //SS to Low
76
  SPI_NSEL_PORT &= ~( 1 << SPI_NSEL_PIN );
77
  SPDR = data;
78
  return 1;
79
}
80
81
void Spi_8W( uint8_t data ) {
82
  while ( 1 ) {
83
    cli();
84
    if ( busyFlag_ISR ) {
85
      sei();
86
      continue;
87
    }
88
    break;
89
  }
90
  Spi_8_ISR( data );
91
  sei();
92
}
93
94
uint8_t Spi_get( void ) {
95
  return rxBuf_ISR;
96
}
97
98
uint8_t Spi_getHi( void ) {
99
  return rxBufHi_ISR;
100
}
101
102
uint8_t Spi_isBusy( void ) {
103
  return busyFlag_ISR;
104
}

Du kannst ersmal deine Software SPI damit ersetzen, in dem du:
1
Spi_16W(w_data);
2
uint16_t r_data = (uint16_t)Spi_getHi() << 8 | Spi_get();
schreibst.

Der Hardware SPI ist um ein vielfaches schneller als über Software, 
daher ist es kein Problem jedes Mal das komplette Statusregister des RFM 
auszulesen.


Die gesamte Ansteuerung kommst in die "rfm_code_here()".

Die Interrupt Routine ist bei mir auch sehr klein, da sie nur den SPI 
startet:
1
volatile uint8_t interruptFlag_ISR = 0;
2
3
/*
4
 * si4420Physical_state_ISR:
5
 * 0 => not init
6
 * 1 => idle
7
 * 2 => listening
8
 * 3 => sending
9
 */
10
volatile uint8_t state_ISR = 0;
11
12
/* si4420Physical_SPIAction_ISR:
13
 * 0 => none
14
 * 1 => status read
15
 * 2 => receiver read
16
 */
17
volatile uint8_t spiAction_ISR = 0;
18
19
ISR (INT0_vect)
20
{
21
  if ( Spi_16_ISR( 0x0000 ) ) {
22
    spiAction_ISR = 1;
23
  } else {
24
    interruptFlag_ISR = 1;
25
  }
26
}
Nicht wundern si4420 ist der eigentliche Chip auf den RFMs. 
interruptFlag_ISR ist ein Merker, das nach der aktuell laufenden 
SPI-Übertragung wieder das Staus Register ausgelesen werden muss.

von ArduStemmi (Gast)


Lesenswert?

Hallo Matthias Sch. Ich habe mal versucht, Dein Rezept umzusetzen, 
leider bisher ohne Erfolg.

Matthias Sch. schrieb:
> Gerade bei der Interuptsteuerung geht sehr wenig verloren, weil die
> Prioritäten hoch sind.
> Leider ist der Grossteil meines IRQ gesteuerten RFM Moduls verloren
> gegangen, aber das Prinzip geht so:
> * Pinchange Interrupt für den Pin, an dem nIRQ vom Modul dran ist,
> einrichten, aber noch nicht freigeben.
1
EICRA |= (1 << ISC01);          // The falling edge of INTx generates an interrupt request
 Zu Beginn des Programmes! Erste Zeile nach main.

> * (an den RFM sende ich dazu : 0xc0a2, 0xce88, 0xce8b)
1
  RF_PORT=(1<<CS);
2
  RF_DDR=(1<<SDI)|(1<<SCK)|(1<<CS);
3
  
4
  for (i=0; i<16; i++)
5
    _delay_ms(10);      // wait until POR done
6
7
  rf01_trans(0xC2E0);      // AVR CLK: 10MHz
8
  rf01_trans(0xC42B);      // Data Filter: internal
9
  rf01_trans(0xCE88);      // FIFO mode
10
  rf01_trans(0xC6F7);      // AFC settings: autotuning: -10kHz...+7,5kHz
11
  rf01_trans(0xE000);      // disable wakeuptimer
12
  rf01_trans(0xCC00);      // disable low duty cycle
13
  
14
  rf01_trans(0xC0E9);      //|((sgain&3)<<4)|((sdrssi&7)<<1));  // RX on
15
  rf01_trans(0xCE89);      // set FIFO mode
16
  rf01_trans(0xCE8B);      // enable FIFO

Die komplette Initialisierung des RFM 01 incl der letzten drei Zeilen, 
die RX anschalten, den FIFO Mode setzen und das den FIFO einschalten.

> * Empfänger initialisieren und Fifo anschalten. Datenpointer
> vorbereiten.
> * ( RFM bekommt 0xc0a3)
> * Pinchange Interrupt freigeben.
1
  EIMSK |= (1 << INT0);          // Turns on INT0
2
  
3
  
4
  sei();                  // turn on interrupts

Hier den Interrupt aktiviert und globale Interrupts freigegeben.
>
> Im Interrupt:
> Daten zum RFM01 dauerhaft lo setzen.
> CS des RFM auf aktiv setzen, also RFM anwählen.
> Jetzt 16 bit vom RFM Datenpin lesen, dabei mit der Clk weitertakten,
> also immer high, dann low, dann lesen. Das ist das Statusword. Wenn
> dadrin das FIFO_IT Bit gesetzt ist (das oberste  Bit15), das Datenbyte
> einlesen, sonst den Empfang abbrechen.
> Byte einlesen mit Clk Hi, einige µs warten, Datenbit lesen, Clk low und
> 8 mal wiederholen, dann das Byte im Buffer speichern.
1
ISR(INT0_vect)
2
{
3
  
4
  uart_puts("Interrupt");
5
  unsigned char c;
6
  
7
  cbi(RF_PORT, SDI);
8
  sbi(RF_PORT, CS);          //low avctiv
9
  cbi(RF_PORT, SCK);
10
  sbi(RF_PORT, SDO);          //low activ
11
  _delay_ms(2);
12
    
13
  cbi(RF_PORT,CS);          //RFM ausgewählt
14
  
15
  for (j=0; j<16; j++)        // lies Status Register
16
  {
17
    StatusFlag <<= 1;
18
    
19
    sbi(RF_PORT, SCK);
20
    _delay_us(0.2);
21
    cbi(RF_PORT, SCK);
22
  
23
    if (RF_PIN & (1<<SDO))
24
    {
25
      StatusFlag|=1;
26
    }
27
  }
28
    
29
  c=0;
30
  for (j=0; j<8; j++)          // lies Daten aus dem FIFO
31
  {
32
    c <<= 1;
33
      
34
    sbi(RF_PORT, SCK);
35
    _delay_us(0.2);
36
    cbi(RF_PORT, SCK);
37
      
38
    if (RF_PIN & (1<<SDO))
39
    {
40
      c|=1;
41
    }
42
    
43
  }
44
    
45
  data[nummer] = c;
46
  nummer += 1;
47
    
48
  }

Meine komplette Interrupt Routine!
>
> Ganz am Ende dann CS wieder deaktivieren. Das wars.


Der Interrupt wird niemals ausgelöst! Dass heißt nIRQ bleibt konstant 
auf High-Level. Wenn ich den Interrupt mit der Hand auslöse, GND an 
INT0, bleibt das Programm im ISR hängen, was ich nun gar nicht verstehe!

von ArduStemmi (Gast)


Lesenswert?

Nachdem es mir in endlicher Zeit und mit vertretbarem Aufwand nicht 
gelungen war, eine Interruptroutine für den RFM02 zu programmieren, 
hatte ich beschlossen, den Datenempfang in einen Attiny zu verlagern. 
Dieser sollte dann mit Polling arbeiten und die Daten an den Atmega328p 
übergeben. Im 328er wollte ich eine Interrupt Routine für den Empfang 
der Daten aus dem Attiny programmieren. Somit hätte der 328er ohne 
Störung mit schon geprüften Daten arbeiten können.

Also habe ich mein Programm wieder auf Poling umgestrickt. Dabei habe 
ich die in diesem thread besprochenen Fehler vermieden 
(Frequenzfestlegung und Frequenzberechnung). Das Ergebnis war 
ernüchternd. Wieder dauert es zwischen 3 Sekunden und 3 Minuten bis der 
Empfänger sicher Daten empfängt. Das ist aus meiner Sicht inakzeptabel.

Aus diesem Grund mache ich jetzt Schluss mit RFM01 und RFM02!

Kann mir einer von euch eine Alternative nennen. Ich möchte eine 
Datenverbindung im Sinne des broadcasting für Innenräume aufbauen. Nach 
wie vor möchte ich mich nicht mit Hochfrequenztechnik beschäftigen. Es 
wäre sehr schön wenn es für diese Alternative Bibliotheken gäbe. Ich 
muss die verlorene Zeit wieder aufholen.

Ich danke euch für eure Hinweise.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.