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
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.