Sensor Cozir-GC0022 und serielle Schnittstelle

Hallo,
ich habe hier einen Cozir GC0022 (Datenblatt) mit serieller Schnittstelle.

Einen UNO oder einen Mega nutze ich als FTDI-Adapter. Auf dem Mac oder mit
Win7 nutze ich ein Terminal-Programm.
Setze ich jetzt mit dem Terminal-Programm einen Befehl ab, dann funktioniert
das einwandfrei.

Der Syntax für die Befehle ist folgend aufgebaut:

A 128\r\n

Das ASCII-Zeichen A, Leerstelle, eine dezimal Zahl, CR & LF
So wird ein bestimmter Wert geschrieben.

Ein Wert wird mit:

a\r\n

gelesen. Als Antwort sendet der Sensor:

a 00032\r\n

Die Antworten des Sensor enden immer mit \r\n

Versteht der Sensor einen Befehl nicht, dann sendet er ein ?

Wie gesagt, mit einem Terminal Programm funktioniert das,
nicht mit einem Arduino.

Auf dem Mega möchte ich etwas an den Sensor senden, und über den seriellen
Monitor die Antwort empfangen…

Das funktioniert aber nicht, was wird denn da gesendet und empfangen?
Mit Serial1.print() sende ich doch ASCII?

Es muß doch möglich sein ein “T\r\n” zu senden, und ein “T 00237\r\n” zu
empfangen und auszugeben. Was mache ich denn da nicht richtig?
Gruß und Dank
Andreas

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <stdint.h>
#include "TouchScreen.h"
#include <SD.h>  
#include <Fonts/FreeMonoOblique9pt7b.h>  //#include <Fonts/FreeSerifBold9pt7b.h> 

#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0
#define LCD_RESET A4
#define YP A4  // must be an analog pin, use "An" notation!
#define XM A5  // must be an analog pin, use "An" notation!
#define YM 30   // can be a digital pin
#define XP 31   // can be a digital pin
#define MINPRESSURE 10
#define MAXPRESSURE 1000
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 220);



#define	BLACK   0x0000
#define	BLUE    0x001F
#define	RED     0xF800
#define	GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define TS_MINX 90
#define TS_MAXX 946//900
#define TS_MINY 192//120
#define TS_MAXY 858//940


//*****************************************  Cozir  ***********************************************************


int bufferCount;    // Anzahl der eingelesenen Zeichen
char buffer[80];    // Serial Input-Buffer

void setup() 
{
  pinMode(LcdHb, OUTPUT);
  digitalWrite(LcdHb, HIGH);// Ausgang für LCD-H-Beleuchtung 
   pinMode(LED, OUTPUT);
//*****************************************  Mega seriell  ***********************************************************  
   Serial1.begin(9600); 
   Serial.begin(9600);
   Serial1.println("K 0");
   
   
//   Serial1.println("K 0"); hier wird "K 0\r\n" gesendet, als Anwort bekomme ich ein ?
   
//   Serial1.print("K 0\r\n"); auch hier wird "K 0\r\n" gesendet, als Anwort bekomme ich ein ?
   
//*****************************************  Mega seriell  ende ***********************************************************  
 tft.begin(0x8357);
 tft.reset();
 tft.setRotation(1);
 tft.fillScreen(BLUE);
 tft.setTextColor(WHITE, BLUE);
 tft.setTextSize(2);

}
void loop() 
{
 //*****************************************  Befehl senden ***********************************************
 
  Serial1.println("T"); // hier wird "T\r\n" gesendet, als Anwort bekomme ich falsche Werte
  // die Antwort sollte lauten: T 00237\r\n

    if (Serial1.available()) serialEvent();


}

void serialEvent() {
  
  char ch = Serial1.read();
  buffer[bufferCount] = ch;
  bufferCount++;
  
  if (ch == 13) 
  {
    evalSerialData();
  }
}

void evalSerialData()
{
  //*****************************************  ausgabe serieller Monitor ***********************************************
  Serial.println(buffer);
  // die Ausgabe sollte lauten: T 00237\r\n
  bufferCount = 0;
  
}

Wenn du nicht verstehst was serialEvent() machst dann solltest du es nicht verwenden! Das ist eine Art primitiver Event Handler der automatisch am Ende von loop() aufgerufen wird.

Ansonsten musst du den eingelesenen String auch mit NULL terminieren.

Hallo,
dieses "serialEvent()" wird doch ausgeführt, wenn neue Daten anliegen?

"Ansonsten musst du den eingelesenen String auch mit NULL terminieren."

Statt

if (ch == 13)

dann so?

if (ch == '\n')

Gruß und Dank
Andreas

Am besten verwende fertigen Code dafür:

Oh, und noch ein riesiges Problem (wenn nicht den Haupt-Problem): Du sendest ständig das Kommando! Du darfst das aber erst wieder senden wenn eine Antwort empfangen wurde. Bis die da ist vergehen mehrere Millisekunden.
Also eine kleine Zustandsmaschine bauen die zwischen Kommando Senden und Antwort Empfangen umschalten kann.

Statt

if (ch == 13)

dann so?
if (ch == '\n')

Ja, mal abgesehen von Serenifly's Tips.
('\n' wäre übrigens 10, nicht 13)
Eine neue Antwort hätte bei der vorigen Abfrage mit dem ungelesenen '\n' angefangen.
Nun hast du als letztes Zeichen der Antwort ein CR ( = 0x0D = 13 = '\r' )

Hallo,
"Du sendest ständig das Kommando!"

Genau! Das könnte sein... Wenn "keine" Daten anliegen, dann sendet der das Kommando ja immer wieder.
Der darf aber nur das Kommando einmal senden,
dann auf die Antwort warten
dann die Antwort verarbeiten- und jetzt- erst wieder das Kommando senden.

Was wäre hier denn nun besser?
if (ch == 13)

oder

if (ch == '\n')

Gruß und Dank
Andreas

P.S.
Die Antwort hört ja immer mit \n auf...
dann liest er doch xxxxx\r\n ein- und sollte nach dem \n stoppen?

Abgesehen davon dass LF 10 ist und CR 13 ist es egal. Zeichen sind so oder so Integer. Aber '\n' ist besser lesbar, da man nicht in ASCII denken muss.

Aber am besten verwende meinen Code aus dem Link. Dann musst du nur noch das Senden des Kommandos verzögern. Bei dem Code weißt du dass es geht und der String wird auch terminiert.

Hallo,
ich werde mal beides probieren. Da habt ihr mir schon einmal auf die Sprünge geholfen.
Schönen Dank dafür.
Gruß und Spaß
Andreas

Rein testweise kannst du auch erst mal ein delay(100) o.ä. zwischen Senden und Empfangen setzen. Meine Funktion verwendet eine while-Schleife. Das heißt wenn alle Daten im Eingangspuffer stehen wird alles in einem Rutsch eingelesen. Idealerweise ruft man die Funktion aber ständig auf damit man nebenbei andere Dinge tun kann.

Aber mit delay() siehst du auf die Schnelle ob es grundlegend geht

Hallo,
ablaufen sollte es so...

im Setup das Kommando K x, damit sage ich dem Sensor, was er machen soll
in der Loop dann nur einmal zu bestimmter Zeit das Kommando T, oder H oder Z.
Als Antwort sollte dann kommen:
T 00236
oder
H 00427
oder
Z 00698

T ist Temperatur hier 23,6 Grad
H ist Feuchte hier 42,7 %
Z ist Co2 hier 698 ppm

So stelle ich mir das vor.
Gruß und Spaß
Andreas

Überhaupt kein Problem wenn das vernünftig gemacht ist. Dass das Zeitverhalten der seriellen Schnittstelle vernachlässigt wird ist ein sehr häufiger Fehler. Viele Leute meinen das ginge alles sofort. Es wird was gesendet und sofort eingelesen. Oder alles in einer while-Schleife gemacht obwohl die Daten noch unterwegs sind.

Die Konvertierung in einen Integer ist auch trivial:

int value = atoi(serialBuffer + 1);
  • 1 damit der Buchstabe übersprungen wird. Whitespace Zeichen werden automatisch ignoriert:
    atoi - C++ Reference

Hallo Serenifly,
Deine Routine funktioniert “wie die wilde Wutz”.
Ausgabe auf den seriellen Monitor:

Ausgabe : T 01233 Integer:233 Float:23.3

Ich habe eine sichere Ein/Ausgabe- der Rest ist Geschichte…

Vielen Dank für Deine Hilfe.
Gruß und Spaß
Andreas

const int SERIAL_BUFFER_SIZE = 9;
char serialBuffer[SERIAL_BUFFER_SIZE];
int zlA = 0;

void setup()
{
  Serial.begin(9600);
  Serial1.begin(9600);
  }

void loop()

{
  if (zlA == 0) //Kommando 1mal senden
 {   
  Serial1.println("T");  
    zlA = 1;
  }

  if (readSerial(Serial1) == true)

 if (zlA == 1)
 {
   delay(5000); // wenn verarbeitung fertig, warte
   zlA = 0;
}
}

bool readSerial(Stream& stream)
{
  static byte index;

  while (stream.available())
  {
    char c = stream.read();

    if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)
    {
      serialBuffer[index++] = c;
    }
    else if ((c == '\n' || c == '\r') && index > 0)
    {
      serialBuffer[index] = '\0';
      index = 0;
  // ***********************************************************Ausgabe serieller Monitor**************    
      Serial.print("Ausgabe :");
           Serial.print(serialBuffer);
           
           Serial.print("  Integer:");
          int Wert = atoi(serialBuffer + 5); //serialBuffer auf richtige länge bringen 
        Serial.print(Wert);
        
        float Zahl=Wert;// int in float
        Zahl /=10;
        Serial.print("  Float:");
      Serial.println(Zahl,1);
      return true;
    }
  }
  return false;
}


// Ausgabe : T 01233  Integer:233  Float:23.3

Vermische nicht das Einlesen mit der Verarbeitung. Der Sinn dieser Funktion ist genau das zu trennen. readSerial() liefert true wenn der String komplett da ist. Darauf fragt man ab und macht die Auswertung in loop(). Oder ruft eine extra Funktion auf:

if (readSerial(Serial1) == true)
{
   auswertung();
}

Und wie gesagt +5 ist nicht unbedingt nötig. Führende Leerzeichen und Nullen sind für atoi() ok. ppm könnte ja theoretisch auch mal > 999 sein

Hallo,
ich habe das Prinzip schon verstanden. Zum probieren und Testen bot es sich
"da unten" aber an.

Die roh-Ausgabe für die Temperatur ist:
Leerstelle, T, 01233, \r, \n

bei Feuchte:
Leerstelle, H, 00397, \r, \n

und für Co2:
Leerstelle, Z, 00698, \r, \n

möchte man alle 3 Werte haben sendet man ein Q\r\n

die Ausgabe ist dann:
Leerstelle, T, 01233, Leerstelle, H, 00397, Leerstelle, Z, 00698, \r, \n

Der Sensor läßt sich ziemlich umfangreich konfigurieren. Dafür wird auch
Software gestellt, oder eben über ein Terminal-Programm.
Wenn man ihn dann seinen Bedürfnissen angepaßt hat, dann kann man ihn
auswerten.
Ne´ Menge der Einstellungen lassen sich auch im EEPromm des Sensor speichern.
Bei Spannungsausfall oder anderen Störungen geht dann nichts verloren.

Wie gesagt, über´s Terminal-Programm alles kein Problem.
Mit dem Arduino habe ich aber nichts vernüftiges zur Anzeige bringen können.
Dank Deiner Hilfe ist der Drops aber gelutscht.
Also, vielen Dank dafür.
Gruß und Spaß
Andreas

Hallo Serenifly,

"Und wie gesagt +5 ist nicht unbedingt nötig."

Wenn ich meinen Verstand öfters so scharf nutzen würde- wie Du Deinen, dann wäre wohl einiges einfacher.

"Die roh-Ausgabe für die Temperatur ist: Leerstelle, T, 01233, \r, \n"

Den Wert "01233" brauche ich nicht zu kürzen- einfach 1000 subtrahieren.

01233 minus 1000 gleich 233 gleich 23,3 Grad. Das Leben kann so einfach sein.

Gruß und Spaß
Andreas