Arduino Uno - Nextion Display - Datenübergabe aus CAN an Display

Hallo in die Runde,
ich arbeite gerade daran Werte aus einem Motorsteuergerät Mithilfe eines Arduino Uno mit einem Seeed Studio CAN-BUS Shield V2 an ein Nextion Display zu übergeben.
Die Werte werden von Motorsteuergerät mittels CAN Broadcasting zur Verfügung gestellt.
Dabei dient dieses Projekt als Grundlage:

Ich kann das soweit beliebig anpassen solange es nur darum geht Werte in dem Display anzuzeigen.

Die Nextion Display Software bietet nun auch die Möglichkeit Fortschrittsbalken (progress bar) o.ä. anzuzeigen.
Leider komme ich da nicht weiter. Das Display benötigt dazu ganzzahlige Werte (Values) um so eine progress bar entsprechend anzuzeigen.
Wenn ich jetzt z.B. den Wert TPS als Wert anzeigen lassen will, klappt das auch, nur wenn ich damit die Progress Bar "füttern" möchte funktioniert das nicht.
Ich gehe davon aus, dass das ein grundsätzliches Zahlenformat Problem ist.
Hier ein Stück Code

    case 1523:
      tps_g.val(           ntohs(*(uint16_t *) &rxBuf[0]));// TPS offset 0 int16_t 0.1pct
      //tps_b.val(           ntohs(*(uint16_t *) &rxBuf[0]));
      {int16_t tmp_tps;
      tmp_tps=           ((uint16_t)rxBuf[0]);
      tps_b.val(map(tmp_tps, 0, 1000, 0, 100));
      }

tps_g.val ist der angezeigte Zahlenwert (funktioniert)
tps_b.val sollte für die progress bar sein. (funktioniert so nicht)

Vielleicht kann mir jemand den "schubs" in die richtige Richtung geben.

Vielen Dank im Voraus.

Gruß, Frank

Kennst Du schon dieses Tutorial?

Gruß Tommy

Hier ein Stück Code

Deiner Problem-Beschreibung fehlt auf jeden Fall die Definition von rxBuf.

Wenn rxBuf (geraten) ein Byte-Array ist, hast du in rxBuf[0] höchstens Werte von 0 .. 255 drin, die die map Funktion auf 0 ..25 runterrechnet.

Die Funktion ntohs tauscht zwei bytes (oder macht nichts), je nach byte order auf deinem System und auf der Leitung.
Auf jeden Fall ist der cast auf ein uint16_t etwas anderes als der cast einer byte - Adresse auf einen uint16_t*, um dann dessen Wert zu holen.

Du solltest auch Auswertung der CAN-Nachricht und Ansteuern des nextion scrollbars voneinander trennen.
Auch wenn du natürlich beides in den Griff kriegen willst.

#include <mcp_can.h>
#include <SPI.h>
#include <SoftwareSerial.h>
#include "nextion.cpp"
#include "tick.cpp"

#define PIN_LCD_RX 5
#define PIN_LCD_TX 4
#define PIN_CAN_CS 9                  // chip select
#define PIN_CAN_INT 2                 // interrupt
//#define DEBUG_CAN_ID 1521           // If defined, dump frames sent to this CAN id
#define HOUSEKEEPING_INTERVAL_MS 50  // How frequently to run watchdog, display refresh, etc
#define HOUSEKEEPING_PHASE_MS 100     // When to start first housekeeping run

#define ADC_PAGE_1_INTERVAL_MS 50     // How frequently to transmit ADC Page 1 data
#define ADC_PAGE_1_PHASE_MS 15        // When to start first ADC Page 1 transmission
#define ADC_PAGE_1_ID 0x20            // Destination CAN ID for Page 1

#define ADC_PAGE_2_INTERVAL_MS 50     // How frequently to transmit ADC Page 2 data
#define ADC_PAGE_2_PHASE_MS 25        // When to start first ADC Page 2 transmission
#define ADC_PAGE_2_ID 0x21            // Destination CAN ID for Page 2

MCP_CAN CAN0(PIN_CAN_CS);
SoftwareSerial lcdstream(PIN_LCD_RX, PIN_LCD_TX);

/*
 *  Set up the gauges.
 *  v0,v1,v2 are guages on the left top; l0,l1,l2 are labels to the right of v0,v1,v2;
 *  v3,v4,v5,v6 are gauges on the top right; l3,l4,l5,l6 are labels to the right of v3,v4,v5,v6;
 *  b0,b1,b2,b3 are along the bottom;
 *  warn is a full-width line of text.
 *  Redlines / yellowlines are specified in raw, unscaled units.
 */
//                                   id,  lid, suffix, scale, decimals, red_low, yellow_low, yellow_high, red_high, ref
NextionObject map_g(&lcdstream,    "v0", "l0",     "",    10,        0,       0,          0,        2600,    2800, 50),
              afr_g(&lcdstream,    "v1", "l1",     "",    100,       2,  -32768,          20,         90,     100, 200),
              rpm_g(&lcdstream,    "v2", "l2",     "",     1,        0,       0,          0,        6500,    7000, 100),
              vss_g(&lcdstream,    "v3", "l3",     "",     1,        0,  -32768,     -32768,       32767,   32767, 200),
              art_g(&lcdstream,    "v4", "l4",     "",    10,        1,  -32768,     -32768,       32767,   32767, 200),
              spk_g(&lcdstream,    "v5", "l5",     "",    10,        1,  -32768,     -32768,       32767,   32767, 50),
              egt_g(&lcdstream,    "v6", "l6",     "",     1,        0,     500,        700,        1600,    1800, 100),
              clt_g(&lcdstream,    "b0",   "",     "",    10,        0,     600,       1500,        2000,    2200, 1010),
              mat_g(&lcdstream,    "b1",   "",     "",    10,        0,     200,        400,        1400,    1600, 200),
              oit_g(&lcdstream,    "b2",   "",     "",     1,        0,  -32768,     -32768,       32767,   32767, 1000),
              bat_g(&lcdstream,    "t1",   "",     "",    10,        1,     120,        130,         147,     150, 500),
              warn_g(&lcdstream, "warn",   "",     "",     0,        0,       0,          0,           0,       0, 50),
              tps_g(&lcdstream,    "t0",   "",     "",     10,       1,       0,          0,         800,   900, 200),
              tps_b(&lcdstream,    "j1",   "",     "",     1,       0,       0,          0,       800,   900, 200),
              rpm_b(&lcdstream,    "j2",   "",     "",     1,        0,  -32768,     -32768,       32767,   32767, 100);

/*
 *  Receive and process an incoming CAN frame.  Any known ids will be
 *  decoded and sent to the LCD.
 */
void handleCANFrame(void)
{
  uint8_t len,        // Number of bytes received
          rxBuf[8];   // CAN BUS receive buffer
  uint32_t rxId,      // CAN BUS device/arbitration ID
           val;       // Temp buffer to decode a particular value

  CAN0.readMsgBuf(&len, rxBuf);
  rxId = CAN0.getCanId();

#ifdef DEBUG_CAN_ID
  if (rxId == DEBUG_CAN_ID) {
    Serial.print(rxId);
    Serial.print(" ");
    uint8_t i;
    for (i = 0; i < 8; i++) {
      Serial.print(rxBuf[i], HEX);
      Serial.print(" ");
    }
    Serial.println(" !");
  }
#endif

  switch (rxId) {
    case 1520:
      rpm_g.val(           ntohs(*( int16_t *) &rxBuf[6]));  // RPM 1rpm
      rpm_b.val(           ntohs(*( int16_t *) &rxBuf[6]) * 100 / 10500);  // RPM in percent
      break;

    case 1521:
      spk_g.val( (int16_t) ntohs(*( int16_t *) &rxBuf[0]));  // SPK total advance 0.1deg
      art_g.val(                                rxBuf[4] );  // AFR target 0.1 ratio
      // Update the red/yellow lines for the AFR gauge.  Note that this frame doesn't update
      // the measured AFR, so there may be a slight flicker if the target changes rapidly.
      // afr_g.update_afr_target(rxBuf[4]);
      break;

    case 1522:
      map_g.val(           ntohs(*(uint16_t *) &rxBuf[2]));  // MAP 0.1 kPa
      //mat_g.val( (int16_t) ntohs(*( int16_t *) &rxBuf[4]));  // MAT 0.1degF
      {
      int16_t tmp_mat;
      tmp_mat = ((int16_t)rxBuf[4] << 8) | (int16_t)rxBuf[5];
      mat_g.val( ((tmp_mat - 320) * 5) / 9 );
      }
      //clt_g.val( (int16_t) ntohs(*( int16_t *) &rxBuf[6]));  // CLT 0.1 degF, cast for sign
      {
      int16_t tmp_clt;
      tmp_clt = ((int16_t)rxBuf[6] << 8) | (int16_t)rxBuf[7];
      clt_g.val( ((tmp_clt - 320) * 5) / 9 );
      }
      break;

    case 1523:
      tps_g.val(           ntohs(*(uint16_t *) &rxBuf[0]));// TPS offset 0 int16_t 0.1pct
      //tps_b.val(           ntohs(*(uint16_t *) &rxBuf[0]));
      {int16_t tmp_tps;
      tmp_tps=           ((uint16_t)rxBuf[0]);
      tps_b.val(map(tmp_tps, 0, 1000, 0, 100));
      }
      bat_g.val(           ntohs(*(uint16_t *) &rxBuf[2]));  // BAT 0.1 volt
      //afr_g.val(            rxBuf[5]);  // AFR 0.1 ratio
      {
      int16_t tmp_lambda;
      tmp_lambda = ((int16_t)rxBuf[5]);
      //afr_g.val( tmp_lambda );
      afr_g.val( tmp_lambda / 1.47 );
      }
      break;

    case 1524:
      // Knock input offset 0 uin16_t 0.1pct
      break;

    case 1542:
      egt_g.val( (int16_t) ntohs(*( int16_t *) &rxBuf[0]));  // EGT 0.1 degF  (or degC ?), cast for sign
      break;

    case 1562:
      // FIXME: suspicious inline constant conversion factor
      // FIXME: floats are evil
      vss_g.val(           ntohs(*(uint16_t *) &rxBuf[0]) / 4.4);  // VSS unknown unit
      break;

    case 1571:
      // Ports: A, B, EH, K, MJ, P, T, CEL_err
      break;

    case 1572:
      val =                ntohs(*(uint16_t *) &rxBuf[2]) ;    // Knock retard 0.1deg
      if (val > 0)
        warn_g.txt("KNOCK");
      else
        warn_g.txt("");
      break;
  }
}

void setup()
{
  CAN0.begin(CAN_500KBPS);      // Assumes module with 16MHz clock - use CAN_1000KBPS for 8MHz clock.
  lcdstream.begin(115200);
  lcdstream.print(EOC);         // Terminate any partial command sent before boot
  warn_g.pco("RED");
  Serial.begin(115200);
  Serial.println("Boot");
}

// Refresh all the label values.
void refresh_labels()
{
  map_g.label("MAP kPa");
  afr_g.label("Lambda");
  rpm_g.label("RPM");
  art_g.label("AFR trgt");
  vss_g.label("VSS mph");
  spk_g.label("Advance");
  egt_g.label("EGT degF");
}

// Check the watchdog on all gauges.
void check_watchdogs()
{
  map_g.watchdog();
  afr_g.watchdog();
  rpm_g.watchdog();
  art_g.watchdog();
  vss_g.watchdog();
  spk_g.watchdog();
  egt_g.watchdog();
  clt_g.watchdog();
  mat_g.watchdog();
  oit_g.watchdog();
  bat_g.watchdog();
  warn_g.watchdog();
  tps_g.watchdog();
  rpm_b.watchdog();
  lcdstream.print("clk.val=0" EOC);  // Reset the Nextion watchdog
}

// Sample ADCs and transmit them to CAN
void can_send_adc(
  uint16_t can_id,     // CAN ID to send sample data
  uint8_t a,           // Analog pin ID
  uint8_t b,           // Analog pin ID
  uint8_t c,           // Analog pin ID
  uint8_t d            // Analog pin ID
  )
{
  uint16_t samples[4];
  samples[0] = htons(analogRead(a));
  samples[1] = htons(analogRead(b));
  samples[2] = htons(analogRead(c));
  samples[3] = htons(analogRead(d));
  CAN0.sendMsgBuf(can_id, 0, 8, (byte *) &samples);
}

void loop()
{
  static Tick housekeeping_tick(HOUSEKEEPING_INTERVAL_MS, HOUSEKEEPING_PHASE_MS),
              adc_page_1_tick(    ADC_PAGE_1_INTERVAL_MS,   ADC_PAGE_1_PHASE_MS),
              adc_page_2_tick(    ADC_PAGE_2_INTERVAL_MS,   ADC_PAGE_2_PHASE_MS);

  if (housekeeping_tick.tocked()) {
    refresh_labels();
    check_watchdogs();
  }

  if (adc_page_1_tick.tocked()) {
    can_send_adc(ADC_PAGE_1_ID, A0, A1, A2, A3);
  }

  if (adc_page_2_tick.tocked()) {
    can_send_adc(ADC_PAGE_2_ID, A4, A5, A6, A7); // Note that A6 and A7 don't physically exist on DIP28-based AVRs
  }

  if (!digitalRead(PIN_CAN_INT)) { // CAN frame waiting in receive buffer
    handleCANFrame();
  }
}
1 Like

hier mal der komplette code.

Da mir ja die tps_g.val schon zur Verfügung steht, bestünde nicht irgendwie die Möglichkeit die tps_g.val in einen ganzzahligen Wert namens tps_b umzurechnen?

Du sollest noch lernen dass Code-Tags eine hilfreiche Sache sind.

Dann würde nämlich nicht plötzlich ab einer gewissen Stelle alles kursiv gesetzt sein:
for (i = 0; i < 8; i++) {
Serial.print(rxBuf*, HEX);*

  • Serial.print(" ");*
    Da stand nämlich bestimmt
for (i = 0; i < 8; i++) {
      Serial.print(rxBuf[i], HEX);
      Serial.print(" ");

Und Smileys zum Shiften würde es dann auch nicht geben:
tmp_mat = ((int16_t)rxBuf[4] << 8) | (int16_t)rxBuf[5];
tmp_mat = ((int16_t)rxBuf[4] << 8) | (int16_t)rxBuf[5];

So werde ich mich leider nicht inhaltlich mit dem Sketch beschäftigen wollen.

hab meinen post mal geändert und den code als solchen eingebettet

bestünde nicht irgendwie die Möglichkeit die tps_g.val in einen ganzzahligen Wert namens tps_b umzurechnen?

Warum nicht einfach so?

case 1523: {
      uint16_t tps = ntohs(*(uint16_t *) &rxBuf[0]));  // Wertebereich 0 .. 1000
      tps_g.val(tps);
      tps_b.val(tps/10);  // 0 .. 100 für den Fortschrittsbalken
      }break;

Hallo in die Runde,

@michael_x: Vielen Dank! Das funktioniert grundsätzlich bzgl. meiner Problembeschreibung, allerdings löst das mein Problem nicht. Durch die #val methode wird augenscheinlich nur der Text des Wertes übertragen und nicht der Wert selbst. Zumindest steht das so in der nextion.cpp die im header mit geladen wird.
Hier mal die nextion.cpp

/*
 *  TurboKitten Nextion display handler
 *
 *  This is an Arduino library which handles updating display objects on a
 *  Nextion LCD over a serial port.
 *
 *  This currently only handles text objects.  The #val method will convert the
 *  value based on the _scale factor and display a fixed _decimals number of places
 *  after the point.
 *
 *  Copyright (C) 2015-2016  Chris "Kai" Frederick
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <Arduino.h>
#include <SoftwareSerial.h>
#include <math.h>
#define EOC "\xff\xff\xff" // Nextion end of command

#define htons(x) ( ((x)<< 8 & 0xFF00) | \
                   ((x)>> 8 & 0x00FF) )
#define ntohs(x) htons(x)
#define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \
                   ((x)<< 8 & 0x00FF0000UL) | \
                   ((x)>> 8 & 0x0000FF00UL) | \
                   ((x)>>24 & 0x000000FFUL) )
#define ntohl(x) htonl(x)

/*
 * Wrapper for an object (text, gauge, etc) on a Nextion display
 */
class NextionObject
{
  private:
    Stream *_stream;                   // Stream object where the LCD is connected
    String _suffix,                    // Print this after the value
           _id,                        // Name of the object on the display
           _lid,                       // Name of the label on the display
           _old_pco,                   // Store the old color to optimize repeated updates
           _old_txt;                   // Store the old text to optimize repeated updates
    uint8_t _scale,                    // Fixed decimal scaling factor
            _decimals;                 // Number of digits to display after the decimal
    int16_t _red_high,                 // Redline (top)
            _red_low,                  // Redline (bottom)
            _yellow_high,              // Yellowline (top)
            _yellow_low,               // Yellowline (bottom)
            _refresh_ms;               // Minimum time between refreshes
    uint32_t _last_update_time;

  public:
    NextionObject(
      Stream *stream,                  // The Stream object where the LCD is connected
      const char *id,                  // The name of the object on the display
      const char *lid,                 // Name of the label on the display
      const char *suffix,              // Print this after the value
      uint16_t scale,                  // Fixed decimal scaling factor
      uint8_t  decimals,               // Number of digits after the decimal
      int16_t  red_low,                // Display red if the PV goes under this
      int16_t  yellow_low,             // Display yellow if the PV goes under this
      int16_t  yellow_high,            // Display yellow if the PV goes over this
      int16_t  red_high,               // Display red if the PV goes over this
      uint16_t refresh_ms              // Minimum time between refreshes
    )
    {
      _stream      = stream;
      _suffix      = suffix;
      _id          = id;
      _lid         = lid;
      _scale       = scale;
      _decimals    = decimals;
      _red_low     = red_low;
      _yellow_low  = yellow_low;
      _yellow_high = yellow_high;
      _red_high    = red_high;
      _refresh_ms  = refresh_ms;
    }

    /*
     *  Update the text on the object.
     */
    void txt(
      String text                      // text - New value to show
    )
    {
      // Ignore update if the current value is recent enough.
      if (millis() < _last_update_time + _refresh_ms) return;
      _last_update_time = millis();

      if (_old_txt == text) return;
      _old_txt = text;
      _stream->print(_id + ".txt=\"" + text + "\"" + EOC);
    }

    /*
     *  Update the object's label
     */
    void label(
      String text                      // Label text
    )
    {
      _stream->print(_lid + ".txt=\"" + text + "\"" + EOC);
    }

    /*
     *  Change the color on the object.  The change takes effect on the next refresh.
     */
    void pco(
      String color                     // color - New color as name "RED" or RGB(5,6,5) value "63488"
    )
    {
      if (color != _old_pco) {         // Don't bother updating if the color is already set
        _stream->print(_id + ".pco=" + color + EOC);
      }
      _old_pco = color;
    }

    /*
     *  Update the text on the object with a fixed-point value.  The
     *  red/yellow lines will be checked, and the text color set if needed.
     */
    void val(
      int32_t value                    // New value in raw, unscaled units
    )
    {

      if (value > _red_high || value < _red_low) { // Red takes precedence
        pco("RED");
      } else if (value > _yellow_high || value < _yellow_low) {
        pco("YELLOW");
      } else {
        pco("GREEN");
      }

      // Print the value with fixed-decimal interpretation
      uint8_t i;
      for (i = 0; i < _decimals; i++) { // left-shift-decimal by _decimals (beware of overflows)
        value *= 10;
      }
      String val_s = String(value / _scale);              // Scale after left-shift-decimal to preserve precision
      String out_s = val_s.substring(0, val_s.length() - _decimals);         // print the part left of decimal
      if (_decimals > 0) {                                                   // If we ARE printing a fraction...
        out_s += ".";                                                        // ... print the decimal ...
        out_s += val_s.substring(val_s.length() - _decimals);                // ... and the part right of decimal
      }
      out_s += _suffix;
      txt(out_s);
    }

    /*
     *  Update the AFR gauge's redline/yellowline based on the current AFR target.
     *  This should only be called on the AFR gauge.
     */
    void update_afr_target(
      uint8_t afr_target               // New AFR target
    )
    {
      // FIXME: inline constants
      _red_low     = afr_target - 10;
      _yellow_low  = afr_target -  5;
      _yellow_high = afr_target +  5;
      _red_high    = afr_target + 10;
    }

    /*
     *  Check this gauge's freshness.  If it's expired, clear the value.
     *  No data is better than bad data.
     */
    void watchdog()
    {
      if (millis() - _last_update_time > _refresh_ms * 5) {
        pco("GRAY");
        txt("---");
      }
    }
};

Durch die .val methode wird augenscheinlich nur der Text des Wertes übertragen

Richtig.

Gibt einige, die die nextion Library nicht mögen, aber dass die serielle Kommunikation mit dem Display nur über Text geht, ist nunmal so.

In dem Example von Seithain für ein Progress Bar wird auch ein Wert in Form von .val übertragen und funktioniert.
https://seithan.com/Easy-Nextion-Library/Examples/

myNex.writeNum("Nvoltage.val", voltage); // Nvoltage.val is a variable that we have create on Nextion.        
                                              // we send the value of the voltage variable on Arduino

Da müsste es doch eine Möglichkeit geben, dass dies irgendwie auch mit dem dash-kitten programm zu verheiraten geht?
Hat da jemand für mich einen Wink mit dem Zaunsfeld ?

Vielen Dank!

Hallo,

ich arbeite an dem identischen Ansatz aber habe bereits bei der Grundkommunikation ein Problem. Es scheint, als dass es ein Timingproblem mit dem CAN gibt da teilweise kein Messwerte im Display aktualisiert werden sondern der Wert zurückgesetzt (---) wird.

Unplausibel für mich ist, dass mit unterschiedlicher Ausgabefrequenz der MS es mehr oder weniger "gut" funktioniert.

Ist zwar nicht gleiche Frage wie der Threadersteller, aber Topic ist identisch und ich wollte keinen neuen Thread aufmachen.

EDIT:
EDIT2:
Edit wurde durchgeführt, aber behebt dass Problem nicht

Danke und viele Grüße
Mario

Ich habe jetzt nochmal den Code kompett überarbeitet und nur auf das eine Signal (TPS) reduziert.
Ist allerdings nicht auf meinem Mist gewachsen. Habe versucht ein skript, welches auf einem Teensy lief, irgendwie einzuarbeiten.

j1 wäre für den Forstschrittsbalken
t0 wäre die Anzeige für den Wert

Kompilieren ist ersteinmal fehlerfrei. Leider ist irgendwo noch der Wurm drin. Ich bekomme am Display noch nichts angezeigt.
Vielleicht kann mir jemand helfen?

#include <mcp_can.h>
#include <SPI.h>
#include <SoftwareSerial.h>


#define PIN_LCD_RX 5
#define PIN_LCD_TX 4
#define PIN_CAN_CS 9                  // chip select


MCP_CAN CAN0(PIN_CAN_CS);
SoftwareSerial lcdstream(PIN_LCD_RX, PIN_LCD_TX);

//Megasquirt data vars
unsigned int TPS;

void setup()
{
  CAN0.begin(CAN_500KBPS);      // Assumes module with 16MHz clock - use CAN_1000KBPS for 8MHz clock.
  lcdstream.begin(115200);
  Serial.begin(115200);
}

void loop(void)
{
  uint8_t len,        // Number of bytes received
          rxBuf[8];   // CAN BUS receive buffer
  uint32_t rxId,      // CAN BUS device/arbitration ID
           val;       // Temp buffer to decode a particular value

  rxId = CAN0.getCanId();

gauge_display();
if (CAN0.readMsgBuf(&len, rxBuf)){
    switch (rxId) { // ID's 1520+ are Megasquirt CAN broadcast frames.
       case 1523: // Group 3
        TPS = (float)(word(rxBuf[0], rxBuf[1]));
        break;
    }
  }
}



void gauge_display() {  //Prints captured data from above to display

    // Display Throttle Position

  Serial.print("j1.val=");
  Serial.print(TPS / 10);
  Serial.write(0xff);
  Serial.write(0xff);
  Serial.write(0xff);

  Serial.print("t0.txt=");
  Serial.write(0x22);
  Serial.print(TPS / 10);
  Serial.write(0x22);
  Serial.write(0xff);
  Serial.write(0xff);
  Serial.write(0xff);
}