Cascadia

Cascadia is a zinc-air powered vehicle that uses an iodine clock timing reaction. The vehicle is actuated with an Arduino controller that has custom electronics and an algorithm designed to reduce operational errors. Safe operation is emphasized in the design features.

View Poster Poster Template (Editable)

Required fonts for template: Hero, Lane Narrow

Mechanical Design

Integrated water tank for even weight distribution. Secure suspension ensure consistent steering. High-traction wheels prevent slipping.

Google Drive link for CAD models (Autodesk Inventor files):

FREE TO DOWNLOAD AND SHARE :)

Please direct any design or CAD questions to Thanos: captain@ubcchemecar.com

Frame

The frame anchors are glued into the top frame section so that we do not have holes pertruding into the water tank. We drill holes and use epoxy glue to secure them. The nut is secured inside the anchor by pausing the 3D print mid print and inserting the nut.

Power Source

The motor runs on six zinc-air batteries in series. Oxidation of zinc on the anode releases electrons which pass through an external circuit and travel to the cathode where oxygen is reduced to hydroxide ions.

Electrochemistry


Cathode (Reduction of Oxygen): $$ O_2 + 2H_2O + 4e^- \rightarrow 4OH^-; \quad E^0 = 0.40 V $$

Anode (Oxidation of Zinc): $$ 2Zn \rightarrow 2Zn^{2+} + 4e^- $$ $$ 2Zn^{2+} + 4OH^{-} \rightarrow 2Zn(OH)_2; \quad E^0 = 1.25 V $$ $$ 2Zn(OH)_2 \rightarrow 2ZnO + 2H_2O $$

Overall Reaction: $$ 2Zn + O_2 \rightarrow 2ZnO; \quad E^0 = 1.65 V $$

Wang, Xianyou, et al. "Studies on the oxygen reduction catalyst for zinc–air battery electrode." Journal of power sources 124.1 (2003): 278-284.

The oxygen reduction reaction (ORR) can proceed through a 2-electron pathway or a more efficient 4-electron pathway depending on the cathode used. Further reading: Nørskov, Jens Kehlet, et al. "Origin of the overpotential for oxygen reduction at a fuel-cell cathode." The Journal of Physical Chemistry B 108.46 (2004): 17886-17892.

Recipe

Zinc powder, 6M potassium hydroxide solution, separator (or membrane, we use Viledon filter paper), Gaskatel MnO2 gas diffusion electrodes (GDE) (alternative: any ORR air cathodes).

  1. Mix KOH solution with zinc powder to form a slurry.
  2. Immerse filter paper in KOH solution for ~5 minutes.
  3. Spread the zinc-KOH slurry onto the current collector (we use a nickel plate, alternative: stainless steel).
  4. Place the separator on top of the slurry.
  5. Place the Gaskatel GDE on top of the separator.
  6. Seal the cell and measure the open circuit potential.

Further Reading (we simplified our procedures by not doing gelling):
Mohamad, A. A. "Zn/gelled 6M KOH/O 2 zinc–air battery." Journal of power sources 159.1 (2006): 752-757. .

Electrical

An Arduino microcontroller is the brain of the car. The Arduino collects sensor readings, performs calculations, drives the motor and transmits test data via Bluetooth.

Arduino Code

                
// Ngai To Lo, Thanos Kritharis
// Build 1.5 Refactor: Siang Lim

/* Regionals 2017 Build 1.5
/* sensor design is LDR */
/* valve control is solenoid valve*/

//initiating digital pins
#define WHEELDIAMETER 15.18             // Actual wheel (not encoder wheel) diameter in cm
#define ENCODERRESOLUTION 60.0      // Number of teeth on the encoder wheel
#define DARKPOINTSTHRESHOLD 20  // Threshold for number of dark points for triggering the clock
#define REPORTTIME 5000             // Interval to print values to mobile app
#define MIXERSPEED 28               // Equation is V=(X/256)*5, where X is a value between 1-256 (How fast the mixer spins)
#define DLDR 60                     // Clock sensitivity: Change in reference value required to trigger clock
#define BUTTONPRESSED 1 //Value to indicate that button is pressed

int ldrRef = 995;       //Reference value average
int ldrRefl = 1000;     //Reference value for sensor lower
int ldrRefu = 1005;     //Reference vallue for sensor upper
int encoderSensor = 2;  //Pin used to measure optosensor input 
int startButton = 7;    //Pin used to control startup (Input)
int onLight = 8;        //Pin used to turn on button led
int motor = 10;         //Pin used to control motor
int laser = 6;          //Pin used to control laser
int mixer = 11;         //Pin used to control mixer motor
int valve = 12;         //Pin used to control valve

// New function to count the number of outliers
int darkPointsCounter = 0; 
bool clockFlag = true; // Flag for turning the clock on/off

//Initiating analog pins
int LDR = A0; //Pin used to measure light dependant resistor (Analog)

//Variables for when the car is running
bool switchOn = false; //Create boolean to check if circuit is switched on or not
bool shutDown = false;//bool for status to shut down system
int optoVal ; //Create optoVal
int switchVal = 0;//Create default value for switch pin (input)
int count;
int countFIVE;
double distance;
int encoderRef;
double carSpeed;

//functions
void checkOnSwitch();
void startClock(double ldrVal);
bool clockAboveThreshold(int ldrRef);

//Assigning pins inputs/outputs
void setup(){   
    pinMode(encoderSensor,INPUT);
    pinMode(startButton,INPUT); //set start in pin to input
    pinMode(onLight,OUTPUT);
    pinMode(motor,OUTPUT); //set motor pin to output
    digitalWrite(motor,LOW); //set motor
    pinMode(laser,OUTPUT); //set start out pin to output
    digitalWrite(laser,HIGH);//keep laser on
    pinMode(mixer,OUTPUT); //set mixer pin to output
    digitalWrite(mixer,LOW); //set mixer pin to off
    pinMode(valve, OUTPUT);      
    Serial.begin(9600);//begin communication with serial monitor
}

// Void loop
void loop (){
    int ldrVal = analogRead(LDR);//Prevent car from starting until ref value is read

    ///////////////////////////////////////////////
    // This block is for flashing LEDs
    ///////////////////////////////////////////////
    while(ldrVal < ldrRefl || ldrVal > ldrRefu){ //Blink light every 100 ms to indicate to user that value is not at reference
        digitalWrite(onLight,HIGH);
        delay(100);
        digitalWrite(onLight,LOW);
        delay(100);
        ldrVal = analogRead(LDR); //read ldr value
    //Serial.println(ldrVal);
        if (digitalRead(startButton) != 0){//hold button for 10 seconds to reset LED REF value
          digitalWrite(onLight,HIGH);
          long resetTimeRef = millis(); //begin countdown to reset
          while(startButton != 0){
            long timeElapsed = millis() - resetTimeRef;
            if (timeElapsed > 10000){
              ldrRef = analogRead(LDR); //reset ref value
              digitalWrite(onLight,LOW);
            }
          }
        }
    }

    delay(300);
    Serial.println("LDR at reference. \n");

    ///////////////////////////////////////////////
    // Car check on/off switch here
    ///////////////////////////////////////////////
    // This function keeps looping until we turn the switch on.
    checkOnSwitch(); 

    // Initialize encoder and time counter variables
    shutDown = false;
    count = 0;
  countFIVE = 0;
    distance = 0;
    darkPointsCounter = 0;
    // Check initial encoder wheel position
    int encoderRef = digitalRead(encoderSensor);
    delay(50);

    // Start the total time timer before the 5 second delay
    long totalTimeInit = millis();
    long totalTime;

    // Start checking clock
    // open valve, and mixer; wait 5 seconds for fluid to pass into reactor, 
    // then begin the motor.
    digitalWrite(valve, HIGH);
    analogWrite(mixer, MIXERSPEED);
    //delay(5000);
    //digitalWrite(valve,LOW);
    digitalWrite(motor,HIGH);

    ///////////////////////////////////////////////
    // INITIALIZE APP TIMER VARIABLES
    ///////////////////////////////////////////////

    // Start the car runtime timer after the 5 second delay
    long carTimeInit = millis();
    long carTime;
    bool encoderFlag;

    // While LDR value is above threshold
    while (clockAboveThreshold(ldrRef) && digitalRead(startButton) == 0){
        carTime = millis() - carTimeInit; // Count car time elapsed in millisecs

        // Report values every 5 seconds
        if (carTime % REPORTTIME == 0){
      distance = (double) countFIVE*1.0/(double)ENCODERRESOLUTION*(double)(WHEELDIAMETER)*3.14;
      carSpeed = (double) distance*1.0/5.0;
      Serial.print("five count: ");
      Serial.print(countFIVE);
            Serial.print("LDR:");
      Serial.println(analogRead(LDR));
            Serial.print("Time (s): ");
      Serial.println(carTime);
      Serial.print("Distance (cm): ");
      Serial.println(distance);      
      Serial.print("Velocity (cm/s): ");
      Serial.println(carSpeed);
      countFIVE = 0;
        }
    
        // Check if encoder position has changed
        // Reference is the initial encoder value (either dark or light, or 1 or 0)
        if(digitalRead(encoderSensor) != encoderRef){
            count++;
      countFIVE++;
            encoderRef = digitalRead(encoderSensor);
        }
    //Serial.println(count);
    //Serial.println(digitalRead(encoderSensor));
    }

    ///////////////////////////////////////////////////////
    // Once the clock is triggered, or button pressed
    // Report final values and turn of motor and mixer
    ///////////////////////////////////////////////////////

    // Calculate total time (which is before the 5 second delay for the motor)
    totalTime = millis() - totalTimeInit;

    // Turn off the motor and valve
    digitalWrite(motor, LOW);
    analogWrite(mixer, 0);
    digitalWrite(onLight,LOW);
  digitalWrite(valve,LOW);

    // Calculate the distance
    distance = (double) count*1.0/(double)(ENCODERRESOLUTION)*WHEELDIAMETER*3.14;

    // Print out 2 timers, before and after delay
    Serial.println("LDR Value: ");
  Serial.println(analogRead(LDR));
    Serial.println("Distance: ");
  Serial.println(distance);
    Serial.println("Car time: ");
  Serial.println(carTime);
    Serial.println("Total time: ");
  Serial.println(totalTime);
    switchOn = false;
    delay(2000);
}

// This function checks the start button.
// It stays in loop while the user holds down the 
// button and only progresses once the user lets go of the button
void checkOnSwitch(){ 
  while(!switchOn){ // While switch is off (false)
    delay(100);//wait 100 ms
      if (digitalRead(startButton)==BUTTONPRESSED){//if button is pressed activate switch
          switchOn=true;
          digitalWrite(onLight, HIGH);
      while(digitalRead(startButton)==BUTTONPRESSED){}//keep car from running until pin reads 0 again
      }
  }

  Serial.println("The car is on. \n");
}
// Check clock if below ref value shut off car
bool clockAboveThreshold(int ldrRef){
    // This function would return true by default if the clock is not triggered
    clockFlag = true;

    // If the LDR value is below the threshold
    // Increment a counter, counting the amount of points below the threshold
    if (analogRead (LDR) < (ldrRef-DLDR)){
        darkPointsCounter = darkPointsCounter + 1;
    }

    // If the number of points below the threshold is above 100 (for example)
    // Set clock to triggered and return this value
    if(darkPointsCounter > DARKPOINTSTHRESHOLD){
        clockFlag = false;
    }

    return clockFlag;
}
                
                

Circuit Diagram

Stopping Mechanism

We use an iodine clock (the peroxide version) as our stopping mechanism.

Two colorless solutions (we call them Solution $A$ and Solution $B$) are mixed and a dark blue color appears after a certain $\Delta T$. In our Iodine Clock Tower, a laser shines through the solution, hitting a light-sensitive sensor on the other side. The formation of a dark blue color lowers the light intensity of the laser. The LDR sensors detect this change and the Arduino then cuts off the motor.

Read our blog article for more information: Link


A finite state machine (FSM) representation of our algorithm. We first check if the LDR reading is at a pre-defined 'normal' range, i.e. the background value when the solution is colorless, if the value is incorrect the algorithm flashes an LED to notify the car operator. Once the start button is pressed, we release a valve to allow solution $A$ and $B$ to mix and activate the motor. We continuously check the LDR reading, if it passes a pre-defined threshold 10 times that means a color change has occured, which triggers the motor to deactivate. We use 10x as a safeguard to prevent accidental triggers by for example, sensor errors or impurities in the solution.

Learn More: Finite State Machines


Reaction Details

This reaction starts from Solution $A$, a mixture of hydrogen peroxide with sulfuric acid. A separate solution, Solution $B$ containing potassium iodide, sodium thiosulfate, and starch is then added to solution $A$.

In the slow reaction, iodine is produced from sulfuric acid and hydrogen peroxide.

$$H_2O_2 + 2I^- + 2H^+ \rightarrow I_2 + 2H_2O$$


In the second, fast reaction, iodine is reconverted to 2 iodide ions by the thiosulfate. Once the thiosulfate ion has been exhausted, this reaction stops and the blue colour caused by the triiodide – starch complex appears.

$$2S_2O_3^{2-} + I_2 + 2H^+ \rightarrow S_4O_6^{2-} + 2I^-$$

Ref: https://en.wikipedia.org/wiki/Iodine_clock_reaction