Multiplexing Composite NTSC Video

This was one of those rare times where I had a hunch, followed it, and had a great result.

So for a project I’m working on for school, we have a robot with multiple composite video cameras onboard. We will be using those cameras seen on DIY drones or in simple security systems. We will be transmitting this video feed via a 5.8GHz video transmitter meant for a drone. We want the operator to be able to switch which feed they’re viewing at a given time, but we don’t want to have to use 3 transmitters and receivers. So to get around this, I thought we might just connect video feeds to a simple analog multiplexer I had laying around from a previous project and see if you could switch the feed that way. Turns out, you totally can. Here’s the eventual block diagram of this part of our project if you’re interested:

The following is the code running on the arduino. Remember, this isn’t doing anything special other than driving the mux:

#define NUMSELECTS 4

int s0 = 2;
int s1 = 3;
int s2 = 4;
int s3 = 5;

int selects[NUMSELECTS] = {s0, s1, s2, s3};
int select_state[NUMSELECTS] = {0, 0, 0, 0};

void setup()
{
  Serial.begin(9600);
  for (int index = 0; index < NUMSELECTS; index++)
  {
    pinMode(selects[index], OUTPUT);
    digitalWrite(selects[index], select_state[index]);
  }
}

void loop() 
{
  if (Serial.available() > 0)
  {
    char inchar = Serial.read(); //assigns one byte (as serial.read()'s only input one byte at a time
    switch(inchar)
    {
      case '0':
        Serial.println("Switching to video signal 0");
        select_state[0] = 0;
        select_state[1] = 0;
        select_state[2] = 0;
        select_state[3] = 0;
        write_selects();
        break;
      case '1':
        Serial.println("Switching to video signal 1");
        select_state[0] = 1;
        select_state[1] = 0;
        select_state[2] = 0;
        select_state[3] = 0;
        write_selects();
        break;
       default:
        Serial.println("Bad input");
        break;
    }
  }
}

void write_selects()
{
  for (int index = 0; index < NUMSELECTS; index++)
  {
    digitalWrite(selects[index], select_state[index]);
  }
}

RS485 Hardware Network 2 – Multi-Byte Communication

Edit: I don’t really make a point of saying it, but the common ground between the modules isn’t necessary.

Here’s a video of the software running on the boards.

So for this example,  there are 4 bytes being transmitted from the master to the slaves.

  1. Start of Transmission (0xDB)
  2. ID of the slave to talk to
  3. The type of command
  4. The data that is transmitted

A transmission to set the pwm value of the green led on the slave with the ID 1 to 100 would be:

0xDB 0x01 0x01 0x64

This is the schematic of the circuit for this example:

This is the code running on the master Arduino:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

#define NUMSWITCHES 3

int ID_switches[NUMSWITCHES] = {2, 3, 4};
int mode_switch = 5; 

byte IDs[NUMSWITCHES] = {1, 2, 3};

#define TXSIZE 4

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

byte target_node_ID;

struct transmission {

  byte start = 0xDB;
  
  byte ID;
  byte type;
  byte message;

  const int tx_size = TXSIZE;
};

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, HIGH); // this disables Receiver Output Enable and enables Driver Output Enable
  
  for (int index = 0; index < NUMSWITCHES; index++)
  {
    pinMode(ID_switches[index], INPUT); 
  }
  
  pinMode(mode_switch, INPUT);
  
  target_node_ID = 0;
}

void loop()
{
  for (int index = 0; index < NUMSWITCHES; index++)
  {
    if ((byte)digitalRead(ID_switches[index]))
    {
      target_node_ID = IDs[index];
      break;
    }
  }
  
  transmission t;
  
  t.ID = target_node_ID;
  t.type = digitalRead(mode_switch);
  t.message = (byte)map(analogRead(0), 0, 1023, 0x00, 0xFF);
  
  byte message[TXSIZE] = {t.start, t.ID, t.type, t.message};
  
  software_serial.write(message, t.tx_size);
}

This is the code running on the slave Arduinos:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int led_1_PIN = 5; 
int led_2_PIN = 6; 

unsigned long time = 0;  
unsigned long oldtime = 0; 
int state = 0;
int next_ID_button_PIN = 2;

#define NUMSTATES 3
int states[NUMSTATES] = {7, 8, 9};
byte IDs[NUMSTATES] = {1, 2, 3};

#define MESSAGELENGTH 3

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, LOW);
  
  pinMode(led_1_PIN, OUTPUT);
  pinMode(led_2_PIN, OUTPUT);
  
  pinMode(next_ID_button_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(next_ID_button_PIN), next_ID, RISING); 
  
  set_LEDs();
}

void loop() // run over and over
{
  
  byte start_of_message = 0xDB;
  byte incoming_byte;
  byte message[MESSAGELENGTH];
  
  while (true)
  {
    if (software_serial.available())
    { 
      incoming_byte = software_serial.read();
      
      if (incoming_byte == start_of_message)
      {
        software_serial.readBytes(message, MESSAGELENGTH);
        process_message(message);
      }
    }
  }
}
 
void process_message(byte message[])
{
  byte system_ID = IDs[state];

  byte incoming_ID = message[0];
  byte incoming_mode = message[1];
  byte LED_brightness = message[2];
   
  if (incoming_ID == system_ID)
  {
    byte pin;
    
    if (incoming_mode == 0)
    {  
      pin = led_1_PIN;
    }
    else if (incoming_mode == 1)
    {
      pin = led_2_PIN; 
    }
    analogWrite(pin, LED_brightness);  
  }
}

void next_ID()
{
  time = millis();
  
  //soft debounce
  if (time - oldtime > 200) 
  {
    oldtime = time;
    
    state++;
    
    if (state >= (NUMSTATES))
    {
      state = 0;
    }
  }
  set_LEDs();
}

void set_LEDs()
{
  for (int index = 0; index < NUMSTATES; index++)
  {  
    if (index == state)
    {
      digitalWrite(states[index], HIGH);
    }
    else 
    {
      digitalWrite(states[index], LOW);
    }
  } 
}

RS485 Hardware Network 1 – Getting Started

For another project I’m currently working on, I need a way to control multiple microcontrollers in a multi-point, multi-drop network configuration. Luckily, we live in amazing times. As I write this, you you can buy a fully assembled breakout board for the MAX485 chip from Maxim Integrated for a mere $0.45 USD shipped from China.

I bought a 5 pack, here are some of the boards:

RS485 is an old protocol, but is the logical next step for devices I’m already communicating with via RS232. For this example, I’m using 4 Arduino boards of various types.

  • An Arduino Micro as the master
  • 2 Slave Arduino Leonardos
  • 1 Slave Arduino Pro Mini (5v)

Here is a video of the setup:

The schematic is really straightforward as well. The only tricky bit is that I’m using a software serial port on each of the Arduinos for ease of debugging. Here’s a schematic:

The code to acomplish this is really intuitive as well.

Here is the code for the master Arduino:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int debug_led = 13;

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  Serial.begin(9600); // begin hardware serial
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, HIGH); // this disables Receiver Output Enable and enables Driver Output Enable
}

void loop()
{
  
  byte to_send = 0; // declare the byte to be sent to the slaves though, init as 0
  int rate;
  
  while (true) 
  {
    // invert the byte to be sent
    if (to_send == 1) to_send = 0;
    else if (to_send == 0) to_send = 1; 
    
    Serial.print("Sending: ");
    Serial.println(to_send);
    
    digitalWrite(debug_led, to_send);
    
    rate = map(analogRead(5), 0, 1023, 0, 1000);
    
    software_serial.write(to_send); // send our byte out to the MAX485
    
    delay(rate);
  }
}

This is the code for the slave Arduinos:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int debug_led_PIN = 9; 

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, LOW);
  
  pinMode(debug_led_PIN, OUTPUT);
  
}

void loop() // run over and over
{
  byte k;
  
  if (software_serial.available() > 0) // make sure there is something to read
  { 
    k = software_serial.read(); // read in a single byte from the serial port    
    digitalWrite(debug_led_PIN, k); 
  }
}

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int debug_led_PIN = 9; 

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, LOW);
  
  pinMode(debug_led_PIN, OUTPUT);
  
}

void loop() // run over and over
{
  byte k;
  
  if (software_serial .available() &gt; 0) // make sure there is something to read
  { 
    k = software_serial.read(); // read in a single byte from the serial port    
    digitalWrite(debug_led_PIN, k); 
  }
}

In subsequent posts, things will start getting more advanced. For now however this should be enough to start from scratch.

Thanks for reading.

Raspberry Pi Digital Hourglass

Trying to get the most out of a day has been big theme of my life lately, as I’m sure it is for many people. I’ve found that I always manage my time better when things are urgent; I’m considerably more productive when I have to be.

I want an ascetically pleasing way to be able to represent how much time is left in the day at a granular scale, like an hourglass. Watching individual seconds disappear will look cool and (hopefully) create that sense of urgency that I want to induce.

Technically, this is a really simple thing to accomplish thanks to python and pygame. Here’s a video of a proof of concept running on my laptop:

At the start of each day, the display is filled with squares at random locations, with a random color. As each second elapses, a square will vanish.

To make it easier to see for the video, I’ve made the squares much bigger than they will actually be for the final build. This is what the display looks like with the squares at their actual size:

The code really really simple, like less than 100 lines simple. Here’s how it works:

Here’s the version of the code running on my computer in the video:

import pygame
from random import randint
from apscheduler.schedulers.blocking import BlockingScheduler
import datetime

class random_square(object):
    def __init__(self, max_x_location, max_y_location):

        self.x_loc = randint(0, max_x_location)
        self.y_loc = randint(0, max_y_location)

        max_color_value = 255

        red = randint(0, max_color_value)
        green = randint(0, max_color_value)
        blue = randint(0, max_color_value)

        self.color = [red, green, blue]


class clock(object):
    def __init__(self, initial_count, max_count, screen_w, screen_h):

        self.max_count = max_count
        self.screen_w = screen_w
        self.screen_h = screen_h

        # create the screen object, force pygame fullscreen mode
        self.screen = pygame.display.set_mode([screen_w, screen_h], pygame.FULLSCREEN)

        # the screen's width in pixels is stored in the 0th element of the array
        self.square_size = screen_w / 200

        # create the list of squares, initially as empty
        self.squares = []

        # fill the squares with the inital seconds until midnight
        for second in range(initial_count):
            self.squares.append(random_square(screen_w, screen_h))


    # starts ticking the clock
    def start(self):
        scheduler = BlockingScheduler()
        scheduler.add_job(self.tick, 'interval', seconds=1)
        try:
            scheduler.start()
        except (KeyboardInterrupt, SystemExit):
            pass

    # this occurs once every time a unit of time elapses
    def tick(self):
        # this will happen once per "day"
        if len(self.squares) == 0:

            # fill the list of squares to be drawn
            for second in range(self.max_count):
                self.squares.append(random_square(self.screen_w, self.screen_h))

        # draw a blank screen
        self.screen.fill([0, 0, 0])

        # draw the squares
        for square in self.squares:
            rect = (square.x_loc, square.y_loc, self.square_size, self.square_size)
            pygame.draw.rect(self.screen, square.color, rect, 0)

        pygame.display.update()

        # remove a single square from the list as one tick has elapsed
        self.squares.pop()

# initialize pygame
pygame.init()

# figure out the parameters of the display we're connected to
screen_width = pygame.display.Info().current_w
screen_height = pygame.display.Info().current_h
screen_size = screen_width, screen_height

# determine the number of seconds until midnight
seconds_in_a_day = 86400
now = datetime.datetime.now()
midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
seconds_until_midnight = seconds_in_a_day - (now - midnight).seconds

# create and start the clock!
cl = clock(seconds_until_midnight, seconds_in_a_day, screen_width, screen_height)
cl.start()

Let’s walk through some of the design decisions of this code. The first thing that’s worth talking about is how the data for the squares is handled:

class random_square(object):
    def __init__(self, max_x_location, max_y_location):

        self.x_loc = randint(0, max_x_location)
        self.y_loc = randint(0, max_y_location)

        max_color_value = 255

        red = randint(0, max_color_value)
        green = randint(0, max_color_value)
        blue = randint(0, max_color_value)

        self.color = [red, green, blue]

It’s just an object with no methods, and on initialization, all the parameters of the square (location and color) are generated randomly as opposed to just floating the raw numbers in arrays around (even though that’s basically what is happening). This let’s us fill the squares array very easily later on in the file here:

# fill the squares with the inital seconds until midnight
for second in range(initial_count):
    self.squares.append(random_square(screen_w, screen_h))

and here:

# this will happen once per "day"
if len(self.squares) == 0:

    # fill the list of squares to be drawn
    for second in range(self.max_count):
        self.squares.append(random_square(self.screen_w, self.screen_h))

When it comes time to draw these squares, it also makes that pretty intuitive:

# draw the squares
for square in self.squares:
    rect = (square.x_loc, square.y_loc, self.square_size, self.square_size)
    pygame.draw.rect(self.screen, square.color, rect, 0)

Again, very simple stuff, but worth it to talk about.

I’ll be back at my place that has the Raspberry Pi and display I would like to use for this project, so more on this then.

Thanks for reading!