LowPowerLab Forum

Hardware support => Projects => Topic started by: Lukapple on June 11, 2019, 03:44:00 PM

Title: Moteino driven DMD (Dot Matrix Dixplay), need help
Post by: Lukapple on June 11, 2019, 03:44:00 PM
Hi,
I'm working on a DMD display project, which will receive data (time, temperature, custom messages,...) from my other Moteino nodes and display them on DMD display.
(http://shrani.si/f/1Z/35/3OeoveBe/dmd.png)

Display, that I'm using in this project, is 32x16 Freetronics DMD display (https://www.freetronics.com.au/collections/all-products/products/dot-matrix-display-32x16-red#.XP_7ay2B235).
And here is the GitHub LIB (https://github.com/freetronics/DMD).

And now the problem:
When I start sketch, everything works fine - time and temperature info is displayed, but then after 10 minutes or so, sketch freezes, DMD screen goes black and I have no idea why. I've added a timer for debugging purposes, that turns on/off LED diod every 5 sec, and after everything freezes, diod also stops blinking.

Now I'm not sure if this is the wiring problem or something.
Here are the pins, used for DMD:
(DMD.h (https://github.com/freetronics/DMD/blob/master/DMD.h))
Code: [Select]
#warning CHANGE THESE TO SEMI-ADJUSTABLE PIN DEFS!
//Arduino pins used for the display connection
#define PIN_DMD_nOE       14//9    // D9 active low Output Enable, setting this low lights all the LEDs in the selected rows. Can pwm it at very high frequency for brightness control.
#define PIN_DMD_A         6    // D6
#define PIN_DMD_B         7    // D7
#define PIN_DMD_CLK       13   // D13_SCK  is SPI Clock if SPI is used
#define PIN_DMD_SCLK      8    // D8
#define PIN_DMD_R_DATA    11   // D11_MOSI is SPI Master Out if SPI is used
//Define this chip select pin that the Ethernet W5100 IC or other SPI device uses
//if it is in use during a DMD scan request then scanDisplayBySPI() will exit without conflict! (and skip that scan)
#define PIN_OTHER_SPI_nCS 10

I've changed pin 9 to pin 14, because 9 is used for Moteino LED. But I'm not sure about other pins. Are maybe there some conflicts with moteino pins - pins from 10 to 13 are used for transceiver.



And here is the sketch (work in progress), that I'm using:
Code: [Select]
//DMD(Dot Matrix Display) used in this project:
//https://github.com/freetronics/DMD

// #include <Arduino.h>
#include <RFM69.h>      //get it here: https://github.com/lowpowerlab/rfm69
#include <RFM69_ATC.h>  //get it here: https://github.com/lowpowerlab/RFM69
#include <TimeAlarms.h>
#include <Time.h>
#include <SPI.h>        //SPI.h must be included as DMD is written by SPI (the IDE complains otherwise)
#include <DMD.h>       
#include <TimerOne.h>   
#include "SystemFont5x7.h"
#include "Arial_black_16.h"

//Fire up the DMD library as dmd
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);
//LED, blinking every x seconds
#define LED_PIN 9
bool ledIsON = false;

//-------
// IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION!
//-------
#define GATEWAYID       1
#define NODEID          111
#define NETWORKID       123
#define FREQUENCY     RF69_433MHZ
//#define FREQUENCY     RF69_868MHZ
//#define FREQUENCY       RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define ENCRYPTKEY      "Removed" //has to be same 16 characters/bytes on all nodes, not more not less!
#define IS_RFM69HW_HCW  //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
//----------
#define ENABLE_ATC      //comment out this line to disable AUTO TRANSMISSION CONTROL
#define ATC_RSSI        -75

#ifdef ENABLE_ATC
  RFM69_ATC radio;
#else
  RFM69 radio;
#endif

#define USE_SERIAL        // to remove all Serial code when testing actual power consumption
#define SERIAL_BAUD_RATE 115200   
//New temperature data is sent every 10 minutes. In case we don't receive new temperature data, we erase old data
#define KEEP_DATA_SEC   0  // 0 seconds
#define KEEP_DATA_MIN   15 //15 minutes

#ifdef USE_SERIAL
#define PRINT(x)      Serial.print(x)
#define PRINTLN(x)    Serial.println(x)
#define SERIAL_BEGIN  Serial.begin(SERIAL_BAUD_RATE)
#else
#define PRINT(x)
#define PRINTLN(x)
#define SERIAL_BEGIN
#endif

//TIME: constants for time sync
#define TIMECMDLEN  11   //T + 10 digits (time_t unix timestamp) e.g. T1398273354
#define TIMEHEADER  'T'  //Time sync header (1 char)
#define TIMENEEDSSYNC "SNC" //Message that is sent to server note for sync request

//DMD - currently displayed minute (default -1)
int currentMinute = -1;

//check if the command is time sync command (e.g. T1398096798)
boolean processTimeSync(char* sourceCommand)
{
  if (sourceCommand[0] != TIMEHEADER) {
    return false;
  }
  PRINT("Time sync:"); PRINTLN(sourceCommand);
  time_t serverTime = 0;
  char c;
  int timeLength = 0;
  for(int i=0; i < TIMECMDLEN ; i++) {
    c = sourceCommand[i];
    if( c >= '0' && c <= '9') {
      serverTime = (10 * serverTime) + (c - '0') ; //convert char to number
      timeLength++;
    }
  }
  //10 digits, without "T" header
  if (timeLength == TIMECMDLEN - 1) {
    setTime(serverTime);
  }
  return true;
}

//format time string HH:MM
void formatCurrentTime(char *timeString) {
  if (timeStatus() == timeNotSet) {
    timeString[0] = '\0';
    strcat(timeString, "--:--");
    timeString[5] = '\0';
  } else {
    timeString[0] = '\0';

    char cstr[16];
    itoa(hour(), cstr, 10);
    strcat(timeString, cstr);
    strcat(timeString, ":");
    int min = minute();
    if (min < 10) {
      strcat(timeString, '0');
    }
    itoa(min, cstr, 10);
    strcat(timeString, cstr);
  }
}

/*--------------------------------------------------------------------------------------
  Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning, this gets
  called at the period set in Timer1.initialize();
--------------------------------------------------------------------------------------*/
void ScanDMD()
{
  dmd.scanDisplayBySPI();
}

//moteino radio
void initRadio() {
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  #ifdef IS_RFM69HW_HCW
  radio.setHighPower(); //must include this only for RFM69HW/HCW!
  #endif
  radio.encrypt(ENCRYPTKEY);

  #ifdef ENABLE_ATC
  radio.enableAutoPower(ATC_RSSI);
  #endif
}

void initDMD() {
  //initialize TimerOne's interrupt/CPU usage used to scan and refresh the display
  Timer1.initialize(5000);           //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
  Timer1.attachInterrupt(ScanDMD);   //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()
  //clear/init the DMD pixels held in RAM
  dmd.clearScreen(true); //true is normal (all pixels off), false is negative (all pixels on)
  //dmd.selectFont(Arial_Black_8);
  dmd.selectFont(System5x7);
}

void setup() {
  SERIAL_BEGIN; // start the serial interface   
  pinMode(LED_PIN, OUTPUT);
 
  delay(200);
  initRadio();
  radio.sendWithRetry(GATEWAYID, "START", 6);
  initDMD();
  PRINTLN("Started");
  //zahteva za uskladitev časa
  radio.sendWithRetry(GATEWAYID, TIMENEEDSSYNC, 6);
}
  //-99.9C //7 chars + \0
  char tempAir[8] = "\0";
  char tempWater[8] = "\0";
  char currentTime[6];// = "\0";
  AlarmId airTimer; //timer - used for deleting old air temperature data
//  AlarmId waterTimer;

  //Turn on/off led diode every 5 seconds
  AlarmId blinkTimer = Alarm.timerRepeat(5, blinkLed);   

//messages:
//M#Hello world\0
void loop() { 
   Alarm.delay(0); //za TimeAlarm, moramo klicati to funkcijo namesto delaya
   //display messages on DMD screen (time, temperature, custom messages,...)
   displayMessages();

  if (radio.receiveDone()) {
    PRINT('[');PRINT(radio.SENDERID);PRINT("] ");
    for (byte i = 0; i < radio.DATALEN; i++)
      PRINT((char)radio.DATA[i]);
    PRINT("   [RX_RSSI:");PRINT(radio.RSSI);PRINTLN("]");
    //TODO: GatewayMightyHatNode.ino - message is converted to uppercase
 
    //Time sync command?(T...)
    if (processTimeSync(radio.DATA)) {
      formatCurrentTime(currentTime);
      PRINT("Time set to:"); PRINTLN(currentTime);

    //Print Time
    } else if (radio.DATALEN > 1 && radio.DATA[0]=='P' && radio.DATA[1]=='T') {
      formatCurrentTime(currentTime);
      PRINT("Current time is:"); PRINTLN(currentTime);

    //Temperature (air)
    //AT#-12.3
    } else if (radio.DATALEN > 3 && radio.DATA[0]=='A' && radio.DATA[1]=='T' && radio.DATA[2]=='#') {
      char *ptrTok;
      ptrTok = strtok(radio.DATA, "#"); //1. token = "TA"
      ptrTok = strtok(NULL, "#"); //2. token = '-12.3'
      strcpy(tempAir, ptrTok); //temp is stored to global variable, which is displayed on DMD
      Alarm.free(airTimer); //we delete old data
      airTimer = Alarm.timerOnce(0,KEEP_DATA_MIN,KEEP_DATA_SEC, airDelete);
      PRINT("Setting temperature Air to:"); PRINTLN(ptrTok);
     
    //M#message
    } else if (radio.DATALEN > 2 && radio.DATA[0]=='M' && radio.DATA[1]=='#') {
      char *ptrMsg;
      ptrMsg = strtok(radio.DATA, "#"); //1. token = 'M'
      ptrMsg = strtok(NULL, "#"); //2. token = 'message'
      PRINT("Message:"); PRINTLN(ptrMsg);
      PRINT("Air temperature:"); PRINTLN(tempAir);
 

    }
    //first send any ACK to request
    if (radio.ACKRequested())
    {
      radio.sendACK();
      PRINTLN(" - ACK sent.");
    }
    PRINTLN();
  }
}

//Blink led diode every x sec
void blinkLed() {
  if (ledIsON) {
    digitalWrite(LED_PIN, LOW); // turn the LED off by making the voltage LOW
  } else {
    digitalWrite(LED_PIN, HIGH); // turn the LED on (HIGH is the voltage level)
  }
  ledIsON = !ledIsON;
}

void airDelete() {
  PRINTLN("Deleting air temperature data.");
  tempAir[0] = '\0';
}

void drawCurrentTime() {
  formatCurrentTime(currentTime);
  dmd.drawString(  2+(32*0),  1+(16*0), currentTime, /*8*/strlen(currentTime), GRAPHICS_NORMAL ); 
  dmd.drawString(  2+(32*0),  9+(16*0), tempAir, /*8*/strlen(tempAir), GRAPHICS_NORMAL );
}

//Prikaz časa
void displayTime() {
  if (timeStatus() == timeNotSet) {
    //TODO
    //send time sync request
  } else {
    if (currentMinute != minute()) {
      currentMinute = minute();
      drawCurrentTime();
    }
  }
}

void displayMessages() {
  //PRINT("Current time is:"); PRINTLN(currentTime);
  //PRINT("Message:"); PRINTLN(ptrMsg);
  //PRINT("Air temperature:"); PRINTLN(tempAir);
 
  displayTime();
}

Any idea what am I doing wrong?

Thanks!
Title: Re: Moteino driven DMD (Dot Matrix Dixplay), need help
Post by: Felix on June 11, 2019, 05:34:02 PM
Looking at your sketch, I see a lot of string pointer manipulation and tokenizing.
I bet it's a pointer gone wild (most likely) or a memory leak if there's any dynamic allocation in those libraries.
Check those strtok's!

Also, you might want to use freeRAM (https://playground.arduino.cc/Code/AvailableMemory/) to watch if your memory is leaking.
Title: Re: Moteino driven DMD (Dot Matrix Dixplay), need help
Post by: Lukapple on June 14, 2019, 05:14:38 PM
Thanks for the hint Felix. Nice library, I'll check if there are any leaks :-\
Title: Re: Moteino driven DMD (Dot Matrix Dixplay), need help
Post by: Lukapple on June 15, 2019, 10:30:26 AM
Ok, now this is getting werid.
I've added "freeMemory" library and print free memory every 5 sec and memory stayed on "935", unchanged till the end of the test, so there is probably no leaks.
I ran sketch from 15:29:48 and it worked until 16:00. Exactly on 16:00 sketch freezes and screen goes black. I've also testet it on 9PM and it also died at full hour.

When I restarted sketch and sent "time sync" signal to node,  it printed only part of received message, then it freezed:

Code: [Select]
16:04:08.290 -> Started
16:04:13.095 -> freeMemory()=935
16:04:17.279 -> [1] T1560614657 

Code:
Code: [Select]
  if (radio.receiveDone()) {
    PRINT('[');PRINT(radio.SENDERID);PRINT("] ");
    for (byte i = 0; i < radio.DATALEN; i++)
      PRINT((char)radio.DATA[i]);
    PRINT("   [RX_RSSI:");PRINT(radio.RSSI);PRINTLN("]");


Any idea what should I check next? Is it possible that moteino transceiver pins are conflicting with DMD? But why it crashes only at full hour? :o

EDIT:
I think that there is a problem with this function, when mintue is from 0 to 9:

Code: [Select]
//format time string HH:MM
void formatCurrentTime(char *timeString) {
  if (timeStatus() == timeNotSet) {
    timeString[0] = '\0';
    strcat(timeString, "--:--");
    timeString[5] = '\0';
  } else {
    timeString[0] = '\0';

    char cstr[16];
    itoa(hour(), cstr, 10);
    strcat(timeString, cstr);
    strcat(timeString, ":");
    int min = minute();
    if (min < 10) {
      strcat(timeString, '0');
    }
    itoa(min, cstr, 10);
    strcat(timeString, cstr);
  }
}


EDIT#2:

I think that problem is solved with fixing this part:

Code: [Select]
if (min < 10) {
      //strcat(timeString, '0'); //OLD CODE (missing null terminator)
      strcat(timeString, "0\0"); //NEW CODE
    }
Title: Re: Moteino driven DMD (Dot Matrix Dixplay), need help
Post by: Felix on June 17, 2019, 09:29:50 AM
Yes, strings in C (or rather char pointers) absolutely need to be null terminated. Or your pointers will happily start walking into ether.