Featured

Welcome to Juvar.fi!

I’m 34 years old programmer from Tampere, Finland.

This is my personal web site. 

I work on electronic design, construction and programming. It is also my hobby. on this site I will present some of the hobby projects I have made. I also have a radio amateur qualification. Self-learning and experimentation through hobby has since taught much more.

Embedded systems have always attracted me. nowadays they are everywhere. It refers to a device with a microprocessor and program code in it. Almost all my hobby projects are like that.

Setting up LoRa-GPS Tracker

Installing bootloader

Bootloader is pre-installed on board. This information is given if you want to make changes to it. You will need Segger J-Link programmer to follow these instructions and custom adapter to connect with Tag-Connect cable to board.

Clone UF2 repository and make a copy of /boards/feather_mo directory.

clone https://github.com/adafruit/uf2-samdx1
cd uf2-samdx1
cd boards
cp -r feather_m0 tracker_m0

Go to that directory and edit board_config.h file as following:

#ifndef BOARD_CONFIG_H
#define BOARD_CONFIG_H

#define VENDOR_NAME "Juha-Pekka Varjonen"
#define PRODUCT_NAME "LoRa-GPS-Tracker"
#define VOLUME_LABEL "TRACKERBOOT"
#define INDEX_URL "https://locateworld.net/"
#define BOARD_ID "SAMD21G18A-Tracker-v1"

#define USB_VID 0x1209
#define USB_PID 0x0005

#define LED_PIN PIN_PA17

#endif

You can fill it with your own data if you want. Note that volume label must be 11 character long uppercase string.

Build bootloader with command:

make BOARD=tracker_m0

Go to newly created directory ‘build’ and it’s subdirectory ‘tracker_m0’. If not yet installed, download and install J-Link software from here. Install with this command:

sudo dpkg -i ~/Downloads/jlink_package.deb

To enable/disable bootloader protection you must create two files accordingly to same directory with bootloader.

set.mot:

S214804000FAC7E0D85DFCFFFFFFFFFFFFFFFFFFFF63
S9030000FC

clear.mot:

S214804000FFC7E0D85DFCFFFFFFFFFFFFFFFFFFFF5E
S9030000FC

Start J-Link Commander.

JLinkExe

Type ‘connect’ to make connection to microcontroller. It follows with three question. To first question press enter and then ‘S’+enter and then enter again. Default settings are ok.

J-Link>connect
Please specify device / core. : ATSAMD21G18
Type '?' for selection dialog
Device>
Please specify target interface:
  J) JTAG (Default)
  S) SWD
TIF>S
Specify target interface speed [kHz]. : 4000 kHz
Speed>
Device "ATSAMD21G18" selected.

Clear bootloader protection fuse:

loadfile clear.mot

Reset the target:

r

Upload the bootloader:

loadbin bootloader.bin,0

Set the bootloader fuse:

loadfile set.mot

Now it should be visible as USB mass storage device and serial port.

Installing CircuitPython

CircuitPython is pre-installed on board. These instructions is given if you want to make changes to it. Clone Adafruit CircuitPython repository:

git clone https://github.com/adafruit/circuitpython

An ARM compiler is required for the build. It can be installed as follows:

sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
sudo apt-get install gcc-arm-embedded

Install necessary submodules before continue:

git submodule update --init --recursive

Next commands must be executed in subdirectory ports/atmel-samd/. Create copy of directory boards/feather_m0_basic and edit file mpconfigboard.h as follows:

#define MICROPY_HW_BOARD_NAME "LoRa-GPS-Tracker"

Then edit file mpconfigboard.mk as follows:

USB_VID = 0x1209
USB_PID = 0x0005
USB_PRODUCT = "LoRa-GPS-Tracker"
USB_MANUFACTURER = "Juha-Pekka Varjonen"

Compile MicroPython cross-compiler:

make -C mpy-cross

To build give this command:

make BOARD=tracker_m0

It creates a lot of files and directories in to directory ‘build-tracker_m0’. We only need one with name firmware.uf2. Easiest method to install it is to drag and drop it in to previously created USB device TRACKERBOOT.

After dropping it takes about 15 seconds and device name changes to CIRCUITPY and it’s ready to use.

Setting up development environment

This step is necessary when programming and testing with Mu-editor. Clone Mu-editor repository and edit mu/modes/circuitpython.py file.

git clone https://github.com/mu-editor/mu
nano mu/mu/modes/circuitpython.py

Under ‘valid boards’ add VID and PID to the list. PID 0x0005 is only for testing. Do not use it in production.

valid_boards = [
...
(0x1209), (0x0005), # LoRa-GPS Tracker development
]

Run Mu-editor from it’s root directory with command:

python run.py

Now it should detect LoRa-GPS Tracker automatically. You will need also Adafruit library bundle. Download it from here.

Testing GPS receiver

At first you need to copy adafruit_gps folder from Adafruit library bundle to lib folder on CIRCUITPY device.

Open Mu editor and create new CircuitPython project with code.py filename. File content should be this:

import board
import busio
import adafruit_gps
import time

RX = board.RX
TX = board.TX

uart = busio.UART(TX, RX, baudrate=9600, timeout=30)
gps = adafruit_gps.GPS(uart, debug=False)
# Turn on the basic GGA and RMC info (what you typically want)
gps.send_command(b'PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
# Set update rate to once a second (1hz) which is what you typically want
gps.send_command(b'PMTK220,1000')

while True:
    gps.update()
    if not gps.has_fix:
        # Try again if we don't have a fix yet.
        continue
    # We have a fix! (gps.has_fix is true)
    # Print out details about the fix like location, date, etc.
    print('=' * 40)  # Print a separator line.
    print('Fix timestamp: {:02}:{:02}:{:02}'.format(
        gps.timestamp_utc.tm_hour,  
        gps.timestamp_utc.tm_min,   
        gps.timestamp_utc.tm_sec))
    print('Latitude: {0:.6f} degrees'.format(gps.latitude))
    print('Longitude: {0:.6f} degrees'.format(gps.longitude))

That code prints out to serial port current coordinates. You can watch serial port communication by pressing Mu editor button ‘serial’.

Testing LoRa radio

At first you need to copy adafruit_tinylora and adafruit_bus_device folder from Adafruit library bundle to lib folder on CIRCUITPY device.

Open Mu editor and create new CircuitPython project with code.py filename. File content should be this:

import busio
import digitalio
import board
from adafruit_tinylora.adafruit_tinylora import TTN, TinyLoRa
import time
import microcontroller

# Create library object using our bus SPI port for radio
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
 
# RFM9x Breakout Pinouts
cs = digitalio.DigitalInOut(microcontroller.pin.PA06)
irq = digitalio.DigitalInOut(microcontroller.pin.PA09)
rst = digitalio.DigitalInOut(microcontroller.pin.PA08)

# TTN Device Address, 4 Bytes, MSB
devaddr = bytearray([0x00, 0x00, 0x00, 0x00])
 
# TTN Network Key, 16 Bytes, MSB
nwkey = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
 
# TTN Application Key, 16 Bytess, MSB
app = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
 
ttn_config = TTN(devaddr, nwkey, app, country='EU')
 
lora = TinyLoRa(spi, cs, irq, rst, ttn_config)
 
# Data Packet to send to TTN
data = bytearray(4)
 
while True:
    # Encode payload as bytes
    data[0] = 0xff
    data[1] = 0x01
    data[2] = 0xff
    data[3] = 0x01
 
    # Send data packet
    print('Sending packet...')
    lora.send_data(data, len(data), lora.frame_counter)
    print('Packet Sent!')
    lora.frame_counter += 1
    time.sleep(15)

Important thing is that TTN device address, network key and application key should be your own unique numbers from TTN Console. More detailed instructions can be found from Adafruit site.

After code is copied to CIRCUITPY drive it runs automatically and starts sending data to internet. You can follow it with TTN Console. If there is no gateway in your area you need one own. Next is described how to setup one.

Setting up LoRaWAN gateway

I have Raspberry Pi with GW-01-RPI LoRa Shield. Start with downloading Rasbian img file from Raspberry Pi’s web site. Extract it with unzip command and transfer to SD card with dd command.

unzip your_image_file_name.zip
sudo umount /dev/sdx*
sudo dd bs=1M if=your_image_file_name.img of=/dev/sdx

Make sure to replace /dev/sdx with correct device name. You can check it with command sudo fdisk -l

Finally put a empty file named ‘ssh’ to the root directory of SD card. This way you enable ssh connection. Insert card to Raspberry Pi and connect power and LAN. Login with default username and password (user: pi, password: raspberry). With remote computer:

ssh pi@192.168.0.6

Change IP address accordingly. It is recommended to change password at this point. Use command passwd.

First enable onboard SPI connection. Go to raspi-config.

sudo raspi-config

Select interfacing options and there is SPI. Enable it by pressing enter and choosing enable.

Create directory and clone and make two necessary repositories on to it.

cd ~
mkdir lora
cd lora
git clone https://github.com/Lora-net/lora_gateway.git
cd lora_gateway
make

cd ~/lora
git clone https://github.com/Lora-net/packet_forwarder.git
cd packet_forwarder
./compile.sh

Then edit config files:

cd ~/lora/packet_forwarder/lora_pkt_fwd
nano global_conf.json
nano local_conf.json

Local config contains only one line for gateway ID. Change it’s value to same than in global_conf.json. You can make up that ID to be anything you like. It must be 8-bit hexadecimal number. Example settings:

gateway_ID: "0102030405060708"
server_address: "router.eu.thethings.network"
serv_port_up: "1700"
serv_port_down: "1700"

One last thing is to make startup file.

cd ~/lora
nano reset.sh

That file should contain following lines:

#!/bin/sh
echo "25" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio25/direction
echo "1" > /sys/class/gpio/gpio25/value
sleep 3
echo "0" > /sys/class/gpio/gpio25/value
sleep 1
echo "0" > /sys/class/gpio/gpio25/value
cd /home/pi/lora/packet_forwarder/lora_pkt_fwd/
./lora_pkt_fwd &

Set that file to be executable and run it:

sudo chmod +x reset.sh
sudo ./reset.sh

If you see line with following text then setup is succesfull.

INFO: [main] concentrator started, packet can now be received

Break that by pressing Ctrl-C. Next thing is to modify rc.local file.

sudo nano /etc/rc.local

Just before ending “exit 0” line, add following:

/home/pi/lora/reset.sh || /bin/true &

And on the beginning of the file change “#!/bin/sh -e” to “#!/bin/sh”. Save and close file. Then set executable bit and enable it:

sudo chmod +x /etc/rc.local
sudo systemctl daemon-reload
sudo systemctl enable rc-local.service

Now it should start every time when Raspberry starts. Restart to confirm that by typing ‘sudo reboot’ and pressing enter. When restarted look with top command if lora_pkt_fwd is running.

Setting up TTN

Start by logging in to TTN console and choose Gateway -> register gateway. Select “I’m using the legacy packet forwarder”. Important thing is that gateway EUI must be same than previously selected Gateway_ID. Also router needs to be same than in gateway settings. Other settings are self-explanatory.

LoRaWAN GPS Tracker

Image is coming as soon as first devices arrives from manufacturer…

Only 20 different component. What could you except for that?

For use in EU and India. Suitable band is software controlled. Fully hackable open source software design with CircuitPython language. Ready to use. Program includes advanced power saving techniques. Extreme small physical size yet powerful tracking device. Free subscription to mapping service. And there’s more!

Includes panic button which can send GPS coordinates to pre-selected recipients through SMS and email. Includes battery level monitoring service through LoRaWAN uplink. You can select where and how to send that data. It is automatically saved to database like all other messages from device. Only you have access to that data from backend which is password protected web site.

Tri-color LED indicates when battery is fully charged and when there is GPS fix. Red color is programmable and by default it is showing operational status of the device. Battery charging and programming through micro USB port.

Device is water resistant to IP65. It can be used outdoor. GPS position accuracy < 3 meters. Stand alone operation time from days to week depending on how often GPS tracking is made.

Advantages to use CircuitPython includes but not limited to:
Perfect choice for data logging applications. Internal storage is seen as USB storage device. Easily update your code. Since your code lives on the disk drive, you can edit it whenever you like. And Python is fastest-growing programming language. It’s taught in schools and universities.

Internal battery voltage 3.7V, battery type LiPo and capacity 105mA. Integrated charging circuit from USB port whenever USB is connected to computer. Over and under voltage protected. Automatic switch between USB and battery depending on USB connection.

Physical dimensions of durable black ABS plastic enclosure are 36mm x 66mm. Height only 17mm. It includes a key fob ring so you can connect it to keys and never lose those keys again!

Usage examples includes ofcourse person tracking obviously but also a industrial logistics tracking applications. And many more. It can freely adopted to different usages by simply changing Python code inside it. And it’s easy than plugging it into computer. It shows up a external drive where you can put your code and it is run instantly!

RF Power Meter V1.1

This device is part of my APRS radio transmitter backpack rig published later on.

PCB layout file, schematic diagram and AVR program available on GitHub: https://github.com/Juvar1/RFPowerMeter
It is designed with KiCad.

Improvements

Here is second version of my RF power meter device. It has improved RF shielding and noise immunity. A little bit more expensive to make but quality pays.

C14 is also added for improving noise immunity from supply line.

C14 added and other SMA connector removed. It’s replaced with T splitter externally.

Casing

Case is bought from Digi-Key. It’s part number is PIP-11766-C. DC jack outer diameter is 5.5mm and inner 2.5mm.

RF Power Meter

This device is part of my APRS radio transmitter backpack rig published later on.

PCB layout file, schematic diagram and AVR program available on GitHub: https://github.com/Juvar1/RFPowerMeter
It is designed with KiCad. Untested!

Introduction

This meter measures the transmitting power of the radio transmitter and the voltage of the battery used as a power source. Power is displayed in watts and dBm. The device has a three-digit LCD display. The functions are controlled by a push button. The unit will automatically turn off after five minutes if the button is not pressed. By pressing once device wakes from sleep mode and show measured power in wats. By pressing again device switches to dBm mode and show power in dBm. Pressing third time device show measured supply voltage.

Comes as a bag of parts kit and is easily assembled if customer can follow the silkscreen indicators and have beginning experience with a soldering iron. Customer will need to read the resistor bands or use a multimeter to determine the resistor sizes.

Works with single 7…14 volts DC power supply connected with DC barrel plug. Internally 5.0V voltage is used through regulator 7805. ADC reference voltage is taken from supply voltage with voltage divider. Its voltage is 2.5V. One analog input is connected with supply voltage also with voltage divider. its voltage is adjusted to a max. of 2.5V at 14V so that accurate voltage measurement is made possible.

The heart of the device is AtMega328P-PU at 16MHz clock frequency. This makes it to be Arduino™ compatible system.

For measuring power, an AD8307 chip is used. It allows the RF power to be converted to a voltage between 0 and 2.5V. The permissible power is between 0 and 1kW. The permissible frequency range is between 10MHz and 1GHz. It is possible to calibrate the measuring range by ± 3dB with on-board trimmer resistor. The output is connected to the analog input of the microcontroller.

Measurement is made with following equations:

dBm = 40(U-1)
W = 10(dBm/10)/1000

The display is a three-digit LCD display. LCD display requires AC voltage. It can be simple square wave signal from DC 5V. Frequency is not critical. It can be between 30-200Hz. Phase difference lights the segment. This device uses regular CMOS series IC’s to control LCD segments. 4056 is used to control digits. Every digit needs own IC. One 4054 is used to control two decimal points and to invert common backplane signal. Frequency is made with Arduino™ PWM output. It is about 30Hz. Below is an self-explanatory illustration.

Current consumption is very low because of CMOS integrated circuits used and AtMega328P power saving mode which is programmatically activated. LCD display consumes very little current compared to regular LED display. Also power measurement circuit ’chip enable’ signal is controlled with AVR.

Physical size of device is with casing 13x13cm. PCB is 10x10cm. Dimensions excludes connectors and push button.

Usage examples

  • Amateur radio station power meter
  • Power measurement

Circuit diagram

Development stage

One prototype has been ordered so far with double sided printed circuit board and with through hole components. UNTESTED at this point. Work is in progress. It is designed with KiCad.

Target MSRP

BOM (bill-of-material) cost with casing and SMA connectors is about 50 euros. It does not include any basic components with standard values (I use my own stock).

10 pieces small batch BOM cost is $64.90 + PCB $3.02. PCB will cost $1.33 in order quantity of 1000 pieces. PCB’s are bought from Itead Studio from China. The components are bought from Digi-Key from USA. The same components can also be purchased from Mouser. The price in larger quantities are much less. If order quantity is 1000 pieces then it costs $40.04 + PCB $1.33. Manufacturing costs should also be taken into account. (In this case packing.) So total cost for parts is roughly $43.00.

Typical distribution margin is 5-7% and retail margin is 15-25% with this type of products. So the MSRP would be between $51.93 and $57.51. Realistic actual selling price would be $79.00.

Advantages

Ease of use. The device has only one button with three functions. ’Plug-and-play’. Safe operating voltage. Safe to use. Hackable design.

Other similar devices

Surecom SW-102 Digital Antenna Power & SWR Meter VHF/UHF 125-525MHz.

Soldering

The project requires no special soldering skills. All components are their through hole versions.

Programming

All parts are open source. The program is solely written in C++. The program is very simple and with its 200 lines of code it uses only 4.6kB of program memory. AtMega328P-PU chip is used.

The bootloader must first be programmed using external programmer. I personally like to use Arduino™ as ISP and Arduino™ IDE. Be sure to choose the Arduino™ Nano from the Tools→Board menu. After installing the bootloader, the program can be uploaded using same setup.

Program does not use any external libraries. Only standard built-in power saving related libraries are used.

Approvals

Arduino is a registered trademark. It is still ok to build a commercial product based on Arduino. Only the name may not be the same. https://www.arduino.cc/en/Main/FAQ#toc10

Battery level indicator

This device monitors battery voltage and shows it on 5 LED’s. Further development will include a use of AVR power saving functions. At this moment circuit uses 31mA. LED position vs. voltage:

  1. <= 12.05
  2. 12.06 – 12.25
  3. 12.26 – 12.45
  4. 12.46 – 12.65
  5. >= 12.66

Full lead acid battery has 12.7V and when it’s empty voltage is under 12.0V. Below is circuit diagram. All works just as planned. Simple math.

Here is Arduino code before power saving functions:

/* battLow2.ino
 * 
 * 
 */

void setup() {
  // initialize LED outputs
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
  pinMode(11,OUTPUT);
  pinMode(12,OUTPUT);
  pinMode(13,OUTPUT);

  // LED's to off state
  digitalWrite(9,HIGH);
  digitalWrite(10,HIGH);
  digitalWrite(11,HIGH);
  digitalWrite(12,HIGH);
  digitalWrite(13,HIGH);
}

void loop() {

  int val = analogRead(A5); // 0 to 1023

  // voltage divider input---56---22---gnd
  // (1023/17.727) * voltage
  if(val <= 695) {
    digitalWrite(9,LOW);
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
    digitalWrite(12,HIGH);
    digitalWrite(13,HIGH);
  } else if(val > 695 && val < 706) {
    digitalWrite(9,HIGH);
    digitalWrite(10,LOW);
    digitalWrite(11,HIGH);
    digitalWrite(12,HIGH);
    digitalWrite(13,HIGH);
  } else if(val >= 706 && val < 718) {
    digitalWrite(9,HIGH);
    digitalWrite(10,HIGH);
    digitalWrite(11,LOW);
    digitalWrite(12,HIGH);
    digitalWrite(13,HIGH);
  } else if(val >= 718 && val < 730) {
    digitalWrite(9,HIGH);
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
    digitalWrite(12,LOW);
    digitalWrite(13,HIGH);
  } else if(val > 730) { // 12.65
    digitalWrite(9,HIGH);
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
    digitalWrite(12,HIGH);
    digitalWrite(13,LOW);
  }
  
  delay(200);
}

After adding power saving things to code current consumption is only 18mA. It contains LED driving current and power dissipation of the linear regulator. AVR restarts every two seconds to make one ADC measurement.

/* battLow2.ino
 * 
 * Displays 12V lead acid battery voltage level on LED bar
 * 
 * Author: jpvarjonen@gmail.com
 * Copyright (C) 2019 Juha-Pekka Varjonen
 */

#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/power.h>

void setup() {

  // power up every 2 seconds
  wdt_enable(WDTO_2S);
  // set sleep mode (not activated yet)
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  // disable things
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();
  power_timer0_disable();
  power_timer1_disable();
  power_timer2_disable();
  
  // initialize LED outputs
  digitalWrite(9,HIGH);
  digitalWrite(10,HIGH);
  digitalWrite(11,HIGH);
  digitalWrite(12,HIGH);
  digitalWrite(13,HIGH);
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
  pinMode(11,OUTPUT);
  pinMode(12,OUTPUT);
  pinMode(13,OUTPUT);

  int val = analogRead(A5); // 0 to 1023

  // disable ADC
  power_adc_disable();

  // voltage divider input---56---22---gnd
  // (1023/17.727) * voltage
  if(val <= 695) {
    digitalWrite(9,LOW);
  } else if(val > 695 && val < 706) {
    digitalWrite(10,LOW);
  } else if(val >= 706 && val < 718) {
    digitalWrite(11,LOW);
  } else if(val >= 718 && val < 730) {
    digitalWrite(12,LOW);
  } else if(val >= 730) { // 12.65
    digitalWrite(13,LOW);
  }

  // go to sleep (watchdog will restart)
  sleep_mode();
}

void loop() {
  // nothing here
}

Little improvement to circuit. Now it’s current consumption is only 11mA.

DMX512 Streaming Recorder & RDM Interface – A Concept

This USB device works with one DMX universe. A DMX universe is 512 channels of 256 levels. The device also has a slot for a memory card. Maximum memory card size is 32GB.

The device also works as a streaming recorder. When show is running you can simultaneously save it to memory card and output to DMX512 port. If USB connection is lost during play it rewinds automatically and starts from beginning of saved show.

With 2GB memory card maximum recording time is 4 days! Record is simple series of CSV text files on FAT file system. Starting from first subdirectory with 1.csv and so on. Each subdirectory is separate show.

Changing operating mode and choosing between saved shows can be done with onboard display and button. Display shows simple to understand symbols for recording and playback functions and numbers for choosing between memory slots. By pressing button shortly it blinks current symbol and it is possible to browse through functions. Long press chooses that function and it’s symbol stops blinking.

The device also works as a RDM interface. With compatible software on computer it allows control and monitor RDM fixtures remotely. Currently on Linux there is no any software available.

AVR Program

The device uses AtMega328PB-AUR microcontroller with 16MHz crystal. It includes two hardware serial ports and two hardware SPI port. First serial port is USB interface and second serial port is DMX/RDM interface. Speed on both serial ports must be 250000 baud. First SPI port is used only for programming and second SPI port is used to communicate with memory card. Program is written with C++ language. Microcontroller is programmed using avr-gcc and avrdude. Program is released under GNU General Public License.

This program uses:

  • Enttec DMX USB Pro protocol to communicate with PC.
    API specification: https://www.enttec.com/products/controls/usb/2-universe-usb-computer-interface-dmx/
    Copyright (c) 2007 Enttec Pty/Ltd
  • Parts from DMXSerial and DMXSerial2 libraries.
    Copyright (c) 2011-2013 by Matthias Hertel, http://www.mathertel.de
    This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
  • Parts from SdFat and Sd2Card libraries.
    Copyright (c) 2009 by William Greiman
    GNU General Public License V3.
  • Parts from Arduino SD-library.
    Copyright (c) 2010 SparkFun Electronics
    GNU General Public License V3.
  • CRC-7 routine and CRC-7 table from
    https://github.com/signal11/m-stack/blob/master/storage/src/crc.c
    Copyright (c) 2013 Alan Ott
    Copyright (c) 2013 Signal 11 Software
    GNU Lesser General Public License V3.

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: