I have done RFID solutions directly on PIC16 as a reader and PIC10F322 as a transponder, with no need for any third party devices.
For the reader, I've done it on a PIC16F1718 using either the on chip opamps or the on chip zero crossing detector, and more recently on the PIC16F15324 using the zero crossing detector. All three require a half dozen or so discrete parts each, with the ZCD versions being the simplest and most reliable solution. These are all 125kHz solutions based on the EM4102 standard.
I tried a number of antennas, and the large coils with a tuning capacitor are certainly the most efficient. You can use a surface mount wire wound and cap, but the range is nowhere near as good.
I attach example code. The PIC16F1718 code examples were in single .c files so I've embedded it in the post in clear text also. The PIC10F322 transponder had some time critical stuff so I had to resort to assembly language for some of that, I've included both source files as a zip. The PIC16F15324 version I split into several source files so I've attached as a zip archive also.
The PIC10F322 transponder is powered by the RFID Reader's emitted energy.
PIC161718 OpAmp reader code:
// PIC16F1718 Configuration Bit Settings
// 'C' source line config statements
// CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = ON // Clock Out Enable (CLKOUT function is enabled on the CLKOUT pin)
#pragma config IESO = OFF // Internal/External Switchover Mode (Internal/External Switchover Mode is disabled)
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config PPS1WAY = OFF // Peripheral Pin Select one-way control (The PPSLOCK bit can be set and cleared repeatedly by software)
#pragma config ZCDDIS = ON // Zero-cross detect disable (Zero-cross detect circuit is disabled at POR and can be enabled with ZCDSEN bit.)
#pragma config PLLEN = ON // Phase Lock Loop enable (4x PLL is always enabled)
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
// Howard Long 12 May 2018
// Implements an EM4102 125kHz RFID reader
//
// Runs at Fosc=32MHz, Fcy=8MHz, from INTOSC+PLL
//
// Diagnostics/debugging pins
// MCLR 10k to Vdd
// RB7 ICSPDAT
// RB6 ICSPCLK
// RA6 has processor Fcy (CLKOUT))
//
// Implementation pins
// RC2 CCP1 PWM output, 125kHz: TMR2 & CCP1 implement a 125kHz 50% PWM
// RA5 OPA1IN- Opamp feedback, 1Mohm to OPA1OUT, 1k to Vss
// RA4 OPA1IN+ From AM envelope detector
// RA1 OPA1OUT This should saturate, tied to RA0 digital input pin
// RA0 Input from opamp amplifier, Schmitt input
//
// o EM4102 tags modulate using ASK by loading the coupled antenna coil in the near field.
// o The 125kHz carrier is very large in comparison to the detected loading.
// o Without complex DSP, it's very simple to demodulate using an AM envelope detector (i.e., diode and RC low pass filter).
// o The bit rate is 125kHz/64, or a period of 512us, so the RC low pass filter on the envelope detector is appropriately set.
// o The envelope detector output will a large DC component, so is AC coupled to the next stage.
//
// o The signal is then referenced to Vdd/2 and limited using back to back diodes.
//
// o Key to the EM4102 model is that the coding on the signal data has no DC residual component, achieved using Manchester encoding.
// o A zero is represented as a falling edge in the middle of the bit time, and a one as a rising edge.
// o The Manchester decoder in this reader is done using a state machine that switches between edge detection and timer inside an ISR.
// o The Manchester decoder algorithm is the wait for and edge fall or rise, then wait for 0.75 bit time (384us) and sample.
// o The decoder algorithm is based on the algorithm in Microchip AN1470, but in software using a timer and IOC.
//
// o The EM4102 protocol present a frame of 64 bits in a total of four phases.
// o - Header: 9 bits, all one;
// o - Data: 10 data fields, each including 5 bits, (four data and one parity bit);
// o - Column Parity: four bits, compared to the bit-wise accumulated four bit parities of the previous ten data fields;
// o - Stop: one bit, zero.
//
// o The EM4102 protocol is also achieved using a state machine within the ISR, but separate to the Manchester decoder,
// o If any problem occurs at any point in the protocol reception, for example no header, or a bad parity, the entire state machine is reset
//
// Two possible improvement I can think of right now:
// o Use a comparator rather than an opamp, there are more cheaper PICs out there in smaller packages that would support this;
// o In the Manchester decoder, check that transitions occur when expected in a given window;
// o Again in the Manchester decoder, check that there was only one transition in the timer ISR part.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#define FOSC 32000000
#define UART_BAUDRATE 9600
static volatile enum // State machine
{
SME_IDLE=0,
SME_HEADER,
SME_DATA,
SME_COLUMNPARITY,
SME_STOP,
SME_WAIT // Wait for superloop to process
} _sme=SME_IDLE;
static uint8_t _au8Data[10]; //
void interrupt ISR(void)
{
// There are two states for the Manchester decoder, either we are waiting for an Interrupt on Change (IOC)
// or we are waiting for the timer TM4, neither should be simultaneously active.
if (INTCONbits.IOCIE && INTCONbits.IOCIF) // IOC?
{
// We've had a transition, so sample the pin, start the timer 384us timer, and disable IOC
TMR4=0;
PIR2bits.TMR4IF=0;
PIE2bits.TMR4IE=1;
T4CONbits.TMR4ON=1;
IOCAFbits.IOCAF0=0;
INTCONbits.IOCIE=0;
INTCONbits.IOCIF=0;
}
if (PIE2bits.TMR4IE && PIR2bits.TMR4IF) // TMR4?
{
// We're at 384us now, so let's disable the timer, grab the bit, and re-enable IOC
static bit b; // This is not stateful, but you can't define a bit in XC8 unless it's static or global
// The rest of the local variables must maintain state outside of the ISR for the next time
static uint8_t u8ColumnParity=0;
static uint8_t u8BitCount=0;
static uint8_t *pu8Data=_au8Data;
static uint8_t u8Accumulator=0;
static uint8_t u8RowParity=0;
b=PORTAbits.RA0; // Capture the bit for later
// Diagnostic GPIO twiddle
LATCbits.LATC1=1;
LATCbits.LATC0=(uint8_t)b;
// Disable the 384us timer
PIR2bits.TMR4IF=0;
PIE2bits.TMR4IE=0;
T4CONbits.TMR4ON=0;
// Re-enable the IOC
IOCAFbits.IOCAF0=0;
INTCONbits.IOCIF=0;
INTCONbits.IOCIE=1;
// This is the EM4102 Protocol decoder
switch (_sme)
{
case SME_IDLE:
if (b)
{
u8BitCount=8;
_sme=SME_HEADER;
}
break;
case SME_HEADER:
if (b)
{
u8BitCount--;
if (u8BitCount==0)
{
u8RowParity=0;
u8ColumnParity=0;
u8Accumulator=0;
u8BitCount=5;
pu8Data=_au8Data;
_sme=SME_DATA;
}
}
else
{
_sme=SME_IDLE; // Give up, not enough 1's
}
break;
case SME_DATA:
u8BitCount--;
if (b)
{
u8RowParity^=1;
}
if (u8BitCount==0)
{
if (u8RowParity)
{
_sme=SME_IDLE; // Give up, parity is wrong
}
else
{
u8ColumnParity^=u8Accumulator; // Update the column parity bits
*pu8Data++=u8Accumulator; // Store the data
if (pu8Data-_au8Data<10) // Are we there yet?
{
u8Accumulator=0;
u8RowParity=0;
u8BitCount=5;
}
else
{
u8Accumulator=0;
u8BitCount=4;
_sme=SME_COLUMNPARITY;
}
}
}
else
{
u8Accumulator<<=1;
if (b)
{
u8Accumulator++;
}
}
break;
case SME_COLUMNPARITY:
u8BitCount--;
u8Accumulator<<=1;
if (b)
{
u8Accumulator++;
}
if (u8BitCount==0)
{
if (u8Accumulator!=u8ColumnParity)
{
_sme=SME_IDLE; // Give up, column parity is wrong
}
else
{
_sme=SME_STOP;
}
}
break;
case SME_STOP:
if (!b)
{
// Got it!
_sme=SME_WAIT; // Let superloop do its thing
}
else
{
_sme=SME_IDLE; // Give up, no stop bit
}
break;
case SME_WAIT: // Main superloop has not yet processed the last data
break;
default: // Should never get here
break;
}
LATCbits.LATC1=0;
}
}
void putch(char c)
{
while (!TXSTAbits.TRMT)
{
NOP();
}
TXREG=c;
}
int main(void)
{
OSCCONbits.IRCF=0b1110; // 0b1110 = 8MHz (32MHz w/PLL)
TRISA=0;
TRISB=0;
TRISC=0;
ANSELA=0;
ANSELB=0;
ANSELC=0;
// Set up opamp 1
TRISAbits.TRISA1=1; // OPA1OUT
ANSELAbits.ANSA1=1;
TRISAbits.TRISA4=1; // OPA1IN+
ANSELAbits.ANSA4=1;
TRISAbits.TRISA5=1; // OPA1IN-
ANSELAbits.ANSA5=1;
OPA1CONbits.OPA1UG=0; // 0 => OPA1IN- pin
OPA1CONbits.OPA1PCH=0b00; // 0b00 => OPA1IN+ pin
OPA1CONbits.OPA1SP=1; // 1 => fast GBWP
OPA1CONbits.OPA1EN=1;
// Setup UART
RC5PPS=0b10100; // 0b10100 => TX/CK
TRISCbits.TRISC5=0;
{
const uint16_t u16=(uint16_t)(((double)FOSC)/UART_BAUDRATE/16-0.5);
SPBRGH=u16>>8;
SPBRGL=(uint8_t)u16;
}
TXSTAbits.BRGH=1;
TXSTAbits.SYNC=0;
RCSTAbits.SPEN=1;
TXSTAbits.TXEN=1;
// Setup 125kHz coil square wave
RC2PPSbits.RC2PPS=0b01100; // 0b01100 => CCP1
T2CON=0;
CCP1CON=0;
PR2=63;
TMR2=0;
CCP1CONbits.CCP1M=0b1100; // 0b1100 => PWM mode
CCPR1L=32;
CCP1CONbits.DC1B=0;
PIR1bits.TMR2IF=0;
PIE1bits.TMR2IE=0;
T2CONbits.TMR2ON=1;
// Prepare a timer for timing manchester coding
// Complete bit time is 512us
// We sample at 3/4 bit time, 384us.
// Use prescaler div-by-64, and time to a 48 step period (div-by-3072, 8MHz Tcy/3072 = 384us)
T4CON=0;
T4CONbits.T4CKPS=0b11; // 0b11 => div-by-64 prescaler
TMR4=0;
PR4=47;
// Interrupt on change
TRISAbits.TRISA0=1; // Externally, RA0 is tied to RA1 (OPA1OUI)
IOCAPbits.IOCAP0=1;
IOCANbits.IOCAN0=1;
IOCAFbits.IOCAF0=0;
INTCONbits.IOCIE=1;
INTCONbits.PEIE=1;
INTCONbits.GIE=1;
while (1)
{
if (_sme==SME_WAIT)
{
static uint8_t au8Data2[10];
const char acHex[]="0123456789ABCDEF";
int i;
NOP();
memcpy(au8Data2,_au8Data,sizeof(au8Data2)); // Grab a copy ASAP to allow another grab
_sme=SME_IDLE;
putch('\r');
putch('\n');
for (i=0;i<10;i++)
{
uint8_t u8=au8Data2[(uint8_t)i];
char c=acHex[u8];
putch(c);
}
}
}
}
PIC16F1718 ZCD reader code:
// PIC16F1718 Configuration Bit Settings
// 'C' source line config statements
// CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = ON // Clock Out Enable (CLKOUT function is enabled on the CLKOUT pin)
#pragma config IESO = OFF // Internal/External Switchover Mode (Internal/External Switchover Mode is disabled)
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config PPS1WAY = OFF // Peripheral Pin Select one-way control (The PPSLOCK bit can be set and cleared repeatedly by software)
#pragma config ZCDDIS = ON // Zero-cross detect disable (Zero-cross detect circuit is disabled at POR and can be enabled with ZCDSEN bit.)
#pragma config PLLEN = ON // Phase Lock Loop enable (4x PLL is always enabled)
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
// Howard Long 12 May 2018
// Implements an EM4102 125kHz RFID reader
//
// Runs at Fosc=32MHz, Fcy=8MHz, from INTOSC+PLL, but pretty sure this is overkill
//
// Diagnostics/debugging pins
// MCLR 10k to Vdd
// RB7 ICSPDAT
// RB6 ICSPCLK
// RA6 has processor Fcy (CLKOUT))
// RA7 is CLC1OUT brings out ZCDOUT
//
// Implementation pins
// RC2 CCP1 PWM output, 125kHz: TMR2 & CCP1 implement a 125kHz 50% PWM
// RB0 is ZCD input
// RC5 is UART TX out
//
// o EM4102 tags modulate using ASK by loading the coupled antenna coil in the near field.
// o The 125kHz carrier has very large amplitude in comparison to the loading modulation (i.e., a very low modulation index, 5% or less).
// o Without complex DSP, it's very simple to demodulate using an AM envelope detector (i.e., diode and RC low pass filter).
// o The bit rate is 125kHz/64, or a period of 512us, so the RC low pass filter on the envelope detector is appropriately set.
// o The envelope detector output will a large DC component, so is AC coupled to the next stage.
//
// o The signal is then referenced to Vdd/2 and limited using back to back diodes.
//
// o Key to the EM4102 model is that the coding on the signal data has no DC residual component, achieved using Manchester encoding.
// o A zero is represented as a falling edge in the middle of the bit time, and a one as a rising edge.
// o The Manchester decoder in this reader is done using a state machine that switches between edge detection and timer inside an ISR.
// o The Manchester decoder algorithm is the wait for and edge fall or rise, then wait for 0.75 bit time (384us) and sample.
// o The decoder algorithm is based on the algorithm in Microchip AN1470, but in software using a timer and zero crossing detector.
// o The zero crossing detector is quite handy: it simplifies the work between the envelope detector and the PIC.
// o The ZCD provides self DC bias to bring us back into PIC's voltage domains, and acts as a comparator.
//
// o The EM4102 protocol presents a frame of 64 bits in a total of four phases:
// o - Header: 9 bits, all one;
// o - Data: 10 data fields, each including 5 bits, (four data and one parity bit);
// o - Column Parity: four bits, compared to the bit-wise accumulated four bit parities of the previous ten data fields;
// o - Stop: one bit, zero.
// o The EM4102 protocol is also achieved using a state machine within the ISR, but separate to the Manchester decoder.
// o There are also IDLE and WAIT phases added to the decoder state machine to provide synchronisation between the ISR and the superloop.
// o If any problem occurs at any point in the protocol reception, for example no header, or a bad parity, the entire state machine is reset.
//
// Two possible improvements I can think of right now:
//
// o In the Manchester decoder, check that transitions occur when expected in a given window;
// o Again in the Manchester decoder, check that there was only one transition in the timer ISR part.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#define FOSC 32000000
#define UART_BAUDRATE 9600
static volatile enum // State machine
{
SME_IDLE=0,
SME_HEADER,
SME_DATA,
SME_COLUMNPARITY,
SME_STOP,
SME_WAIT // Wait for superloop to process
} _sme=SME_IDLE;
static uint8_t _au8Data[10]; //
void interrupt ISR(void)
{
// There are two states for the Manchester decoder, in this ISR.
// Either we are waiting for a zero crossing (ZCD)
// or we are waiting for the timer TM4, neither should be simultaneously active.
if (PIE3bits.ZCDIE && PIR3bits.ZCDIF)
{
// We've had a transition, so sample the pin, start the timer 384us timer, and disable ZCD for now
TMR4=0;
PIR2bits.TMR4IF=0;
PIE2bits.TMR4IE=1;
T4CONbits.TMR4ON=1;
PIE3bits.ZCDIE=0;
PIR3bits.ZCDIF=0;
}
if (PIE2bits.TMR4IE && PIR2bits.TMR4IF) // TMR4?
{
// We're at 384us now, so let's disable the timer, grab the bit, and re-enable ZCD
static bit b; // This is not stateful, but you can't define a bit in XC8 unless it's static or global
// The rest of the local variables must maintain state outside of the ISR for the next time
static uint8_t u8ColumnParity=0;
static uint8_t u8BitCount=0;
static uint8_t *pu8Data=_au8Data;
static uint8_t u8Accumulator=0;
static uint8_t u8RowParity=0;
// b=PORTAbits.RA0; // Capture the bit for later
b=ZCD1CONbits.ZCD1OUT;
// Diagnostic GPIO twiddle
LATCbits.LATC1=1;
LATCbits.LATC0=(uint8_t)b;
// Disable the 384us timer
PIR2bits.TMR4IF=0;
PIE2bits.TMR4IE=0;
T4CONbits.TMR4ON=0;
// Re-enable the ZCD
PIR3bits.ZCDIF=0;
PIE3bits.ZCDIE=1;
// This is the EM4102 Protocol decoder
switch (_sme)
{
case SME_IDLE:
if (b)
{
u8BitCount=8;
_sme=SME_HEADER;
}
break;
case SME_HEADER:
if (b)
{
u8BitCount--;
if (u8BitCount==0)
{
u8RowParity=0;
u8ColumnParity=0;
u8Accumulator=0;
u8BitCount=5;
pu8Data=_au8Data;
_sme=SME_DATA;
}
}
else
{
_sme=SME_IDLE; // Give up, not enough 1's
}
break;
case SME_DATA:
u8BitCount--;
if (b)
{
u8RowParity^=1;
}
if (u8BitCount==0)
{
if (u8RowParity)
{
_sme=SME_IDLE; // Give up, parity is wrong
}
else
{
u8ColumnParity^=u8Accumulator; // Update the column parity bits
*pu8Data++=u8Accumulator; // Store the data
if (pu8Data-_au8Data<10) // Are we there yet?
{
u8Accumulator=0;
u8RowParity=0;
u8BitCount=5;
}
else
{
u8Accumulator=0;
u8BitCount=4;
_sme=SME_COLUMNPARITY;
}
}
}
else
{
u8Accumulator<<=1;
if (b)
{
u8Accumulator++;
}
}
break;
case SME_COLUMNPARITY:
u8BitCount--;
u8Accumulator<<=1;
if (b)
{
u8Accumulator++;
}
if (u8BitCount==0)
{
if (u8Accumulator!=u8ColumnParity)
{
_sme=SME_IDLE; // Give up, column parity is wrong
}
else
{
_sme=SME_STOP;
}
}
break;
case SME_STOP:
if (!b)
{
// Got it!
_sme=SME_WAIT; // Let superloop do its thing
}
else
{
_sme=SME_IDLE; // Give up, no stop bit
}
break;
case SME_WAIT: // Main superloop has not yet processed the last data
break;
default: // Should never get here
break;
}
LATCbits.LATC1=0;
}
}
void putch(char c)
{
while (!TXSTAbits.TRMT)
{
NOP();
}
TXREG=c;
}
int main(void)
{
OSCCONbits.IRCF=0b1110; // 0b1110 = 8MHz (32MHz w/PLL)
TRISA=0;
TRISB=0;
TRISC=0;
ANSELA=0;
ANSELB=0;
ANSELC=0;
// Set up Zero crossing detector
TRISBbits.TRISB0=1;
ANSELBbits.ANSB0=1;
ZCD1CON=0;
ZCD1CONbits.ZCD1INTN=1;
ZCD1CONbits.ZCD1INTP=1;
PIR3bits.ZCDIF=0;
PIE3bits.ZCDIE=1;
ZCD1CONbits.ZCD1EN=1;
// Configure CLC1OUT to show ZCDOUT for diagnostics
RA7PPSbits.RA7PPS=0b00100;
TRISAbits.TRISA7=0;
CLC1CON=0;
CLC1POL=0;
CLC1SEL0=0;
CLC1SEL1=0;
CLC1SEL2=0;
CLC1SEL3=0;
CLC1GLS0=0;
CLC1GLS1=0;
CLC1GLS2=0;
CLC1GLS3=0;
CLC1CONbits.MODE=0b010; // 0b010 => 4-input AND
CLC1SEL0bits.LC1D1S=0b10011; // 0b10011 => ZCD1OUT
CLC1POLbits.LC1POL=0;
CLC1POLbits.LC1G1POL=0;
CLC1POLbits.LC1G2POL=1;
CLC1POLbits.LC1G3POL=1;
CLC1POLbits.LC1G4POL=1;
CLC1GLS0bits.LC1G1D1T=1;
CLC1GLS0bits.LC1G1D1N=0;
CLC1GLS0bits.LC1G1D2T=0;
CLC1GLS0bits.LC1G1D2N=0;
CLC1GLS0bits.LC1G1D3T=0;
CLC1GLS0bits.LC1G1D3N=0;
CLC1GLS0bits.LC1G1D4T=0;
CLC1GLS0bits.LC1G1D4N=0;
CLC1CONbits.EN=1;
// Setup UART
RC5PPS=0b10100; // 0b10100 => TX/CK
TRISCbits.TRISC5=0;
{
const uint16_t u16=(uint16_t)(((double)FOSC)/UART_BAUDRATE/16-0.5);
SPBRGH=u16>>8;
SPBRGL=(uint8_t)u16;
}
TXSTAbits.BRGH=1;
TXSTAbits.SYNC=0;
RCSTAbits.SPEN=1;
TXSTAbits.TXEN=1;
// Setup 125kHz coil square wave
RC2PPSbits.RC2PPS=0b01100; // 0b01100 => CCP1
T2CON=0;
CCP1CON=0;
PR2=63;
TMR2=0;
CCP1CONbits.CCP1M=0b1100; // 0b1100 => PWM mode
CCPR1L=32;
CCP1CONbits.DC1B=0;
PIR1bits.TMR2IF=0;
PIE1bits.TMR2IE=0;
T2CONbits.TMR2ON=1;
// Prepare a timer for timing Manchester coding, but don't start it until needed after a transition is detected
// Complete bit time is 512us
// We sample at 3/4 bit time, 384us.
// Use prescaler div-by-64, and time to a 48 step period (div-by-3072, 8MHz Tcy/3072 = 384us)
T4CON=0;
T4CONbits.T4CKPS=0b11; // 0b11 => div-by-64 prescaler
TMR4=0;
PR4=47;
while (1)
{
if (_sme==SME_WAIT)
{
static uint8_t au8Data2[10];
const char acHex[]="0123456789ABCDEF";
int i;
NOP();
memcpy(au8Data2,_au8Data,sizeof(au8Data2)); // Grab a copy ASAP to allow another grab
_sme=SME_IDLE;
putch('\r');
putch('\n');
for (i=0;i<10;i++)
{
uint8_t u8=au8Data2[(uint8_t)i];
char c=acHex[u8];
putch(c);
}
}
}
}
Edit: Some people may be wondering why I have apparently pointless NOPs in my code: functionally, they usually have no purpose (except for timing reasons where it's explicitly stated), they are usually there so I can place a convenient breakpoint. Occasionally I might place three or so adjacent NOPs, this is to deal with breakpoint skidding on hardware breakpoints.