Freitag, 3. September 2021

Ein FX-B Reader für 134 MHz RFID Transmitter

Wie im Post 'Ein RFID Reader für 125Hz' bereits erwähnt, möchte ich gerne ein Lesegerät für RFID Transponder bauen, die für die Kennzeichnung von Tieren verwendet werden. Die Anregung dieser Transponder erfolgt in der Regel bei 134 kHz und es wird ein ein Protokoll nach ISO 11784 & 11785 verwendet.

Ich habe mir daher eine einfachen Logik Analyzer  und ein RDM6300 RFID Modul für Arduino und Raspberry Pi besorgt. Der Schaltung des Modules sieht wie folgt aus.

Eine Messung ergibt, dass der von Controller C8051 an Pin 1 ausgegebene Erregertakt ca. 129 kHz beträgt

Sie liegt damit zwischen 125kHz und 134kHz. Am Pin 7 des OpAmp LM358 kann das digital aufbereitete Empfangssignal abgegriffen werden. Ich habe ein Kabel angelötet, und versucht über den Analyzer einen Tag für Katzenhalsbänder auszulesen.   

Überraschenderweise ist auch der Halsband Chip EM4100 codiert

Ein richtiger Glastag zur subkutanen Injektion lässt sich allerdings nicht auslesen.

Der EM4100 Decoder liefert unsinnige Werte. Das Protokoll nach ISO 11784 & 11785 wird nicht unterstützt. Ich bin daher der Idee verfallen den Datenstrom durch ein Python Programm zu dekodieren.

Folgenden Aufbau für ein Lesegerät habe ich realisiert. Er besteht aus einem Raspberry Zero W, einem OLED Display, dem Logic Analyzer und dem RDM6300 Modul. 

Das OLED Display mit einer Diagonalen von 0,96 Zoll hat eine Auflösung von 128 x 64 Pixel. Zur Ansteuerung steht eine I²C Schnittstelle mit dem Standard-Controller SSD 1306 zur Verfügung. Es wird entsprechend mit den SDA und SCL Datenpins, sowie VCC und GND des Raspi verbunden.

Folgende Systemprogramme werden benötigt.

'sudo apt-get install -y python-dev python3-dev python-imaging python-smbus i2c-tools git python3-pip python-setuptools build-essential git-core libi2c-dev i2c-tools lm-sensors python-pip'

Dann müsen noch einige Bibiotheken installiert werden.

'git clone git://github.com/rm-hull/ssd1306.git

cd ssd1306

sudo python setup.py install

cd examples

git clone https://github.com/rm-hull/luma.examples.git

cd luma.examples/examples'

und schon kann man sich einige mitgelieferte Beispiele anschauen. Für animierten Text und Logo z.B. python crawl.py --display ssd1306 aufrufen.

Um den Logik Analyzer unter Raspian (Raspberry Pi OS 5.10) zum Laufen zu bringen, sind keine zusätzlichen Treiber nötig. Nach

'sudo apt-get install pulseview

sudo apt-get install sigrok'

kann die graphische Bedienungsumgebung PulseView unter Entwicklung/PulseView auf dem Desktop aufgerufen werden. In PulseView muss u.U. 'Saleae Logic' ausgewählt werden.

Um Daten automatisch aufzuzeichnen bietet sich die Verwendung von siglok-cli an. Um z.B. 200ms lang auf D0 in eine csv-Datei ohne header zu schreiben ruft man

'sigrok-cli --config samplerate=50000 --samples 10000 --channels D0 -o /home/pi/RFID/example.csv -O csv:dedup:header=false'

auf.

Noch ein paar Worte zum Setup des Raspi Zero. Für die einfachere Administration empfiehlt es sich einen Zugang als root einzurichten. Das Passwort wird mit 'sudo passwd root' gesetzt. Nachdem xrdp installiert ist, kann man sich per remote desktop Verbindung einwählen. Damit jedoch im remote desktop die task bar korrekt angezeigt wird sollte in 'root/.config/lxpanel/LXDE-pi/panels' der Eintrag

Plugin {

type=volumepulse

Config {

}

}

entfernt werden.

Um ein sauberes Herunterfahren auch ohne einloggen zu gewährleisten, habe ich zwischen GPIO4=Pin7 und GND=Pin9 einen Taster geschalten. Durch den Eintrag

'dtoverlay=gpio-shutdown,gpio_pin=4, active_low=1,gpio_pull=up'

in der '/boot/config.txt' fährt der Raspi dann auf Kopfdruck herunter. Ein schöner Nebeneffekt dabei ist, dass die Anzeige des OLED Displays dabei erhalten bleibt und z.B. weiterhin die ausgelesene ID des Transponders anzeigt.

Damit unser Phyton Programm zum Auslesen des Tags später automatisch beim booten ausgeführt wird, muss man in '/etc/rc.local' vor 'exit 0' 

'python /home/pi/RFID/tag_reader7.py &'

eintragen. Das '&' Zeichen gewährleistet dass 'tag_reader7' im Hintergrund startet, und die weitere Boot Sequenz nicht beeinträchtigt wird. Optional kann dann später, wenn man sich in den Raspi einloggt, Python im Taskmanager gestoppt, beendet oder gekillt werden.

Die eigentliche Software des Lesegerätes macht im Wesentlichen folgendes. Nach dem Laden des wird zunächst ein kleines Bildchen angezeigt

Dann werden 5k Datenpunkte mit 50k Abtastrate in einem Textfile aufgezeichnet. Wird keine '00000000001' header Folge gefunden, wird 'No header detected' angezeigt. Wird ein header gefunden, stimmt der tag aber nicht mit dem nachfolgenden tag überein wird 'ID Error' ausgegeben. Stimmt Alles werden 'Country Code' und 'ID' ausgewertet und angezeigt. Fehlermeldungen und ausgelesene IDs werden zusätzlich in einer einfachen Logdatei gespeichert.

Nach den ersten Funktionstests habe ich die gesamte Elektronik in ein selbst gedrucktes Gehäuse gepackt.

Der gesamte Reader sieht dann so aus

Die 5V Stromversorgung erfolgt aus einer kleinen Powerbank. Zusätzlich habe ich noch einen kleinen An/aus Schalter eingebaut.

Wie im Folgenden Bild zu sehen kann damit nun ein kleiner subkutaner Transponder ausgelesen werden. Der RFID Transponder ist im Hintergrund zu erkennen.

Leider beschränkt sich die Reichweite auf wenige Millimeter. Die Anregung mit 129 kHz durch das RDM6300 Modul ist offenbar nicht ideal. Weitergehende Optimierung der 'Antenne' wäre nötig. Auch die relativ lange Boot Zeit des Raspi von ca. 30 Sekunden ist störend. Die Verwendung eines Micro Controllers z.B. eines Arduinos ist hier von Vorteil. Nichtsdestrotrotz zeigt der Aufbau das Funktionsprinzip sehr schön wie ich finde.


Viel Spaß beim Nachbauen und selber Experimentieren...

 

Abschließend noch das gesamte PythonSkript

#!/usr/bin/env python

# -*- coding: utf-8 -*-


from luma.core.interface.serial import i2c

from luma.core.render import canvas

from luma.oled.device import ssd1306

import time

from PIL import ImageFont

import os.path

from PIL import Image

import re

import array as arr

import sys

from bitarray import bitarray

from datetime import datetime


def str2bool(v):

  return v == "1"


serial = i2c(port=1, address=0x3C)

device = ssd1306(serial, rotate=2)


def show_logo():

    img_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'images', 'FX-B_reader.png'))

    logo = Image.open(img_path)

    

    #show logo

    with canvas(device) as draw:       

            draw.bitmap((20, 0), logo, fill="white")

            draw.text((0, 40), "presented by..."+'\n'+"allaboutplainwhite", fill="white")

    time.sleep(3)


def loop():

    #read tag

    #5k samles at 50k rate and save as text file, 1 bit per line only 

    os.system("sigrok-cli --config samplerate=50000 --samples 5000 --channels D0=In -o /home/pi/RFID/bit_session.bin -O bits:width=1")

    

    #decode tag

    #read analyzer data

    c = bitarray() #bitarray to store analyzer data

    with open('/home/pi/RFID/bit_session.bin', 'r') as fh:

        next(fh) #skip header

        next(fh)

        for line in fh:

            c.append(str2bool(re.sub(r'[^0-1]', '', str(line)))) #remove everything beside 0 and 1 convert to bool and write to bitaray

            

    #decode biphase encoding

    times = arr.array('i',[]) #array of int to store ellapsed time since last signal change (signal edge)

    edge=0 #position of last signal change

    i=0

    for i in range(len(c)-1): #walk through analyzer data

        if c[i] != c[i+1]: #if signal change detected

            times.append(i+1-edge) #note down ellapsed time since last change

            edge = i+1

    i=0

    tags=bitarray() #array to store decoded data for all tags

    while i in range(len(times)-1): #decode information acc to FX-B protocol

        if times[i] > 7 : #if long time since last change (> 32*field cycle time*sampling rate *1/2)

            tags.append(1) #note down tag bit 1

            i=i+1

        elif times[i+1] < 12 : #else if short time and next entry also short (< 32*field cycle time*sampling rate *2/2)

            tags.append(0) #note down tag bit 0

            i=i+2

        else: i=i+1

    

    #calculate tags

    header=bitarray('00000000001')

    header_flag=False

    message="No header detected"

    for i in range(len(tags)-2*128): #find header

        if tags[i:i+11]==header:

            header_flag=True

            break


            

    if header_flag: #proceed if header detected

        

        tag1=tags[i+11:i+128] #extract tag1 without header

        tag2=tags[i+128+11:i+2*128] #extract tag2 without header

        if tag1==tag2:

            del tag1[8::9] #remove seperator bit

            ID1=bitarray(tag1[0:38]) #extract reverse ID (LSB first)

            j=0

            while j<19: #rotate ID

                b=ID1[j]

                ID1[j]=ID1[37-j]

                ID1[37-j]=b

                j=j+1

            #print("ID:",int(ID1.to01(),2)) #convert to decimal and print

            ID=int(ID1.to01(),2)

            #ID_str= "ID:" + str(ID)


            CC1=bitarray(tag1[38:48]) #extract lsb first Country Code

            j=0

            while j<5: #rotate Country Code

                b=CC1[j]

                CC1[j]=CC1[9-j]

                CC1[9-j]=b

                j=j+1

            #print("Country Code:",int(CC1.to01(),2)) #convert to decimal and print

            Country=int(CC1.to01(),2)

            message = "Country Code:" + str(Country) +'\n' + "ID:" + str(ID)


        else:

            message="ID Error"

           

                    

    #show tag

    #print(message)

    with canvas(device) as draw:

        draw.text((0, 0), message, fill="white")

    

    #time.sleep(1)


    datei = open('/home/pi/RFID/logfile.txt','a') #save message in logfile

    now = datetime.now() #get current date and time

    dt_string = now.strftime("%d/%m/%Y %H:%M:%S ") # dd/mm/YY H:M:S

    datei.write("\r\n" + dt_string + message)


#main

show_logo()

while True:

     loop()


Keine Kommentare:

Kommentar veröffentlichen