Bestand op SD kaart draadloos versturen met RFM73

Hallo allemaal,

Voor een datalogger welke onderwater de temperatuur meet, ben ik bezig met het uitwerken van de mogelijkheid om de gelogde data welke op een SD-kaartje staat, draadloos te versturen.
Op dit moment werkt de logger goed en meet een aantal sensoren elk uur en schrijft deze data weg naar een SD kaartje als een komma gescheiden rij (.CSV).

Ik heb een test gedaan met een 2Kbps 433 zender en ontvanger maar dat ging erg langzaam. Nu ben ik overgestapt naar 2.4Ghz en dan gaat de transfer rate aardig omhoog.

De bedoeling is dat ik het databestand (.CSV) lees en dan verstuur middels de RFM73.
Op het forum heb ik gezocht naar de verschillende mogelijkheden maar ik kom er toch niet helemaal uit.

Voor de goede orde, ik gebruik een Uno of Pro Mini met een SD-kaart breakout via SPI en een RFM73 module die ook aan de SPI bus vast zit.

Het log gedeelte en het schrijven van het bestand werkt allemaal, nu het andere deel.
Mijn idee is als volgt;

  • Maak SS pin SD kaart laag en SS en CE RFM73 hoog
  • Open het bestand
  • Lees 28 bytes in een array
  • Maak SS pin SDkaart hoog, maak SS en CE pin van RFM73 laag
  • verstuur deze array
  • wacht op acknowledgement van de RFM73 (gaat automatisch voor zover ik gevonden heb)
  • Leeg de array
  • Maak SS pin RFM73 hoog, maak SS pin SDkaart laag
  • Lees de volgende 28 bytes in....

Mijn vragen zijn;

  • Is de SPI library zo opgezet dat het onthoudt wat de laatste locatie is geweest waar geschreven/gelezen is? Ik weet dat bij het wegschrijven van de data, dit correct gebeurd maar dit is waarschijnlijk omdat er dan een Append actie wordt uitgevoerd denk ik.

  • De array inlezen kan in veel verschillende soorten en maten, kan ik dit gewoon als Byte doen of is Char beter? Een array van één rij van 28 bytes is volgens mij voldoende maar ik kom niet uit welke format ik zou moeten gebruiken

  • Ik kan een bestand per lijn inlezen (inlezen totdat \n gezien wordt), Indien deze array groter is als 28 bytes, kan ik deze dan in stukken van 28 bytes versturen?

Aan de ontvangende kant wordt een groot databestand steeds aangevuld, afhankelijk van de antwoorden op bovenstaande vragen moet ik uitzoeken hoe ik de data zo kan wegschrijven dat het een leesbaar geheel wordt :).

Alvast bedankt

De SPI library onthoudt niets. De SD library heeft een buffer (ik meen 256 bytes waar jou dat in gezet wordt. als je de flush() functie uitvoert direct na het schrijven wordt de data aangevuld op de SD kaart. Anders pas bij het sluiten. of als de writebuffer vol is.

Byte of char maakt verder niets uit. In feite geef je het adres van het eerste byte door en de lengte en klaar.

Wat je nog wel zou kunnen overwegen (maakt het wel een stuk complexer) is om een CRC (cyclic redunancy check) te berekenen per verzonden record en deze ook mee te sturen. Aan de ontvangende kant doe je hetzelfde. Als blijkt dat de ontvangende CRC afwijkt met de verzonden CRC, heb je bitjes verloren of erbij gekregen. Dan zou je een bericht terug kunnen sturen dat de data niet goed is en opnieuw verzonden dient te worden.

Hoi Leroy,

Ik dacht dat een bestand op SD altijd sequentieel uitgelezen wordt en dat de SD-logica zelf bijhoudt welke byte laatst gelezen is.
Is het dus niet zo dat als je begint met lezen je het bestand opent en gaat lezen, (SD kaart houdt teller bij hoever je bent)
Dan maak je de SS van SD kaart laag en doe de RFM73 zaken.
Dan maak je SS van SD weer hoog en probeer gewoon verder te lezen, dus zonder het bestand opnieuw te openen. Naar mijn gevoel weet de SD-kaart logica dan waar je was gebleven en spuit de data die je nodig hebt verder uit.
Je blijft net zolang data opvragen totdat je het EndOfFile symbool (/n/r dacht ik) hebt ontvangen en je bent klaar.

Maar ik ben geen expert op het gebied van SD kaarten en databestanden , dus ik kan het mis hebben.

edit: woordje "niet" doorgestreept, dit was een verschrijving

Bedankt voor de antwoorden tot zover. De library van de RFM73, Radiohead, maakt standaard gebruik van de CRC, ACK en auto retransmission die de RFM73 rijk is, daar hoef ik geen werk voor te verzetten, dit staat standaard aan.

Ik ga me wat meer verdiepen in de array en of en hoe de SD library omgaat met het bijhouden waar het is in een bestand.

Leroy2007:
Bedankt voor de antwoorden tot zover. De library van de RFM73, Radiohead, maakt standaard gebruik van de CRC, ACK en auto retransmission die de RFM73 rijk is, daar hoef ik geen werk voor te verzetten, dit staat standaard aan.

Ik ga me wat meer verdiepen in de array en of en hoe de SD library omgaat met het bijhouden waar het is in een bestand.

Een Array kun je gewoon zien als een zooi bytes achter elkaar. Ik weet niet wat de waardes zijn (int, float oid) maar je kan er altijd een union van maken en dan een buffer byters er parallel overheen

Leroy2007:
Ik ga me wat meer verdiepen in de array en of en hoe de SD library omgaat met het bijhouden waar het is in een bestand.

Je hebt gelijk dat de SD library bepaalde dingen kan bijhouden d.m.v. seek, position en size. Maar voorbeelden daarvan zijn dun gezaaid.

Jouw 'case' doet me denken aan een webserver bestaand uit ethernetkaart met SD slot.
De ethernetkaart gebruikt SPI en de SD gebruikt SPI.
Data wordt gelezen via SD.h library en wordt tegelijkertijd verstuurd via Ethernet.h library.
De S(lave)S(elect) sturing wordt volgens mij volledig door de betreffende libraries afgehandeld.
Kijk maar naar dit stukje programma uit een webserver programma:

                    {  // web page request
                        // send web page
                        webFile = SD.open("index.htm");        // open web page file
                        if (webFile) {
                            while(webFile.available()) {
                                client.write(webFile.read()); // send web page to client
                            }
                            webFile.close();
                        }
                    }

Data wordt byte voor byte van SD gelezen en direct via SPI naar de Ethernet client gestuurd.
Dus moet het mogelijk zijn, in een loop 28 bytes van SD te lezen en direct naar de RFM73 te sturen, wachten op OK van RFM73 en opnieuw 28 bytes lezen en versturen.

Tenzij de RFM73 library dit niet ondersteunt.

Maar zoals ik al eerder zij: ik ben geen expert op het gebied van SD kaarten en databestanden , dus ik kan het helemaal mis hebben.

@nicoverduin, de array bestaat uit cijfers, letters, puntkommas, dubbele punten en kommas. Zou een Char array dit kunnen doen? Ik moet zeggen dat een union nog niet iets is waar ik eerder over gehoord heb..

@cartoonist Heh, interressant, ik ga vanavond dat eens proberen.

voorbeeld:

union  {
   struct {
      uint8_t code;
      char[10] boodschap;
     char   teken;
   };
   char buffer[12];
} verzendInfo;

Buffer ligt over de struct heen. Derhalve kun je de individuele parameters benaderen alsook in een keer de hele buffer.

Ik heb net mezelf ingelezen in char en ik denk dat ik hert met een char array af kan. Vanavond proberen dus.

Waar je in ieder geval voor op moet passen is dat je geen "speciale" tekens in de strings zet. Zeker als je aan de ontvangende kant bijvoorbeeld test op NL of CR. Je zal niet de eerste zijn die allerlei binaire waardes overpomt en dan stukken mist omdat er binair het getal 0x0A of 0x0D tussen zat (= int 10 en int 13). Zelf bouw ik een struct op met alle variabelen, en bereken de CRC. Vervolgens wordt het hele ding omgezet in een array met alleen Ascii tekens waarbij elke 4bits wordt omgezet in een Ascii teken die ik in een buffer zet. Aan de ontvangende kant decodeer ik alles weer en heb alle variabelen keurig gevuld.
De CRC gebruik ik dan om te valideren of alles goed is overgekomen. Geen 100% maar wel vrij effectief.

De string van gegevens bevat alleen maar 123.. ; : , . en ABC.. geen underscores of iets wat niet in de ASCII tabel staat.

Het geval NL CR was ik ook tegen gekomen maar hoe dat op te lossen, dat werk ik nog uit :). Ik ga de Radiohead library proberen te gebruiken met één van de managers waardoor CRC, ACK en auto retransmission al voorzien zijn.

De datalogger doet nu niets anders dan elk uur de naam van de boei (het is een onderwater systeem) wegschrijven, gevolgd door datum, tijd, temperaturen (2 cijfers punt 2 cijfers) en voltage (cijfer punt 2 cijfers) en dit alles gescheiden middels een ; en schrijft dit weg op een SDkaart, steeds hetzelfde bestand.

Goed, na een avondje experimenteren nu even rust. Het inlezen gaat me niet goed af. Het werken met een array al evenmin haha. Morgen vrolijk verder.

Wat in ieder geval compilede was deze:

// Test-data.ino
#include <SdFat.h>
#include <SPI.h>
//#include <RH_NRF24.h>

//RH_NRF24 nrf24(8, 7);

SdFat sd;
SdFile myFile;

const int chipSelect = 10;

void setup() {
Serial.begin(9600);
  while (!Serial) 
    ; // wait for serial port to connect. Needed for Leonardo only
  //if (!nrf24.init())
  //  Serial.println("init failed");
  // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm
  //if (!nrf24.setChannel(1))
  //  Serial.println("setChannel failed");
  //if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm))
  //  Serial.println("setRF failed");    
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();

  // open the file for reading:
  if (!myFile.open("datatest.csv", O_READ)) {
    sd.errorHalt("File read failed!");
	}

char data[28];

  int i;
  for (i = 0; i < 28; i = i + 1) {
	(myFile.read());
        Serial.println(data[i]);
}
}

void loop() {

}

Vergeet even de Nrf24 library en lines, ik liep wat te hard van stapel.

De seriale output gaf helaas niets leesbaars, gewoon bagger eigenlijk. Ook bij herhaaldelijk inlezen ook niet dezelfde bagger, wellicht dat de file pointer van de SD kaart wel werkt?

Een andere waar ik morgen meer mee ga doen is deze:

#include <SdFat.h>
const int chipSelect = 10;
int Stringdata [24];
int index = 0;

void setup() {
  Serial.begin(9600);
  Serial.print("Initializing SD Card....");
if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();

if (!myFile.open("datatest.csv", O_READ)) {
    Serial.println("Card failed, or not Present");
    return;
  }
  Serial.println("Card Initialized");
 
  if( !myFile.open("datatest.csv", O_READ)) { 
    while((data = myFile.read()) >=0 {
      
Serial.write(dataFile.read());
      String3_5[index] = Serial.read();
      index++;
      String3_5[index] = '\0';
    }
    dataFile.close();
  } else {
    Serial.println("File does not exist or named wrong");
  }
}

void loop() {
}

Maar deze is nog werk in uitvoering..

klopt dat je niets leest. Je doet een read maar wat je gelezen hebt wordt nergens in opgeslagen. Daarnaast doe je eerder een lees en zet je alles in het eerste byte. Inderdaad tijd voor een tukkie :slight_smile:

Leroy'

heb je eerste test progje ietsje gewijzigd, en bij mij werkt het prima
Ik gebruik welliswaar een ethernet WS5100 shield waarop de SD pin 4 als SS heeft.
Pin 10 is de ethernet die ik gedisabled heb.
heb ook een andere file naam gekozen, index15.htm een bestand wat al als html text op de SD staat.

// Test-data.ino
#include <SdFat.h>
#include <SPI.h>
//#include <RH_NRF24.h>

//RH_NRF24 nrf24(8, 7);

SdFat sd;  // or SD
SdFile myFile;

const int chipSelect = 4;  /

void setup() {
Serial.begin(9600);
  pinMode (10,OUTPUT); digitalWrite(10,HIGH);  // disable WS5100
  
  while (!Serial) 
    ; // wait for serial port to connect. Needed for Leonardo only
  //if (!nrf24.init())
  //  Serial.println("init failed");
  // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm
  //if (!nrf24.setChannel(1))
  //  Serial.println("setChannel failed");
  //if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm))
  //  Serial.println("setRF failed");    
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();
  
  // open the file for reading:
  if (!myFile.open("index15.htm", O_READ)) {
    sd.errorHalt("File read failed!");
	}

char data[28];

  int i;
  for (i = 0; i <= 28; i = i + 1) {
	//(myFile.read());
        Serial.print(char(myFile.read()));  // omgezet in char leesbare text te zien, werkt voor cijfers, letters en leestekens.
  }
}

void loop()
{}

edit toegevoegd:
Zoals Nico al zegt "klopt dat je niets leest. Je doet een read maar wat je gelezen hebt wordt nergens in opgeslagen."

Je kan wel wat je leest gelijk printen, maar wel eerst in leesbare karakters omzetten d.m.v. char() :

Serial.print(char(myFile.read())); // char werkt goed bij cijfers, letters en leestekens

Als je vooraf de lengte van je bestand bepaalt kun je berekenen hoe vaak je die for-loop moet doorlopen.
In deze volgorde:

  1. bepaal lengte van de file en bereken hoe vaak de for-loop doorlopen wordt.
  2. for loop (fileLengte/28)
  3. vul buffer met die 28 tekens.
  4. verzend de buffer.
  5. maak de buffer leeg
  6. als de for loop doorlopen is zit je waarschijnlijk nog met een rest ongelijk aan 28.
  7. vul een buffer met de rest en verzend die apart als een kortere buffer.

Je kunt dan na een uur gewoon verder gaan om je bestand aan de ontvangende kant met append aan te vullen.

Moet niet zo moeilijk zijn lijkt me.

p.s. Interessant project, ik heb een paar NRF905 in bestelling en wil iets soortgelijks daarmee gaan doen.

Ik heb nog eens zitten puzzelen hoe het zou moeten kunnen werken
Eerst een buffer[28] gemaakt en dat gevuld maar dat had als ongewenst effekt dat en een 29ste karakter aangeplakt werd. Het hele bestand werd daardoor om de 28 tekens vervuild met een null-karakter , \0 , asci teken '0'
Dus eens geprobeerd met de data type String en dat gaat beter. Het hele bestand wordt helemaal hetzelfde geprint als het origineel op SD-kaart.

// Test-data.ino
#include <SdFat.h>
#include <SPI.h>
//#include <RH_NRF24.h>

//RH_NRF24 nrf24(8, 7);

SdFat sd;  // or SD
SdFile myFile;

const int chipSelect = 4;  //

void setup() {
Serial.begin(9600);
  pinMode (10,OUTPUT); digitalWrite(10,HIGH);  // disable WS5100
  
  while (!Serial) 
    ; // wait for serial port to connect. Needed for Leonardo only
  //if (!nrf24.init())
  //  Serial.println("init failed");
  // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm
  //if (!nrf24.setChannel(1))
  //  Serial.println("setChannel failed");
  //if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm))
  //  Serial.println("setRF failed");    
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();
  
  // open the file for reading:
  if (!myFile.open("index16.htm", O_READ)) {
    sd.errorHalt("File read failed!");
  }

long lengte = myFile.fileSize();
long rec; //als filelengte klein is mogen rec en records als 'int' gedeclareerd worden
long records=lengte/28;          // aantal packets van 28 bytes  dat verstuurd wordt
int rest = lengte-(records*28);  // zit in laatste packet
int i;                           // 
//Serial.println(lengte);
//Serial.println(records);
//Serial.println(rest);
char c;
String data = "";  // char buffer[28]  werkt niet goed 

  for (rec = 0; rec < records; rec++){
      for (i = 0; i < 28; i++) {
      	 c = myFile.read();
          //Serial.print(c);     // of print karakter voor karakter , of print String data
          data = data + c;       // voeg c toe aan string totdat die 28 char's heeft
         // hier tussen routine verstuur data met RFM73 
       }
       Serial.print(data);       // packet van 28 bytes
       data = "";                // maak string null
       }
       for (i = 0; i < rest; i++){
            c = myFile.read();
            //Serial.print(c);     // of print karakter voor karakter , of print String data
            data = data + c;       
            // routine verstuur data met RFM73 
       }
    Serial.print(data);  // packet van rest bytes
    data = "";
    myFile.close();
}

void loop()
{
}

Ik zou oppassen met het constant veranderen van de grootte van String class. Grote kans dat je door fragmentatie door je RAM geheugen bent. De Arduino kent geen garbage collection.

Daarnaast kun je voor het schrijven gewoon gebruikmaken van Serial.write(). Dan heb je ook geen String class nodig.

Ook kun je gerust de buffer 29 bytes maken,. inlezen van 28 tekens en op buffer[28] (=29ste byte) zet je een \0 teken.
En dan Serial,print. Voor het laatste record die evt. korter is, hoeft het ook geen probleem te zijn.
vb:

char   buffer[29];     // lees buffer
int8_t c = myFile.read();   // lees eerste teken
uint8_t i = 0;         // buffer index op 0
while (c != -1) {     // zolang het geen eof is
   buffer[i] = c;      // teken in de verzendbuffer
   i++;
   if (i == 28) {  
      //
      // buffertje is vol
      //
      buffer[i] = '\0';
      //
      // schrijf buffer weg
      //
      Serial.print(buffer);
      // 
      // en start opnieuw
      //
      i = 0;
   }
   //
   // volgende lees
   //
   c = myFile.read();
}
//
// eventueel laatste stukje wegschrijven
//
if (i != 0) {
   //
   // er staat nog een rest stukje in de buffer
   //
   buffer[i] = '\0';
   Serial.print(buffer);
}

Daar zat mijn fout, ik moest \0 achteraan de buffer plakken. Heb het uitgeprobeerd met buffer en nu ben ik die gekke tekens om de 28 karakters kwijt.

Omdat ik met buffer[28] zo snel niet voor elkaar kreeg had ik mijn toevlucht tot de String class genomen.
Omdat je me er op attent maakte heb ik wat gegoogle'd en zie dat dit geen goed idee is geweest. De String wordt verdacht van het veroorzaken van willekeurige crashes en zolang er geen mogelijkheid is het beperkte RAM te defragmenteren is het sterk af te raden om de String class te gebruiken zoals ik hem in mijn voorbeeld gebruikte.

Ik blijf hier steeds wat bijleren.

Goed om te weten. Ik zou gisteravond gaan proberen/testen maar niet aan toe gekomen. Vanavond wordt waarschijnlijk ook niets dus vrijdagavond maar weer proberen.

Bedankt voor de input tot zover, heb aardig wat bijgeleerd!

Zo, vandaag weer wat verder met programmeren. De sketch zoals door jullie aangedragen werkt, mijn hartelijke dank daarvoor.

Nu probeer ik i.p.v. de serial print de array aan te bieden aan de zender. Ik struikel hierbij over de volgende foutmelding;

sketch_nov18a.ino: In function 'void setup()':
sketch_nov18a:45: error: invalid conversion from 'char*' to 'const uint8_t*'
sketch_nov18a:45: error: initializing argument 1 of 'virtual bool RH_NRF24::send(const uint8_t*, uint8_t)'
sketch_nov18a:68: error: invalid conversion from 'char*' to 'const uint8_t*'
sketch_nov18a:68: error: initializing argument 1 of 'virtual bool RH_NRF24::send(const uint8_t*, uint8_t)'

Is dit omdat er een char buffer is aangemaakt en de library van de nrf24 hier iets anders verwacht of is het dat in mijn sketch, zie onderstaand, ik nrf24.send(buffer, sizeof(buffer)); //zenden van buffer aanroep en hier de verkeerde variabele declareer?

Ik heb geprobeerd om te kijken of ik wellicht "c" kan versturen als in nrf24.send(c, sizeof(c)); //zenden van buffer maar ook hier dezelfde foutmelding wat me doet geloven dat het probleem meer bovenin de sketch zit..

// Test-data.ino
#include <SdFat.h>
#include <SPI.h>
#include <RH_NRF24.h>

RH_NRF24 nrf24(8, 7);

SdFat sd;
SdFile myFile;

const int chipSelect = 10;

void setup() {
Serial.begin(57600);
  while (!Serial) 
    ; // wait for serial port to connect. Needed for Leonardo only
  if (!nrf24.init())
  Serial.println("init failed");
  // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm
  if (!nrf24.setChannel(1))
  Serial.println("setChannel failed");
  if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm))
  Serial.println("setRF failed");    
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();

  // open the file for reading:
  if (!myFile.open("datatest.csv", O_READ)) {
    sd.errorHalt("File read failed!");
	}

char   buffer[29];     // lees buffer
int8_t c = myFile.read();   // lees eerste teken
uint8_t i = 0;         // buffer index op 0
while (c != -1) {     // zolang het geen eof is
   buffer[i] = c;      // teken in de verzendbuffer
   i++;
   if (i == 28) {  
      //
      // buffertje is vol
      //
      buffer[i] = '\0';
      //
      // schrijf buffer weg
      //
      nrf24.send(buffer, sizeof(buffer)); //zenden van buffer
      //Serial.print(buffer); nu eerst testen met zender
      nrf24.waitPacketSent(); //wachten totdat de zender klaar is
      // Now wait for a reply
      uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN]; //vaststellen lengte buffer
      uint8_t len = sizeof(buf);//meesturen lengte van de buffer
      // en start opnieuw
      //
      i = 0;
   }
   //
   // volgende lees
   //
   c = myFile.read();
}
//
// eventueel laatste stukje wegschrijven
//
if (i != 0) {
   //
   // er staat nog een rest stukje in de buffer
   //
   buffer[i] = '\0';
   nrf24.send(buffer, sizeof(buffer)); //zenden van buffer
   /Serial.print(buffer);
}
}
void loop() {

}

je geeft een char* aan een functie die (const uint8_t*) verwacht.
Je kan best expliciete casts toevoegen.
nrf24.send((const uint8_t*)buffer, (uint8_t)sizeof(buffer));

Ik verwijs naar deze post over het hoe en waarom Tijd instellen dmv drukknoppen arduino uno klok - #31 by Jantje - Nederlands - Arduino Forum

Met vriendelijke groet
Jantje