Reading 1-Wire iButtons using a UART
This post takes a look at a simple 1-Wire protocol reader/writer using a standard UART for detecting and communicating with 1-Wire devices. Written in C for Linux, and running on Intel Galileo. It could easily be ported to most other platforms.
The iButton I am using is a the DS1990A, which is a common electronic key, it is a 1-Wire read-only unique serial number device. They are used for access control, vending machines, point of sale terminal and so on. They use what’s called the “1-Wire” protocol, meaning that they use a single wire to provide power to the device and to communicate with it. The 1-Wire protocol, including it’s timings, is described here. There are many different types of 1-Wire devices, including simple ROMs with unique serial numbers and data loggers. In this article I present a method of communicating with these devices using a standard UART, as found on just about any modern micro controller and/or dev board such as Raspberry PI, Intel Galileo, any PIC from Microchip, M-Bed, ATMega, Arduino and just about any other micro-controller you can think of.
Reading an writing to an iButton doesn’t use standard UART timings, and so normally you would use either a dedicated IO Pin, where you have direct control over the port timings, or a dedicated iButton Interface chip. If you can’t control the timing on an IO pin down to as accurate as about 6 uS (micro seconds) then you need another approach.
Hardware
To write to and read from a1-Wire device, I’m using the UART pins at CMOS levels, not driven to RS232 Levels by a driver chip. So +5v is a logic ‘1’ and 0V is a logic ‘0’. I tie the TX and RX lines directly together, and since one is an output and the other an input this is perfectly safe to do. In my instance I’m using the Arduino UNO compatible Intel Galileo board, and using pins 0 (RX) and 1 (TX) joined directly together, and then taken to the iButton device along with a GND signal.
Software
What I’ve done with this code, is use the UART to generate the pulses on the wire. I’m not using the UART to actually read and write normal bytes, but to generate pulses on the wire and read back pulses with deterministic timings. By tying the TX and RX lines of the UART together we can read back our outgoing pulses, and any pulses coming from the iButton.
Detecting the iButton
I found that transmitting 0xF0 at 9,600 bps, generates an acceptable reset-pulse, which the iButton replies to with it’s own pulse. We write 0xF0 to the wire, which is preceded by a start-bit generated by the UART. Since UARTs send data least significant bit first, on the wire the 9 bits of data are transmitted as: 000001111 – this gives an almost perfect reset pulse to the device. The device reacts by pulling our line low after about 6 micro-sceonds. This corrupts the byte which we transmitted, and when we read it back in we get: 000000111 – one bit was pulled low by the iButton. So we transmitted 0xF0, but we read back 0xE0. Thus we know that there is something on the line.
Here’s my code to detect a 1-Wire device: (fd is the file description obtained from an ‘open’ call on the uart). Full code is at the end of the post.
int OW_detectKey(int fd) { int bytesRead; unsigned char out; unsigned char inBuf[64]; setBaud(fd,9600); out=0xF0; write_port(fd,&out,1); usleep(1000*10); bytesRead = read_port(fd,inBuf,64); if (bytesRead==1) { if(inBuf[0]==0xe0) { //printf("Key detected\n"); return 1; } } return 0; }
Now that we have detected the iButton, to read it’s contents we need to send the single byte ‘Read ROM’ command which is 0x33 (hex).
Transmitting bytes
To transmit on the 1-Wire interface is also quite simple. We take the line low for 60 micro-seconds to write a 0, or for just 6 micro-seconds to write a ‘1’. So we take our command byte, and split it into 8 bits, and transmit long or short pulses representing the 1’s and 0’s of the byte. The iButton does not respond to these writes, so we just flush our incoming buffer to keep it clear.
9600 bps is too slow to transmit the ~6uS pulses we need, so we up the speed to 115200 bps now.
The following code transmits any number of complete bytes, one bit at a time, onto the 1-Wire interface:
void OW_WriteByte(int fd, unsigned char dataByte) { int i, res; unsigned char out; unsigned char inBuf[64]; // for flushing our echos setBaud(fd,115200); for(i=0; i<8; i++) // 8 Bits in a byte. { // Are we sending a '1' or a '0' for this bit? out = (dataByte & 0x01) ? 0xff : 0xc0; res = write(fd,&out,1); if(res == -1) // check for failure. { printf("Failed to write (%d)\n",i); exit(-1); } dataByte >>= 1; // shift next bit into position. } // Since TX and RX are shorted, we must flush our output from the inbuffer // where it will have been received. We read our own writes. while(1) { i = read(fd,inBuf,64); if(i<=0) break; } }
Receiving Bytes
In order to read bytes back from the device, we again need to create short pulses on the wire. The device sees a short ~6 micro second pulse as a sign to transmit one bit of data. If that bit is a 0 it pulls the line low for a short period, if it’s a 1 it does nothing. We can detect the 0’s because they will corrupt the byte we send on the UART to creare the start pulse. Since the timing of this corruption is deterministic, it will be the same every time, so we can rely on it to read data back from the device. The following function reads any number of bytes from the device, into a buffer, one bit at a time:
int OW_ReadBytes(int fd, unsigned char* pBuf, int len) { int i; int bits, bytes; unsigned char startSlot = 0xff; unsigned char inBuf[64]; unsigned char newByte; // Read len bytes from the bus, 1-Bit at a time. for(bytes = 0; bytes < len; bytes++) { newByte=0; for(bits=0; bits<8; bits++) { newByte >>= 1; write(fd,&startSlot,1); usleep(500); // time for byte to exit i = read(fd,inBuf,64); if(i<=0) { // printf("End read\n"); return bytes; } if(inBuf[0]==0xff) // iButton did not bring line down at all. { newByte |= 0x80; } } pBuf[bytes]=newByte; } return bytes; }
Putting it all together
So, now we can detect a device, write to it, and read back from it.
The iButtons that I am using in my tests always reply with exactly 8 Bytes of data which is:
DeviceFamily - 1 Byte (always 0x01) DeviceID - 6 Bytes of unique serial n umber. CRC - 1 Byte of computed CRC data.
We can determine that we have read the data correctly, by examining the device ID byte, and calculating the check sum. The checksum (CRC) is not the usual rolling addition checksum, but a complex routine, which is fortunately quite simple to code up. The following calculates the checksum on the given buffer of bytes:
unsigned char OW_CRC(unsigned char *pBuf, int len) { unsigned char loop, i, bit; unsigned char crc = 0x00; for(loop=0; loop<len; loop++) { crc = (crc ^ pBuf[loop]); for(i=8; i>0; i--) { bit = (crc & 0x01); crc >>= 1; if(bit) { crc = (crc ^ 0x8c); } } } return crc; }
So, that’s it – using a UART at standard speeds to detect, write to and read from a 1-Wire interface device such as an iButton.
Here is the complete code listing, including some stuff specific to the Intel Galileo for setting up the pins correctly. To port it to other platforms should be pretty simple as it only uses standard POSIX calls.
Complete code listing.
// // OWUart.c // OWUart // // Created by Kenny Milar on 1/June/2014. // Copyright (c) 2014 SpiderElectron. All rights reserved. //
#include <stdio.h> #include <stdlib.h> #include <string.h> /* String function definitions */ #include <unistd.h> /* UNIX standard function definitions */ #include <fcntl.h> /* File control definitions */ #include <errno.h> /* Error number definitions */ #include <termios.h> /* POSIX terminal control definitions */
#define DEVICE_FAMILY_IBUTTON 0x01
/* errExit - helper function, log message and quit. */ void errExit(char *p) { if(p) { printf("Exiting due to: %s\n",p); } else { printf("Error. Exit. Sorry.\n"); } exit(-1); }
/* Set specified GPIO pin for use. */ void setGPIOPin(char* pin, char* dir, char* drive, char* val) { char buf[256]; int fd; // Open the GPIO Export file fd = open("/sys/class/gpio/export",O_WRONLY); if(fd == -1) errExit("GPIO Export"); // Export the required pin. write(fd, pin, strlen(pin)); // Export GPIO pin close(fd); // Open exported pin's DIRECTION file sprintf(buf,"/sys/class/gpio/gpio%s/direction",pin); fd = open(buf,O_WRONLY); // open GPIOxx direction file if(fd==-1) errExit("Gpio Direction"); // write out the direction write(fd,dir,strlen(dir)); // set GPIOxx direction to out close(fd); // open the drive file sprintf(buf,"/sys/class/gpio/gpio%s/drive",pin); fd = open(buf,O_WRONLY); if(fd==-1) errExit("Gpio Drive"); // Write the drive type. write(fd,drive,strlen(drive)); // set GPIO drive close(fd); // Open the initial value file. sprintf(buf,"/sys/class/gpio/gpio%s/value",pin); fd = open(buf,O_WRONLY); if(fd==-1) errExit("Gpio Value"); write(fd,val,strlen(val)); // set GPIO initial value close(fd); }
/* setMux - set up the multiplexor on A0 to connect it to ADC VIN0 */ void setMux(void) { // See the Intel Galileo Port Mapping document for details of GPIO numbers. // Switch all the SPI1 pins through to the header pins. And enable level shifter. setGPIOPin("40","out","strong","0"); // ttyS0 connects to RX (Arduino 0) setGPIOPin("41","out","strong","0"); // ttyS0 to TX (Arduino 1) setGPIOPin("4","out","strong","1"); // Level shifter enabled (enable the final driver). }
/* Open a file descripto to the specified path */ int open_port(char *path) { int fd; /* File descriptor for the port */ fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("open_port: Unable to open specified port. "); } else fcntl(fd, F_SETFL, 0); // ASYNC access. return (fd); }
int setBaud(int fd, unsigned int baudrate) { struct termios options; // Get the current port options. tcgetattr(fd, &options); // Set the baud rates to one of our favourite bauds if(baudrate==9600) { cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); } else if (baudrate==57600) { cfsetispeed(&options, B57600); cfsetospeed(&options, B57600); } else if (baudrate==115200) { cfsetispeed(&options, B115200); cfsetospeed(&options, B115200); } else { printf("I didn't bother setting the port speed.\n"); } // Enable the receiver and set local mode... options.c_cflag |= (CLOCAL | CREAD); // 8N1 options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; // No flow control options.c_cflag &= ~CRTSCTS; options.c_iflag &= ~(IXON | IXOFF | IXANY); // raw input options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw output options.c_oflag &= ~OPOST; // Set the new options for the port tcsetattr(fd, TCSADRAIN, &options); // No waiting for characters fcntl(fd, F_SETFL, FNDELAY); }
// Write a buffer of data to the port. int write_port(int fd, unsigned char *pData, int len) { int bytesWritten; bytesWritten = write(fd, pData, len); // printf("Write of %d bytes returned %d\n",len,bytesWritten); return bytesWritten; }
// Read from the port into a buffer of data. int read_port(int fd, unsigned char *buf, int len) { int i,bytesRead; int totalBytes; totalBytes=0; while (1) { bytesRead = read(fd,buf,len); if(bytesRead <=0) break; totalBytes+=bytesRead; } return totalBytes; }
// 1-Wire protocol byte write. // You must understand the 1-Wire protocol to see whats happening here. // We are using the UART to create pulses NOT to send actual bytes. // A short pulse is a '1' bit and a longer pulse is a '0' bit. // so we use the start bit, followed by 0 or more '0' bits to // send a pulse of the requried length. // // Effectively we are runing a software UART over a faster hardware UART. // void OW_WriteByte(int fd, unsigned char dataByte) { int i, res; unsigned char out; unsigned char inBuf[64]; // for flushin our echos setBaud(fd,115200); for(i=0; i<8; i++) { // Are we sending a '1' or a '0' for this bit? out = (dataByte & 0x01) ? 0xff : 0xc0; res = write(fd,&out,1); if(res == -1) // check for failure. { printf("Failed to write (%d)\n",i); exit(-1); } dataByte >>= 1; // shift next bit into position. } // Since TX and RX are shorted, we must flush our output from the inbuffer // where it will have been received. We read our own writes. while(1) { i = read(fd,inBuf,64); if(i<=0) break; // printf("Flushed %d bytes\n",i); } }
// Using the UART to create a read time slot, then // detecting if the iButton created a 0-pulse or not. // Effectively creating a software uart over the hardware uart. int OW_ReadBytes(int fd, unsigned char* pBuf, int len) { int i; int bits, bytes; unsigned char startSlot = 0xff; unsigned char inBuf[64]; unsigned char newByte; // Read len bytes from the bus, 1-Bit at a time. for(bytes = 0; bytes < len; bytes++) { newByte=0; for(bits=0; bits<8; bits++) { newByte >>= 1; write(fd,&startSlot,1); usleep(500); // time for byte to exit i = read(fd,inBuf,64); if(i<=0) { // printf("End read\n"); return bytes; } if(inBuf[0]==0xff) // iButton did not bring line down at all. { newByte |= 0x80; } } pBuf[bytes]=newByte; } return bytes; }
unsigned char OW_CRC(unsigned char *pBuf, int len) { unsigned char loop, i, shiftedBit; unsigned char crc = 0x00; for(loop=0; loop<len; loop++) { crc = (crc ^ pBuf[loop]); for(i=8; i>0; i--) { shiftedBit= (crc & 0x01); crc >>= 1; if(shiftedBit) { crc = (crc ^ 0x8c); } } } return crc; }
int OW_detectKey(int fd) { int bytesRead; unsigned char out; unsigned char inBuf[64]; setBaud(fd,9600); out=0xF0; write_port(fd,&out,1); usleep(1000*10); bytesRead = read_port(fd,inBuf,64); if (bytesRead==1) { if(inBuf[0]==0xe0) { //printf("Key detected\n"); return 1; } } return 0; }
int main(int argc, char *argv[]) { int fd; int i, bytesRead; unsigned char buf[64] = {0,}; unsigned char inBuf[512] = {0,}; unsigned char crc; setMux(); if(argc != 2) { printf("Usage: %s <path to serialPort>\n",argv[0]); exit(-1); } fd = open_port(argv[1]); if(fd == -1) { printf("Failed to open serial port at %s\n",argv[1]); exit(-1); } while(1) //one infinte loop { if(OW_detectKey(fd)) // is there a key on the probe? { OW_WriteByte(fd, 0x33); // if so, write the 'read rom' command to it. if(OW_ReadBytes(fd, inBuf, 8) == 8) // then try to read back 8 bytes. { if(inBuf[0] == DEVICE_FAMILY_IBUTTON) // 1st byte is device family. We want device family 1. { if(OW_CRC(inBuf,7)==inBuf[7]) // If CRC matches the last byte in the buffer { printf("Valid key detected with ID:"); printf("%02x%02x%02x%02x%02x%02x\n",inBuf[6],inBuf[5],inBuf[4],inBuf[3],inBuf[2],inBuf[1]); sleep(1); // Wait 1 second before trying again. } } } } usleep(1000*100); } close(fd); }
/* end of file */
LED Matrix built and working Reading 1-Wire iButtons using Cypress PSoC 3 and 5/5LP