Nathaniel James Chapman

Design Portfolio

Embedded Code Sample

What follows is a basic Arduino code sample written for one of my course homework assignments. It is included to show my understanding of C++ concepts, and design constraints, as well as my general coding and commenting style. This code should probably be split into multiple header files for clarity, but is included here in its entirety. The assignment was to create a basic state machine that could control a tricolor LED’s hue and brightness from a variety of control sources including a button (with debouncing), a potentiometer, and serial software control from a PC. The full assignment details can be read here.

Code Sample

************************
* 16-681: MRSD Project Course
* Homework 2: Software familiarization
* Nathaniel Chapman
* njc@andrew.cmu.edu
************************/

#include <Arduino.h>

////  Pin definitions from the assignment    ////
#define BUTTON_0  2  //INT 0
#define BUTTON_1  3  //INT 1
#define RED_LED   9  //PB1
#define GREEN_LED 10 //PB2
#define BLUE_LED  11 //PB3
#define POT_PIN   A0
////  End Pin Defs  ////

////  Custom Data Types ////
class RGB {
	public:
		void setRGB(uint8_t r, uint8_t g, uint8_t b);
		void setHue(uint16_t Value);
		uint8_t red;
		uint8_t green;
		uint8_t blue;
		uint8_t alpha; //not used, but whatevs, fits nicely in a dword
};

void RGB::setRGB(uint8_t r, uint8_t g, uint8_t b) {
	this->red = r;
	this->green = g;
	this->blue = b;
}

//State 1 Hue Change function
//Takes a 10-bit (or greater) value to create a simple Hue changer
void RGB::setHue(uint16_t val) {
	//NOTE: this code was originally designed with drive-high LEDs (that's what I had at home)
	// but the pattern is merely color-inverted for drive-low ones
	uint8_t hue = (uint8_t) (val << 1);
	uint8_t antihue = 0xff - hue;
	uint8_t phase = ((uint8_t) (val >> 7)) & 0x07;
	switch(phase){
		case 0: //white -> red
			this->setRGB(0xff, antihue, antihue);
			break;
		case 1: //red -> yellow
			this->setRGB(0xff, hue, 0);
			break;
		case 2: //yellow -> green
			this->setRGB(antihue, 0xff, 0);
			break;
		case 3: //green -> cyan
			this->setRGB(0, 0xff, hue);
			break;
		case 4: //cyan -> blue
			this->setRGB(0, antihue, 0xff);
			break;
		case 5: //blue -> magenta
			this->setRGB(hue, 0, 0xff);
			break;
		case 6: //magenta -> red
			this->setRGB(0xff, 0, antihue);
			break;
		case 7: //red -> white
			this->setRGB(0xff, hue, hue);
			break;
	}
}

union Color {
	RGB rgb;
	uint32_t serialized;
};
//// End Data Types ////



//// State Machine Stuff ////
uint8_t state;
Color stateColors[3];

void initState() {
  state = 0; 
}

void nextState() {
  state = state + 1;
  if (state > 2) state = 0; //Compiles smaller than emulated % operator
  Serial.print("State: ");
  Serial.println(state);
}

//// End State Machine Stuff ////

////  Soft-debounced External Interrupt Handling  ////
#define BOUNCE_DELAY_MS 5  //the debounce time

//We don't need more than 255 ms worth of debouncing
//could be moved to GPIORs for faster ISRs, but not sure if Arduino clobbers those
volatile int8_t lastBounce0;
volatile int8_t lastBounce1;

//NOTE: millis() does not increment in ISRs, but should return the last value before the ISR fired
void extInt0() {lastBounce0 = (int8_t) millis();}
void extInt1() {lastBounce1 = (int8_t) millis();}

uint8_t lastValue0;
uint8_t lastValue1;

//separate init function for more modular code
//(could move to a separate file, or make a software-debounced interrupt library)
void initButtons(){
  attachInterrupt(0, extInt0, CHANGE);
  attachInterrupt(1, extInt1, CHANGE);
  
  //activate internal pullups
  digitalWrite(BUTTON_0, HIGH); 
  digitalWrite(BUTTON_1, HIGH); 
  
  lastValue0 = LOW;
  lastValue1 = LOW;
}

//poll the debounced button state
void pollButtons() {
  int8_t now = ((unsigned char) millis());
  uint8_t value0 = digitalRead(BUTTON_0);
  uint8_t value1 = digitalRead(BUTTON_1);
  
  if ((value0 != lastValue0) && ((now - lastBounce0) > BOUNCE_DELAY_MS)) {
	lastValue0 = value0;
	if(value0) nextState();
  }
  
  if ((value1 != lastValue1) && ((now - lastBounce1) > BOUNCE_DELAY_MS)) {
	lastValue1 = value1;
	if((value1) && (state == 0)) state0NextColor();
  }
}

//// End Button Debouncer//////

//// Serial Comms Stuff ////

//// Valid Commands are rXXX, gXXX, bXXX, p0, p1, sX
//where r,g,b sets the red, green, blue values respectively
//p0/p1 enables/disables Party Mode (LED Color Auto-cycles)
//and s0, s1, s2 sets the Device state to modes 0, 1, or 2

#define CMD_BUFFER_SIZE 6
char cmdBuffer[CMD_BUFFER_SIZE];
uint8_t cmdPtr; 

bool partyMode;
uint16_t partyStep;
uint16_t partyLastms;

void initSerial(){
	Serial.begin(9600);
	while (!Serial); //wait for serial port (for Leonardo compatiblity)
	Serial.setTimeout(0);
	partyMode = false;
	partyStep = 0;
	cmdPtr = 0; 
	
}

void pollSerial(){
	char recvByte = Serial.read();
	if(recvByte != -1) {
		if (recvByte == '\n' || recvByte == ' ') {
			cmdBuffer[cmdPtr++] = '\0';
			("Command Recieved!");
			uint8_t numVal = atoi(cmdBuffer + 1);
			Serial.println(cmdBuffer);
			//parse command
			switch(cmdBuffer[0]){
				case 'r':
					stateColors[2].rgb.red = numVal;
					break;
				case 'g':
					stateColors[2].rgb.green = numVal;
					break;
				case 'b':
					stateColors[2].rgb.blue = numVal;
					break;
				case 'p':
					if(numVal < 2) partyMode = numVal;
					break;
				case 's':
					if(numVal < 3) state = numVal;
					break;
			}
			cmdPtr = 0;
		} else {
			cmdBuffer[cmdPtr++] = recvByte;
			if (cmdPtr >= CMD_BUFFER_SIZE){
				Serial.println("Invalid Command!");
				cmdPtr = 0; //invalid command, discard buffer
			}
		}			
	} else {
		delay(10);
	}
}
//// End Serial Comms Stuff ////

////  LED Stuff  ////

//Allows us to define custom color cycle patterns for state 0 (can even use mixed colors)
//could move this to PROGMEM to save space, but that was causing problems...


//ROYGBIV
#define NUM_COLORS 7
uint32_t state0Colors[] = {0x000000ff, 0x000060ff, 0x0000ffff, 0x0000ff00, 0x00ff0000, 0x00ff0060, 0x006000ff}; 
uint8_t state0ColorIndex;

void state0NextColor() {
	state0ColorIndex++;
	if(state0ColorIndex >= NUM_COLORS) state0ColorIndex = 0; //Compiles smaller than emulated % function
	stateColors[0].serialized = state0Colors[state0ColorIndex]; 
	Serial.print("Next Color: ");
	Serial.print(stateColors[0].rgb.red, HEX);
	Serial.print(", ");
	Serial.print(stateColors[0].rgb.green, HEX);
	Serial.print(", ");
	Serial.println(stateColors[0].rgb.blue, HEX);
}

void initLed() {
  stateColors[0].serialized = state0Colors[0];
  state0ColorIndex = 0;
  
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
}

void setLedColor(union Color c) {    
	//0xff - inverts the LED colors for Drive Low System
	analogWrite(RED_LED, 0xff - c.rgb.red);
	analogWrite(GREEN_LED, 0xff - c.rgb.green);
	analogWrite(BLUE_LED, 0xff - c.rgb.blue);
}
////  End LED Stuff  ////

void setup() {
	state = 0;
	initButtons();
	initLed();
	initSerial();
}

void loop() {
	pollButtons();
	switch(state){
	  //case 0: do nothing... Button handler does it all
	  case 1:
		{
			stateColors[1].rgb.setHue(analogRead(POT_PIN));
			break;
		}
	  case 2:
		{
			pollSerial();
			if(partyMode){
				uint16_t now = millis();
				if (now - partyLastms >= 5) { //non-blocking 5 ms update
					stateColors[2].rgb.setHue(partyStep++);
					partyLastms = now;
				}
			}
			break;
		}
	} 
	setLedColor(stateColors[state]);
}