ItsyBitsy vs ProTrinket RX/TX

Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
biod101
 
Posts: 183
Joined: Sun Apr 19, 2015 4:21 pm

ItsyBitsy vs ProTrinket RX/TX

Post by biod101 »

Hello Adafruit,

I work for a state agency that has been using your ProTrinkets with Iridium satellite modems. The combo works great - managed to get two years of telemetry out of one of our boxes until it was wiped out by a flood. We are trying to standardize our various designs so I decided it was time to upgrade the ProTrinkets to the ItsyBitsy platform as recommended by Adafruit. I had to make some minor wiring and pin modifications in code to work with the ItsyBitsys which is fairly simple thing to do.

Unfortunately, I cannot get the ItsyBitsy board to communicate via RX/TX with the modem. I have no trouble starting the modem with my tested code, but then it just hangs waiting for a response when the iridium library functions issues an AT commands. I've tried using multiple modems to ensure it wasn't a modem specific issue - the modems work with the same code running on a ProTrinket, but no luck with the ItsyBitsy.

I decided to check the chip hosted on the ItsyBitsy and learned it is hosting an Atmega32u4 rather than what's hosted on the ProTrinket- being an ATmega328P. This is the only significant difference between the two that I managed to decipher as potentially interfering with the iridium modem communications. Upon learning the ItsyBitsy hosts a Atmega32u4, I then recalled having similar issues when trying to use a 32U4 Feather with the modem, specifically RX/TX is handicapped.

Does anyone know why I lose RX/TX with iridium modems when migrating from a ATmega328P to Atmega32u4 based platform? Is there any way to fix this?

I'd like to migrate away from ProTrinkets - can you recommend a different microcontroller that keeps RX/TX working with the iridium library?

Here's my original code that works with the ProTrinket:

Code: Select all

// Code prepared by Hans Huth and licencsed under creative commons
// Modified from ROCKBLOCK_MENGES_REV5 and barebones_ROCKBLOCK_REV3G

/*
 RockBLOCK modem code for use with and Adafruit Trinket (Arduino clone)
 https://www.sparkfun.com/products/13745
 https://www.adafruit.com/product/2010
 https://www.adafruit.com/product/1137
 https://www.adafruit.com/product/2900

 Irridium SBD libraries available here:
 https://github.com/mikalhart/IridiumSBD/releases/tag/v1.0

 Please acknowlege Hans Huth and Sean Keane if you use/modify this code
*/

// Libraries

#include <IridiumSBD.h>
#include <SoftwareSerial.h>
#include <SSD1306Ascii.h>       // for OLED display
#include <SSD1306AsciiAvrI2c.h> // for OLED display

// Display Settings
  #define OLEDScanner 11      // determines power state of OLED
  #define I2C_ADDRESS 0x3C
  SSD1306AsciiAvrI2c oled;
  
// Rockblock settings

#define STATUS 13             // onboard pin to determine if modem engaged.
#define ROCKBLOCK_RX_PIN A1   // Recieve data pin from Rockblock (seial data from RockBLOCK)
#define ROCKBLOCK_TX_PIN A2   // Transmit data pin to Rockblock (serial data to RockBLOCK)
#define ROCKBLOCK_SLEEP_PIN 4 // on/off pin for power savings
#define ROCKBLOCK_BAUD 19200  // serial modem communication baud rate
#define CONSOLE_BAUD 115200   // serial terminal communications baud rate
#define DIAGNOSTICS true      // Set "true" to see serial diagnostics

// Timer Variables

unsigned long resetLoopTime = 0;  // Resests the loop timer back to 0 so it can be compared with telemInterval each loop
unsigned long loopStartTime = 0; // Used to register start time at top of loop()
unsigned long telemInterval = 1800000; // status reporting interval (24hours where 1000 = 1 second) **Can be modified remotely
unsigned long pollingInterval = 60000;  // (60 secs) interval that rangeFind takes readings for change detect **Can be modifed remotely

SoftwareSerial ssIridium(ROCKBLOCK_RX_PIN, ROCKBLOCK_TX_PIN);  // type Arduino Stream
IridiumSBD isbd(ssIridium, ROCKBLOCK_SLEEP_PIN);               // this is my RockBLOCK

// Rangefinder pins/variables

 #define RangeTrig 6    // Attach to pin 4 on range sensor
 #define RangePin A3    // Attach to pin 3 on range sensor

// Ultrasonic variables
int8_t arraysize = 9;   // quanitity of values for median (odd numbers)
uint16_t rangevalue[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0}; // group array for 9 ultrasonic values to calculate median/mode
uint16_t mode;          // calculated mode distance (value that occurs most often) (cm or volts)
uint16_t modeOld;       // used to store old rangeFind values if needed (i.e. if there is a change) (cm or volts)
int change;             // used to measure change. INITALLY SET TO 10 SO THE LOOP WILL RUN WHEN INITALLY SET UP
                        // GETS SET TO ACTUAL CHANGE IN mode AND modeOld DURING FIRST LOOP.   
int changeCond = 10;    // Conditional change value to start transmission, can be changed through message retrival. **Can be modifed remotly
String myHeight;        // string for adding stage field to myUrl
String myChange;        // string for adding change field to myURL

// String and character array for posting to RockBLOCK server
String myUrl;           // String where all fields will be captured and posted
char url[256];          // for passing to modem stream
char url_old [256];     // If telemetry failed, this array stores the old value 
                        // for telemetry retry while ultraonic measures a new value

// Message Retrival Variables
uint8_t buffer[200];    // buffer for saving messages.
size_t bufferSize = sizeof(buffer); // Determines the size of the buffer. This gets set to 0 if no message is recieved. 
                                    // or if the message takes to long to download.

// Variables for battery voltage
long myVcc = 0.00;
String myVolts = "";

// Variables to determine message status
bool timeout = false; // If telemetry times out, this becomes true.
int err;              // Error code status for Iridium processes.
byte Setup = 1;       // Allows the loop to run immediatly for setup

//Variables to modify conditions through SMS
long varChange; // Variable for saving recieved message data and saving it to global variables. 
int n = 25; // Variable for setting adjustSendReceivetimeout or how long a message sending attempt will last.

void setup()
{

 // Start the serial port at 115200 baud rate so that I can 
 // see output on the a terminal from modem initiation 
 Serial.begin(CONSOLE_BAUD);
 Serial.println("REM Setup");
  
 // OLED Button pins
 pinMode(OLEDScanner, INPUT);
 
 // LED on 13 for status on modem communication
 pinMode(STATUS, OUTPUT); 

 // Sleep pin on modem
 pinMode(ROCKBLOCK_SLEEP_PIN, OUTPUT);

 // Ultrasonic pins
 pinMode(RangeTrig, OUTPUT);   // Pin for triggering sensor                     
 pinMode(RangePin, INPUT);     // Pin for reading sensor (NOTE: analog is input by default)     
 digitalWrite(RangeTrig, LOW); // Pin on sensor is pulled high internally,
                               // sensor will stop ranging when low (power savings)
 
 // Setup the RockBLOCK

 isbd.adjustSendReceiveTimeout(n); // This deterimnes how long RockBLOCK will try to send message before timeout
                                   // Here, set to 25 seconds per attempt for timeout
  
 isbd.setPowerProfile(0); // Since we are running off battery in field, set to 0 
                          // Available power settings: 
                          // 1 = low current  (90 mA USB; 60 secs between transmit retries)
                          // 0 = high current (high current battery; 20 secs between transmit retries)
 
 rangeFind();

 modeOld = mode; // Set old rangefinder value to current in the event transmission doesn't work

 if (digitalRead(OLEDScanner) == 1) { // Check that the OLED is on
  oledReset();     
  oled.println(F("REM SETUP:"));
  oled.println(F("Lets test connection"));
  oled.print(F("Stage Level= "));
  oled.print(mode);
  oled.print(F(" cm"));
  delay(2000);  // Wait seconds so the user can read the message
 }
}

void loop() {
  
 Serial.println(F("In Loop"));
 if (digitalRead(OLEDScanner) == 1) {  //Check to see if OLED is on before sending data
  oledReset(); 
  oled.print(F("Stage Level="));
  oled.print(mode);
  oled.println(F(" cm"));

  if(Setup == 1) { // Setup is equal to 1 the first run through the loop. It will be set to 1 if the first message is sent
   oled.println(F("Sending Message to"));
   oled.println(F("Test Connection"));
  }
  else { // This message prints after setup is complete to remind the user to shut off the OLED before leaving
   oled.println(F("Power off OLED"));
   oled.println(F("before leaving site"));
  }
  delay(5000); // Allow the user to read this message
 }

 loopStartTime = millis(); // Setting loop start time.
                           // millis() returns the number of milliseconds since the start of current program.

 rangeFind();              // Determines rangefinder value
 change = mode-modeOld;    // Calculate change in rangeFind height based on last reading (cm) (Will run initally because modeOld is initally 0.)
 Serial.print("Interval is "); Serial.println(telemInterval); 
 Serial.print("Change condition = "); Serial.println(changeCond);
 Serial.print("Polling Interval = "); Serial.println(pollingInterval);

 // Uncomment (abs(change) >= changeCond) to have message sent if changeCond is exceeded

 if (/*abs(change) >= changeCond ||*/ ((loopStartTime-resetLoopTime) >= telemInterval) || Setup == 1) {
  // If I made it here, I'm setting things up for first telemetry test
  // OR my interval for readings has been exceeded - time to report status
  // OR my change condition has been exceeded (assuming respective code has been uncommented)
  resetLoopTime = loopStartTime; // reset the timer 
  if (digitalRead(OLEDScanner) == 1) { 
   oledReset();  //Start the OLED
   oled.println(F("In the Loop"));
   oled.println(F("Gathering Data"));
   delay(2000);
  }

 if(timeout == true) // if message failed to send during last attempt, increase the attempt to send interval.
  {
   n +=25; // If message doesnt send add 25 seconds to timeout.
   if(n >= 300) {
    n = 300;  // never allow n to get over the default value of 300 seconds or 5 mins
   }
  isbd.adjustSendReceiveTimeout(n);
  }
  
  Serial.println(F("Transmit")); // Conditions met - start the transmission sequence

  // Start the serial port
  ssIridium.begin(ROCKBLOCK_BAUD);
  
  // Start talking to RockBLOCK
  Serial.println("Beginning to talk to the RockBLOCK...");
  ssIridium.listen();

  if (digitalRead(OLEDScanner) == 1) {
   oledReset();
   oled.println(F("Talking to RockBlock"));
   oled.println(F("This may take a min"));
  }
    
  // Try sending a text if awoken okay
  if (isbd.begin() == ISBD_SUCCESS) { 
   digitalWrite(STATUS, HIGH); // modem awoke okay    
   isbd.useMSSTMWorkaround(false); //isbd.begin() causes this to be set true.
   // To prevent lockout keep it set to false.
     
   if (digitalRead(OLEDScanner) == 1) {
    oledReset();
    oled.print(F("RockBlock Turned on"));
    delay(2000);
   }
   // Monitoring functions
   
   myVcc = readVcc();    // returns REM battery voltage
   
   // varialbes to String  
   myChange = String(change); 
   myHeight = String(mode);
    
   // battery
   myVolts = String(myVcc);

   // build post string
   myUrl = "," + myVolts + "," + myHeight + "," + myChange + ",0"; // 0 required as terminator byte for RockBLOCK servers

   if (digitalRead(OLEDScanner) == 1) {
    oledReset();
    oled.println(F("The String is:"));
    oled.println(F("volt, cm, change(cm)"));
    oled.println(myUrl);
    delay(5000);
   }
  
   myUrl.toCharArray(url,256);
   if(timeout == true) {
    messageRetry(); // Function for resending previous unsent message (only attempts resends one time)
   }


 /* Note whenever a message is sent the avaliable incoming messages are also downloaded. 
  *  If no message is in the inbox the buffer size (or sizeof(buffer)) = 0. 
  *  handleMessage() will only be called if a message was sucessfully downloaded.
  */
  
  // 35 corresponds to # so any data sent must start with "#" (e.g. #time8640000)
  
   if(buffer[0] == 35) { // Sending messages also downloads available messages
    Serial.print("telemInterval = "); Serial.println(telemInterval);
    Serial.print("Change cond = "); Serial.println(changeCond);
    Serial.print("Polling Interval = "); Serial.println(pollingInterval);

    for(int kk=1;kk<201; kk++) {
     Serial.println(buffer[kk]);
    }
    
    handleMessage();        // Function for converting recieved messages
    
    Serial.print("telemInterval = "); Serial.println(telemInterval);
    Serial.print("Reading rate = "); Serial.println(pollingInterval);
    Serial.print("Change cond = "); Serial.println(changeCond);
   }
 
   // Okay - let's send the most recent data
   if (digitalRead(OLEDScanner) == 1) {
    oledReset();
    oled.println(F("Trying to send"));
    oled.println(F("field data for a"));
    oled.println(F("channel update"));
   }
 
   timeout= false; // Reset timeout variable (if a previous timeout message was not sent do not try it again).
   
   adaptiveRetry();  // function for sending/recieving data to server including retries

   /* Again since a message was attempted to be sent it also checks if a message is in the inbox. 
    *  If a message was recieved the convert it and save the command.
    */

   if(buffer[0] == 35) {
    Serial.print("telemInterval = "); Serial.println(telemInterval);
    Serial.print("Change cond = "); Serial.println(changeCond);
    Serial.print("Reading rate = "); Serial.println(pollingInterval);
    Serial.print("buffer = "); for(int kk=1;kk<201; kk++){Serial.println(buffer[kk]);}
 
    handleMessage();

    Serial.print("telemInterval = "); Serial.println(telemInterval);
    Serial.print("Change cond = "); Serial.println(changeCond);
    Serial.print("Reading rate = "); Serial.println(pollingInterval);
   }

   // End incoming message handling 
   
   if (timeout == true){
    if (digitalRead(OLEDScanner) == 1) {
     oledReset();
     oled.println(F("Message timeouted"));
     oled.println(F("saving message"));
     oled.println(F("for retry later"));
     delay(5000);
    }       
    memcpy(url_old, url, 256); // Copies unsent message for retry next loop. (Erases old url_old, if one was saved).
   }
  
   else if (digitalRead(OLEDScanner) == 1 && timeout == false) { // Confirm message was sent
    oledReset();
    oled.println(F("Message Sent"));
    oled.println(F("Powering down modem"));
   }
   
   // All done - go to sleep 
   
   isbd.sleep();
   ssIridium.end();
   digitalWrite(STATUS, LOW); // modem asleep     

   modeOld = mode; // save old rangeFind value

   if ((Setup == 1 && timeout == false) && digitalRead(OLEDScanner) == 1) { 
   // If the first run through the loop is succesful, prompt user to turn off OLED and set Setup = 0    
    oled.clear();                             
    oled.println(F("Message Sent"));
    oled.println(F("Setup complete"));
    oled.println(F("Turn off OLED"));
    oled.println(F("With on/off switch"));
    Setup = 0; 
    delay(15000);
   }
   
   else if ((Setup == 1 && timeout == true)  && digitalRead(OLEDScanner) == 1) {
   // If the message wasn't sent during setup, the attempt will be tried in a few seconds, but prompt user to move antenna
    oled.clear(); 
    oled.println(F("Couldnt send message"));
    oled.println(F("Move Antenna"));
    oled.println(F("Restart in 30 sec"));
    delay(30000);
   }
  if(Setup == 1 && timeout == false)
   // make sure Setup is set to zero irrespective of state of OLED
   Setup = 0;
  }
 }
 else {
   // there hasn't been a signficant change OR I am still within timer interval
   Serial.println(F("Waiting for condition"));
   delay(pollingInterval); // check condition after polling interval 

   if(digitalRead(OLEDScanner) == 1) { 
    oled.clear();                             
    oled.println(F("Waiting for condition"));
    delay(15000);   
   } 
 }
} // end loop

///// FUNCTIONS /////

void rangeFind(){ 
  
 int16_t pulse;  // number of pulses from sensor
 int i=0;

 digitalWrite(RangeTrig, HIGH); 
  
 while( i < arraysize ) {                                 
  pulse = analogRead(RangePin);  // read in time for pin to transition
  rangevalue[i]=pulse;           // pulses to centimeters (use 147 for inches)
  if( rangevalue[i] < 725 && rangevalue[i] >= 10 ) 
   // value in range
   i++;  
   delay(10);                    // wait between samples
 }
 isort(rangevalue,arraysize);        // sort samples
 mode = getMyMode(rangevalue,arraysize);  // get mode

 digitalWrite(RangeTrig, LOW); 
 //print_range(); //Debuggin Function
}
 
// Sorting function (Author: Bill Gentles, Nov. 12, 2010)

void isort(uint16_t *a, int8_t n){
 for (int i = 1; i < n; ++i)  {
  uint16_t j = a[i];
  int k;
  for (k = i - 1; (k >= 0) && (j < a[k]); k--) {
   a[k + 1] = a[k];
  }
  a[k + 1] = j;
 }
}
 
// Mode function, returning the mode or median.
uint16_t getMyMode(uint16_t *x,int n){
 int i = 0;
 int count = 0;
 int maxCount = 0;
 uint16_t mode = 0;
 int bimodal;
 int prevCount = 0;

 while(i<(n-1)){
  prevCount=count;
  count=0;
  while( x[i]==x[i+1] ) {
   count++;
   i++;
  }
  if( count > prevCount & count > maxCount) {
   mode=x[i];
   maxCount=count;
   bimodal=0;
  }
  if( count == 0 ) {
   i++;
  }
  
  if( count == maxCount ) {      //If the dataset has 2 or more modes.
   bimodal=1;
  }
  
  if( mode==0 || bimodal==1 ) {  // Return the median if there is no mode.
      mode=x[(n/2)];
  }
  
  return mode;
 }
}


void adaptiveRetry() {
  
  // When a new message is sent, the modem attempts to recieve a message. 
  // If one is available, it gets saved to buffer and the size gets saved to buffersize.
  // buffersize has shown to be unreliable:
 
   /* 
      A message might send without a 0 code, so another sendRecieveSBD session is started.
      In this case, buffersize gets set to 0 even though buffer has a messaged saved in it. 
     
      To counteract this issue, all messages sent must start with a "#" and end with a "!" 
      or the program will not register it.
   */

  memset(buffer, 0, sizeof(buffer)); // Reset the buffer before a new message comes in 
                                     // or it will be added to the next open space in the buffer.
  
   // Details regarding this function for handling bad transmissions are avaialble here:
   // https://docs.rockblock.rock7.com/docs/adaptive-retry

  for(int i=0;i<5;i++) 
  { // consider adding OLED output here  
    Serial.print(F("Attempt# "));
    Serial.print(i+1);
    delay(5000);
    err = isbd.sendReceiveSBDText(url,buffer,bufferSize);
    Serial.print(F("Error Code = ")); Serial.println(err);
    // check status of sent message
    if (err != 0) { 
     // Latest attempt didn't work so echo status   
     Serial.println(F("sendSBDText failed."));
     Serial.println(F("Delaying before retry."));
     Serial.println(F("                      "));
     Serial.println(F("----------------------"));
     Serial.println(F("                      "));
     // Try again after specified delay
     if(i==0) 
      delay(random(0, 5000)); // 1st failure- trying second after random time 0-5 seconds
     else if(i<=2)
      delay(random(0, 30000)); // 2nd, 3rd failure- trying third, fourth after random time 0-30 seconds
     else if(i==3)
      delay(random(120000, 300000)); // 4th failure- trying fifth after random time between 120-300 seconds
     else if(i==4) {
      // If I'm here, the first five tries failed
      timeout = true;
      Serial.print(i+1);
      Serial.println(F(" attempts failed;"));  
      Serial.println(F("message not sent."));
      break;
     }
    }
   else
    // Message transmitted during first else, so
    // notificaiton will be provided in main loop.
    break;
  } // for loop closed
 
  // Since buffer would have been changed above if a message was recieved,
  // check that the first character was a # (Ascii value = 35).     
   
  if(buffer[0]==35){ 
  handleMessage(); // for incoming messages
 }
}

void messageRetry(){
 // If I'm here, timeout was set to true in prior iteration given failure after multiple tries,
 // so attempt to resend previous unsent post.
 if (digitalRead(OLEDScanner) == 1){
  oledReset();
  oled.clear();      
  oled.println(F("Sending old message"));
 }
 
 //Attempt to download any message while sending.  
 err = isbd.sendReceiveSBDText(url_old,buffer,bufferSize);  
    
 if (err != 0) {
  if (digitalRead(OLEDScanner) == 1){
   oledReset();
   oled.println(F("Old message not sent"));
   oled.println(F("trying one last time"));
  }
  isbd.sendReceiveSBDText(url_old,buffer,bufferSize);
  delay(15000); // Allow 15 seconds before thingspeak posts. 
 }
 else {
  delay(15000);
 }
}

/*
 * handleMessage() works by taking the buffer that the inbound message was saved in and initally reading the first four characters' 
 * ASCII values.  * These first four ASCII values are added together to form a command code. All the subsequent characters are read 
 * and they are turned into one value and saved into varChange. Using the unique command code a global variable is chosen and the 
 * value of varChange is saved inside to be used in the next loop cycles. Example: to change global variable for reporting interval 
 * (telemInterval) send in message #time600000 . The first four ASCII values for time add up to 116 + 105 + 109 + 101 = 431. 
 * The subsequent values [6,0,0,0,0,0] get converted from their ASCII values and saved as one number '600000'. This then changes the 
 * global Variable telemInterval to one hour (telemInterval = 600000);
 */

void handleMessage(){
  
 long varChange = 0;    // variable to change values inside program
 int commandCode = 0;   // variable to determine which command was recieved

 // If no message was recieved buffer size is 0
 // if message is succesfully downloaded, convert to desired format  
 // Debugging code to read message through serial terminal
  
 Serial.println(F("Message received!"));
 Serial.print(F("Inbound message size is "));
 Serial.println(bufferSize);
 for (byte i=0; i<(byte)bufferSize; ++i)
  {
   Serial.print(F("Decimal = ")); Serial.println(buffer[i],DEC);  // displays message in decimal (ASCII) format
   Serial.print(F("HEX = ")); Serial.println(buffer[i], HEX);    // displays message in hex format
   Serial.print(F("Actual Message = "));
   if (isprint(buffer[i])) {
    Serial.print(("("));
    Serial.write(buffer[i]);
    Serial.println((")"));
    Serial.print(("("));
    Serial.print(buffer[i]);
    Serial.print((")"));
   }
   Serial.println((" "));
  }
  Serial.println();
    
  // data handling to store recieved data in variable
  
  if((byte)bufferSize!=0){
   for(byte i=1; i<5; ++i) {  // adds first four ASCII values together to form command code
    commandCode += buffer[i];
    oled.print(F("Command Code =")); Serial.print("command code  ="); Serial.println(commandCode);
   }
  
   for(byte i=5; i<(byte)bufferSize; ++i) { // adds subsequent characters together to get varChange value.
    byte ASCII_CONVERSION = buffer[i] - 48;   // Convert ASCII numbers to acutal numbers
    varChange = varChange *10 + ASCII_CONVERSION;           // Algorithm to get individual numbers as one value.
    oled.print(F("Variable Change = ")); Serial.print("VarChange = "); Serial.println(varChange);
   }
  }
 // Determine which code was sent and change respective variable.
   
 if(commandCode == 431) { // time: sum of ASCII values = 431
  Serial.print("right before interval is changed interval = "); Serial.println(telemInterval);
  telemInterval = varChange; // Change the conditional interval time to send a message
  Serial.print("in interval change new interval =  "); Serial.println(telemInterval);
 }
 if(commandCode == 416) { // chng: sum of ASCII values = 416
  // Must send in value 10x desired value (ex. sent: 10 actual 1.0)
  Serial.print("change condition is "); Serial.println(changeCond);
  changeCond = varChange / 10.0; // Change the conditional change value to send a message
  Serial.print("change condition was just changed to "); Serial.println(changeCond);
 }

 if(commandCode == 430) { // rari: sum of ASCII values = 430
  pollingInterval = varChange;   // Change the rate the rangeFind takes readings
 }

 if(commandCode == 418) { // isbd: sum of ASCII values = 418
  n = varChange; 
  isbd.adjustSendReceiveTimeout(n); // Adjust how long RockBLOCK will try to send message before timeout                                           
 }
}
void oledReset() {
  oled.begin(&Adafruit128x32, I2C_ADDRESS);
  oled.setFont(System5x7);
  oled.clear();
  oled.setCursor(0,0); 
}

long readVcc() {
 // Function provided by:
 // https://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
 
 // Read 1.1V reference against AVcc
 // set the reference to Vcc and the measurement to the internal 1.1V reference
 #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
   ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
   ADMUX = _BV(MUX5) | _BV(MUX0);
 #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
   ADMUX = _BV(MUX3) | _BV(MUX2);
 #else
   ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 #endif  

 delay(2); // Wait for Vref to settle
 ADCSRA |= _BV(ADSC); // Start conversion

 while (bit_is_set(ADCSRA,ADSC)); // measuring

 uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
 uint8_t high = ADCH; // unlocks both

 long result = (high<<8) | low;

 result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
 return result; // Vcc in millivolts
}

// diagnostic functions
#if DIAGNOSTICS

void ISBDConsoleCallback(IridiumSBD *device, char c)
{
  Serial.write(c);
}

void ISBDDiagsCallback(IridiumSBD *device, char c)
{
  Serial.write(c);
}

#endif
... and here are the pin modifications to make this work with the ItsyBitsy-
pinItsyBitsy.jpg
pinItsyBitsy.jpg (377.52 KiB) Viewed 233 times
I have spent many many hours trying to figure this out through various debugging strategies - tried various pins for RX/TX with no luck. I am certain the bug is buried somewhere within RX/TX. Any help would be greatly appreciated.
Last edited by biod101 on Mon Aug 09, 2021 6:49 pm, edited 2 times in total.

User avatar
adafruit_support_carter
 
Posts: 29150
Joined: Tue Nov 29, 2016 2:45 pm

Re: Itsy Bitsy vs ProTrinket RX/TX

Post by adafruit_support_carter »

Can you recreate the issue with a simple stand alone sketch? Something that doesn't use OLED, etc. Just something that sets up modem and then tries to talk to it.

User avatar
biod101
 
Posts: 183
Joined: Sun Apr 19, 2015 4:21 pm

Re: Itsy Bitsy vs ProTrinket RX/TX

Post by biod101 »

Yes,

The sketch below removes all references to OLED libraries and functions. and just tries to send a simple text message in the Loop. I verified the pins were mapped correctly and the lipo battery powering the modem was good. In response, the uploaded sketch successfully turns on the modem as verified from serial terminal output "Powering on modem..." coincident with power light on 9603 modem coming on, but the results are the same: No response on repeated AT command requests for feedback, and the process eventually times out. (On the ProTrinket, the modem responds immediately as long as the capacitor on the modem is charged, which I verified it is.)

See below for screenshot of the first three tries. The same thing happens when I try using a Feather hosting an ATmega32u4.

Could the problem have something to do with the native USB properties of this chip- similar to how a Leonardo can be recognized as a keyboard (same chip)? Maybe that property is interfering with the RX/TX communication required by the modem?

Code: Select all

// Please acknowledge Hans Huth and Sean Keane you use/modify this code
// simple test that should return RX/TX status to serial terminal upon AT command from Microcontroller

// Libraries

#include <IridiumSBD.h>
#include <SoftwareSerial.h>
  
// Rockblock settings

#define STATUS 13             // onboard pin (LED) to determine if modem engaged.
#define ROCKBLOCK_RX_PIN A0   // Recieve data pin from Rockblock (seial data from RockBLOCK)
#define ROCKBLOCK_TX_PIN A1   // Transmit data pin to Rockblock (serial data to RockBLOCK)
#define ROCKBLOCK_SLEEP_PIN 12 // on/off pin for power savings
#define ROCKBLOCK_BAUD 19200  // serial modem communication baud rate
#define CONSOLE_BAUD 115200   // serial terminal communications baud rate
#define DIAGNOSTICS true      // Set "true" to see serial diagnostics

// Timer Variables

unsigned long resetLoopTime = 0;  // Resets the loop timer back to 0 so it can be compared with telemInterval each loop
unsigned long loopStartTime = 0; // Used to register start time at top of loop()
unsigned long telemInterval = 86400000; // Time frequency for reporting status (30 minutes where 1000 = 1 second) **Can be modified remotely
                                       // In the field, set this to 24 hours, or 86400 seconds, or 86400000 milliseconds
unsigned long pollingInterval = 900000; // Time interval for checking status AND REPORTING DURING FLOW **Can be modified remotely
                                       // pollingInterval will impact cost: higher resolution = higher cost
                                       // Leave polling interval on two minutes, or 120000 milliseconds
                                       
SoftwareSerial ssIridium(ROCKBLOCK_RX_PIN, ROCKBLOCK_TX_PIN);  // type Arduino Stream
IridiumSBD isbd(ssIridium, ROCKBLOCK_SLEEP_PIN);               // this is my RockBLOCK

// Trigger pins/variables

String myStatus;
boolean flowDetected = 0;

// String and character array for posting to RockBLOCK server
String myUrl;           // String where all fields will be captured and posted
char url[256];          // for passing to modem stream
char url_old [256];     // If telemetry failed, this array stores the old field values

// Message Retrival Variables
uint8_t buffer[200];    // buffer for saving messages.
size_t bufferSize = sizeof(buffer); // Determines the size of the buffer. This gets set to 0 if no
                                    // message is recieved or if the message takes to long to download.

// Variables for battery voltage
long myVcc = 0.00;
String myVolts = "";

// Variables to determine message status
bool timeout = false; // If telemetry times out, this becomes true.
int err;              // Error code status for Iridium processes.
byte Setup = 1;       // Allows the loop to run immediatly for setup

//Variables to modify conditions through SMS
long varChange; // Variable for saving recieved message data and saving it to global variables. 
int n = 25; // Variable for setting adjustSendReceivetimeout or how long a message sending attempt will last.

void setup()
{
 // Start the serial port at 115200 baud rate so that I can 
 // see output on the a terminal from modem initiation 
 Serial.begin(CONSOLE_BAUD);
 Serial.println("REM Setup");
 
 // LED on 13 for status on modem communication
 pinMode(STATUS, OUTPUT); 

 // Sleep pin on modem
 pinMode(ROCKBLOCK_SLEEP_PIN, OUTPUT);

 // Setup the RockBLOCK
 isbd.adjustSendReceiveTimeout(n); // This deterimnes how long RockBLOCK will try to send message before timeout
                                   // Here, set to 25 seconds per attempt for timeout
  
 isbd.setPowerProfile(0); // Since we are running off battery in field, set to 0 
                          // Available power settings: 
                          // 1 = low current  (90 mA USB; 60 secs between transmit retries)
                          // 0 = high current (high current battery; 20 secs between transmit retries)

}

void loop() {
  
 Serial.println(F("In Loop")); 
 isbd.adjustSendReceiveTimeout(n);
  
 Serial.println(F("Transmit")); // Conditions met - start the transmission sequence

 // Start the serial port
 ssIridium.begin(ROCKBLOCK_BAUD);
 
 // Start talking to RockBLOCK
 Serial.println("Beginning to talk to the RockBLOCK...");
 ssIridium.listen();
    
 // Try sending a text if awoken okay
 if (isbd.begin() == ISBD_SUCCESS) { 
  digitalWrite(STATUS, HIGH); // modem awoke okay - turn on LED for 9602 for visual confirmation    
  isbd.useMSSTMWorkaround(false); 
  // isbd.begin() causes this to be set true.
  // To prevent lockout keep it set to false.
  
  // Monitoring functions
   
  myVcc = 4.00;    // returns REM battery voltage
   
  // variables to String  
  myStatus = String(flowDetected); 
    
  // battery
  myVolts = String(myVcc);

  // build post string
  myUrl = "," + myVolts + "," + myStatus + ",0"; // 0 required as terminator byte for RockBLOCK servers
  
  myUrl.toCharArray(url,256);
  if(timeout == true) {
   messageRetry();    // Function for resending previous unsent message (only attempts resends one time)
   incomingMessage(); // Check for incoming message
  }

  timeout= false; // Reset timeout variable (if a previous timeout message was not sent do not try it again).
  adaptiveRetry();    // function for sending/recieving data to server including retries
  incomingMessage();  // Check for incoming message
   
  // All done - go to sleep 
  
  isbd.sleep();
  ssIridium.end();
  digitalWrite(STATUS, LOW); // modem asleep   

  while(1);
 }
}




///// FUNCTIONS /////

void flowSense()
{
// simplified / ignore
    
}


void incomingMessage() {
   
 /* 
  *  Note whenever a message is sent the avaliable incoming messages are also downloaded. 
  *  If no message is in the inbox the buffer size (or sizeof(buffer)) = 0. 
  *  handleMessage() will only be called if a message was sucessfully downloaded.
  *
  */
  
  // 35 corresponds to # so any data sent must start with "#" (e.g. #time8640000)
  
   if(buffer[0] == 35) { // Sending messages also downloads available messages
    // First, report original telemetry and polling intervals 
    Serial.print("telemInterval = "); Serial.println(telemInterval);
    Serial.print("pollingInterval = "); Serial.println(pollingInterval);

    for(int kk=1;kk<201; kk++) {
     Serial.println(buffer[kk]);
    }
    
    handleMessage();        // Function for converting recieved messages
    // Now, report updated telemetry and polling intervals 
    Serial.print("telemInterval = "); Serial.println(telemInterval);
    Serial.print("Reading rate = "); Serial.println(pollingInterval);
   } // End incoming message handler
  }
 
void adaptiveRetry() {
  
  // When a new message is sent, the modem attempts to recieve a message. 
  // If one is available, it gets saved to buffer and the size gets saved to buffersize.
  // buffersize has shown to be unreliable:
 
   /* 
      A message might send without a 0 code, so another sendRecieveSBD session is started.
      In this case, buffersize gets set to 0 even though buffer has a messaged saved in it. 
     
      To counteract this issue, all messages sent must start with a "#" and end with a "!" 
      or the program will not register it.
   */

  memset(buffer, 0, sizeof(buffer)); // Reset the buffer before a new message comes in 
                                     // or it will be added to the next open space in the buffer.
  
   // Details regarding this function for handling bad transmissions are avaialble here:
   // https://docs.rockblock.rock7.com/docs/adaptive-retry

  for(int i=0;i<5;i++) 
  { // consider adding OLED output here  
    Serial.print(F("-------->>> Attempt# "));
    Serial.println(i+1);
    
    delay(5000);
    err = isbd.sendReceiveSBDText(url,buffer,bufferSize);
    Serial.print(F("Error Code = ")); Serial.println(err);
    // check status of sent message
    if (err != 0) { 
     // Latest attempt didn't work so echo status   
     Serial.println(F("sendSBDText failed."));
     Serial.println(F("Delaying before retry."));
     Serial.println(F("                      "));
     Serial.println(F("----------------------"));
     Serial.println(F("                      "));
     // Try again after specified delay
     if(i==0) 
      delay(random(0, 5000)); // 1st failure- trying second after random time 0-5 seconds
     else if(i<=2)
      delay(random(0, 30000)); // 2nd, 3rd failure- trying third, fourth after random time 0-30 seconds
     else if(i==3)
      delay(random(120000, 300000)); // 4th failure- trying fifth after random time between 120-300 seconds
     else if(i==4) {
      // If I'm here, the first five tries failed
      timeout = true;
      Serial.print(i+1);
      Serial.println(F(" attempts failed;"));  
      Serial.println(F("message not sent."));
      break;
     }
    }
   else
    // Message transmitted during first else, so
    // notificaiton will be provided in main loop.
    break;
  } // for loop closed
 
  // Since buffer would have been changed above if a message was recieved,
  // check that the first character was a # (Ascii value = 35).     
   
  if(buffer[0]==35){ 
  handleMessage(); // for incoming messages
 }
}

void messageRetry() {
 // If I'm here, timeout was set to true in prior iteration given failure after multiple tries,
 // so attempt to resend previous unsent post.

 
 //Attempt to download any message while sending.  
 err = isbd.sendReceiveSBDText(url_old,buffer,bufferSize);  
    
 if (err != 0) {
  isbd.sendReceiveSBDText(url_old,buffer,bufferSize);
  delay(15000); // Allow 15 seconds before thingspeak posts. 
 }
 else {
  delay(15000);
 }
}

/*
 * handleMessage() works by taking the buffer that the inbound message was saved in and initally reading the first four characters' 
 * ASCII values.  * These first four ASCII values are added together to form a command code. All the subsequent characters are read 
 * and they are turned into one value and saved into varChange. Using the unique command code a global variable is chosen and the 
 * value of varChange is saved inside to be used in the next loop cycles. Example: to change global variable for reporting interval 
 * (telemInterval) send in message #time600000 . The first four ASCII values for time add up to 116 + 105 + 109 + 101 = 431. 
 * The subsequent values [6,0,0,0,0,0] get converted from their ASCII values and saved as one number '600000'. This then changes the 
 * global Variable telemInterval to one hour (telemInterval = 600000);
 */

void handleMessage(){
  
 long varChange = 0;    // variable to change values inside program
 int commandCode = 0;   // variable to determine which command was recieved

 // If no message was recieved buffer size is 0
 // if message is succesfully downloaded, convert to desired format  
 // Debugging code to read message through serial terminal
  
 Serial.println(F("Message received!"));
 Serial.print(F("Inbound message size is "));
 Serial.println(bufferSize);
 for (byte i=0; i<(byte)bufferSize; ++i)
  {
   Serial.print(F("Decimal = ")); Serial.println(buffer[i],DEC);  // displays message in decimal (ASCII) format
   Serial.print(F("HEX = ")); Serial.println(buffer[i], HEX);    // displays message in hex format
   Serial.print(F("Actual Message = "));
   if (isprint(buffer[i])) {
    Serial.print(("("));
    Serial.write(buffer[i]);
    Serial.println((")"));
    Serial.print(("("));
    Serial.print(buffer[i]);
    Serial.print((")"));
   }
   Serial.println((" "));
  }
  Serial.println();
    
  // data handling to store recieved data in variable
  
  if((byte)bufferSize!=0){
   for(byte i=1; i<5; ++i) {  // adds first four ASCII values together to form command code
    commandCode += buffer[i];
   }
  
   for(byte i=5; i<(byte)bufferSize; ++i) { // adds subsequent characters together to get varChange value.
    byte ASCII_CONVERSION = buffer[i] - 48;   // Convert ASCII numbers to acutal numbers
    varChange = varChange *10 + ASCII_CONVERSION;           // Algorithm to get individual numbers as one value.
   }
  }
 // Determine which code was sent and change respective variable.
   
 if(commandCode == 431) { // time: sum of ASCII values = 431
  Serial.print("right before interval is changed interval = "); Serial.println(telemInterval);
  telemInterval = varChange; // Change the conditional interval time to send a message
  Serial.print("in interval change new interval =  "); Serial.println(telemInterval);
 }

 if(commandCode == 430) { // rari: sum of ASCII values = 430
  pollingInterval = varChange;   // Change the rate the rangeFind takes readings
 }

 if(commandCode == 418) { // isbd: sum of ASCII values = 418
  n = varChange; 
  isbd.adjustSendReceiveTimeout(n); // Adjust how long RockBLOCK will try to send message before timeout                                           
 }
}


long readVcc() {
 // Function provided by:
 // https://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
 
 // Read 1.1V reference against AVcc
 // set the reference to Vcc and the measurement to the internal 1.1V reference
 #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
   ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
   ADMUX = _BV(MUX5) | _BV(MUX0);
 #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
   ADMUX = _BV(MUX3) | _BV(MUX2);
 #else
   ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 #endif  

 delay(2); // Wait for Vref to settle
 ADCSRA |= _BV(ADSC); // Start conversion

 while (bit_is_set(ADCSRA,ADSC)); // measuring

 uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
 uint8_t high = ADCH; // unlocks both

 long result = (high<<8) | low;

 result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
 return result; // Vcc in millivolts
}

// diagnostic functions
#if DIAGNOSTICS

void ISBDConsoleCallback(IridiumSBD *device, char c)
{
  Serial.write(c);
}

void ISBDDiagsCallback(IridiumSBD *device, char c)
{
  Serial.write(c);
}

#endif
Attachments
serialOutput.jpg
serialOutput.jpg (124.33 KiB) Viewed 226 times

User avatar
biod101
 
Posts: 183
Joined: Sun Apr 19, 2015 4:21 pm

Re: ItsyBitsy vs ProTrinket RX/TX

Post by biod101 »

Here are the photos of the setup.
2.jpg
2.jpg (949.38 KiB) Viewed 224 times
1.jpg
1.jpg (833.61 KiB) Viewed 224 times
I'm using a wire harness for the modem for which I've mapped the associated wire colors to the modem pins as outlined here:
https://youtu.be/QlltAG-dOKc?t=669

I would absolutely LOVE to migrate our boxes away from ProTrinkets for which we've had some PC-communication issues recently. Any help or recommendations on alternatives is greatly appreciated - maybe something with a feather form factor? That would be SWEET since we could then take advantage of those terminal header mounts for wiring stuff up in the field. Any reason this might not work with a Feather MO?

User avatar
adafruit_support_bill
 
Posts: 88087
Joined: Sat Feb 07, 2009 10:11 am

Re: ItsyBitsy vs ProTrinket RX/TX

Post by adafruit_support_bill »

The 32U4 has native USB/Serial capability and communication via "Serial" uses the USB interface. It also supports TTL serial via the Rx and Tx pins. But these are accessed via the "Serial1" interface: https://learn.adafruit.com/introducting ... 2979410-14

User avatar
biod101
 
Posts: 183
Joined: Sun Apr 19, 2015 4:21 pm

Re: ItsyBitsy vs ProTrinket RX/TX

Post by biod101 »

It also supports TTL serial via the Rx and Tx pins. But these are accessed via the "Serial1" interface.
It's the Iridium library that is sending and receiving those RX/TX commands via the micro-controller. Sorry if I'm misunderstanding, but does your quote indicate that I would have to edit those libraries to use Serial1 (via pins 0 and 1 for RX/TX) in order to use this board?

BTW, I tried compiling the code for a Feather M4 which I have in stock, and received compilation errors associated with not finding SoftwareSerial.h.

Perhaps this would fix all my problems since it uses the original chipset found in the ProTrinket: Adafruit Feather 328P - Atmega328P 3.3V @ 8 MHz https://www.adafruit.com/product/3458 . I don't need speed - just reliability.

User avatar
adafruit_support_bill
 
Posts: 88087
Joined: Sat Feb 07, 2009 10:11 am

Re: ItsyBitsy vs ProTrinket RX/TX

Post by adafruit_support_bill »

but does your quote indicate that I would have to edit those libraries to use Serial1 (via pins 0 and 1 for RX/TX) in order to use this board?
Yes. Communication via "Serial" will go out over the USB interface. Your library would need to use Serial1 to communicate via TTL serial over the Rx and Tx pins. I'm not familiar with the library you are using. But it may be as simple as changing all occurrences of "Serial" to "Serial1".
BTW, I tried compiling the code for a Feather M4 which I have in stock, and received compilation errors associated with not finding SoftwareSerial.h.
SoftwareSerial works on the Atmega series processors like the 328P and 32U4. ATSAMD processors like the M0 and M4 have a totally different processor architecture. ATSAMD processors allow you to create additional hardware serial ports via SERCOM. This is much more powerful - albeit substantially more complicated than SoftwareSerial: https://learn.adafruit.com/using-atsamd ... rial-ports

User avatar
biod101
 
Posts: 183
Joined: Sun Apr 19, 2015 4:21 pm

Re: ItsyBitsy vs ProTrinket RX/TX

Post by biod101 »

As always, thanks Bill for your prompt and helpful guidance. Your response confirmed what I thought.

On a bright note, I found a Feather 328P in stock-- managed to get it to talk to and respond to the Iridium modem. The failure of the ItsyBitsy is fortuitous since it forced research into this much better alternative, at least for my purposes. I'll update this thread once I confirm the integration and testing of the OLED and ultrasonic is successful.

Locked
Please be positive and constructive with your questions and comments.

Return to “Itsy Bitsy Boards”