Additional material for lesson 2

Exercise: D-flip flop, and state machines (voluntary)

The ideal way to implement state machines in software differs from the hardware approach. The concepts covered so far however, limits our possibilities, and in this exercise you will implement a state machine using the same techniques as you know from digital electronics. In digital electronics we use flip-flops to store data, but in our microcontroller we can easily store data by declaring a variable for it. Thus the implementation of flip-flops in this way is rather silly, but still a useful learning experience.

  1. Implement a D latch

  2. Implement a D flip-flop using two D latches

  3. Use two D flip-flops to implement a state machine with 4 states. Usa a push button to provide the clock signal, and 4 LEDs to decode the current state.

Todo

Add a drawing of the state diagram that we are going to use. A regular 3-bit counter which counts from 0 to 7, and then resets back to 0.

Warning

Turns out the code becomes rather complex. In order to prevent a mess, it was also necessary to use some advanced programming techniques to structure the code. You should note however that it would be possible to make it work using only the theory covered so far.

The following code listing provides a solution proposal, where the D flip-flop is defined in a function to allow easy re-use of the code. If you do not know how to use functions, you will have to duplicate some of the code in order to make two flip-flops.

/**
 * @file main.cpp
 * @author Eirik Haustveit (ehau@hvl.no)
 * @brief A D flip flop based state machine
 * @version 0.1
 * @date 2022-01-18
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <Arduino.h>

const uint8_t LED1 = 3;
const uint8_t LED2 = 4;
const uint8_t LED3 = 5;
const uint8_t LED4 = 6;

const uint8_t CLK_IN = 8;
const uint8_t USR_SIG = 9;

typedef struct _d_latch_ctx
{
  uint8_t q;
  uint8_t q_inv;
} d_latch_t;


typedef struct _d_flip_flop_ctx
{
  uint8_t q;
  uint8_t q_inv;
  uint8_t q_master;
  uint8_t q_inv_master;
} d_flip_flop_t;


uint8_t exclusive_or(uint8_t a, uint8_t b);
void d_flip_flop(d_flip_flop_t *ctx, uint8_t d, uint8_t clk);
void d_latch(d_latch_t *ctx, uint8_t d, uint8_t en);

void d_latch_test();
void d_flip_flop_test();
void d_flip_flop_state_machine();

void setup() {
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
  pinMode(LED4, OUTPUT);

  pinMode(CLK_IN, INPUT);
  pinMode(USR_SIG, INPUT);

  Serial.begin(9600);
}

void loop() {


  //d_latch_test();
  //d_flip_flop_test();

  d_flip_flop_state_machine();
}

void d_flip_flop_state_machine(){

  d_flip_flop_t dff1;
  d_flip_flop_t dff2;
  d_flip_flop_t dff3;
  
  for(;;){

    // Perform a very crude debouncing of the switch
    uint8_t clk = !digitalRead(CLK_IN);
    delay(10);
    uint8_t clk2 = !digitalRead(CLK_IN);
    clk = clk && clk2;

    uint8_t usr = !digitalRead(USR_SIG);

    uint8_t d1 = dff1.q_inv;
    uint8_t d2 = exclusive_or(dff1.q, dff2.q);
    uint8_t d3 = exclusive_or((dff1.q && dff2.q), dff3.q);

    d_flip_flop(&dff1, d1, clk);
    d_flip_flop(&dff2, d2, clk);
    d_flip_flop(&dff3, d3, clk);

    // State decoding
    //digitalWrite(LED1, dff1.q && !dff2.q);
    //digitalWrite(LED2, dff1.q && !dff2.q);
    //digitalWrite(LED3, dff1.q && !dff2.q);
    //digitalWrite(LED4, dff1.q && !dff2.q);


    Serial.print("Q: ");
    Serial.print(dff1.q);
    Serial.print(dff2.q);
    Serial.print(dff3.q);
    Serial.print('\r');

    delay(50);
  }
}

void d_flip_flop_test(){

  d_flip_flop_t dff1;
  
  for(;;){

    uint8_t clk = !digitalRead(CLK_IN);
    uint8_t usr = !digitalRead(USR_SIG);


    d_flip_flop(&dff1, usr, clk);

    Serial.print("Q1: ");
    Serial.println(dff1.q);
    delay(500);
  }

}


uint8_t exclusive_or(uint8_t a, uint8_t b){
  return (!a && b) || (a && !b);
}

void d_latch_test(){

  d_latch_t latch1 = {0};

  for(;;){

    uint8_t clk = !digitalRead(CLK_IN);
    uint8_t usr = !digitalRead(USR_SIG);


    d_latch(&latch1, usr, clk);

    Serial.print("Q1: ");
    Serial.println(latch1.q);
    delay(500);
  }

}

/**
 * @brief Implements a D flip flop with negative edge triggering 
 *
 * This function implements a D flip flop using the master / slave principle. This is
 * likely not the optimum way to implement it in software though.
 *  
 * @param ctx 
 * @param d 
 * @param clk 
 */
void d_flip_flop(d_flip_flop_t *ctx, uint8_t d, uint8_t clk){

  uint8_t s_master = d && clk;
  uint8_t r_master = !d && clk;

  uint8_t s_slave = ctx->q_master && !clk;
  uint8_t r_slave = ctx->q_inv_master && !clk;

  ctx->q_master = !(r_master || ctx->q_inv_master);
  ctx->q_inv_master = !(s_master || ctx->q_master);

  ctx->q = !(r_slave || ctx->q_inv);
  ctx->q_inv = !(s_slave || ctx->q);

}

void d_latch(d_latch_t *ctx, uint8_t d, uint8_t en){

  uint8_t s = d && en;
  uint8_t r = !d && en;

  ctx->q = !(r || ctx->q_inv);
  ctx->q_inv = !(s || ctx->q);

}

Todo

Add solution proposal using common software techniques, in order to demonstrate the proper way to solve such problems in software.