Forum: Compiler & IDEs NRF51 programmierung Zeiger, arrays, seltsames Verhalten.


von Wolfgang (Gast)


Lesenswert?

Hallo liebe MC-net Gemeinde, ich habe eine Frage an die C cracks.
Ich arbeite mich in die Programmierung des NRF51422 ( BT4 & ANT+ device 
) ein. Ich habe ein Teil via I2C and den NRF angeschlossen und kann auch 
mit dem reden. Die I2C Routinen verwenden einen Zeiger auf einen Buffer 
um die via I2C gelesenen Daten zu buffern:
1
static uint8_t ldc_p_buffer[2] = {0,0};            // buffer for read back values from LDC
2
ldc_register_address = LDC_DATA_MSB_CH1;

Es werden "transfers" und "transactions" definiert und dann an einen 
Scheduler geschickt, da der Core nebenbei noch die wireless protokolle 
bearbeitet und daher saemtlicher user-code nur scheduled und 
"irgendwann" ausgefuehrt wird. Vermutlich deshalb werden alle Parameter 
auch "static" definiert, der code kann jederzeit durch eine ISR des 
wireless-teils unterbrochen werden:
1
static app_twi_transfer_t const transfers[] =      // transfer definition
2
  {
3
    APP_TWI_WRITE(ldc_device_address, &ldc_register_address, 1, APP_TWI_NO_STOP),
4
    APP_TWI_READ (ldc_device_address, ldc_p_buffer, 2, 0)
5
  }
6
static app_twi_transaction_t const transaction =    // transaction definition
7
  {
8
     .callback            = ldc_twi_callback_func,
9
     .p_user_data         = NULL,
10
     .p_transfers         = transfers,
11
     .number_of_transfers = 2
12
  };

Dann wird der I2C transfer "scheduled":
1
APP_ERROR_CHECK(app_twi_schedule(&m_app_twi, &transaction));    // read the LDC Data MSB

Und jetzt zum Mysterium:
Ich schreibe die zwei Bytes aus dem "ldc_p_buffer" dann in ein unit_16 :
1
ldc_msb = ((uint16_t)ldc_p_buffer[0] << 8)| ldc_p_buffer[1];    // assemble the MSB

Dann gehe ich zur neachsten registeradresse des I2C bauteils...
1
ldc_register_address = ldc_register_address + 1;                // next register address to read the LSB

und schedule den naechsten Transer.
1
APP_ERROR_CHECK(app_twi_schedule(&m_app_twi, &transaction));  // read the LSB. We can modify the register address without creating a new instance as it is accessed by a pointer.
Das geht auch alles, allerdings ueberschreibt der zweite I2C transfer 
die Daten in "ldc_msb" mit den neuen Werten. und DAS verstehe ich gar 
nicht.
Daher: Wenn ich den zweiten I2C transfer nicht schedule, bekomme ich 
die erwarteten 2 Bytes in "ldc_msb". wenn ich den 2-ten Transfer 
schedule bekomme ich diese Daten vom I2C device in "ldc_msb". Wieso ?
Klar, "ldc_p_buffer" wird im I2C transfer als Pointer gesehen und somit 
wird der Inhalt an der Speicherstelle beim 2-ten I2C Transfer 
ueberschrieben. Aber ich weise das ja dann an "ldc_msb" zu. WARUM wird 
der Inhalt von "ldc_msb" ueberschrieben wenn die I2C funktion auf den 
Speicherbereich von "ldc_p_buffer" zugreift ?

Leider ist in der Nordic Softwarewelt alles unglaublich zerpflueckt, 
alles ist hinter mehreren Stufen von macros, #defines und noch mehr 
macros versteckt....

Jeder Tip is willkommen...

Danke und Gruss,
   Wolfgang

von Karl H. (kbuchegg)


Lesenswert?

Wolfgang schrieb:

> Dann wird der I2C transfer "scheduled":
>
1
> APP_ERROR_CHECK(app_twi_schedule(&m_app_twi, &transaction));    // read 
2
> the LDC Data MSB
3
>
>

Du hast aber an dieser Stelle keine Garantie, wann genau die Transaktion 
ausgeführt wird.


> Das geht auch alles, allerdings ueberschreibt der zweite I2C transfer
> die Daten in "ldc_msb" mit den neuen Werten. und DAS verstehe ich gar
> nicht.

Warum nicht?

Wenn du nur 1 Stück Papier hast, auf dem du eine Nachricht aufschreiben 
kannst, dann musst du eben warten, bis dein Gegenüber die Nachricht 
gelesen hast. Radierst du vorher die Nachricht aus und schreibst eine 
neue drauf, dann kriegt dein Gegenüber die erste Nachricht nie zu 
Gesicht.

> der Inhalt von "ldc_msb" ueberschrieben wenn die I2C funktion auf den
> Speicherbereich von "ldc_p_buffer" zugreift ?

Weil ein Scheduler sich einfach nur die Transaktion merkt und sie zur 
Bearbeitung in einen Puffer stellt. Teil deiner Transaktion ist der 
Datenpuffer, in dem du deine Nnutzdaten hast. Diesen Puffer gibt es aber 
nur ein einziges mal bei dir und der Scheduler legt sich offenbar keine 
Kopie davon an. Was ja auch nicht seine Aufgabe ist.

Du wirst also mehrere derartige Puffer brauchen, für die du eine 
Verwaltung benötigst, die jeden derartigen Puffer als belegt markiert, 
bis du vom Scheduler die Nachricht erhältst, dass die Transaktion 
abgeschlossen wurde. Erst dann kannst du diesen Puffer wieder verwenden.

Wenn garantiert ist, dass der Scheduler die Transkationen in genau der 
von dir definierten Reihenfolge abarbeitet (was wohl der Normalfall sein 
wird), dann reicht eine einfache Queue, mit der du deine 
Nachrichtenpuffer verwaltest.

von Wolfgang E. (wolfgang2)


Lesenswert?

Hallo Karl Heinz,

was du schreibst scheint sich auf "ldc_p_buffer" zu beziehen, da erwarte 
ich das auch. Das ist der Buffer fuer die I2C transaktionen und wird 
natuerlich bei jeder Transaktion ueberschrieben. Aber genau darum mach 
ich ja:
1
ldc_msb = ((uint16_t)ldc_p_buffer[0] << 8)| ldc_p_buffer[1];    // assemble the MSB

"ldc_msb" sollte doch eine Kopie des Inhalts von der Speicherstelle sein 
auf die "ldc_p_buffer" zeigt, oder ? Oder genauer, es sollte 
"ldc_p_buffer" byte1 und "Ldc_p_buffer" byte2 beinhalten, und zwar als 
Kopie.
Warum aendert sich dieser Wert dann wenn ich "ldc_p_buffer" 
ueberschreibe ? "ldc_msb" ist ja eben kein Zeiger.

Gruss,
   Wolfgang

von Wolfgang E. (wolfgang2)


Lesenswert?

Karl-Heinz,

meinst du dass erst die zweite Transaktion ausgefuehrt wird und dann 
die erste, so dass ich einfach zum falschen Zeitpunkt "ldc_p_buffer" 
kopiere ?

Beobachtung ist aber dass ich, wenn ich die 2-te Transaktion (nach der 
ersten scheduled) mache, ich NUR die Werte der 2-ten Transaktion in 
"ldc_msb" habe.

Das kann es also nicht sein. Aber trotzdem Danke fuer die Anregung, ev. 
verursacht der Start der 2ten Transaktion ja einen Abbruch der 1-ten 
o.ae. Das wuerde das Verhalten erklaeren.
Auf dem Oszilloskop kann ich allerdings beide Transaktionen sehen und 
dank I2C Bus decoder auch verifizieren dass "ldc_msb" tatsaechlich die 
korrekten Werte enthaelt, nur halt ENTWEDER den von der 1-ten 
Transaktion ODER den von der 2-ten, obwohl es immer nur der von der 
1-ten sein sollte.
Fuer die 2-te gibt es "ldc_lsb".

So sieht das Ganze komplett aus, mit zwei getrennten "Ergebnisvariablen" 
fuer die beiden Transfers:
1
/////////// do the LDC communication
2
void ldc_read_all(void){
3
  ldc_register_address = LDC_DATA_MSB_CH1;
4
  uint32_t LdcRawData = 0;
5
  const uint8_t ldc_filter_length = 5;      // we filter over 4 values. This does cut off ldc_filter_length-1 bits from the result. Why ?
6
  static uint8_t ldc_p_buffer[2] = {0,0};            // buffer for read back values from LDC
7
  static uint16_t ldc_msb = 0;
8
  static uint16_t ldc_lsb = 0;
9
  
10
  static app_twi_transfer_t const transfers[] =      // transfer definition
11
  {
12
    APP_TWI_WRITE(ldc_device_address, &ldc_register_address, 1, APP_TWI_NO_STOP),
13
    APP_TWI_READ (ldc_device_address, ldc_p_buffer, 2, 0)
14
  };
15
  static app_twi_transaction_t const transaction =    // transaction definition
16
  {
17
     .callback            = ldc_twi_callback_func,
18
     .p_user_data         = NULL,
19
     .p_transfers         = transfers,
20
     .number_of_transfers = 2
21
  };
22
  APP_ERROR_CHECK(app_twi_schedule(&m_app_twi, &transaction));    // read the LDC Data MSB
23
  ldc_msb = ((uint16_t)ldc_p_buffer[0] << 8)| ldc_p_buffer[1];    // assemble the MSB
24
  
25
  ldc_register_address = ldc_register_address + 1;                // next register address to read the LSB
26
  
27
  APP_ERROR_CHECK(app_twi_schedule(&m_app_twi, &transaction));  // read the LSB. We can modify the register address without creating a new instance as it is accessed by a pointer.
28
ldc_lsb = ((uint16_t)ldc_p_buffer[0] << 8) | ldc_p_buffer[1];  // assemble the LSB into the long variable
29
  
30
  LdcRawData = (ldc_msb<<16) | ldc_lsb;  
31
  LdcAverage[0] = LdcRawData;    // debug
32
  //LdcAverage[0] = ((ldc_filter_length - 1)*LdcAverage[0]+LdcRawData)/ldc_filter_length;    // channel 0 average
33
}

Wenn ich das zweite "app_twi_schedule" auskommentiere bekomme ich meine 
erwarteten 2 bytes in "ldc_msb", wenn ich es drin lasse bekomme ich 
dessen 2 bytes in "ldc_msb" und dazu noch in "ldc_lsb" wo sie auch hin 
gehoeren...

Gruss,
   Wolfgang

von Falk B. (falk)


Lesenswert?

Hmm, ich hab nicht alles im Detail gelesen, aber ist das nicht ein wenig 
Overkill für so ein bischen LCD? Und vor allem wozu muss man da was 
zurücklesen? Das braucht man ja nun weiß Gott SEHR selten, meist ist es 
deutlich einfacher und schneller, einfach eine Kopie im lokalen RAM zu 
halten und dort nachsehen, was zuletzt geschrieben wurde.

Callback-Handler und PiPaPo um ZWEI Bytes zurückzulesen? Neee!

von Wolfgang E. (wolfgang2)


Lesenswert?

Hallo Falk,

Nein kein LCD ! Ein LDC ! Kein Schreibfehler sondern ein nettes Teil 
von TI:
http://www.ti.com/lit/ds/symlink/ldc1614.pdf

Gruss,
    Wolfgang

von Karl H. (kbuchegg)


Lesenswert?

Wolfgang E. schrieb:

>   APP_ERROR_CHECK(app_twi_schedule(&m_app_twi, &transaction));    //
> read the LDC Data MSB
>   ldc_msb = ((uint16_t)ldc_p_buffer[0] << 8)| ldc_p_buffer[1];    //
> assemble the MSB

Moment mal.
Wie verhält sich denn jetzt der Scheduler?
Arbeitet er die Transaktion gleich ab, oder passiert das irgendwann.
Im Eröffnungsposting jast du noch gesagt 'irgendwann'.

Wann genau kehrt die Funtkion app_twi_schedule zum Aufrufer zurück? 
Nachdem die Transaktion durchgeführt wurde oder nachdem die die 
Transaktion zur Bearbeitung entgegen genommen hat?

Wenn du hier sowieso zwingend darauf wartest, dass das Ergebnis 
vorliegen muss, dann brauchst du doch auch keinen Scheduler. IMHO.

von Falk B. (falk)


Lesenswert?

@ Wolfgang Ebersbach (wolfgang2)

>Nein kein LCD ! Ein LDC ! Kein Schreibfehler sondern ein nettes Teil

Ohh, Freudsche Fehlleistung ;-)

Trotzdem erscheint mir der Aufwand zum Auslesen von ZWEI Bytes ein wenig 
hoch.

von Wolfgang E. (wolfgang2)


Lesenswert?

Na ja, ich weiss nicht WANN das stattfindet.
Den Scheduler moechte ich ja nicht, er wird mir aufgezwungen. Wie 
gesagt, der M0 core muss noch die wireless protokoll Geschichten 
abarbeiten und das macht er mit Priority. Daher auch der Scheduler, es 
ist verboten einfach zu einem beliebigen Zeitpunkt eine Operation zu 
starten die den Core blockiert. Man muss alles ueber Scheduler 
"anfragen".

Die Frage reduziert sich also zu: Wenn ich zwei Tasks schedule, werden 
die dann auch in Reihenfolge abgearbeitet ?
Kein Ahnung. Muss ich rausfinden...

Trotzdem:
* Ich schedule einen Task.
* Ich lese sofort danach ( das koennte das Problem sein..) zwei Bytes 
aus einem Buffer der fuer jeden schedulten Task wiederverwendet wird 
("ldc_p_buffer") und schreibe sie in die Variable "ldc_msb".
* Ich schedule den naechsten Task
* Ich lese (sofort danach) zwei Bytes aus dem o.g. immer gleichen Buffer 
und schreibe sie in "ldc_lsb"
* Ich habe in "ldc_msb" UND in "ldc_lsb" die zwei bytes des 2-ten Tasks 
stehen.

Wenn ich den 2-ten Task nicht schedule, habe ich in "ldc_msb" die 
erwarteten 2 Bytes des 1-ten Tasks stehen und in "ldc_lsb" nichts (0).

Ich glaube inzwischen dass ich da ein timing-problem habe. Das das Ganze 
ja endlos wiederholt wird, kann es schon sein dass ich im Buffer einen 
Wert habe der nicht von diesem task kommt sondern von einem 
"aelteren".

Gruss,
   Wolfgang

von Falk B. (falk)


Lesenswert?

@Wolfgang Ebersbach (wolfgang2)

>Die Frage reduziert sich also zu: Wenn ich zwei Tasks schedule,

 . . . rollen sich nicht nur bei Germanisten die Fußnägel hoch!

>* Ich schedule einen Task.

du schedulst
er schedult
wir schedulen
ihr schedult
sie schedulen

(Goethe hilf!)

von Wolfgang E. (wolfgang2)


Lesenswert?

Goethe ist Kuer, sobald die Funktion das tut was sie soll.
Kultur kann man sich nur leisten wenn man was zu Essen hat... Genau wie 
Umlaute :-)

Soll ich "eintakten" schreiben ? So wie Kibibyte ? Oder "drahtlose 
kommunikationsvorrichtung" ?

Ich hab jetzt versucht in der Callback routine ein Flag zu setzen und 
mit dem Lesen des Buffers auf das Flag zu warten, mit dem Ergebnis dass 
ich im Buffer ( Oha, "Zwischenspeicher" ) jetzt immer eine "0" finde...

Gruss,
    Goethe...

von Falk B. (falk)


Lesenswert?

@ Wolfgang Ebersbach (wolfgang2)

>Kultur kann man sich nur leisten wenn man was zu Essen hat... Genau wie
>Umlaute :-)

Goethe wurde noch nie mit Umlaut geschrieben.

https://sprachlupe.wordpress.com/2012/06/15/goethe-und-die-rechtschreibreform/

von Wolfgang E. (wolfgang2)


Lesenswert?

Den meinte ich auch nicht sondern meine Beitraege die wegen englischer 
Tastatur (und meiner zu-faulheit ALT-xxx zu nehmen) keine Umlaute 
beinhalten.

von Fritz R. (fritz)


Lesenswert?

Da hast du aber etwas falsch verstanden. Der Scheduler wird einen 
keineswegs aufgezwungen, das geht auch sehr wohl ohne.

Dein eigentliches Problem ist aber, dass du die Funktionsweise des 
Schedulers nicht verstanden hast. Der Scheduler ist dazu da, um z.B. 
längere Berechnungen von einen Interrupt Kontext in den Main Kontext zu 
verlagern. Ausgeführt werden diese Funktionen sobald app_sched_execute() 
aufgerufen wird (was normalerweise irgendwo in der main Schleife ist).

Du übergibst dem Scheduler jetzt eine Funktion, liest anschließend 
sofort den Puffer auf, übergibst die nächste Funktion und liest gleich 
noch mal den Puffer aus. Die beiden Funktionen wurden da aber noch gar 
nicht ausgeführt, deswegen liest du in beiden Fällen die Daten des 
zuletzt statt gefundenen Transfers aus, und das ist eben der zweite 
Transfer des vorhergehenden Durchgangs.
Du darfst die Daten natürlich erst dann auslesen, wenn der Transfer 
statt gefunden hat, also in ldc_twi_callback_func.

von Wolfgang E. (wolfgang2)


Lesenswert?

Hallo Fritz,

doch, das mit dem Scheduler ist mir schon klar, auch dass er noetig ist 
um deterministisches Verhalten fuer die wirelss - protokollebene 
herzustellen und sich daher die user programme unterzuordnen haben.

Nur: Der beruehmte Schlauch ! Auf dem stand ich halt mit all meinem 
Gewicht.
Nachdem ich jetzt korrekt sowohl das Lesen des Buffers als auch das 
Hochzaehlen der Registeradresse !! in der callback routine mache, also 
wenn der "schedulte (1)" I2C transfer auch wirklich fertig ist, geht 
alles wie erwartet.

(1) : Extra fuer Falk !

Danke dafuer dass Ihr mich von dem Schlach geschubst habt :-)

Wolfgang

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.