13. Interface and application programming

Individual Assignment:

  • write an application that interfaces a user with an input and/or output device that you made

Group Assignment:

  • compare as many tool options as possible

I decided to interface to my output device from Week 12 because it would be really useful as UI for a chip to receive and display data. I used the same OLED display code from Week 12 to display strings. Since I the TX and RX ports are occupied by I2C for the OLED screen, I needed to use a software implementation of serial communications in order to communicate with other pins.

Porting Neil’s Code

I first tried to port Neil’s code for software serial communication by replacing the macros. While the ported code worked perfectly, the code couldn’t read more than one character of input. Calling get_char() in a while loop gets no input.

#include <avr/io.h>
#include <util/delay.h>

#define output(pin) (PORTA.DIR |= pin) // set port direction for output
#define input(pin) (PORTA.DIR &= (~pin)) // set port direction for input
#define set(pin) (PORTA.OUT |= pin) // set port pin
#define clear(pin) (PORTA.OUT &= (~pin)) // clear port pin
#define pin_test(pin) (PORTA.IN & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 102 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay

#define serial_pin_in (1 << 6) //TXD
#define serial_pin_out (1 << 7) //RXD
#define max_buffer 25

void get_char(unsigned char pin, char *rxbyte) {
  //
  // read character into rxbyte on pins pin
  //    assumes line driver (inverts bits)
  //
  *rxbyte = 0;
  while (pin_test(pin))
    //
    // wait for start bit
    //
    ;
  //
  // delay to middle of first data bit
  //
  half_bit_delay();
  bit_delay();
  //
  // unrolled loop to read data bits
  //
  if pin_test(pin)
    *rxbyte |= (1 << 0);
  else
    *rxbyte |= (0 << 0);
  bit_delay();
  if pin_test(pin)
    *rxbyte |= (1 << 1);
  else
    *rxbyte |= (0 << 1);
  bit_delay();
  if pin_test(pin)
    *rxbyte |= (1 << 2);
  else
    *rxbyte |= (0 << 2);
  bit_delay();
  if pin_test(pin)
    *rxbyte |= (1 << 3);
  else
    *rxbyte |= (0 << 3);
  bit_delay();
  if pin_test(pin)
    *rxbyte |= (1 << 4);
  else
    *rxbyte |= (0 << 4);
  bit_delay();
  if pin_test(pin)
    *rxbyte |= (1 << 5);
  else
    *rxbyte |= (0 << 5);
  bit_delay();
  if pin_test(pin)
    *rxbyte |= (1 << 6);
  else
    *rxbyte |= (0 << 6);
  bit_delay();
  if pin_test(pin)
    *rxbyte |= (1 << 7);
  else
    *rxbyte |= (0 << 7);
  //
  // wait for stop bit
  //
  bit_delay();
  half_bit_delay();
}

void put_char(unsigned char pin, char txchar) {
  //
  // send character in txchar on port pin
  //    assumes line driver (inverts bits)
  //
  // start bit
  //
  clear(pin);
  bit_delay();
  //
  // unrolled loop to write data bits
  //
  if bit_test(txchar,0)
    set(pin);
  else
    clear(pin);
  bit_delay();
  if bit_test(txchar,1)
    set(pin);
  else
    clear(pin);
  bit_delay();
  if bit_test(txchar,2)
    set(pin);
  else
    clear(pin);
  bit_delay();
  if bit_test(txchar,3)
    set(pin);
  else
    clear(pin);
  bit_delay();
  if bit_test(txchar,4)
    set(pin);
  else
    clear(pin);
  bit_delay();
  if bit_test(txchar,5)
    set(pin);
  else
    clear(pin);
  bit_delay();
  if bit_test(txchar,6)
    set(pin);
  else
    clear(pin);
  bit_delay();
  if bit_test(txchar,7)
    set(pin);
  else
    clear(pin);
  bit_delay();
  //
  // stop bit
  //
  set(pin);
  bit_delay();
  //
  // char delay
  //
  bit_delay();
}

void put_string(unsigned char pin, String str) {
  //
  // print a null-terminated string
  //
  static int index;
  index = 0;
  do {
    put_char(pin, str[index]);
    ++index;
    } while (str[index] != 0);
}

//static char chr;
static char buffer[max_buffer] = {0};
static int index = 0;

void setup() {
  set(serial_pin_out);
  output(serial_pin_out);
}
void loop() {
  char curr;
  while (curr != "\n") {
    get_char(serial_pin_in, &curr);
    buffer[index] = curr;
    index++;
  }
  put_string(serial_pin_out, buffer);
}

SoftwareSerial library

However, I searched for a software implementation and I found that arduino already has a software library included with the Arduino IDE called SoftwareSerial. I included the header and created test code to echo any messages sent over serial. I wanted to see if SoftwareSerial was compatible with the Attiny412 MCU and if the SoftwareSerial library was small enough to fit with the OLED code..

#include <SoftwareSerial.h>
#define rxPin 7
#define txPin 6

SoftwareSerial serial(rxPin, txPin); // RX, TX

void setup() {
  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);

  serial.begin(9600);
}
void loop() {
  if (serial.available()) {
    serial.write(serial.read());
  }
}

Unfortunately, the program size exceeded the flash memory of the chip. I surmised that the main culprit was the big arrays that defined the fonts. So, I commented out the 10x16 font arrays and any methods that used them. Then I was able to flash the program.

static const uint8_t MonospaceBold7x8[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,223,223,0,0,0,0,7,0,7,0,0,32,228,62,231,124,39,4,0,76,154,255,154,112,0,39,37,23,232,168,228,0,0,112,247,159,241,192,240,0,0,0,7,0,0,0,0,0,24,126,195,0,0,0,0,195,126,24,0,0,0,18,12,63,12,18,0,0,16,16,124,16,16,0,0,128,112,48,0,0,0,0,0,48,48,48,0,0,0,0,192,192,0,0,0,0,0,192,56,6,1,0,0,126,255,129,137,255,126,0,129,129,255,255,128,128,0,129,193,161,153,143,134,0,129,137,137,137,247,118,0,48,44,38,255,255,32,0,143,143,137,137,249,113,0,124,255,139,137,249,112,0,1,129,241,125,31,3,0,118,247,137,137,247,118,0,14,159,145,209,255,62,0,0,204,204,0,0,0,0,0,204,204,0,0,0,0,48,48,120,72,72,204,0,40,40,40,40,40,40,0,204,72,72,120,48,48,0,2,217,215,7,0,0,124,130,57,69,69,254,0,0,192,252,47,47,252,192,0,255,255,137,137,247,118,0,60,126,195,129,129,66,0,255,255,129,129,255,60,0,255,255,137,137,137,129,0,255,255,9,9,9,1,0,60,126,195,145,241,242,0,255,255,8,8,255,255,0,129,129,255,255,129,129,0,64,128,129,129,255,127,0,255,255,24,62,227,193,0,255,255,128,128,128,128,0,255,254,28,28,254,255,0,255,255,14,112,255,255,0,126,255,129,129,255,126,0,255,255,17,17,31,14,0,126,255,129,129,255,126,0,255,255,17,17,111,206,128,78,143,153,153,241,114,0,1,1,255,255,1,1,0,127,255,128,128,255,127,0,3,63,240,240,63,3,15,255,240,12,240,255,15,0,129,231,60,60,231,129,0,1,15,252,252,15,1,0,193,225,185,141,135,131,0,0,255,255,129,0,0,0,1,6,56,192,0,0,0,0,129,255,255,0,0,4,6,3,3,6,4,0,128,128,128,128,128,128,128,0,1,3,2,0,0,0,0,96,244,148,148,252,248,0,255,255,136,136,248,112,0,120,252,204,132,132,132,0,112,248,136,136,255,255,0,120,252,148,148,156,152,0,8,254,255,9,9,0,0,28,190,162,162,254,126,0,255,255,8,8,248,240,0,136,136,251,251,128,128,0,136,136,251,123,0,0,0,255,255,16,124,196,128,1,1,127,255,128,128,0,0,252,252,4,252,4,252,0,252,252,4,4,252,248,0,120,252,132,132,252,120,0,254,254,34,34,62,28,0,28,62,34,34,254,254,0,0,252,252,4,4,4,0,88,156,180,180,228,104,4,4,255,255,132,132,0,0,124,252,128,128,252,252,0,12,124,224,224,124,12,12,252,224,16,224,252,12,0,132,204,120,120,204,132,0,130,142,248,56,14,2,0,196,228,164,148,156,140,0,16,16,239,239,129,129,0,0,0,255,0,0,0,0,129,129,239,239,16,16,0,16,16,16,32,32,32};

//static const uint8_t MonospaceBold10x16lo[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,254,0,0,0,0,0,0,30,30,0,0,30,30,0,0,0,48,176,248,62,50,240,254,62,48,0,112,248,216,254,152,152,0,0,0,28,34,162,162,156,64,64,32,32,0,0,0,220,126,230,198,134,12,128,128,0,0,0,0,30,30,0,0,0,0,0,0,0,224,252,30,2,0,0,0,0,0,2,30,252,224,0,0,0,0,0,72,120,48,254,48,120,72,0,0,0,128,128,128,240,240,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,240,60,14,2,0,0,240,252,14,198,198,14,252,248,0,0,0,12,6,254,254,0,0,0,0,0,12,6,6,6,134,198,124,56,0,0,12,6,198,198,198,198,252,56,0,0,128,192,112,24,14,254,254,0,0,0,254,126,102,102,102,230,198,128,0,0,240,252,206,102,102,230,204,128,0,0,6,6,6,6,230,254,62,14,0,0,56,252,198,198,198,198,252,56,0,0,120,252,206,134,134,206,252,248,0,0,0,0,0,224,224,0,0,0,0,0,0,0,0,224,224,0,0,0,0,0,128,128,192,64,96,96,32,48,0,0,96,96,96,96,96,96,96,96,0,0,48,32,96,96,64,192,128,128,0,0,12,6,134,198,102,126,28,0,0,224,240,56,156,204,204,220,248,240,0,0,0,192,252,62,62,252,192,0,0,0,254,254,198,198,198,198,252,60,0,0,240,252,12,6,6,6,6,12,0,0,254,254,6,6,6,12,252,240,0,0,254,254,198,198,198,198,198,6,0,0,254,254,198,198,198,198,198,6,0,0,240,252,12,6,134,134,134,140,0,0,254,254,192,192,192,192,254,254,0,0,0,6,6,254,254,6,6,0,0,0,0,0,0,6,6,6,254,254,0,0,254,254,224,240,252,14,6,2,0,0,254,254,0,0,0,0,0,0,0,0,254,254,62,240,240,62,254,254,0,0,254,254,30,240,192,0,254,254,0,0,240,252,14,6,6,14,252,240,0,0,254,254,198,198,198,198,124,124,0,0,240,252,14,6,6,14,252,240,0,0,254,254,198,198,198,198,252,60,0,0,56,124,230,198,198,198,140,0,0,0,6,6,6,254,254,6,6,6,0,0,254,254,0,0,0,0,254,254,0,0,6,254,252,0,0,252,254,6,0,30,254,224,0,240,240,0,224,254,30,0,2,14,62,248,248,62,14,2,0,2,14,62,120,224,224,120,62,14,2,0,6,6,134,198,246,126,30,14,0,0,0,0,254,254,2,2,0,0,0,0,2,14,56,224,128,0,0,0,0,0,0,2,2,254,254,0,0,0,0,16,24,28,14,6,14,28,24,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,6,4,0,0,0,0,0,0,96,48,176,176,176,240,224,0,0,254,254,96,48,48,112,224,192,0,0,192,224,112,48,48,48,48,96,0,0,192,224,112,48,48,96,254,254,0,0,192,224,176,176,176,176,224,192,0,0,48,48,252,254,54,54,54,0,0,0,192,224,112,48,48,96,240,240,0,0,254,254,96,48,48,48,240,224,0,0,0,48,48,247,247,0,0,0,0,0,0,48,48,247,247,0,0,0,0,0,254,254,128,224,112,48,16,0,0,0,6,6,254,254,0,0,0,0,0,0,240,240,48,240,224,48,240,224,0,0,240,240,96,48,48,48,240,224,0,0,192,224,112,48,48,112,224,192,0,0,240,240,96,48,48,112,224,192,0,0,192,224,112,48,48,96,240,240,0,0,0,240,240,96,48,48,48,48,0,0,224,240,176,176,176,176,48,96,0,0,48,48,252,252,48,48,48,0,0,0,240,240,0,0,0,0,240,240,0,0,48,240,224,0,0,224,240,48,0,112,240,0,0,192,192,0,0,240,112,0,16,48,240,192,192,240,48,16,0,0,16,240,240,128,0,240,240,48,0,0,48,48,48,48,176,240,112,48,0,0,0,0,0,252,254,2,2,0,0,0,0,0,0,254,254,0,0,0,0,0,0,2,2,254,252,0,0,0,0,0,128,192,192,192,128,128,128,192,0,0,0,0,0};

//static const uint8_t MonospaceBold10x16hi[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,25,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,27,31,3,3,31,15,3,3,0,0,12,24,24,127,25,31,15,0,0,1,1,0,0,14,17,17,17,14,0,0,7,15,28,24,25,31,30,31,19,0,0,0,0,0,0,0,0,0,0,0,0,0,7,63,120,64,0,0,0,0,0,64,120,63,7,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,15,15,1,1,1,0,0,0,0,64,124,60,0,0,0,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,28,28,0,0,0,0,0,32,56,30,7,1,0,0,0,0,0,3,15,28,24,24,28,15,7,0,0,24,24,24,31,31,24,24,24,0,0,24,28,30,27,25,24,24,24,0,0,12,24,24,24,24,25,15,15,0,0,3,3,3,3,3,31,31,3,0,0,12,24,24,24,24,28,15,7,0,0,7,15,28,24,24,28,15,7,0,0,0,16,28,15,3,0,0,0,0,0,15,15,24,24,24,24,15,15,0,0,0,12,25,25,25,28,15,3,0,0,0,0,0,28,28,0,0,0,0,0,0,0,64,124,60,0,0,0,0,0,1,1,3,2,6,6,4,12,0,0,6,6,6,6,6,6,6,6,0,0,12,4,6,6,2,3,1,1,0,0,0,0,27,27,0,0,0,0,0,7,31,56,115,103,102,102,119,39,0,0,24,31,15,3,3,15,31,24,0,0,31,31,24,24,24,24,31,15,0,0,3,15,12,24,24,24,24,12,0,0,31,31,24,24,24,12,15,3,0,0,31,31,24,24,24,24,24,24,0,0,31,31,0,0,0,0,0,0,0,0,3,15,12,24,25,25,31,15,0,0,31,31,0,0,0,0,31,31,0,0,0,24,24,31,31,24,24,0,0,0,12,24,24,24,24,24,15,15,0,0,31,31,0,0,3,15,30,24,0,0,31,31,24,24,24,24,24,24,0,0,31,31,0,1,1,0,31,31,0,0,31,31,0,0,3,30,31,31,0,0,3,15,28,24,24,28,15,3,0,0,31,31,0,0,0,0,0,0,0,0,3,15,28,24,24,60,111,7,0,0,31,31,0,0,1,3,31,30,16,0,12,24,24,24,24,25,15,15,0,0,0,0,0,31,31,0,0,0,0,0,7,15,28,24,24,28,15,7,0,0,0,0,31,31,31,31,0,0,0,0,31,31,30,1,1,30,31,31,0,0,16,28,31,3,3,31,28,16,0,0,0,0,0,31,31,0,0,0,0,0,28,30,31,27,24,24,24,24,0,0,0,0,127,127,64,64,0,0,0,0,0,0,0,0,3,14,56,32,0,0,0,64,64,127,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,31,27,25,25,13,31,31,0,0,31,31,12,24,24,28,15,7,0,0,7,15,28,24,24,24,24,12,0,0,7,15,28,24,24,12,31,31,0,0,7,15,29,25,25,25,25,13,0,0,0,0,31,31,0,0,0,0,0,0,7,111,220,216,216,204,255,127,0,0,31,31,0,0,0,0,31,31,0,0,24,24,24,31,31,24,24,24,0,0,192,192,192,255,127,0,0,0,0,0,31,31,1,3,7,30,24,16,0,0,0,0,15,31,24,24,24,0,0,0,31,31,0,31,31,0,31,31,0,0,31,31,0,0,0,0,31,31,0,0,7,15,28,24,24,28,15,7,0,0,255,255,12,24,24,28,15,7,0,0,7,15,28,24,24,12,255,255,0,0,0,31,31,0,0,0,0,0,0,0,12,25,25,25,25,27,31,14,0,0,0,0,15,31,24,24,24,0,0,0,15,31,24,24,24,12,31,31,0,0,0,1,15,30,30,15,1,0,0,0,7,31,30,3,3,30,31,7,0,0,16,24,30,7,7,30,24,16,0,0,0,192,195,255,63,15,1,0,0,0,24,28,30,27,25,24,24,24,0,0,0,1,1,126,254,128,128,0,0,0,0,0,0,255,255,0,0,0,0,0,0,128,128,254,126,1,1,0,0,0,1,0,0,0,1,1,1,0,0,0,0,0,0};

However, the chip wasn’t receiving or printing anything on the serial monitor. I realized that Arduino numbers pins differently. Arduino numbers pins based on the numbers labeled by “Analog | Digital” on the pinout diagram.

#define rxPin 0
#define txPin 1

SoftwareSerial serial(rxPin, txPin); // RX, TX

Fixing serial instability

Through trial and error, I found that the 57600 baud rate was the most stable baud rate. However, the output shows consistent and random bit loss.

void setup() {
  // Initialize OLED
  OLED128x64init();

  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);

  serial.begin(57600);
}

I searched online for the problem and I found an AVRFreaks thread where a user had suggested that adding a delay of about 110 ms would solve the problem. Sure enough, the stability of the serial communications improved dramatically.

https://www.avrfreaks.net/forum/attiny84-softwareserial

void loop() {
  if (serial.available()) {
    char chr = serial.read();
    serial.write(chr);
  }
  _delay_ms(110); //Gibberish fix
}

The instability of serial communication also came with the caveat that I couldn’t read characters too fast or the characters wouldn’t read at all. Adding a delay to a for loop failed to address the issue.

I ended up concatenating the characters into a char array throughout the interations of the main loop. I also made the backslash character clear the char array.

char str[100];
int index = 0;

void loop() {
  if (serial.available()) {
    char chr = serial.read();
    serial.write(chr);
    if (isalnum(chr)) {
      str[index] = chr;
      index++;
    }
    else if (chr == '\\') {
      memset(str, 0, sizeof(str));
      index = 0;
      serial.println("Cleared");
    }
  }
  OLED7x8string(0, 0, str);
  _delay_ms(110); //Gibberish fix
}

Full Code

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <stdlib.h>
#include <SoftwareSerial.h>

#define I2C_slave_address 0x78
#define I2C_delay() _delay_us(5)

#define F_CPU 20000000

#define TWI0_BAUD(F_SCL)      ((((float)F_CPU / (float)F_SCL)) - 10 )
void TWI_init()
{
  TWI0.MBAUD = (uint8_t)TWI0_BAUD(100000);  // Set MBAUD register for 100kHz
  TWI0.MCTRLA = 1 << TWI_ENABLE_bp          // Enable TWI Master: enabled
  | 0 << TWI_QCEN_bp                        // Quick Command Enable: disabled
  | 0 << TWI_RIEN_bp                        // Read Interrupt Enable: disabled
  | 1 << TWI_SMEN_bp                        // Smart Mode Enable: enabled
  | TWI_TIMEOUT_DISABLED_gc                 // Bus Timeout Disabled
  | 0 << TWI_WIEN_bp;                       // Write Interrupt Enable: disabled

  TWI0.MCTRLB |= TWI_FLUSH_bm;              // Purge MADDR and MDATA 
  TWI0.MSTATUS |= TWI_BUSSTATE_IDLE_gc;     // Force TWI state machine into IDLE state
  TWI0.MSTATUS |= (TWI_RIF_bm | TWI_WIF_bm);
}

uint8_t TWI_start(uint8_t deviceAddr)
{
  if ((TWI0.MSTATUS & TWI_BUSSTATE_gm) != TWI_BUSSTATE_BUSY_gc)   //Verify Bus is not busy
  {
    TWI0.MCTRLB &= ~(1 << TWI_ACKACT_bp);
    TWI0.MADDR = deviceAddr ;
    if (deviceAddr&0x01)  {while(!(TWI0.MSTATUS & TWI_RIF_bm));}  // If addressRead
    else                  {while(!(TWI0.MSTATUS & TWI_WIF_bm));}  // If addressWrite
    return 0;
  }
  else return 1;                                                  // Bus is busy
}

uint8_t TWI_read(uint8_t ACK)                                     // ACK=1 send ACK ; ACK=0 send NACK
{

  if ((TWI0.MSTATUS & TWI_BUSSTATE_gm) == TWI_BUSSTATE_OWNER_gc)  // Verify Master owns the bus
  {
    while(!(TWI0.MSTATUS & TWI_RIF_bm));                          // Wait until RIF set
    uint8_t data=TWI0.MDATA;
    if(ACK==1)  {TWI0.MCTRLB &= ~(1<<TWI_ACKACT_bp);}             // If ACK=1 Put 0 ACKACT => action send ack
    else        {TWI0.MCTRLB |= (1<<TWI_ACKACT_bp); }             // Else if (ACK=0) => put 1 ACKACT => nack prepared for actionstop

    return data ;
  }
  else
  return 1;                                                       //Master does not own the bus

}
uint8_t TWI_write(uint8_t write_data)
{
  if ((TWI0.MSTATUS&TWI_BUSSTATE_gm) == TWI_BUSSTATE_OWNER_gc)              // Verify Master owns the bus
  {
    TWI0.MDATA = write_data;
    while (!((TWI0.MSTATUS & TWI_WIF_bm) | (TWI0.MSTATUS & TWI_RXACK_bm))); // Wait until WIF set and RXACK cleared
    return 0;
  }
  else
  return 1;                                                                 // Master does not own the bus
}

void TWI_stop(void)
{
  TWI0.MCTRLB |= TWI_ACKACT_NACK_gc;
  TWI0.MCTRLB |= TWI_MCMD_STOP_gc;
}

// 
char TWI_master_write(unsigned char* data,unsigned char nbytes,unsigned char slave_address) 
{
  unsigned char index, ret, slave_address_write;

  // Send start and send slave address
  slave_address_write = slave_address << 1;
  if (TWI_start(slave_address) != 0)
    // No ACK,return 1
    return 1;

  // Loop over bytes
  for (index = 0; index < nbytes; ++index) {
    ret = TWI_write((data[index]));
    if (ret != 0) 
       // no ACK, return 1
       break;
    // yes ACK, continue
  }

  //Send stop
  TWI_stop();
  return ret;
}

static const uint8_t MonospaceBold7x8[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,223,223,0,0,0,0,7,0,7,0,0,32,228,62,231,124,39,4,0,76,154,255,154,112,0,39,37,23,232,168,228,0,0,112,247,159,241,192,240,0,0,0,7,0,0,0,0,0,24,126,195,0,0,0,0,195,126,24,0,0,0,18,12,63,12,18,0,0,16,16,124,16,16,0,0,128,112,48,0,0,0,0,0,48,48,48,0,0,0,0,192,192,0,0,0,0,0,192,56,6,1,0,0,126,255,129,137,255,126,0,129,129,255,255,128,128,0,129,193,161,153,143,134,0,129,137,137,137,247,118,0,48,44,38,255,255,32,0,143,143,137,137,249,113,0,124,255,139,137,249,112,0,1,129,241,125,31,3,0,118,247,137,137,247,118,0,14,159,145,209,255,62,0,0,204,204,0,0,0,0,0,204,204,0,0,0,0,48,48,120,72,72,204,0,40,40,40,40,40,40,0,204,72,72,120,48,48,0,2,217,215,7,0,0,124,130,57,69,69,254,0,0,192,252,47,47,252,192,0,255,255,137,137,247,118,0,60,126,195,129,129,66,0,255,255,129,129,255,60,0,255,255,137,137,137,129,0,255,255,9,9,9,1,0,60,126,195,145,241,242,0,255,255,8,8,255,255,0,129,129,255,255,129,129,0,64,128,129,129,255,127,0,255,255,24,62,227,193,0,255,255,128,128,128,128,0,255,254,28,28,254,255,0,255,255,14,112,255,255,0,126,255,129,129,255,126,0,255,255,17,17,31,14,0,126,255,129,129,255,126,0,255,255,17,17,111,206,128,78,143,153,153,241,114,0,1,1,255,255,1,1,0,127,255,128,128,255,127,0,3,63,240,240,63,3,15,255,240,12,240,255,15,0,129,231,60,60,231,129,0,1,15,252,252,15,1,0,193,225,185,141,135,131,0,0,255,255,129,0,0,0,1,6,56,192,0,0,0,0,129,255,255,0,0,4,6,3,3,6,4,0,128,128,128,128,128,128,128,0,1,3,2,0,0,0,0,96,244,148,148,252,248,0,255,255,136,136,248,112,0,120,252,204,132,132,132,0,112,248,136,136,255,255,0,120,252,148,148,156,152,0,8,254,255,9,9,0,0,28,190,162,162,254,126,0,255,255,8,8,248,240,0,136,136,251,251,128,128,0,136,136,251,123,0,0,0,255,255,16,124,196,128,1,1,127,255,128,128,0,0,252,252,4,252,4,252,0,252,252,4,4,252,248,0,120,252,132,132,252,120,0,254,254,34,34,62,28,0,28,62,34,34,254,254,0,0,252,252,4,4,4,0,88,156,180,180,228,104,4,4,255,255,132,132,0,0,124,252,128,128,252,252,0,12,124,224,224,124,12,12,252,224,16,224,252,12,0,132,204,120,120,204,132,0,130,142,248,56,14,2,0,196,228,164,148,156,140,0,16,16,239,239,129,129,0,0,0,255,0,0,0,0,129,129,239,239,16,16,0,16,16,16,32,32,32};

//static const uint8_t MonospaceBold10x16lo[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,254,0,0,0,0,0,0,30,30,0,0,30,30,0,0,0,48,176,248,62,50,240,254,62,48,0,112,248,216,254,152,152,0,0,0,28,34,162,162,156,64,64,32,32,0,0,0,220,126,230,198,134,12,128,128,0,0,0,0,30,30,0,0,0,0,0,0,0,224,252,30,2,0,0,0,0,0,2,30,252,224,0,0,0,0,0,72,120,48,254,48,120,72,0,0,0,128,128,128,240,240,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,240,60,14,2,0,0,240,252,14,198,198,14,252,248,0,0,0,12,6,254,254,0,0,0,0,0,12,6,6,6,134,198,124,56,0,0,12,6,198,198,198,198,252,56,0,0,128,192,112,24,14,254,254,0,0,0,254,126,102,102,102,230,198,128,0,0,240,252,206,102,102,230,204,128,0,0,6,6,6,6,230,254,62,14,0,0,56,252,198,198,198,198,252,56,0,0,120,252,206,134,134,206,252,248,0,0,0,0,0,224,224,0,0,0,0,0,0,0,0,224,224,0,0,0,0,0,128,128,192,64,96,96,32,48,0,0,96,96,96,96,96,96,96,96,0,0,48,32,96,96,64,192,128,128,0,0,12,6,134,198,102,126,28,0,0,224,240,56,156,204,204,220,248,240,0,0,0,192,252,62,62,252,192,0,0,0,254,254,198,198,198,198,252,60,0,0,240,252,12,6,6,6,6,12,0,0,254,254,6,6,6,12,252,240,0,0,254,254,198,198,198,198,198,6,0,0,254,254,198,198,198,198,198,6,0,0,240,252,12,6,134,134,134,140,0,0,254,254,192,192,192,192,254,254,0,0,0,6,6,254,254,6,6,0,0,0,0,0,0,6,6,6,254,254,0,0,254,254,224,240,252,14,6,2,0,0,254,254,0,0,0,0,0,0,0,0,254,254,62,240,240,62,254,254,0,0,254,254,30,240,192,0,254,254,0,0,240,252,14,6,6,14,252,240,0,0,254,254,198,198,198,198,124,124,0,0,240,252,14,6,6,14,252,240,0,0,254,254,198,198,198,198,252,60,0,0,56,124,230,198,198,198,140,0,0,0,6,6,6,254,254,6,6,6,0,0,254,254,0,0,0,0,254,254,0,0,6,254,252,0,0,252,254,6,0,30,254,224,0,240,240,0,224,254,30,0,2,14,62,248,248,62,14,2,0,2,14,62,120,224,224,120,62,14,2,0,6,6,134,198,246,126,30,14,0,0,0,0,254,254,2,2,0,0,0,0,2,14,56,224,128,0,0,0,0,0,0,2,2,254,254,0,0,0,0,16,24,28,14,6,14,28,24,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,6,4,0,0,0,0,0,0,96,48,176,176,176,240,224,0,0,254,254,96,48,48,112,224,192,0,0,192,224,112,48,48,48,48,96,0,0,192,224,112,48,48,96,254,254,0,0,192,224,176,176,176,176,224,192,0,0,48,48,252,254,54,54,54,0,0,0,192,224,112,48,48,96,240,240,0,0,254,254,96,48,48,48,240,224,0,0,0,48,48,247,247,0,0,0,0,0,0,48,48,247,247,0,0,0,0,0,254,254,128,224,112,48,16,0,0,0,6,6,254,254,0,0,0,0,0,0,240,240,48,240,224,48,240,224,0,0,240,240,96,48,48,48,240,224,0,0,192,224,112,48,48,112,224,192,0,0,240,240,96,48,48,112,224,192,0,0,192,224,112,48,48,96,240,240,0,0,0,240,240,96,48,48,48,48,0,0,224,240,176,176,176,176,48,96,0,0,48,48,252,252,48,48,48,0,0,0,240,240,0,0,0,0,240,240,0,0,48,240,224,0,0,224,240,48,0,112,240,0,0,192,192,0,0,240,112,0,16,48,240,192,192,240,48,16,0,0,16,240,240,128,0,240,240,48,0,0,48,48,48,48,176,240,112,48,0,0,0,0,0,252,254,2,2,0,0,0,0,0,0,254,254,0,0,0,0,0,0,2,2,254,252,0,0,0,0,0,128,192,192,192,128,128,128,192,0,0,0,0,0};
//
//static const uint8_t MonospaceBold10x16hi[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,25,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,27,31,3,3,31,15,3,3,0,0,12,24,24,127,25,31,15,0,0,1,1,0,0,14,17,17,17,14,0,0,7,15,28,24,25,31,30,31,19,0,0,0,0,0,0,0,0,0,0,0,0,0,7,63,120,64,0,0,0,0,0,64,120,63,7,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,15,15,1,1,1,0,0,0,0,64,124,60,0,0,0,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,28,28,0,0,0,0,0,32,56,30,7,1,0,0,0,0,0,3,15,28,24,24,28,15,7,0,0,24,24,24,31,31,24,24,24,0,0,24,28,30,27,25,24,24,24,0,0,12,24,24,24,24,25,15,15,0,0,3,3,3,3,3,31,31,3,0,0,12,24,24,24,24,28,15,7,0,0,7,15,28,24,24,28,15,7,0,0,0,16,28,15,3,0,0,0,0,0,15,15,24,24,24,24,15,15,0,0,0,12,25,25,25,28,15,3,0,0,0,0,0,28,28,0,0,0,0,0,0,0,64,124,60,0,0,0,0,0,1,1,3,2,6,6,4,12,0,0,6,6,6,6,6,6,6,6,0,0,12,4,6,6,2,3,1,1,0,0,0,0,27,27,0,0,0,0,0,7,31,56,115,103,102,102,119,39,0,0,24,31,15,3,3,15,31,24,0,0,31,31,24,24,24,24,31,15,0,0,3,15,12,24,24,24,24,12,0,0,31,31,24,24,24,12,15,3,0,0,31,31,24,24,24,24,24,24,0,0,31,31,0,0,0,0,0,0,0,0,3,15,12,24,25,25,31,15,0,0,31,31,0,0,0,0,31,31,0,0,0,24,24,31,31,24,24,0,0,0,12,24,24,24,24,24,15,15,0,0,31,31,0,0,3,15,30,24,0,0,31,31,24,24,24,24,24,24,0,0,31,31,0,1,1,0,31,31,0,0,31,31,0,0,3,30,31,31,0,0,3,15,28,24,24,28,15,3,0,0,31,31,0,0,0,0,0,0,0,0,3,15,28,24,24,60,111,7,0,0,31,31,0,0,1,3,31,30,16,0,12,24,24,24,24,25,15,15,0,0,0,0,0,31,31,0,0,0,0,0,7,15,28,24,24,28,15,7,0,0,0,0,31,31,31,31,0,0,0,0,31,31,30,1,1,30,31,31,0,0,16,28,31,3,3,31,28,16,0,0,0,0,0,31,31,0,0,0,0,0,28,30,31,27,24,24,24,24,0,0,0,0,127,127,64,64,0,0,0,0,0,0,0,0,3,14,56,32,0,0,0,64,64,127,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,31,27,25,25,13,31,31,0,0,31,31,12,24,24,28,15,7,0,0,7,15,28,24,24,24,24,12,0,0,7,15,28,24,24,12,31,31,0,0,7,15,29,25,25,25,25,13,0,0,0,0,31,31,0,0,0,0,0,0,7,111,220,216,216,204,255,127,0,0,31,31,0,0,0,0,31,31,0,0,24,24,24,31,31,24,24,24,0,0,192,192,192,255,127,0,0,0,0,0,31,31,1,3,7,30,24,16,0,0,0,0,15,31,24,24,24,0,0,0,31,31,0,31,31,0,31,31,0,0,31,31,0,0,0,0,31,31,0,0,7,15,28,24,24,28,15,7,0,0,255,255,12,24,24,28,15,7,0,0,7,15,28,24,24,12,255,255,0,0,0,31,31,0,0,0,0,0,0,0,12,25,25,25,25,27,31,14,0,0,0,0,15,31,24,24,24,0,0,0,15,31,24,24,24,12,31,31,0,0,0,1,15,30,30,15,1,0,0,0,7,31,30,3,3,30,31,7,0,0,16,24,30,7,7,30,24,16,0,0,0,192,195,255,63,15,1,0,0,0,24,28,30,27,25,24,24,24,0,0,0,1,1,126,254,128,128,0,0,0,0,0,0,255,255,0,0,0,0,0,0,128,128,254,126,1,1,0,0,0,1,0,0,0,1,1,1,0,0,0,0,0,0};

void OLEDcommand(uint8_t c) {
  static uint8_t data[2];
  data[0] = 0;
  data[1] = c;
  TWI_master_write(data,2,I2C_slave_address);
}

void OLEDcommands(uint8_t c1,uint8_t c2) {
  static uint8_t data[3];
  data[0] = 0;
  data[1] = c1;
  data[2] = c2;
  TWI_master_write(data,3,I2C_slave_address);
}

void OLEDdata(uint8_t d) {
  static uint8_t data[2];
  data[0] = 0x40;
  data[1] = d;
  TWI_master_write(data,2,I2C_slave_address);
}

void OLED7x8string(uint8_t row,uint8_t col,char str[]) {
  static uint8_t index,offset,pointer;
  static char chr;
  OLEDcommands(0x00+(col & 0x0F),0x10+((col >> 4) & 0x0F));
  OLEDcommand(0xB0+row);
  index = 0;
  while (1) {
    chr = str[index];
    if (chr == '\0') break;
    pointer = chr-' ';
    for (offset = 0; offset < 7; ++offset) {
       OLEDdata(pgm_read_byte(&(MonospaceBold7x8[7*pointer+offset])));
    }
    ++index;
  }
}

//void OLED10x16string(uint8_t row,uint8_t col,char str[]) {
//  static uint8_t index,offset,pointer;
//  static char chr;
//  OLEDcommands(0x00+(col & 0x0F),0x10+((col >> 4) & 0x0F));
//  OLEDcommand(0xB0+row);
//  index = 0;
//  while (1) {
//    chr = str[index];
//    if (chr == '\0') break;
//    pointer = chr-' ';
//    for (offset = 0; offset < 10; ++offset) {
//       OLEDdata(pgm_read_byte(&(MonospaceBold10x16lo[10*pointer+offset])));
//    }
//    ++index;
//  }
//  OLEDcommands(0x00+(col & 0x0F),0x10+((col >> 4) & 0x0F));
//  OLEDcommand(0xB0+row+1);
//  index = 0;
//  while (1) {
//    chr = str[index];
//    if (chr == '\0') break;
//    pointer = chr-' ';
//    for (offset = 0; offset < 10; ++offset) {
//       OLEDdata(pgm_read_byte(&(MonospaceBold10x16hi[10*pointer+offset])));
//    }
//    ++index;
//    }
//  }
void OLED128x64init() {
  uint8_t i,j;
  //Init TWI
  TWI_init();

  //Init SSD1306
  OLEDcommand(0xae);       // Display off
  OLEDcommands(0xa8,0x3f); // Set multiplex ratio, ratio 63
  OLEDcommands(0xd3,0x00); // Set display offset, no offset
  OLEDcommand(0x40);       // Set display start line
  OLEDcommand(0xa1);       // Set segment remap col 127 to seg 0
  OLEDcommand(0xc8);       // Set COM output reverse
  OLEDcommands(0xda,0x12); // COM pin config, alt bottom to top
  OLEDcommands(0x81,0xff); // Set contrast, max contrast
  OLEDcommand(0xa4);       // Resume to RAM display
  OLEDcommand(0xa6);       // Normal non-inverted display
  OLEDcommands(0xd5,0x80); // Set clock divider, default
  OLEDcommands(0x8d,0x14); // Set charge pump, enable
  OLEDcommands(0x20,0x02); // Set memory mode, page addressing
  OLEDcommand(0xaf);       // Display on
  //
  // clear screen
  //
  for (j = 0; j < 8; ++j) {
    OLEDcommands(0x00,0x10);
    OLEDcommand(0xB0+j);
    for (i = 0; i < 128; ++i) OLEDdata(0);
    }
}

uint16_t count = 0;

//https://www.arduino.cc/en/tutorial/SoftwareSerialExample

#define rxPin 0
#define txPin 1

SoftwareSerial serial(rxPin, txPin); // RX, TX

void setup() {
  // Initialize OLED
  OLED128x64init();

  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);

  serial.begin(57600);
}

//Gibberish fix
//https://www.avrfreaks.net/forum/attiny84-softwareserial

char str[100];
int index = 0;

void loop() {
  if (serial.available()) {
    char chr = serial.read();
    serial.write(chr);
    if (isalnum(chr) || chr == ',') {
      str[index] = chr;
      index++;
    }
    else if (chr == '\\') {
      memset(str, 0, sizeof(str));
      index = 0;
      serial.println("Cleared");
    }
  }
  OLED7x8string(0, 0, str);
  _delay_ms(50); //Gibberish fix
}

CEFPython and HTML

For the UI, I reused portion os the simple UI that I created in the Input Devices Week using HTML and PythonCEF. For style, I included the Bootstrap front-end framework in the HTML file and used the Lux theme.

In order to find the port for the USB to serial adapter, I scan through all the device’s description until I find the description that the adapter has.

serial2usb_description = "FT232R USB UART"
baud_rate = 57600

ports = serial.tools.list_ports.comports()
serial_port = None
for port in ports:
  print(port.description)
  print(port.device)
  if serial2usb_description in port.description:
    serial_port = port
device = serial.Serial(serial_port.device, baud_rate)

A neat thing about CEFPython is the ability to bind python functions to javascript and vice versa. To do so, I created a JavascriptBindings object and used the SetFunction() function to give the binded function a name in JS and the reference to the python function.

def serial_send_string(string):
  device.write(string.encode())
  print("send: " + string)

def serial_clear_string():
  device.write("\\".encode())
  print("cleared")

bindings = cef.JavascriptBindings(bindToFrames=False, bindToPopups=False)
bindings.SetFunction("serial_send_string", serial_send_string)
bindings.SetFunction("serial_clear_string", serial_clear_string)
browser.SetJavascriptBindings(bindings)

In javascript, I call the function name that I set in the bindings. Here, I also make use of Jquery which Bootstrap already includes.

$("#inputButton").click(function (e) {
  e.preventDefault();
  var string = $("#input").val();
  serial_send_string(string);
});
$("#clearButton").click(function (e) {
  e.preventDefault();
  serial_clear_string();
});

Full Code

main.py

from cefpython3 import cefpython as cef
import serial.tools.list_ports
import sys

serial2usb_description = "FT232R USB UART"
baud_rate = 57600

ports = serial.tools.list_ports.comports()
serial_port = None
for port in ports:
    print(port.description)
    print(port.device)
    if serial2usb_description in port.description:
        serial_port = port
device = serial.Serial(serial_port.device, baud_rate)

sys.excepthook = cef.ExceptHook  # To shutdown all CEF processes on error
cef.Initialize()
browser = cef.CreateBrowserSync(url="file:///Users/vincentzhou/PycharmProjects/FabInterface/src/interface.html",
                                window_title="Interface")

def serial_send_string(string):
    device.write(string.encode())
    print("send: " + string)

def serial_clear_string():
    device.write("\\".encode())
    print("cleared")

bindings = cef.JavascriptBindings(bindToFrames=False, bindToPopups=False)
bindings.SetFunction("serial_send_string", serial_send_string)
bindings.SetFunction("serial_clear_string", serial_clear_string)
browser.SetJavascriptBindings(bindings)

cef.MessageLoop()
device.close()
cef.Shutdown()

interface.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/lux/bootstrap.min.css" integrity="sha384-oOs/gFavzADqv3i5nCM+9CzXe3e5vXLXZ5LZ7PplpsWpTCufB7kqkTlC9FtZ5nJo" rel="stylesheet">
</head>
<body>
    <div class="container-fluid">
        <div class="card mb-3">
            <h3 class="card-header">Input Field</h3>
            <div class="card-body">
                <div class="form-group">
                    <input class="form-control form-control-lg" id="input" placeholder="Input String" type="text" onkeydown="inputKeyDown(event)">
                    <br>
                    <button class="btn btn-primary btn-lg btn-block" id="inputButton" type="button" disabled>Input</button>
                    <button class="btn btn-danger btn-lg btn-block" id="clearButton" type="button">Clear</button>
                </div>
            </div>
        </div>
    </div>

    <script crossorigin="anonymous" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
    <script crossorigin="anonymous" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
    <script crossorigin="anonymous" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>

    <script>
        function evaluateNewString(str, event) {
            var selectionLength = window.getSelection().toString().length;
            if (event.keyCode == 8) {
                // If there is a selected region, backspace will remove selected regions
                if (selectionLength > 0) {
                    return str.substring(0, event.target.selectionStart) + str.substring(event.target.selectionStart + selectionLength);
                }
                // Else backspace will remove character behind cursor
                else {
                    return str.substring(0, event.target.selectionStart - 1) + str.substring(event.target.selectionStart);
                }
            }
            else {
                return str.substring(0, event.target.selectionStart) + event.key + str.substring(event.target.selectionStart + selectionLength);
            }
        }

        function inputKeyDown(event) {
            var newString = evaluateNewString(event.target.value, event);
            console.log(newString);
            if (newString) {
                $("#inputButton").removeAttr('disabled');
            }
            else {
                $("#inputButton").attr('disabled', true);
            }
        }

        $("#inputButton").click(function (e) {
            e.preventDefault();
            var string = $("#input").val();
            serial_send_string(string);
        });
        $("#clearButton").click(function (e) {
            e.preventDefault();
            serial_clear_string();
        });

    </script>
</body>
</html>

Video

Here is a video of the code working:

Group Site

Site

Resources

Files