Author Topic: Moteino driven DMD (Dot Matrix Dixplay), need help  (Read 304 times)

Lukapple

  • Full Member
  • ***
  • Posts: 157
Moteino driven DMD (Dot Matrix Dixplay), need help
« 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.


Display, that I'm using in this project, is 32x16 Freetronics DMD display.
And here is the GitHub LIB.

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)
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!

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6006
  • Country: us
    • LowPowerLab
Re: Moteino driven DMD (Dot Matrix Dixplay), need help
« Reply #1 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 to watch if your memory is leaking.

Lukapple

  • Full Member
  • ***
  • Posts: 157
Re: Moteino driven DMD (Dot Matrix Dixplay), need help
« Reply #2 on: June 14, 2019, 05:14:38 PM »
Thanks for the hint Felix. Nice library, I'll check if there are any leaks :-\

Lukapple

  • Full Member
  • ***
  • Posts: 157
Re: Moteino driven DMD (Dot Matrix Dixplay), need help
« Reply #3 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
    }
« Last Edit: June 15, 2019, 11:08:50 AM by Lukapple »

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6006
  • Country: us
    • LowPowerLab
Re: Moteino driven DMD (Dot Matrix Dixplay), need help
« Reply #4 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.