UART-controlled 7-segment display with Python GUI

This project uses this 7-segment display. It is based on Arduino. Display shows current gasoline price in Tampere, Finland. Gasoline price is middle price for 95E10 and it’s downloaded from www.polttoaine.net with Python code.

GUI (graphical user interface) is made with wxPython and wxFormBuilder. Update interval and serial port is user selectable.

Python code:

#! /usr/bin/python
# -*- coding: utf-8 -*-

import wx, wx.xrc
import threading
import serial
import urllib
from lxml.html import fromstring

class MyApp(wx.App):

    delay = 0
    runTime = 0

    def OnInit(self):
        self.res = wx.xrc.XmlResource("gui.xrc")
        self.frame = self.res.LoadFrame(None, "MyFrame1")
        self.text1 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl1")
        self.text2 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl2")
        self.slider1 = wx.xrc.XRCCTRL(self.frame, "m_slider1")
        self.frame.Bind(wx.EVT_BUTTON, self.on_evt_button, id=wx.xrc.XRCID("m_button1"))

        self.SetTopWindow(self.frame)
        self.frame.Show()
        
        self.delay = self.slider1.GetValue()*60
        self.updatePrice()
        self.thread = threading.Thread(target=self.delayTimer)
        self.thread.daemon = True
        self.thread.start()
        return True

    def on_evt_button(self, evt):
        self.delay = self.slider1.GetValue()*60

    def updatePrice(self):
        fopen = urllib.urlopen("https://www.polttoaine.net/Tampere")
        content = fopen.read()
        doc = fromstring(content)
        price = doc.find_class("Hinnat")[3].text_content()
        self.text1.SetValue(price)

        try:
            with serial.Serial(self.text2.GetValue(), 9600, timeout=1) as ser:
                time.sleep(5)
                ser.write(price+"\r")
        except IOError:
            print("Port cannot be opened")

    def delayTimer(self):
        self.runTime += 1
        if self.runTime >= self.delay:
            self.runTime = 0
            self.updatePrice()
        self.thread = threading.Timer(1,self.delayTimer)
        self.thread.daemon = True
        self.thread.start()       

app = MyApp(False)
app.MainLoop()

XRC layout file generated from wxFormBuilder:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resource xmlns="http://www.wxwindows.org/wxxrc" version="2.3.0.1">
  <object class="wxFrame" name="MyFrame1">
    <style>wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL</style>
    <size>380,165</size>
    <title>95E10 Gasoline Price Display</title>
    <centered>1</centered>
    <aui_managed>0</aui_managed>
    <object class="wxFlexGridSizer">
      <rows>3</rows>
      <cols>3</cols>
      <minsize>380,165</minsize>
      <vgap>0</vgap>
      <hgap>0</hgap>
      <growablecols></growablecols>
      <growablerows></growablerows>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText1">
          <label>Current output</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl1">
          <style>wxTE_READONLY</style>
          <value></value>
        </object>
      </object>
      <object class="spacer">
        <option>1</option>
        <flag>wxEXPAND</flag>
        <border>5</border>
        <size>0,0</size>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText2">
          <label>Update interval (minutes)</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_BOTTOM|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxSlider" name="m_slider1">
          <style>wxSL_HORIZONTAL|wxSL_LABELS</style>
          <size>100,-1</size>
          <value>30</value>
          <min>10</min>
          <max>120</max>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxButton" name="m_button1">
          <label>Save settings</label>
          <default>0</default>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText3">
          <label>Serial port</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl2">
          <value>/dev/ttyUSB0</value>
        </object>
      </object>
    </object>
  </object>
</resource>

 

Reading UART with Python GUI application

Finally, I can say that I can program in the Python language. I learned Python from this tutorial. It is a good combination of the languages that I previously knew (PHP, Javascript and SQL). Structure also reminds me of Delphi and Basic languages.

Here is a program that reads 1-wire temperature sensor DS18B20 and displays it’s value on GUI (graphical user interface). GUI is made with wxPython library and with wxFormBuilder software.

In between sensor and computer there is Arduino Nano which transforms data from 1-wire to UART.

Python code:

#! /usr/bin/python
# -*- coding: utf-8 -*-

import wx
import wx.xrc
import serial
from thread import start_new_thread

class MyApp(wx.App):

    def OnInit(self):
        self.res = wx.xrc.XmlResource("gui.xrc")
        self.frame = self.res.LoadFrame(None, "MyFrame1")
        self.text1 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl1")
        self.text2 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl2")
        self.button1 = wx.xrc.XRCCTRL(self.frame, "m_button1")
        self.frame.Bind(wx.EVT_BUTTON, self.on_evt_button, id=wx.xrc.XRCID("m_button1"))
        
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

    def on_evt_button(self, evt):
        self.button1.SetLabel("Wait...")
        self.button1.Enable(False)
        start_new_thread(readSerial,(self,))

def readSerial(self):
    try:
        with serial.Serial(self.text2.GetValue(), 9600, timeout=1) as ser:
            line = ""
            while len(line) == 0:
                ser.write("\n")
                line = ser.readline()
            self.text1.SetValue(line.splitlines()[0] + u"°C")
    except IOError:
        print("Port cannot be opened")
    self.button1.Enable(True)
    self.button1.SetLabel("Read sensor")

app = MyApp(False)
app.MainLoop()

XRC layout file generated from wxFormBuilder:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resource xmlns="http://www.wxwindows.org/wxxrc" version="2.3.0.1">
  <object class="wxFrame" name="MyFrame1">
    <style>wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL</style>
    <size>-1,100</size>
    <title>1-Wire Temperature</title>
    <centered>1</centered>
    <aui_managed>0</aui_managed>
    <object class="wxGridSizer">
      <minsize>-1,100</minsize>
      <rows>2</rows>
      <cols>3</cols>
      <vgap>0</vgap>
      <hgap>0</hgap>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText1">
          <label>Outdoor temp</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl1">
          <style>wxTE_READONLY</style>
          <size>100,-1</size>
          <value>---</value>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxButton" name="m_button1">
          <label>Read sensor</label>
          <default>0</default>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText2">
          <label>Port addr</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl2">
          <size>100,-1</size>
          <value>/dev/ttyUSB0</value>
        </object>
      </object>
    </object>
  </object>
</resource>

Arduino code:

#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

void setup(void) {
  Serial.begin(9600); // start serial port
  sensors.begin(); // Start up the library
}

void loop(void) { 
  while (Serial.available() > 0) { // if there's any serial available, read it
    if (Serial.read() == '\n') { // look for the newline
      sensors.requestTemperatures(); // Send the command to get temperatures
      Serial.println(sensors.getTempCByIndex(0)); // get temperature from first sensor only
    }
  }
}

 

Dual DC motor controller with Arduino

Two DC motor is independently controlled with Arduino Nano (Micro compatible)  and with a dual H-bridge using PWM. Speed and direction is selectable with two potentiometers. Each potentiometer value is read with Arduino analog input. When potentiometer is on it’s center position then motor is off state.

Video: https://youtu.be/dS4pnsJSH2k

Arduino Nano and Micro contains three timers. Timer0 is used with Arduino internal commands so it leaves two timer (timer1 and timer2) to use with custom programs. Timer1 works with pins 9, 10 and timer2 works with pins 11 and 3.

Pulse width is selected with command analogWrite(pin, value). Analog input is read with command analogRead(pin). Code is below.

void setup() {
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
}

void loop() {
  
  int pot_1 = analogRead(1); // potentiometers values
  int pot_2 = analogRead(0);

// MOTOR ONE
  // direction backward
  if(pot_1 < 512-20) {
    digitalWrite(7,0); // motor direction
    int mot_1 = map(pot_1, 0, 512-20, 255, 0);
    analogWrite(10,mot_1);
  }
  // stop motor in middle position
  else if((pot_1 > 512-19) && (pot_1 < 512+19)) {
    digitalWrite(7,0); // motor direction
    analogWrite(10,0); // motor speed
  }
  // direction forward
  else if(pot_1 > 512+20) {
    digitalWrite(7,1); // motor direction
    int mot_1 = map(pot_1, 512+20, 1023, 255, 0);
    analogWrite(10,mot_1);
  }

// MOTOR TWO
  // direction backward
  if(pot_2 < 512-20) {
    digitalWrite(8,0); // motor direction
    int mot_2 = map(pot_2, 0, 512-20, 255, 0);
    analogWrite(11,mot_2);
  }
  // stop motor in middle position
  else if((pot_2 > 512-19) && (pot_2 < 512+19)) {
    digitalWrite(8,0); // motor direction
    analogWrite(11,0); // motor speed
  }
  // direction forward
  else if(pot_2 > 512+20) {
    digitalWrite(8,1); // motor direction
    int mot_2 = map(pot_2, 512+20, 1023, 255, 0);
    analogWrite(11,mot_2);
  }
}

 

8 Channel IoT Triac Controller

Introduction

With this low-cost IoT device it is easy to control a standard wall outlet devices with your internet browser. No additional software installation required. Working with single 5 volts DC power supply and controlling eight outputs with voltages between 1.7-600VAC. The power can be taken from the USB port or external power supply. There is a screw terminal for that.

Video https://youtu.be/RsZQNslSJ4I

Outputs uses a VO2223 power phototriacs. Designed to drive low power (<0.6A per channel) AC loads. Can be extended with external AC relay or power triac module. The outputs have screw terminals.

Ethernet connection is established using ENC28J60 chip and HanRun HR911105A ethernet connector because it includes transformers and filter elements needed for proper working. This simplifies board design.

The heart of the device is AtMega2560 chip. This makes it to be Arduino™ Mega compatible system. On board standard ICSP connector makes it easy to upload bootloader with using Arduino™ as ISP or other AVR programmers.

Device uses FT232RL chip to communicate via USB. With bootloader installed can be programmed via USB using Arduino™ IDE or similar programs. The DTR pin is connected through capacitor to the AtMega2560 reset pin. So reset works when it is needed by software.

AtMega2560 is programmed so that ethernet connection supports automatic DHCP, so you do not have to enter the IP address manually.

It includes easy to use web interface to modify and save custom triac control programs. The web interface is stored in to the AtMega2560’s program memory and it is included in the program code. It is programmed using HTML, CSS and javascript. It uses AJAX and HTTP Get request to communicate with the AtMega2560 which acts as a web server. The web interface is available with computers connected to the same LAN network.

Custom triac control programs is saved in to EEPROM memory. It automatically begins on startup and loops forevermore regardless of the ethernet connection. There is also individual manual override possibility with external pulse from AC side on each channel.

The device supports additional cards via the P2 connector. It is also used as an ICSP connector (SPI line) but it contains two extra pins that are serial port 2 RX and TX line. They can also be used as GPIO or as an SS signal in the SPI line. Additional cards are not yet designed but it can be anything. For example real time clock or digital input port expander or RS485 interface.

Usage examples

  • amusement park lighting systems
  • carneval lights
  • building face lights
  • household lights and appliances

Software

The background of the menu bar is green if the connection is ok and red if there is no connection. It is possible to upload and download user program to/from local hard drive by using ‘Open list’ and ‘Save list’ buttons. It is in JSON format.

The control panel has line numbering and lines can be added and deleted with related buttons. Eight channels are individually programmed on each line by clicking it on or off. If it is on ‘on’-state then it’s background color is green otherwise it’s white. The delay in each line is given in milliseconds and it can be any between zero and near infinity (32-bit number from 0 to 4,294,967,295).

The program is automatically saved once per minute if changes are made. Program runs continuously and is editable in real time.

Development stage

One prototype has been made so far with double sided printed circuit board and with through hole components. Thoroughly tested and proven to work.

Further development ideas includes DIN rail mounting option, power input protective diode and controlling outputs over the USB connection. The top idea for an additional card is the RS485 interface.

Bill Of Materials

  • R1, 10k, 1*0.01
  • R2, R3, R6, R7, R10, R11, R14, R15, R23-R28, 470 ohm, 14*0.01
  • R4, R5, R8, R9, R12, R13, R16, R17, 180 ohm (optional), 8*0.01
  • R18, 2.7k, 1*0.01
  • R19-R22, 50 ohm, 4*0.01

Subtotal $0.28

  • C1, C2, C7, C10, 18pF, 4*0.01
  • C3, C4-C6, C9, C11, C12, C14, C19-C22, 100nF, 12*0.01
  • C16-C18, 10nF, 3*0.01
  • C8, C13, C15, 10µF tantalum 3*0.12

Subtotal $0.55

  • L1, L2, Ferrite bead with 4-14 turns (10-100µH) of thin enameled wire, 2*0.08
  • D17-D20, 5mm LED, 4*0.03
  • Y1, 16MHz, 0.1
  • Y2, 25MHz, 0.1

Subtotal $0.39

  • IC1, ATMEGA2560-A, 5.75
  • U2-U9, VO2223A, 8*0.79
  • U10, LM1117-3.3, 0.4
  • U11, ENC28J60-I/SO, 5.03
  • U12, FT232RL, 1.88
  • U13, MC74VHC1GT125, 2.08

Subtotal $21.46

  • P1, USB mini connector, 1.38
  • P2, pin header 2X4, 0.021
  • J1, HANRUN HR911105A, 1.76
  • J2-J9, screw terminal 1X3, 8*0.20
  • J10, screw terminal 1X2, 0.15
  • 8-pin DIP socket, 8*0.03
  • PCB, 8.88

Subtotal $14.031
Total $36.711

Connections

Reference schematics for how to make connection between the ENC28J60 and HR911105A.
https://www.banggood.com/ENC28J60-Ethernet-LAN-Network-Module-Schematic-For-Arduino-51-AVR-LPC-p-87596.html

Direct link from above
https://img.banggood.com/thumb/water/upload/2012/lidanpo/SKU076708.jpg

ENC28J60 datasheet
http://ww1.microchip.com/downloads/en/DeviceDoc/39662b.pdf

HR911105A datasheet
http://www.kosmodrom.com.ua/pdf/HR911105A.pdf

Schematics and board layout

Here is the schematics. Click to enlarge.

Soldering

The project requires special soldering skills. Microcontroller AtMega2560 is 100 pin TQFP package. USB-to-serial interface FT232RL is 28-pin SSOP package. And ENC28J60 is 28 pin SOIC package. Board also contains MC74VHC1GT125 buffer and level shifter ic in SOT-353 package. Hot air soldering gun is recommended tool and use of solder paste.

All trough hole and SMD components are hand-soldered on the PCB. After the SMD components are soldered, the remaining components are easy to solder on the circuit board. VO2223A is sensitive to heat so using a ic socket is recommended.

Advantages

My device works when simple interface is needed with little effort. If more current driving capacity is needed then user can use AC relays with DIN rail mounting. For example in the amusement park lighting.

This product works on all the different mains voltages in Europe and rest of the world too. My product has eight outputs and internet connection as IoT means.

This product does not use bulky relays so is smaller than its competitors.

USB control is also an option on my device. It just depends on how it is programmed. I also have an option for additional cards which can be anything. For example RS485 connection.

Programming

All parts are open source.

The program is written in C++, HTML, Javascript and CSS languages. The code uses more than 41KB of program memory so it is too long to fit into AtMega328 chip therefore AtMega2560 chip has been used.

The bootloader must first be programmed using on board standard ICSP connector and ISP. I personally like to use Arduino™ as ISP and Arduino™ IDE. Be sure to choose the Arduino™ Mega from the Tools→Board menu. After installing the bootloader, the program can be uploaded using a normal on board USB-to-serial interface.

FT232RL also needs to be programmed. Programming is done with FT-PROG program found on the FTDIchip.com website. It only works in Windows. Simplest method is to clone other ready made device. I cloned memory content from Sparkfun FTDI Basic board.

ENC28J60 is configurable via SPI but default settings are fine. Full-duplex operation is selected with LEDB. Connection of the LED is automatically read on reset. When the pin sinks current to illuminate LED, the PHCON1.PDPXMD bit is set and the PHY defaults to full-duplex operation. In full-duplex mode, switch and station can send and receive simultaneously, and therefore it is completely collision-free. Connection speed is 100Mbit/s.

I’m using UIPEthernet library for ENC28J60. It is licensed under GNU General Public License version 3. Other libraries available are Arduino uIP by Norbert Truchsess (GPL3), EtherCard (GPL2), EtherSia (3-clause BSD), ETHER_28J60 (LGPLv2.1).

Program code.

/*  Ethernet Triac Board
 *   
 *    - Program ver. 1.0
 *    - Compatible with board ver. 1.1
 *  
 *  Eight channel digital triac control board with Ethernet and USB 
 *  connection. Drives 8 output with AC load. Ethernet connection 
 *  supports automatic DHCP. No need to manual enter IP address. 
 *  Arduino Mega based system with AtMega2560 chip.
 *  
 *  Easy to use web interface to modify and save custom triac control 
 *  programs. Program is saved in to EEPROM. It automatically begins on 
 *  startup and loops forevermore.
 *  
 *  Control Panel layout by Lari Varjonen
 * 
 *  Copyright (C) 2017-2018  Juha-Pekka Varjonen
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <UIPEthernet.h>
#include <EEPROM.h>

unsigned long LedMillis = 0; // delays
unsigned long PrgMillis = 0;
unsigned long SaveMillis = 0;

int pos = 0;
boolean changed = false; // save program to EEPROM

// MAC address
const uint8_t mac[6] PROGMEM = {
  0x80, 0xAA, 0xBB, 0xCC, 0xDE, 0x02
};

// Stores the HTTP request
String HTTP_req;

// stores current program
int rowTotal = 0;
char contrArr[30][8];
int delayArr[30];

// Initialize the Ethernet server library
EthernetServer server(80); // port 80

void setup() {
  pinMode(4, OUTPUT); // LED output

  pinMode(33, OUTPUT); // data outputs
  pinMode(34, OUTPUT);
  pinMode(35, OUTPUT);
  pinMode(36, OUTPUT);
  pinMode(37, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(10, OUTPUT);
  
  // Open serial communication
  Serial.begin(9600);
  Serial.println(F("Ethernet Triac Board Copyright (C) 2017-2018  Juha-Pekka Varjonen"));
  Serial.println(F("This program comes with ABSOLUTELY NO WARRANTY; for details "
    "see license. This is free software, and you are welcome to redistribute it "
    "under certain conditions; see license for details."));

  // EEPROM initial content
//  String temp = "[[1,1,1,1,1,1,1,1,500],[0,0,0,0,0,0,0,0,500]]";
//  for (int i = 0; i < temp.length(); i++) {
//    EEPROM.write(i,temp.charAt(i));
//  }

  // Read saved program from EEPROM
  String temp;  
  boolean valid = false;
  for (int i = 0; i < EEPROM.length(); i++) {
    char n = EEPROM.read(i);
    char nn = EEPROM.read(i+1);
    if (n == '[' && nn == '[') valid = true;
    if (valid) temp += String(n);
    if (n == ']' && nn == ']') {
      valid = false;
      temp += String(nn);
      temp += String('\0');
      break;
    }
  }
  parseJSON(temp);
  
  // Ethernet Chip Select
  pinMode(53, OUTPUT);
  digitalWrite(53, LOW);
  
  // start the Ethernet connection
  Ethernet.begin(mac);//,IPAddress(192,168,1,36));
  // Start listening for clients
  server.begin();
}

void loop() {
  unsigned long currentMillis = millis();
  
  if (currentMillis - LedMillis >= 100) {
    digitalWrite(4, !digitalRead(4));
    LedMillis = currentMillis;
  }
  
  // Wait for a new client
  EthernetClient client = server.available();

  // Got client?
  if (client) {
    Serial.println(F("New client"));
    // Empty string
    HTTP_req = "";
    // An HTTP request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      // Client data available to read
      if (client.available()) {
        // Read one byte from client
        char c = client.read();
        // Save the HTTP request one char at a time
        HTTP_req += c;
        
        // Last line of client request is blank and ends with \n
        // Respond to client only after last line received        
        if (c == '\n' && currentLineIsBlank) {
          // GET receive program data
          if (HTTP_req.indexOf(F("?json=")) > -1) {
            int subStart = HTTP_req.indexOf(F("?json=")) + 6;
            int subEnd = HTTP_req.indexOf("HTTP") - 1;
            String input = HTTP_req.substring(subStart, subEnd);
            
            // Parse JSON string
            parseJSON(input);
            
            pos = 0;
            // Save to EEPROM
            changed = true;
            // start save counter from zero
            SaveMillis = currentMillis;
          }
          // AJAX request current line
          if (HTTP_req.indexOf(F("line.txt")) > -1) {
            // Send current line number only
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/plain"));
            client.println(F("Connection: keep-alive"));
            client.println();
            client.print(pos+1);
          }
          // AJAX request program code
          else if (HTTP_req.indexOf(F("file.json")) > -1) {
            // Send program in JSON format
            String temp;
            temp = '[';
            for (int i = 0; i <= rowTotal; i++) {
              temp += '[';
              for (int ii = 0; ii < 8; ii++) {
                temp += contrArr[i][ii];
                temp += ',';
              }
              temp += delayArr[i];
              temp += ']';
              if (i < rowTotal) temp += ',';
            }
            temp += ']';
            
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: application/json"));
            client.println(F("Connection: keep-alive"));
            client.println();
            client.print(temp);
          }
          // HTTP request
          else {
            if (HTTP_req.indexOf(F("index.html")) > -1 || HTTP_req.indexOf(F("GET / ")) > -1) {
              // Send html page
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/html"));
              client.println(F("Connection: keep-alive"));
              client.println();
              client.println(F("<!DOCTYPE HTML><html><head><meta charset=\"UTF-8\"><title>8 Channel Triac Board - Control Panel</title><link rel=\"stylesheet\" type=\"text/css\" href=\"css/style.css\"></head><body><div class=\"top\"><div class=\"bar\"><div class=\"col\"><label for=\"file-upload\">Open list</label><input type=\"file\" name=\"open_button\" value=\"Open list\" id=\"file-upload\"/></div><div class=\"col\"><input type=\"button\" name=\"save_button\" value=\"Save list\"/></div><div class=\"col\"><input type=\"button\" name=\"remove_button\" value=\"Remove line\"/></div><div class=\"col\"><input type=\"button\" name=\"add_button\" value=\"Add line\"/></div></div></div><div class=\"background\"></div><div class=\"container\"><div class=\"row\" style=\"display:none\" id=\"1\"><div class=\"line_n\"><div class=\"arrow-right\"></div><div class=\"n\">1</div></div><input type=\"checkbox\" name=\"checkbox\"/><div class=\"checkbox_light\"></div><div class=\"led_background\"><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">1</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">2</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">3</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">4</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">5</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">6</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">7</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">8</div></div><input type=\"number\" min=\"0\" name=\"delay_t\" value=\"\" onchange=\"javascript: delayChange(this);\"/></div></div></div><script type=\"text/javascript\" src=\"js/java.js\" ></script></body></html>"));

            } else if (HTTP_req.indexOf(F("style.css")) > -1) {
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/css"));
              client.println(F("Connection: keep-alive"));
              client.println();
              client.println(F("body{margin:0;padding:0;background:#10162E;overflow-y:scroll;overflow-x:hidden}body,html{height:100%}@-moz-document url-prefix(){*::-moz-focus-inner,*::-moz-focus-outer,*:focus{border:none;outline:none;max-width:100%;border-radius:0;box-shadow:none;padding:0}}.top{position:fixed;z-index:10;width:100%;height:36px;background:#364168;display:block;margin:0;padding:0;top:0;left:0}.background,.container,.top .bar{width:269px}.top .bar{height:32px;margin:2px auto;display:table}.top .bar .col{display:table-cell;height:32px}input[type=file]{display:none !important}.top .bar input,label{width:100%;display:block;height:24px;border:none;outline:none;border-radius:0;box-shadow:none;margin:4px 0 0;padding:0;background:#fff;border-radius:6px;text-align:center;font-family:arial;font-size:14px;color:#7E7E7E;line-height:24px}.top .bar .col:nth-child(odd){padding:0 4px}.col:nth-child(1){padding:0 4px 0 0 !important}.top .bar input:hover,label:hover{background:#F8F8F8}.top .bar input:active{background:#F0F0F0;color:#000}.background{position:fixed;z-index:0;height:100%;margin:0 0 0 -134px;left:50%;background:#3E4769}.container{position:absolute;z-index:1;margin:36px 0 53px -134px;left:50%;padding:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.container .row,.container .row .led_background,.container .row .led_background .led,.container .row .led_background .led .light,.container .row .led_background .led input,.container .row input[name=checkbox],.container .row input[name=delay_t]{height:24px}.container .row .led_background .led,.container .row input[name=delay_t]{float:left}.container .row .led_background .led .light,.container .row .line_n .n,.container .row input[name=delay_t]{font-family:arial;font-size:10px;color:#848484;text-align:center}.container .row .led_background .led .light,.container .row input[name=delay_t]{line-height:24px}.container .row,.container .row .checkbox_light,.container .row .led_background,.container .row .line_n,.top{-o-transition:0.2s;-ms-transition:0.2s;-moz-transition:0.2s;-webkit-transition:0.2s;transition:0.2s}.container .row,.container .row .led_background{width:269px}.container .row{position:relative;background:#fff}.container .row:hover{background:#FBF7E1}.container .row:hover > .line_n{opacity:1}.container .row .line_n:hover{opacity:0.8}.container .row .line_n{height:16px;position:absolute;right:100%;margin-right:8px;opacity:0.8;margin-top:4px;background:#fff;border-radius:4px;padding:0 4px}.container .row .line_n .n{line-height:16px}.container .row .line_n .arrow-right{width:0;height:0;position:absolute;left:100%;margin-top:4px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #fff}.container .row input[name=delay_t]{border:none;outline:none;border-radius:0;box-shadow:none;margin:0;padding:0;width:68px;background:transparent;border-radius:0;line-height:30px;letter-spacing:1px}.container .row .led_background .led .light,.container .row .led_background .led input{position:absolute;top:0;left:0}.container .row .led_background .led,.container .row .led_background .led .light,.container .row .led_background .led input,.container .row input[name=checkbox]{width:24px}.container .row input[name=checkbox]{position:absolute;left:100%;margin:0}.container .row .led_background .led{position:relative;float:left;border-right:1px solid #F2F2F2;overflow:hidden}.container .row .led_background .led:last-child{border:none}.container .row .checkbox_light,.container .row .led_background .led .light,.container .row input[name=checkbox]{position:absolute}.container .row .checkbox_light{left:100%;width:12px;height:12px;margin:6px 0 0 6px;border-radius:6px}.container .row .led_background .led input,.container .row input[name=checkbox]{border:none;outline:none;box-shadow:none;margin:0;padding:0;vertical-align:middle;-webkit-appearance:none;cursor:pointer;z-index:1;opacity:0}.container .row .led_background .led input:checked + .light{color:#424242;background:#CEF6CE}.container .row input[name=checkbox]:hover + .checkbox_light{background:#3B0B0B}.container .row input[name=checkbox]:checked + .checkbox_light{background:red;background:-webkit-linear-gradient(left top, red, #610B0B);background:-o-linear-gradient(bottom right, red, #610B0B);background:-moz-linear-gradient(bottom right, red, #610B0B);background:linear-gradient(to bottom right, red, #610B0B)}.container .row input[name=checkbox]:checked ~ .led_background{background:#FFE1E1}"));
            } else if (HTTP_req.indexOf(F("java.js")) > -1) {
              
              String IPaddr;
              for (byte thisByte = 0; thisByte < 4; thisByte++) {
                // print the value of each byte of the IP address:
                IPaddr += String(Ethernet.localIP()[thisByte], DEC);
                if (thisByte < 3) IPaddr += ".";
              }
              
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/javascript"));
              client.println(F("Connection: keep-alive"));
              client.println();
              client.print(F("var container,clone,data;var json_list=[];var button_click=false;function updateRemote(){var xhr=new XMLHttpRequest();xhr.open(\"GET\",\"http://"));
              client.print(IPaddr);
              client.print(F("/index.html?json=\"+JSON.stringify(json_list),true);xhr.timeout=2000;xhr.send(null)}function parseResult(){container.innerHTML=\"\";for(var i=0;i<json_list.length;i+=1){add_line(json_list[i][0],json_list[i][1],json_list[i][2],json_list[i][3],json_list[i][4],json_list[i][5],json_list[i][6],json_list[i][7],json_list[i][8],)};updateRemote()}function read_json_file(){var nocache=\"&nocache=\"+Math.random()*1000000;var xhr=new XMLHttpRequest();xhr.open(\"GET\",\"http://"));
              client.print(IPaddr);
              client.print(F("/file.json\"+nocache,true);xhr.timeout=2000;xhr.onreadystatechange=function(){if(this.readyState==4){if(this.status==200){if(this.responseText!=null){json_list=JSON.parse(this.responseText);parseResult()}}}};xhr.send(null)};function getCurrentLine(){var nocache=\"&nocache=\"+Math.random()*1000000;var xhr=new XMLHttpRequest();xhr.open(\"GET\",\"http://"));
              client.print(IPaddr);
              client.print(F("/line.txt\"+nocache,true);xhr.timeout=2000;var err=0;xhr.onreadystatechange=function(){if(this.readyState==4){if(this.status==200){if(this.responseText!=null){/*var pos=this.responseText;var obj=container.getElementsByClassName(\"row\");for(var i=0;i<obj.length;i+=1){obj[i].style.background=\"#fff\"}obj[pos].style.background=\"#FBF7E1\";*/setTimeout(\"getCurrentLine()\",10500);document.getElementsByClassName(\"top\")[0].title=\"online\";document.getElementsByClassName(\"top\")[0].style.background=\"#3bb151\"}else{err=1}}else{err=1}}; if(err==1){setTimeout(\"getCurrentLine()\",10500);document.getElementsByClassName(\"top\")[0].title=\"offline\";document.getElementsByClassName(\"top\")[0].style.background=\"#b13b3b\"}};xhr.send(null)}function readFile(event){json_list=JSON.parse(event.target.result);parseResult()}function open_button_change(){var file=document.getElementsByName(\"open_button\")[0].files[0];var reader=new FileReader();var tiedosto=reader.readAsText(file);reader.addEventListener('load',readFile)}function remove_button_click(){for(var i=0;i<container.getElementsByClassName(\"row\").length;i+=1){var obj=container.getElementsByClassName(\"row\")[i];if(obj.getElementsByTagName(\"input\")[0].checked){if(json_list.length==1){json_list=[]}else{var deletedItem=json_list.splice(i,1)}}}parseResult()}function save_button_click(){var blob=new Blob([JSON.stringify(json_list)],{type:'application/x-download'});var blobUrl=URL.createObjectURL(blob);var a=document.createElement('A');a.href=blobUrl;a.download=\"file.json\";document.body.appendChild(a);a.click();document.body.removeChild(a)}function ledClick(obj){var i=obj.parentNode.getElementsByClassName(\"light\")[0].innerHTML;var a=obj.parentNode.parentNode.parentNode.id;json_list[a-1][i-1]=obj.checked+0;updateRemote()}function delayChange(obj){var a=obj.parentNode.parentNode.id;json_list[a-1][8]=parseInt(obj.value);updateRemote()}function add_line(i1,i2,i3,i4,i5,i6,i7,i8,delay_t){var new_size=container.getElementsByClassName(\"row\").length+1;(clone.firstElementChild||clone.firstChild).getElementsByClassName(\"n\")[0].innerHTML=new_size;clone.id=new_size;container.appendChild(clone.cloneNode(true));var leds=document.getElementsByName(\"led\");leds[(new_size-1)*8].checked=i1;leds[((new_size-1)*8)+1].checked=i2;leds[((new_size-1)*8)+2].checked=i3;leds[((new_size-1)*8)+3].checked=i4;leds[((new_size-1)*8)+4].checked=i5;leds[((new_size-1)*8)+5].checked=i6;leds[((new_size-1)*8)+6].checked=i7;leds[((new_size-1)*8)+7].checked=i8;document.getElementsByName(\"delay_t\")[new_size-1].value=delay_t;button_click=true;window.location.hash=clone.id}function add_button_click(){add_line(0,0,0,0,0,0,0,0,500);json_list.push([0,0,0,0,0,0,0,0,500])}window.onload=function(){container=document.getElementsByClassName(\"container\")[0];clone=container.getElementsByClassName(\"row\")[0].cloneNode(true);clone.removeAttribute(\"style\");container.innerHTML=\"\";read_json_file();getCurrentLine();document.getElementsByName(\"open_button\")[0].onchange=open_button_change;document.getElementsByName(\"remove_button\")[0].onclick=remove_button_click;document.getElementsByName(\"save_button\")[0].onclick=save_button_click;document.getElementsByName(\"add_button\")[0].onclick=add_button_click};window.onhashchange=function(){if(!button_click){window.scrollBy(0,-36)}else{button_click=false}};"));
            } else {
              client.println(F("HTTP/1.1 404 Not Found"));
              client.println(F("Content-Type: text/html"));
              client.println(F("Connection: close"));
              client.println();
            }
          }
          // Display received HTTP request on serial port
          Serial.print(HTTP_req);
          break;
        }
        if (c == '\n') currentLineIsBlank = true;
        else if (c != '\r') currentLineIsBlank = false;
      }
    }
    // Give the web browser time to receive the data
    delay(10);
    // Close the connection
    client.stop();
  }

  // Delayed save to EEPROM
  if (changed && currentMillis - SaveMillis >= 60000) {
    changed = false;
    SaveMillis = currentMillis;
    
    // make JSON string
    String temp;
    temp = '[';
    for (int i = 0; i <= rowTotal; i++) {
      temp += '[';
      for (int ii = 0; ii < 8; ii++) {
        temp += contrArr[i][ii];
        temp += ',';
      }
      temp += delayArr[i];
      temp += ']';
      if (i < rowTotal) temp += ',';
    }
    temp += ']';
    // save to EEPROM
    Serial.println("Saving to EEPROM");
    //EEPROM.put(0, temp); Does not work - too easy
    for (int i = 0; i < temp.length(); i++) {
      EEPROM.update(i,temp.charAt(i));
    }
  }

  // Update outputs
  if (currentMillis - PrgMillis >= delayArr[pos]) {
    PrgMillis = currentMillis;
    digitalWrite(33,String(contrArr[pos][0]).toInt());
    digitalWrite(34,String(contrArr[pos][1]).toInt());
    digitalWrite(35,String(contrArr[pos][2]).toInt());
    digitalWrite(36,String(contrArr[pos][3]).toInt());
    digitalWrite(37,String(contrArr[pos][4]).toInt());
    digitalWrite(12,String(contrArr[pos][5]).toInt());
    digitalWrite(11,String(contrArr[pos][6]).toInt());
    digitalWrite(10,String(contrArr[pos][7]).toInt());
    pos++;
    if (pos > rowTotal) pos = 0;
  }
}

void parseJSON(String input) {
  String tempStr = "";
  int row = 0;
  int col = 0;
  input.replace("[","");
  input.replace("]","");
  for (int i = 0; i < input.length(); i++) {
    if (input.charAt(i) == ',') col++;
    else if (col < 8) contrArr[row][col] = input.charAt(i);
    else if (col == 8) tempStr += input.charAt(i);
    if (col == 9) {
      delayArr[row] = tempStr.toInt();
      tempStr = "";
      row++;
      col = 0;
    }
  }
  delayArr[row] = tempStr.toInt(); // last line
  rowTotal = row;
}

Other projects

Other projects which uses VO2223 phototriac

ARDUINO is a registered trademark.

Cathode Ray Tube (CRT) Display

The most complex project ever, but now it works. You can use Arduino to enter text and graphics on the screen.

The power supply makes a ±350 volt DC voltage. Deflection circuitry works with positive voltage and blanking circuit works with negative voltage. Before connecting CRT tube adjust the voltages according to the circuit diagram. Anode voltage is about +300V. Cathode voltage is about -290V. Focus voltage is about -200V and brightness voltage is about -320V. This circuit works for most small picture tubes found in eBay. I use 6LO1i. I will try others later.

The cathode voltage must be less (closer to zero) than brightness voltage otherwise blanking circuit does not work. First disconnect DAC from deflection circuit and adjust the dot in the center of the screen. Then adjust focus and anode voltage (astigmatism) so that dot is as sharp as possible. Be careful not to burn the phosphor coating of the CRT tube. Decrease brightness if necessary.

Digital to Analog Converter (DAC) circuit is commonly used R-2R resistor ladder network. The same circuit to horizontal and vertical deflection. Differential amplifier is used to amplify result.

Arduino example code:

static const byte ASCII[][5] =
{
 {0x00, 0x00, 0x00, 0x00, 0x00} // 20  
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c <
,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥
,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j 
,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ←
,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f →
};

unsigned long newMillis = 0;
unsigned long clockMillis = 0;
int second = 30;
int minute = 16;
int hour = 18;

long rndX=30, rndY=20;

void setup() {
  pinMode(2, OUTPUT); // 1 serial  D2   1 = vertical deflection
  pinMode(3, OUTPUT); // 1 OE      D3
  pinMode(4, OUTPUT); // 1 CLK     D4
  pinMode(5, OUTPUT); // 1 clear   D5
  
  pinMode(6, OUTPUT); // 2 serial  D6   2 = horizontal deflection
  pinMode(7, OUTPUT); // 2 OE      D7
  pinMode(8, OUTPUT); // 2 CLK     B0
  pinMode(9, OUTPUT); // 2 clear   B1

  pinMode(11, OUTPUT); // blanking B3

  digitalWrite(5, 1); // disable clear
  digitalWrite(9, 1);

  randomSeed(analogRead(0));
}

void loop() {
  
  drawDot(0,0);
  drawDot(0,255);
  drawDot(255,0);
  drawDot(255,255);

  unsigned long currentMillis = millis();
  
  if (currentMillis - newMillis >= 5000) {  
    newMillis = currentMillis;
    rndX = random(28,35);
    rndY = random(18,23);
  }

  if (currentMillis - clockMillis >= 1000) {  
    clockMillis = currentMillis;
    second++;
    if (second >= 60) {
      second = 0;
      minute++;
      if (minute >= 60) {
        minute = 0;
        hour++;
        if (hour >= 24) {
          hour = 0;
        }
      }
    }
  }

  int upperHour = hour/10;
  int lowerHour = hour - (upperHour*10);
  
  int upperMin = minute/10;
  int lowerMin = minute - (upperMin*10);

  int upperSec = second/10;
  int lowerSec = second - (upperSec*10);
  
  byte text[] = {(0x30 + upperHour),
    (0x30 + lowerHour),
    (0x3a),
    (0x30 + upperMin),
    (0x30 + lowerMin),
    (0x3a),
    (0x30 + upperSec),
    (0x30 + lowerSec)};

  printText(text,8,rndY,rndX);
}

void printText(byte arr[], int len, int line, int col) {
  for (int i = 0; i < len; i++) {
    printASCII(arr[i], 6*(col+i),9*line);
  }
}

void printASCII(int e, int x, int y) {
  for (int i = 0; i < 5; i++) {
    for (int ii = 0; ii <= 7; ii++) {
      if (bitRead(ASCII[e-32][i],ii)) drawDot(x+i,y+ii);
    }
  }
}

void drawDot(int x, int y) {

  if (x > 255 || y > 255) return;

  for (int i = 7; i >= 0; i--) {
    
    // send data
    (bitRead(y,i)) ? (PORTD |= (1 << 2)) : (PORTD &=~(1 << 2));
    (bitRead(x,i)) ? (PORTD |= (1 << 6)) : (PORTD &=~(1 << 6));

    PORTD |= (1 << 4); // toggle clock
    PORTD &=~(1 << 4);
    PORTB |= (1 << 0);
    PORTB &=~(1 << 0);
  }

  PORTD |= (1 << 4); // toggle clock
  PORTD &=~(1 << 4);
  PORTB |= (1 << 0);
  PORTB &=~(1 << 0);

  delayMicroseconds(150);
  PORTB |= (1 << 3); // blanking 1
  delayMicroseconds(150);
  PORTB &=~(1 << 3); // blanking 0
  
  PORTD &=~(1 << 5); // toggle clear
  PORTD |= (1 << 5);
  PORTB &=~(1 << 1);
  PORTB |= (1 << 1);
}

 

LED Matrix Clock

This clock is made of 60 LEDs and the AtMega328 chip with Arduino bootloader. Video of clock in operation. https://www.youtube.com/watch?v=L1F2BAmQfWg

Here is schematics. Board has two button to adjust hours and minutes and Real-time clock ic to keep time accurate.

Back side is not perfect because of cheap soldering iron.

Program code is below.

#include "Wire.h"

boolean LEDarr[60];
unsigned long newMillis = 0;
unsigned long blinkMillis = 0;
int second = 0;
int minute = 0;
int hour = 1;

unsigned long debounceTimer;
boolean A2state = 1;
boolean A3state = 1;

void setup() {
  pinMode(0, OUTPUT); // matrix
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);

  pinMode(8, OUTPUT); // matrix
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);

  pinMode(A2, INPUT_PULLUP); // buttons
  pinMode(A3, INPUT_PULLUP);

  Wire.begin(); // A4,A5
  getRTC();
}

int ii = 0;

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - newMillis >= 500) {  
    newMillis = currentMillis;
    getRTC();
  }
  if(!digitalRead(A2)) {
    if(A2state) {
      A2state = 0;
      debounceTimer = millis();
    }
    if(millis() - debounceTimer > 150) {
      A2state = 1;
      if(++hour == 13) hour = 1;
      newMillis = currentMillis;
      setRTC();
    }
  }
  if(!digitalRead(A3)) {
    if(A3state) {
      A3state = 0;
      debounceTimer = millis();
    }
    if(millis() - debounceTimer > 150) {
      A3state = 1;
      if(++minute == 60) minute = 0;
      newMillis = currentMillis;
      setRTC();
    }
  }
  for(int i = 0; i <= 59; i++) LEDarr[i] = 0;
  
  if(second == 59) {
    if (ii < 59) for(int i = 0; i <= ii; i++) LEDarr[i] = 1;
    ii = ii + 2;
  } else {
    ii = 0;
    LEDarr[second] = 1;
  }
  
  LEDarr[minute] = 1;
  LEDarr[(hour*5)-1] = 1;
  
  if(second == (hour*5)-1 && currentMillis - blinkMillis > 200) {
    blinkMillis = currentMillis;
    LEDarr[second] = !LEDarr[second];
  }
  if(second == minute && currentMillis - blinkMillis > 200) {
    blinkMillis = currentMillis;
    LEDarr[second] = !LEDarr[second];
  }
  if(minute == (hour*5)-1 && currentMillis - blinkMillis > 200) {
    blinkMillis = currentMillis;
    LEDarr[minute] = !LEDarr[minute];
  }
  LEDloop();
}

byte decToBcd(byte val) {
  return ((val/10*16) + (val%10));
}

byte bcdToDec(byte val) {
  return ((val/16*10) + (val%16));
}

void setRTC() {
  Wire.beginTransmission(0x51);
  Wire.write(0x03);
  //Wire.write(decToBcd(second));  
  Wire.write(decToBcd(minute));
  Wire.write(decToBcd(hour-1));
  Wire.endTransmission();
}

void getRTC() {
  Wire.beginTransmission(0x51);
  Wire.write(0x02);
  Wire.endTransmission();
  Wire.requestFrom(0x51, 3);
  second = bcdToDec(Wire.read() & B01111111); // remove VL error bit
  minute = bcdToDec(Wire.read() & B01111111); // remove unwanted bits from MSB
  hour = bcdToDec(Wire.read() & B00111111);
  if(hour >= 12) hour = hour - 12;
  hour = hour + 1;
}

void LEDloop() {
  int led = 0;
  for(int a = 8; a <= 15; a++) {
    if (a <= 13) digitalWrite(a, 1);
    else if(a == 14) digitalWrite(A0, 1);
    else if(a == 15) digitalWrite(A1, 1);
    if(a != 8) {
      for(int i = 4; i <= 7; i++) {
        digitalWrite(i, !LEDarr[led++]);
        delayMicroseconds(340);
        digitalWrite(i, 1);
      }
    }
    for(int i = 0; i <= 3; i++) {
      digitalWrite(i, !LEDarr[led++]);
      delayMicroseconds(340);
      digitalWrite(i, 1);
    }
    if (a <= 13) digitalWrite(a, 0);
    else if(a == 14) digitalWrite(A0, 0);
    else if(a == 15) digitalWrite(A1, 0);
  }
}

 

DMX Controlled Relay Board

Now automation is made easy. With this nifty module one can control four different application by using an computer with a DMX software installed. Module uses four DMX512 address. Starting address is selectable withing DIP switches. Relay is switched on by sending value over 138 and switched off by sending value under 118. Module remember last states during DMX signal loss.

Easy to solder. Arduino compatible and programmable. Can use BSD licensed free library for receiving DMX data. Example code is coming later.

With this and previously published USB-DMX Interface its very easy to build home automation or stage lightning effects.

All relays capable to drive 5A, 230V loads. Uses only one supply voltage of 5V.

Part list

All parts can be bought from www.taydaelectronics.com.

  • R1, R2, R4, R6, R8 – 10k resistor
  • R3, R5, R7, R9 – 200 ohm resistor
  • C1, C2 – 100nF ceramic disc capacitor
  • C3, C4 – 12pF ceramic disc capacitor
  • D1, D3, D5, D7 – 5mm red led
  • D2, D4, D6, D8 – 1N4004 diode
  • Q1-Q4 – BC547 or 2N3904 general purpose transistor
  • Y1 – 16MHz crystal
  • U1 – atMega328P-PU microcontroller ic
  • U5 – MAX485 line driver ic
  • U2, U3, U4, U6 – HJF-3FF relay
  • SW1 – DIP switch 9 positions
  • J3 – 4 pin header
  • J1 – 2 pin screw terminal
  • J2, J4-J8 – 3 pin screw terminal

USB/Ethernet Relay Card with 8 relays

 

Arduino compatible web server based relay card which also can be used with USB connection. No special soldering skills required. Easy to program with popular ENC28J60 Ethernet controller. All Arduino Ethernet libraries are compatible e.g. this ENC28J60 library. Only one +5V USB power supply needed. All relais can be controlled individually. Relais can drive 5A load from 230V.

Board uses FTDI USB i.c. Arduino bootloader natively supports USB and virtual serial port. Device is programmable via USB port with Arduino IDE or similar programs.

All parts can buy from http://www.taydaelectronics.com. Only exception is ethernet connector. It must include transformers and filter elements.