Hello all,
I'm having a bit of a strange issue. Sometimes, when requested to send data, the Arduino simply stops responding. The RX LED flashes in time with the request over serial, but the LEDs I am controlling at the moment do not hit their initialized states, and the Arduino fails to emit a response over serial, evidenced by a lack of TX flash. This seems to happen when booting up the program which sends serial messages to the Arduino, at random. This is (somewhat) reproducible by resetting the Arduino when it is in the live environment, and solved by doing the same.
To elaborate on the last bit, if I am letting it run and respond and all is well, then hit the reset button on the Arduino MEGA, three things can happen:
-
The onboard LED turns off, and TX flashing stops, while RX continues to pulse in time with messages being sent to the Arduino. No other LEDs I have connected work properly.
-
The onboard LED turns off, then back on, and TX flashing stops, while RX continues to pulse in time with messages being sent to the Arduino. No other LEDs I have connected work "properly" (the LED on pin13 is lit up as it shares the state of the onboard LED, but it shouldn't be if the program initializes correctly.)
-
The onboard LED turns off, then flashes twice, and TX/RX respond normally. The program executes as intended and handles messages perfectly.
Any of these can occur with an approximate distribution of 40/40/20, so that the desired state is harder to reach but can be achieved by resetting many times in a row.
The setup into which I am inserting my Arduino MEGA is serving HTTP requests from a client for the state of 7 LEDs. The HTTP Flask server is hosted on a Raspberry Pi which writes serial messages to the Arduino over USB serial.
The above behavior does not occur when connected to my desktop and using either the Arduino IDE serial monitor or an equivalent Python script, although I do notice that if I disconnect it in a failed state and connect it to the desktop, it takes quite a lot longer than normal to start up and initialize to the desired state.
Side note, is posting gists acceptable here?
#include <Wire.h> // Enables I2C
#include <stdint.h> // Enables strict byte-size of variable types
#include <HardwareSerial.h> // Enables Serial.read
#include <Regexp.h> // Enables regex
// Pin Declarations
int FUEL_Press_ACTPIN = 13;
int LOX_Press_ACTPIN = 12;
int FUEL_Vent_ACTPIN = 11;
int LOX_Vent_ACTPIN = 10;
int MAIN_ACTPIN = 9;
int FUEL_Purge_ACTPIN = 8;
int LOX_Purge_ACTPIN = 7;
int FUEL_Press_READPIN = 2;
int LOX_Press_READPIN = A5;
int FUEL_Vent_READPIN = A4;
int LOX_Vent_READPIN = A3;
int MAIN_READPIN = A2;
int FUEL_Purge_READPIN = A1;
int LOX_Purge_READPIN = A0;
// Union Declarations
typedef union FourBytes{
uint32_t int_dat;
unsigned char bytes[4];
};
// Union instantiations in packet structure order
// For sending status updates
FourBytes Packet_Start;
char FUEL_Press_Send;
char LOX_Press_Send;
char FUEL_Vent_Send;
char LOX_Vent_Send;
char MAIN_Send;
char FUEL_Purge_Send;
char LOX_Purge_Send;
FourBytes Terminator;
const int VALVE_MESSAGE_LENGTH = 15;
char ValveDataMessage[VALVE_MESSAGE_LENGTH];
// Thereotically 1.3ms
// Practically has to be found empirically
static int BUFFER_DELAY = 5;
// Global variables for storing current state values
bool FUEL_Press;
bool LOX_Press;
bool FUEL_Vent;
bool LOX_Vent;
bool MAIN;
bool FUEL_Purge;
bool LOX_Purge;
// Global variables for receiving instructions over serial
const int MESSAGE_LENGTH = 14;
char ReceivedChars[MESSAGE_LENGTH+2];
// Comparison Packet for verifying instruction messages
// FUEL_Pres(S), LOX_Pres(s), FUEL_Ven(T), LOX_Ven(t), (M)ain, FUEL_Purg(E), FUEL_Purg(e)
// Interspersed with dummy request character holders
const char InstructionTemplate[14] = {'S','\0','s','\0','T','\1','t','\1','M','\0','E','\0','e','\0'};
// Global variables for storing desired states
// For receiving instructions
bool FUEL_Press_Desired;
bool LOX_Press_Desired;
bool FUEL_Vent_Desired;
bool LOX_Vent_Desired;
bool MAIN_Desired;
bool FUEL_Purge_Desired;
bool LOX_Purge_Desired;
bool MESSAGE_GOOD = false;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
// Initialize the constant union items
Packet_Start.int_dat = 0;
Terminator.int_dat = pow(2,32)-1; //4294967295;
// Initialize the unchanging parts of the valve message
memcpy(&ValveDataMessage[0], Packet_Start.bytes, 4);
memcpy(&ValveDataMessage[11], Terminator.bytes, 4);
// Initialize the normally <open/closed> behavior of the actuators
FUEL_Press_Desired = false; // NORMALLY CLOSED
LOX_Press_Desired = false; // NORMALLY CLOSED
FUEL_Vent_Desired = true; // NORMALLY OPEN
LOX_Vent_Desired = true; // NORMALLY OPEN
MAIN_Desired = false; // NORMALLY CLOSED
FUEL_Purge_Desired = false; // NORMALLY CLOSED
LOX_Purge_Desired = false; // NORMALLY CLOSED
// Initialize output pins
pinMode(FUEL_Press_ACTPIN, OUTPUT);
pinMode(LOX_Press_ACTPIN, OUTPUT);
pinMode(FUEL_Vent_ACTPIN, OUTPUT);
pinMode(LOX_Vent_ACTPIN, OUTPUT);
pinMode(MAIN_ACTPIN, OUTPUT);
pinMode(FUEL_Purge_ACTPIN, OUTPUT);
pinMode(LOX_Purge_ACTPIN, OUTPUT);
// Initialize input pins for sensing actuator state
pinMode(FUEL_Press_READPIN, INPUT);
pinMode(LOX_Press_READPIN, INPUT);
pinMode(FUEL_Vent_READPIN, INPUT);
pinMode(LOX_Vent_READPIN, INPUT);
pinMode(MAIN_READPIN, INPUT);
pinMode(FUEL_Purge_READPIN, INPUT);
pinMode(LOX_Purge_READPIN, INPUT);
// Execute Initializations
for ( int a = 0; a < sizeof(ReceivedChars); a++ ) {
ReceivedChars[a] = '0';
}
VerifyStates();
}
void loop() {
// put your main code here, to run repeatedly:
// Pull the timestamp at the start of the loop
ReceiveData();
if (MESSAGE_GOOD == true) {
ParseMessage();
VerifyStates();
SendUpdate();
}
}
void ReceiveData() {
// Label for resetting the reads
READ_RESET:
// Control variables
static byte MessageIndex = 0;
char Starter = '<';
char Terminator = '>';
char ReceivedChar;
MESSAGE_GOOD = false;
if (Serial.available() > 0) {
// move everything back by 1 index and read the next byte into the last index
for(int i = 0; i < MESSAGE_LENGTH+1 /*All but the last character*/;i++){
ReceivedChars[i] = ReceivedChars[i+1];
}
ReceivedChars[MESSAGE_LENGTH+1] = Serial.read();
// Validate
if (ReceivedChars[0] != Starter) {
// First check that the first character is a message starter
goto READ_RESET;
}else if (ReceivedChars[MESSAGE_LENGTH+1] != Terminator) {
// Check that the last character in the buffer is a message terminator
goto READ_RESET;
} else {
// Perform regex on the received message
static MatchState ms;
ms.Target( ReceivedChars );
// Check if the message is a status update request
char result = ms.Match ("(%?%?%?%?%?%?%?%?%?%?%?%?%?%?)");
if (result == REGEXP_MATCHED) {
// Status update request
SendUpdate();
goto READ_RESET;
}
// Then check if the message is an instruction
result = ms.Match ("(S[01]s[01]T[01]t[01]M[01]E[01]e[01])");
if (result == REGEXP_MATCHED) {
// Matches template, allow MESSAGE_GOOD = true
}
else if (result == REGEXP_NOMATCH) {
// Does not match template, reject
goto READ_RESET;
}
else {
// shit pant
goto READ_RESET;
}
}
// If no gotos are executed, the message is correct and can be used
MESSAGE_GOOD = true;
}
}
void ParseMessage() {
// Pull the instructions out of the message
// '1' needs to be mapped to True
// '0' needs to be mapped to False
FUEL_Press_Desired = ByteToBool(ReceivedChars[2]);
LOX_Press_Desired = ByteToBool(ReceivedChars[4]);
FUEL_Vent_Desired = ByteToBool(ReceivedChars[6]);
LOX_Vent_Desired = ByteToBool(ReceivedChars[8]);
MAIN_Desired = ByteToBool(ReceivedChars[10]);
FUEL_Purge_Desired = ByteToBool(ReceivedChars[12]);
LOX_Purge_Desired = ByteToBool(ReceivedChars[14]);
// With the data extracted, prepare to read a new instruction
MESSAGE_GOOD = false;
}
void VerifyStates() {
// Read the sense wire states
FUEL_Press = digitalRead(FUEL_Press_READPIN);
LOX_Press = digitalRead(LOX_Press_READPIN);
FUEL_Vent = digitalRead(FUEL_Vent_READPIN);
LOX_Vent = digitalRead(LOX_Vent_READPIN);
MAIN = digitalRead(MAIN_READPIN);
FUEL_Purge = digitalRead(FUEL_Purge_READPIN);
LOX_Purge = digitalRead(LOX_Purge_READPIN);
// Compare the read states to the desired states and set them to be equal to the desired
// If FUEL_Press does not match the ordered state
if (FUEL_Press != FUEL_Press_Desired) {
// Write the new state to the pin
digitalWrite(FUEL_Press_ACTPIN, FUEL_Press_Desired);
}
// If LOX_Press does not match the ordered state
if (LOX_Press != LOX_Press_Desired) {
// Write the new state to the pin
digitalWrite(LOX_Press_ACTPIN, LOX_Press_Desired);
}
// If FUEL_Vent does not match the ordered state
if (FUEL_Vent != FUEL_Vent_Desired) {
// Write the new state to the pin
digitalWrite(FUEL_Vent_ACTPIN, FUEL_Vent_Desired);
}
// If LOX_Vent does not match the ordered state
if (LOX_Vent != LOX_Vent_Desired) {
// Write the new state to the pin
digitalWrite(LOX_Vent_ACTPIN, LOX_Vent_Desired);
}
// If MAIN does not match the ordered state
if (MAIN != MAIN_Desired) {
// Write the new state to the pin
digitalWrite(MAIN_ACTPIN, MAIN_Desired);
}
// If FUEL_Purge does not match the ordered state
if (FUEL_Purge != FUEL_Purge_Desired) {
// Write the new state to the pin
digitalWrite(FUEL_Purge_ACTPIN, FUEL_Purge_Desired);
}
// If FUEL_Purge does not match the ordered state
if (LOX_Purge != LOX_Purge_Desired) {
// Write the new state to the pin
digitalWrite(LOX_Purge_ACTPIN, LOX_Purge_Desired);
}
}
void SendUpdate() {
// Check the current status
FUEL_Press = digitalRead(FUEL_Press_READPIN);
LOX_Press = digitalRead(LOX_Press_READPIN);
FUEL_Vent = digitalRead(FUEL_Vent_READPIN);
LOX_Vent = digitalRead(LOX_Vent_READPIN);
MAIN = digitalRead(MAIN_READPIN);
FUEL_Purge = digitalRead(FUEL_Purge_READPIN);
LOX_Purge = digitalRead(LOX_Purge_READPIN);
// Map the boolean values to the corresponding bytes
FUEL_Press_Send = BoolToByte(FUEL_Press);
LOX_Press_Send = BoolToByte(LOX_Press);
FUEL_Vent_Send = BoolToByte(FUEL_Vent);
LOX_Vent_Send = BoolToByte(LOX_Vent);
MAIN_Send = BoolToByte(MAIN);
FUEL_Purge_Send = BoolToByte(FUEL_Purge);
LOX_Purge_Send = BoolToByte(LOX_Purge);
// Serial Writes
// Writing the actuator states to the Serial Buffer
memcpy(&ValveDataMessage[4], &FUEL_Press_Send, 1);
memcpy(&ValveDataMessage[5], &LOX_Press_Send, 1);
memcpy(&ValveDataMessage[6], &FUEL_Vent_Send, 1);
memcpy(&ValveDataMessage[7], &LOX_Vent_Send, 1);
memcpy(&ValveDataMessage[8], &MAIN_Send, 1);
memcpy(&ValveDataMessage[9], &FUEL_Purge_Send, 1);
memcpy(&ValveDataMessage[10], &LOX_Purge_Send, 1);
// Write the array to the serial buffer
Serial.write(ValveDataMessage, VALVE_MESSAGE_LENGTH);
// Wait for the buffer to clear
delay(BUFFER_DELAY);
}
char BoolToByte(const bool Boolean) {
char Byte;
if (Boolean == false) {
return Byte = '0';
} else if (Boolean == true) {
return Byte = '1';
}
}
bool ByteToBool(const char Byte) {
bool Boolean;
if (Byte == '0') {
return Boolean = false;
} else if (Byte == '1') {
return Boolean = true;
}
}
This is how the Python program sends a status request (Formatted as 14 ?'s surround by <>):
# Generate a polling message for the Arduino
# A string of same length as the instruction message for simplicity
status_request_char = b'\x3F'
status_request = b'\x3C'
for i in range(0,14):
status_request += status_request_char
status_request += b'\x3E'
ser.write(status_request)
As stated, this works intermittently, so the serial setup is "proper" in that in best cases it works fine. I am not sure how to start debugging this, really, so I figured I would ask here.
If all else fails, it seems like once the setup is running properly it does not randomly shut down, so it could be made to work, though it seems inexplicably weird that this occurs at all.