AVR/Arduino RFID Reader

This simple reader is based on previous post circuit and code. LCD display and custom AVR/Arduino circuit is added. Tag number is displayed on screen when detected. Detecting distance is about 2-3cm. Working voltage is 4.5V. This is maximum voltage of LCD display.

Video: https://www.youtube.com/watch?v=PWo2UU3oikA

Modified code with LCD driving:

#include <LiquidCrystal.h>

uint8_t cparity;
uint8_t numbers[5];

LiquidCrystal lcd(4,2,5,6,8,9);

void setup() {
  //Serial.begin(115200);
  pinMode(11, OUTPUT); // PWM output
  pinMode(13, OUTPUT); // Status LED

  lcd.begin(8,2);
  lcd.print("RFID");
  lcd.setCursor(0,1);
  lcd.print("reader");

  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0;
  TCCR2A = 0b01000001;
  TCCR2B = 0b00001001;
  OCR2A  = 32; // 32=125kHz, 27=143kHz == (16000000/(2*1*143000))/2
}

void wait(uint8_t t){
  uint8_t counter = 0,last = 0,next;
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (counter < t);
}

uint8_t readBit(uint8_t time1, uint8_t time2) {
  
  wait(time1);
  
  uint8_t counter = 0, last = 0, state1 = PIND & (1 << 7), state2;
  do {
    uint8_t next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
    state2 = PIND & (1 << 7);
  } while (counter <= time2 && state1 == state2);
  
  if (state1 == state2) return 2;
  if (state2 == 0) return 0; else return 1;
}

int8_t read4Bits(uint8_t time1, uint8_t time2) {
  
  uint8_t number = 0, parity = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(time1, time2);
    if (bit1 == 2) return -1;
    number = (number << 1) + bit1;
    parity += bit1;
  }
  
  uint8_t bit1 = readBit(time1, time2);
  if (bit1 == 2 || (parity & 1) != bit1) return -1;
  cparity ^= number;
  return number;
}

uint8_t readCard() {
  loop_until_bit_is_clear(PIND, 7);
  loop_until_bit_is_set(PIND, 7);
  
  uint8_t counter = 0, last = 0, next;  
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (bit_is_set(PIND, 7) && counter<0xFF);
  
  if (counter == 0xFF && bit_is_set(PIND, 7)) return 1;
  
  uint8_t halfbit = counter, offset = counter >> 1;  
  if (readBit(offset, halfbit) != 1) return 1;
  
  for (uint8_t i = 7; i > 0; i--)
    if (readBit(halfbit + offset, halfbit) != 1) return 1;
    
  cparity=0;
  for (uint8_t i = 0; i < 5; i++) {
    int8_t n1 = read4Bits(halfbit + offset, halfbit),
           n2 = read4Bits(halfbit + offset, halfbit);
    if (n1 < 0 || n2 < 0) return 1;
    numbers[i] = (n1 << 4) + n2;
  }
  
  uint8_t cp = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(halfbit + offset, halfbit);
    if (bit1 == 2) return 1;
    cp = (cp << 1) + bit1;
  }
  
  if (cparity != cp) return 1;
  if (readBit(halfbit + offset, halfbit) != 0) return 1;
  
  return 0;
}

void loop() {

  uint8_t result;
  do {
    result = readCard();
    PORTB ^= (1<<5); // LED drive
  } while (result != 0);

  lcd.clear();
  lcd.print("Raw data");
  lcd.setCursor(0,1);
  //Serial.print("Raw data: ");
  for (uint8_t i=0;i<5;i++) lcd.print(numbers[i],HEX); //Serial.print(numbers[i],HEX);
  //Serial.println();

  delay(1000);
  lcd.clear();
  lcd.print("Number");
  lcd.setCursor(0,1);

  //Serial.print("Card number: ");
  uint8_t numbersr[4];
  numbersr[0]=numbers[4];
  numbersr[1]=numbers[3];
  numbersr[2]=numbers[2];
  numbersr[3]=numbers[1];
  //Serial.print(*(uint32_t*)(&numbersr),DEC);
  //Serial.println();
  lcd.print(*(uint32_t*)(&numbersr),DEC);
  
  delay(1000);
}

Part list:

  • R1, R9, R10 – 1k resistor
  • R2 – 47k resistor
  • R3, R6, R7, R8 – 22k resistor
  • R4, R5 – 2k2 resistor
  • R11 – 680 resistor
  • R12 – 10k resistor trimmer
  • C1 – 4n7 capacitor
  • C2 – 2nF capacitor
  • C3, C4, C6, C9 – 100nF capacitor
  • C5 – 47pF capacitor
  • C7, C8 – 18pF capacitor
  • D1, D2, D3 – 1N4148 diode
  • D4 – Green LED diode
  • Y1 – 16MHz crystal
  • Q1 – 2N3904 transistor
  • Q2 – 2N3906 transistor
  • U1 – LM324 operational amplifier ic
  • IC1 – AtMega328P microcontroller ic

L1 is not normal component. Instead it is hand wound coil with inductance of 375µH. It can be physically any size or shape. Only important thing is inductance. Online calculator is good tool for this. Calculator asks permeability for air in this case. It is 1.

LCD connection:

  1. GND
  2. VCC
  3. V5 – (CONTR)
  4. RS – (D4)
  5. RW – (GND)
  6. E – (D2)
  7. DB0 – (NC = No Connection)
  8. DB1 – (NC)
  9. DB2 – (NC)
  10. DB3 – (NC)
  11. DB4 – (D5)
  12. DB5 – (D6)
  13. DB6 – (B0)
  14. DB7 – (B1)

Schematics:

Simple Arduino RFID reader

This simple circuit does not use any external modules to interfacing RFID reader to Arduino. The code below generates 125kHz frequency to other end of circuit. Then the other end sends the received data to Arduino input.

Inductor must be 375µH, but can be any size physically. Good reference to calculate correct value is here: https://www.allaboutcircuits.com/tools/coil-inductance-calculator/. I personally used 46 turns 58mm diameter coil of 0.5mm normal interconnecting wire. I used an empty can of Red Bull energy drink as former. Coil diameter is measured in the center of coil ‘edges’. Detecting distance is about 2-3cm.

Arduino program sends RFID tag data to serial port. This can then be used to for example open an electric lock. 125kHz output is pin 11 and data input is pin 7.

uint8_t cparity;
uint8_t numbers[5];

void setup() {
  Serial.begin(115200);

  pinMode(11, OUTPUT); // PWM output

  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0;
  TCCR2A = 0b01000001;
  TCCR2B = 0b00001001;
  OCR2A  = 32; // 32=125kHz, 27=143kHz == (16000000/(2*1*143000))/2
}

void wait(uint8_t t){
  uint8_t counter = 0,last = 0,next;
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (counter < t);
}

uint8_t readBit(uint8_t time1, uint8_t time2) {
  
  wait(time1);
  
  uint8_t counter = 0, last = 0, state1 = PIND & (1 << 7), state2;
  do {
    uint8_t next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
    state2 = PIND & (1 << 7);
  } while (counter <= time2 && state1 == state2);
  
  if (state1 == state2) return 2;
  if (state2 == 0) return 0; else return 1;
}

int8_t read4Bits(uint8_t time1, uint8_t time2) {
  
  uint8_t number = 0, parity = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(time1, time2);
    if (bit1 == 2) return -1;
    number = (number << 1) + bit1;
    parity += bit1;
  }
  
  uint8_t bit1 = readBit(time1, time2);
  if (bit1 == 2 || (parity & 1) != bit1) return -1;
  cparity ^= number;
  return number;
}

uint8_t readCard() {
  loop_until_bit_is_clear(PIND, 7);
  loop_until_bit_is_set(PIND, 7);
  
  uint8_t counter = 0, last = 0, next;  
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (bit_is_set(PIND, 7) && counter<0xFF);
  
  if (counter == 0xFF && bit_is_set(PIND, 7)) return 1;
  
  uint8_t halfbit = counter, offset = counter >> 1;  
  if (readBit(offset, halfbit) != 1) return 1;
  
  for (uint8_t i = 7; i > 0; i--)
    if (readBit(halfbit + offset, halfbit) != 1) return 1;
    
  cparity=0;
  for (uint8_t i = 0; i < 5; i++) {
    int8_t n1 = read4Bits(halfbit + offset, halfbit),
           n2 = read4Bits(halfbit + offset, halfbit);
    if (n1 < 0 || n2 < 0) return 1;
    numbers[i] = (n1 << 4) + n2;
  }
  
  uint8_t cp = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(halfbit + offset, halfbit);
    if (bit1 == 2) return 1;
    cp = (cp << 1) + bit1;
  }
  
  if (cparity != cp) return 1;
  if (readBit(halfbit + offset, halfbit) != 0) return 1;
  
  return 0;
}

void loop() {

  uint8_t result;
  do {
    result = readCard();
  } while (result != 0);

  Serial.print("Raw data: ");
  for (uint8_t i=0;i<5;i++) Serial.print(numbers[i],HEX);
  Serial.println();

  Serial.print("Card number: ");
  uint8_t numbersr[4];
  numbersr[0]=numbers[4];
  numbersr[1]=numbers[3];
  numbersr[2]=numbers[2];
  numbersr[3]=numbers[1];
  Serial.print(*(uint32_t*)(&numbersr),DEC);
  Serial.println();

  delay(1000);
}

References:

OpenDMX Compatible Unbuffered USB-DMX Interface (part 2)

First prototype is coming in October, 2018.

UPDATE Oct. 20. Now it’s ready and it’s working. Tested with QLC+ and with three channel DMX receiver. It is however impractical large and heavy to connect directly in to USB port. So next version fixes this. Coming soon.

UPDATE Oct. 12. Almost ready.

Open DMX USB is an open USB to DMX hardware design developed by Enttec. The Open in Open DMX USB refers to the fact that everybody is free to use the design and produce its own USB DMX dongle without paying any licenses.

The Open DMX USB is inexpensive way to get into the world of controlling DMX devices from a PC. It relies on the computer for all calculations and timing of the DMX signal. It is ideal for small live music and theater applications.

Powered by a computer over USB. Standard 3pin DMX port. Compatible with a wide range of 3rd Party software. Control up to 512 channels. Plugged directly on to the USB port.

This DMX USB interface is based on the FTDI 232RL chip, it’s a USB to serial converter. It’s designed accordingly to the USB specification and will enter suspend mode when there is no activity on the bus. It then shutdown the device and draw no more than rated suspend current.

Using a simple application on a PC you can send and receive DMX512. Recommended application is QLC+ because it is a free and cross-platform software to control DMX or analog lighting systems like moving heads, dimmers, scanners etc. QLC+ runs on Windows, MacOS and Linux.

D2XX drivers. https://www.ftdichip.com/Drivers/D2XX.htm

List of compatible control software and programming examples. https://www.enttec.com/products/controls/usb/open-dmx-usb/

Reference schematics for DMX bus termination. http://www.mathertel.de/Arduino/DMXShield.aspx

DMX 3pin XLR pinout reference. https://en.wikipedia.org/wiki/DMX512#XLR-3_pinout

Text sources:

Dual DC motor controller with Arduino (part 2)

Now with wireless control using 433MHz frequency. Code uses RadioHead-library.

Transmitter Arduino code:

#include <RH_ASK.h>
#include <SPI.h> // Not actualy used but needed to compile

RH_ASK driver(2000, 2, A5, 4);

byte old_mot_1;
byte old_mot_2;

void setup() {
  driver.init();
}

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

  byte mot_1 = map(pot_1, 0, 1023, 0, 255);
  byte mot_2 = map(pot_2, 0, 1023, 0, 255);

  byte msg[3];
  msg[0] = mot_1;
  msg[1] = mot_2;
  driver.send(msg, 3);
  driver.waitPacketSent();
  delay(200);
}

Receiver Arduino code:

#include <RH_ASK.h>
#include <SPI.h> // Not actualy used but needed to compile

RH_ASK driver(2000, 13, 2, 4);

int pot_1 = 128;
int pot_2 = 128;

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

void loop() {
  byte buf[3];
  if (driver.recv(buf, 3)) { // Non-blocking
    pot_1 = buf[0]; // potentiometers values
    pot_2 = buf[1];
  }

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

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

 

Python APRS Terminal

A simple Python GUI program to view APRS data from radio via serial port or Bluetooth. Easy to send messages and update status. Serial port and Bluetooth settings is available in settings panel. Autoscrolling feature is included to program so latest messages are always visible.

Features

  • Graphical user interface for those who not like text only mode.
  • Decodes KISS frame
  • Decodes ax.25 U frame
  • Decodes MIC-E format
  • Decodes APRS symbol and displays it on screen
  • Decodes APRS compressed data formats
  • Removes old items from the map
  • Removes traces of moving objects on the map

Supported and tested Hardware

  • Supports virtually any TNC which supports real or virtual serial port and KISS protocol.
  • Tested with Mobilinkd TNC2 through Bluetooth (with virtual serial port).

How does it work

Program decodes KISS frame and then ax.25 frame and lastly MIC-E data if available. Result is printed on window with colorful notation.

HELP – What to do if it does not work somehow?

Program should work on Python version 2.7.xx and onward. Run it with command “sudo python APRSterminal.py” without quotes. Contact me. I’m willing to help on any questions and can even add some features to program if necessary.

Source code is available from GitHub.

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);
}