"Firmware, CAD, and ECAD, almost all med-tech design can be boiled down to a lightbox!"
For BME 290: Med-tech Prototyping, I was given an open-ended task of designing and assembling a "lightbox." A lightbox can be considered an enclosure housing a PCB and battery that powers three LEDs that perform various functions when buttons and potentiometers are used. My lightbox was meant to be turned on and off by a switch, and then activated by a push-button. When activated, depending on the position of the potentiometers, two LEDs would turn on at a particular brightness for a duration, and a third LED would flash at a rate prescribed by a second potentiometer.
I learned a great deal of CAD, ECAD, C++ firmware development, and most of all, effective methods of troubleshooting and problem-solving. My project was a resounding success, earning me an A+ and even a TA position for the class the following semester.
CAD Design
The key constraints were as follows: the design must be fun and creative, no linear dimension can exceed 15 cm, the enclosure must fit snuggly together, and the internal components must be intentionally placed inside, not just floating.
I am a big Batman fan, so I chose to do Batman's head, with the side view of the enclosure being the utility belt, complete with the buttons and knobs that control the LEDs. Four heat-set inserts secure the PCB to the enclosure and a rectangular slot houses the 9V battery. The lid sits snugly, secured by a rectangular wedge. If I could change one thing about the design, I would add a hole to the bottom of the enclosure so that I could plug in the board to upload code without disassembling everything.
ECAD DESIGN
Using KiCAD, I designed a PCB powered by an Arduino Nano Every to control the LEDs. The board was ground-filled and housed the Arduino, five resistors, and seven pins for the three LEDs, button, battery switch, and two potentiometers. The board also sported four mounting holes.
// LOAD ALL LIBRARIES FIRST
#include <Arduino.h>
#include <avr/sleep.h>
// DEFINE ALL PREPROCESSOR MACROS
// see pinout in datasheet to know what pins are where / do what
// NOTE: macros are defined in ALL CAPS to help identify their usage in the code (in contrast to variables)
// All device specification-related values should be defined here, *not* in the downstream code! You should
// not have to search the code for where values are defined / used.
#define LED_HEART_PIN 13 // D13 On-board LED
#define LED_FLASH_PIN 2 //D2 controls flashing LED
#define LED_BRIGHT_PIN 6 //D6 controls both dimable LEDs
#define ANALOG_IN_BRIGHT A0 //pot control of brightness
#define ANALOG_IN_BLINK A1 //pot control of blink rate
#define BUTTON_IN 4 //spst button is D4
#define HEARTBEAT_FREQ_HZ 1 //macro for LED blinking HB at 1 HZ
#define HEARTBEAT_DUTY_CYCLE 0.25 //macro for duty cycle of 25%
#define BUTTON_STATE_DURATION 10000 //10 second button state
#define LED_BRIGHT_BUTTON_DURATION 5000 //5 seconds on for the brightness controlled LEDs during button state.
#define HEART_ON_TIME_MS (1000 * (HEARTBEAT_DUTY_CYCLE)) //macro for time on = 0.25 seconds
#define HEART_OFF_TIME_MS (1000*(HEARTBEAT_FREQ_HZ - HEARTBEAT_DUTY_CYCLE)) //macro for time off = 0.75 seconds
#define MIN_BLINK_INT 100 //10 hz (1/10 seconds to ms)
#define MAX_BLINK_INT 200 //5 hz (1/5 seconds to ms)
// DEFINE ALL GLOBAL VARIABLES;
unsigned long last_heartbeat_time = 0;
unsigned long last_blink_time = 0;
bool led_heartbeat_state= LOW; //state of LED
bool led_blink_state = LOW;
bool button_pressed = false;
bool in_button_state = false;
unsigned long button_state_start_time = 0;
int blink_interval = MAX_BLINK_INT; //if dont work set to 0
int brightness_pwm = 0;
void button_press(void);
void heartbeat(void);
void blink_control(void);
void enter_button_state(void);
void button_state(void);
enum ButtonState { IDLE, BRIGHTNESS_LED_ACTIVE, FLASH_LED_ONLY,COMPLETE};
ButtonState current_state = IDLE;
// setup is run once when the device is powered on
void setup() {
// configure GPIO pin to be an output
pinMode(LED_HEART_PIN, OUTPUT);
pinMode(LED_FLASH_PIN, OUTPUT);
pinMode(LED_BRIGHT_PIN,OUTPUT);
pinMode(BUTTON_IN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_IN), button_press, FALLING);
// start the serial (communication) monitor
// 9600 is the baud rate (rate of data communication, 9600 bits/s)
// set increments; slower is more reliable
Serial.begin(9600);
Serial.println("INF: Device initialized.");
}
void button_press(void){
if(!in_button_state){
button_pressed = true;
detachInterrupt(digitalPinToInterrupt(BUTTON_IN)); // prevents multiple button presses
}
}
void heartbeat(void) {
unsigned long current_time = millis();
if (current_time - last_heartbeat_time >= (led_heartbeat_state ? HEART_ON_TIME_MS : HEART_OFF_TIME_MS)) {
led_heartbeat_state = !led_heartbeat_state;
digitalWrite(LED_HEART_PIN, led_heartbeat_state);
last_heartbeat_time = current_time;
}
}
void blink_control(void){
unsigned long current_time = millis();
int on_time = blink_interval*0.25;
int off_time = blink_interval-on_time;
if (led_blink_state && current_time - last_blink_time >= on_time){
digitalWrite(LED_FLASH_PIN, LOW);
led_blink_state = LOW;
last_blink_time = current_time;
} else if (!led_blink_state && current_time - last_blink_time >= off_time){
digitalWrite(LED_FLASH_PIN, HIGH);
led_blink_state = HIGH;
last_blink_time = current_time;
}
}
void enter_button_state(void){
Serial.println("Entering button state.");
in_button_state = true;
//take pot readings
float blink_pot_value = analogRead(ANALOG_IN_BLINK)*(5.0/1024.0); //adjust to account for maximum voltage expected after resistors
blink_interval = MIN_BLINK_INT + (MAX_BLINK_INT - MIN_BLINK_INT) * (blink_pot_value / 2.5); // Map 0-2.5V to MIN_BLINK_INT-MAX_BLINK_INT
blink_interval = constrain(blink_interval, MIN_BLINK_INT, MAX_BLINK_INT);
Serial.print("blink_interval: ");
Serial.println(blink_interval);
float raw_blink_value = analogRead(ANALOG_IN_BLINK)*(5.0/1024.0);
Serial.print("Raw Blink Potentiometer Value (V): ");
Serial.println(raw_blink_value);
float bright_pot_value = analogRead(ANALOG_IN_BRIGHT)*(5.0/1024.0);//adjust to account for maximum voltage expected after resistors
brightness_pwm = (255) * (bright_pot_value / 2.5); // Map 0-2.5V to 0-255 for PWM
brightness_pwm = constrain(brightness_pwm, 0, 255);
//Serial.print("Percentage of max brightness: ");
//Serial.println((brightness_pwm/255.0)*100);
// int raw_bright_value = analogRead(ANALOG_IN_BLINK);
//Serial.print("Raw Bright Potentiometer Value: ");
//Serial.println(raw_bright_value);
//initialize button state
Serial.println("Turning on LEDs");
analogWrite(LED_BRIGHT_PIN, brightness_pwm);
digitalWrite(LED_FLASH_PIN, HIGH);
led_blink_state = HIGH;
last_blink_time = millis();
button_state_start_time = millis();
button_pressed = false;
current_state = BRIGHTNESS_LED_ACTIVE; //switch into brightness LED active case
}
void button_state(void){
unsigned long time_elapsed = millis() - button_state_start_time;
switch (current_state) {
case IDLE:
Serial.println("Error: Called button_state in IDLE.");
break;
case BRIGHTNESS_LED_ACTIVE:
if (time_elapsed >= BUTTON_STATE_DURATION) {
// Both LEDs turn off after 10 seconds
digitalWrite(LED_FLASH_PIN, LOW);
digitalWrite(LED_BRIGHT_PIN, LOW);
Serial.println("Button state complete. Exiting.");
current_state = COMPLETE;
} else if (time_elapsed >= LED_BRIGHT_BUTTON_DURATION) {
// Turn off "brightness" LEDs after 5 seconds
if (digitalRead(LED_BRIGHT_PIN) == HIGH) { // Only log once
digitalWrite(LED_BRIGHT_PIN, LOW);
Serial.println("Brightness LED duration complete.");
}
}
// "Flash" LEDs blink throughout the 10-second duration
blink_control();
break;
case COMPLETE:
// Reset state and re-enable interrupt
digitalWrite(LED_FLASH_PIN, LOW);
digitalWrite(LED_BRIGHT_PIN, LOW);
in_button_state = false;
current_state = IDLE;
Serial.println("Resetting to IDLE.");
attachInterrupt(digitalPinToInterrupt(BUTTON_IN), button_press, FALLING);
break;
}
}
void loop(){
heartbeat();
if (button_pressed){
Serial.println("Button Press Detected");
enter_button_state();
}
if (in_button_state){
button_state();
}
}
Click above to see the full Main.c firmware file. I chose to use a state machine to power the lightbox, flipping between idle and activated whenever a button press was detected. I used PWM to control the brightness of one of the LEDs, while each potentiometer supplied a varying voltage reading to the Arduino, which was then converted into a range of blinking frequencies or brightness percentage using a linear relationship. Finally, I added a "heartbeat" to the Arduino onboard LED so that I would know whether my code had been successfully uploaded.