I'm working on OSX Mavericks, standard Arduino.app, and Arduino 2560 board.
This is by far, the single strangest bug I have ever encountered.
Long story short, this code:
#include "Arduino.h"
#include "Bitstreamer.h"
#include "TimerOne.h"
#include "BusyWaitByteProtocol.h"
Bitstreamer::Bitstreamer(int pin)
{
_pin = pin;
stage = 0;
bitholder = new boolean[8];
bitindex = 0;
bp = new BusyWaitByteProtocol (128);
}
Bitstreamer* Bitstreamer::Daemonholder = NULL;
void Bitstreamer::RunDaemon()
{
Bitstreamer::Daemonholder->Run();
}
void Bitstreamer::Listen()
{
myTurn = false;
Init();
}
void Bitstreamer::Shout()
{
myTurn = true;
Init();
}
void Bitstreamer::Init()
{
Daemonholder = this;
Timer1.initialize(cycleRate);
Timer1.attachInterrupt(RunDaemon);
stage++;
screamcounter = 0;
bitcounter = 0;
lastbit = false;
}
void Bitstreamer::Run()
{
if (stage == 1)
{
//We are initializing the protocol. If we are the shouter, scream for a bit. If we are the listener, wait for scream to end.
//The scream really just serves as an initial syncronizer.
if (myTurn)
{
pinMode(_pin, OUTPUT);
digitalWrite(_pin, HIGH);
}
else
{
pinMode(_pin, INPUT);
}
stage++;
}
else if (stage == 2)
{
if (myTurn)
{
screamcounter++;
if (screamcounter > 100)
{
digitalWrite(_pin, LOW);
stage++;
}
}
else
{
int state = digitalRead(_pin);
if (state == 1)
{
screamcounter++;
}
else
{
if (screamcounter > 30)
{
stage++;
}
screamcounter = 0;
}
}
}
else if (stage == 3)
{
if (myTurn)
{
bitcounter++;
if (bitcounter == 0)
{
digitalWrite(_pin, bitholder[bitindex]?HIGH:LOW);
}
else
{
if (bitcounter >= resendrate)
{
bitcounter = -1;
bitindex++;
}
if (bitindex >= 8)
{
bitindex = 0;
uint8_t nb = bp->RequestByte();
ReadBits(nb, bitholder);
//Swap turns as well
}
}
}
else
{
boolean state = (digitalRead(_pin) != 1);
if (state != lastbit)
{
float bits = ((float)bitcounter)/((float)resendrate);
int finalbits = round(bits);
debugstring = String(debugstring + String(state) + String(" ") + String(finalbits) + String("\n"));
//Process received bits
for (int i = 0; i < finalbits; i++)
{
bitholder[bitindex] = state;
bitindex++;
if (bitindex >= 8)
{
bitindex = 0;
uint8_t nb = WriteBits(bitholder);
bp->ProcessByte(nb);
//Swap turns
}
}
lastbit = state;
bitcounter = 0;
}
else
{
bitcounter++;
}
}
}
/*
if (myTurn)
{
if (hitcount < hits + 3)
{
digitalWrite(_pin, ((hitcount++)<hits)?HIGH:LOW);
}
else
{
hitcount = 0;
hits++;
lhits = hits;
}
}
else
{
int state = digitalRead(_pin);
if (state == 0)
{
hitcount++;
}
else
{
hitcount = 0;
hits++;
}
//lhits = hits;
if (hitcount >= 3)
{
lhits = hits;
hits = 0;
}
}
*/
}
byte Bitstreamer::Read()
{
//Load from incoming byte buffer
return 0;
}
void Bitstreamer::Write(byte b)
{
//Send to outgoing byte buffer
}
void Bitstreamer::ReadBits(uint8_t b, boolean* bitarray)
{
//DRAGONHERE: For loop would have been less code, but required initialization of another variable, and slightly more complicated logic for the processor
//Also, not having it in a for loop makes it easier for others to understand
bitarray[0] = (b & 1) != 0; // 1 = 00000001, IE 1st bit. (1 & b) lets only 1st bit through. != 0 checks if zero.
bitarray[1] = (b & 2) != 0; // 1 = 00000010, IE 2nd bit. (2 & b) lets only 2nd bit through. != 0 checks if zero.
bitarray[2] = (b & 4) != 0; // 1 = 00000100, IE 3rd bit. (4 & b) lets only 3rd bit through. != 0 checks if zero.
bitarray[3] = (b & 8) != 0; // 1 = 00001000, IE 4th bit. (8 & b) lets only 4th bit through. != 0 checks if zero.
bitarray[4] = (b & 16) != 0; // 1 = 00010000, IE 5th bit. (16 & b) lets only 5th bit through. != 0 checks if zero.
bitarray[5] = (b & 32) != 0; // 1 = 00100000, IE 6th bit. (32 & b) lets only 6th bit through. != 0 checks if zero.
bitarray[6] = (b & 64) != 0; // 1 = 01000000, IE 7th bit. (64 & b) lets only 7th bit through. != 0 checks if zero.
bitarray[7] = (b & 128) != 0; // 1 = 10000000, IE 8th bit. (128 & b) lets only 8th bit through. != 0 checks if zero.
//PLEASE NOTE: These explainations are based on the assumption that the bit order on Arduino is MSB-Left.
//Notably, this method of exploding bits will actually dynamically adjust to the bit order of the given system.
}
uint8_t Bitstreamer::WriteBits(boolean* b)
{
//DRAGONHERE: same as above
return
(b[0]? 1 : 0) +
(b[1]? 2 : 0) +
(b[2]? 4 : 0) +
(b[3]? 8 : 0) +
(b[4]? 16 : 0) +
(b[5]? 32 : 0) +
(b[6]? 64 : 0) +
(b[7]? 128 : 0);
}
compiles just fine, uploads to the Arduino, and works just as expected.
But this code:
#include "Arduino.h"
#include "Bitstreamer.h"
#include "TimerOne.h"
#include "BusyWaitByteProtocol.h"
Bitstreamer::Bitstreamer(int pin)
{
_pin = pin;
stage = 0;
bitholder = new boolean[8];
bitindex = 0;
bp = new BusyWaitByteProtocol (128);
}
Bitstreamer* Bitstreamer::Daemonholder = NULL;
void Bitstreamer::RunDaemon()
{
Bitstreamer::Daemonholder->Run();
}
void Bitstreamer::Listen()
{
myTurn = false;
Init();
}
void Bitstreamer::Shout()
{
myTurn = true;
Init();
}
void Bitstreamer::Init()
{
Daemonholder = this;
Timer1.initialize(cycleRate);
Timer1.attachInterrupt(RunDaemon);
stage++;
screamcounter = 0;
bitcounter = 0;
lastbit = false;
}
void Bitstreamer::Run()
{
if (stage == 1)
{
//We are initializing the protocol. If we are the shouter, scream for a bit. If we are the listener, wait for scream to end.
//The scream really just serves as an initial syncronizer.
if (myTurn)
{
pinMode(_pin, OUTPUT);
digitalWrite(_pin, HIGH);
}
else
{
pinMode(_pin, INPUT);
}
stage++;
}
else if (stage == 2)
{
if (myTurn)
{
screamcounter++;
if (screamcounter > 100)
{
digitalWrite(_pin, LOW);
stage++;
}
}
else
{
int state = digitalRead(_pin);
if (state == 1)
{
screamcounter++;
}
else
{
if (screamcounter > 30)
{
stage++;
}
screamcounter = 0;
}
}
}
else if (stage == 3)
{
if (myTurn)
{
bitcounter++;
if (bitcounter == 1)
{
digitalWrite(_pin, bitholder[bitindex]?HIGH:LOW);
}
else
{
if (bitcounter >= resendrate)
{
bitcounter = 0;
bitindex++;
}
if (bitindex >= 8)
{
bitindex = 0;
uint8_t nb = bp->RequestByte();
ReadBits(nb, bitholder);
//Swap turns as well
}
}
}
else
{
boolean state = (digitalRead(_pin) != 1);
if (state != lastbit)
{
float bits = ((float)bitcounter)/((float)resendrate);
int finalbits = round(bits);
debugstring = String(debugstring + String(state) + String(" ") + String(finalbits) + String("\n"));
//Process received bits
for (int i = 0; i < finalbits; i++)
{
bitholder[bitindex] = state;
bitindex++;
if (bitindex >= 8)
{
bitindex = 0;
uint8_t nb = WriteBits(bitholder);
bp->ProcessByte(nb);
//Swap turns
}
}
lastbit = state;
bitcounter = 0;
}
else
{
bitcounter++;
}
}
}
/*
if (myTurn)
{
if (hitcount < hits + 3)
{
digitalWrite(_pin, ((hitcount++)<hits)?HIGH:LOW);
}
else
{
hitcount = 0;
hits++;
lhits = hits;
}
}
else
{
int state = digitalRead(_pin);
if (state == 0)
{
hitcount++;
}
else
{
hitcount = 0;
hits++;
}
//lhits = hits;
if (hitcount >= 3)
{
lhits = hits;
hits = 0;
}
}
*/
}
byte Bitstreamer::Read()
{
//Load from incoming byte buffer
return 0;
}
void Bitstreamer::Write(byte b)
{
//Send to outgoing byte buffer
}
void Bitstreamer::ReadBits(uint8_t b, boolean* bitarray)
{
//DRAGONHERE: For loop would have been less code, but required initialization of another variable, and slightly more complicated logic for the processor
//Also, not having it in a for loop makes it easier for others to understand
bitarray[0] = (b & 1) != 0; // 1 = 00000001, IE 1st bit. (1 & b) lets only 1st bit through. != 0 checks if zero.
bitarray[1] = (b & 2) != 0; // 1 = 00000010, IE 2nd bit. (2 & b) lets only 2nd bit through. != 0 checks if zero.
bitarray[2] = (b & 4) != 0; // 1 = 00000100, IE 3rd bit. (4 & b) lets only 3rd bit through. != 0 checks if zero.
bitarray[3] = (b & 8) != 0; // 1 = 00001000, IE 4th bit. (8 & b) lets only 4th bit through. != 0 checks if zero.
bitarray[4] = (b & 16) != 0; // 1 = 00010000, IE 5th bit. (16 & b) lets only 5th bit through. != 0 checks if zero.
bitarray[5] = (b & 32) != 0; // 1 = 00100000, IE 6th bit. (32 & b) lets only 6th bit through. != 0 checks if zero.
bitarray[6] = (b & 64) != 0; // 1 = 01000000, IE 7th bit. (64 & b) lets only 7th bit through. != 0 checks if zero.
bitarray[7] = (b & 128) != 0; // 1 = 10000000, IE 8th bit. (128 & b) lets only 8th bit through. != 0 checks if zero.
//PLEASE NOTE: These explainations are based on the assumption that the bit order on Arduino is MSB-Left.
//Notably, this method of exploding bits will actually dynamically adjust to the bit order of the given system.
}
uint8_t Bitstreamer::WriteBits(boolean* b)
{
//DRAGONHERE: same as above
return
(b[0]? 1 : 0) +
(b[1]? 2 : 0) +
(b[2]? 4 : 0) +
(b[3]? 8 : 0) +
(b[4]? 16 : 0) +
(b[5]? 32 : 0) +
(b[6]? 64 : 0) +
(b[7]? 128 : 0);
}
fails to compile and gives the error:
/Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/../lib/gcc/avr/4.3.2/../../../../avr/lib/avr6/crtm2560.o: In function
__vector_default': (.vectors+0x90): relocation truncated to fit: R_AVR_13_PCREL against symbol
__vector_36' defined in .text.__vector_36 section in core.a(HardwareSerial.cpp.o)
The difference? You'll laugh...
In the working piece of code, I have:
Line #98: if (bitcounter == 0)
Line #106: bitcounter = -1;
In the not-working piece of code, I have:
Line #98: if (bitcounter == 1)
Line #106: bitcounter = 0;
Those are literally the only two differences. I, quite frankly, do not understand why in the name of goodness this does not work. What's even better is that:
Line #98: if (bitcounter == 0)
Line #106: bitcounter = 0;
And:
Line #98: if (bitcounter == 1)
Line #106: bitcounter = -1;
And even:
Line #98: if (bitcounter == 0)
Line #106: bitcounter = 1;
all work. It is the unique combination of 1, and 0, in that order, that causes my compiler to go berserk.
Is someone able to explain what the heck is going wrong here? I am seriously disturbed by this, and I can think of only one reason for this to happen -- Maybe my program is using up too much memory on the arduino? Nope. The original working code takes up, and I quote:
Binary sketch size: 10,178 bytes (of a 258,048 byte maximum)
I'm sorry, but I just don't see how changing two constants in my program can increase the final binary size by a total of 247,870 bytes.
So, what on earth am I doing wrong? Have I stumbled upon some insane bug in the compiler? Is this the result of my code somehow compiling to the same binary pattern as some poorly implemented developer-signal that tweaks the settings on the program-upload-receiver system on the Arduino? Or is there a much more logical answer to this mystery?