8051 RFID reader for Atmel microcontroller I am currently using an AT89S52 microcontroller for a major project that involves a use of an RFID reader that outputs data in a serial format (9600 baud, no parity, 8 data bits, 1 stop bit).

The only hardware UART embedded in the microcontroller is used for other purposes and I don't want to redesign my circuit board or pay higher dollars only for a second hardware UART so my only option is to use a software UART. 

I made a routine employing the software UART technique that is able to constantly read data from an RFID card module. Fortunately I am able to read the data correctly (example: the 1st and last bytes of the data packet are correct). 

The problem I have with this code is that it is synchronous because of all the halting loops (see the DJNZ lines).

The card reader (#RDM6300) produces at least 13 bytes of data per card.

Each bit at 9600bps is 0.1ms. which is 1.0ms/byte (due to adding the start and stop bit) which is 13.5ms delay for all 13 bytes (provided the reader module sends them all at once). I want to somehow modify my code so that I have a lower delay (1-2ms is more tolerable). There are also buttons on the board that users may press very fast and I don't want the buttons to appear frozen (unresponsive) only because the system is stuck in the middle of reading an RFID card.

What can I do to my code to improve it so that the required delay isn't large?

  ; crystal used = 22.1184Mhz
  ; micro used = AT89S52
  ; 1 clock cycle = 12 oscillator cycles

  ; Took calculation partly from
  ; https://www.8051projects.net/wiki/8051_Software_UART_Tutorial
  ; 1 bit time = (((crystal/baud)/12) - 5) / 2
  ; 1 bit time = ((22118400/9600/12) - 5) / 2 = 187
  ; was told on a forum to use half as full bit time...
  ; 187/2 = 93.5 = 5Dh
  ; 1/2 = 93.5 / 2 = 46.75 = 2Eh
  ; 1/2 bit time seemed to read correct data

  BITTIM equ 05Dh ;1 bit time for 9600 baud
  BITTIMH equ 2Eh ;1/2 bit time
  RX equ Px.x ;serial data

  main:

; -- insert asynchronous function here -- 

initserial:

;wait for serial start (data=0)
jb RX,$

;wait 1/2 bit time
mov R2,#BITTIMH
djnz R2,$

;If data isn't zero after 1/2 bit time
;we assume out of sync and start over
jb RX,initserial

mov R3,#8h ;setup to read 8 bits
clr A      ;and clear byte

readnextbit:

  ;wait 1 full bit time
  mov R2,#BITTIM
  djnz R2,$

  ;add 1 clock cycle waiting time (about .5uS)
  ; -- I'm trying to align data here --
  nop

  ;get bit and shift into accumulator
  mov C,RX
  RRC A

  ;continue until all 8 bits are read
DJNZ R3,readnextbit
;result stored in accumulator
  sjmp main
Here are some ideas that may be useful to you.

Decide what's most important

Since the 8051, including your AT89S52 variant, has few resources, you'll have to make some hard decisions on how to use them.  If you're already using the single serial port for some other purpose and it's fixed for some reason (e.g. can't respin boards), then perhaps it's out of your hands.  If not, then taking a good look at both UART uses would likely be useful.  It would be most useful to put the less frequent, lower bit rate, less important traffic on a software UART.  Sometimes the right choice is readily apparent; other times not so much.  Another possibility is to multiplex them into the single hardware UART, but of course that only works if it's acceptable to block reception of one UART while the other is receiving.

Use an interrupt

The sole purpose for the delays is timing, so I'd suggest using a timer instead.  As you know, this CPU only has three timers and Timer 2 is tied to the Baud Rate Generator for the hardware UART, so that leaves Timer 0 and Timer 1.  You may be able to use one of those in either mode 0 (13-bit timer) or mode 1 (16-bit timer).  One way to do this would be to use two interrupts.  One interrupt would be set to trigger on the falling edge of RX.  Once that's detected, disable that interrupt and enable a timer interrupt for half bit time.  When the timer triggers, read the bit and if it's not a 0, that is, not a true start bit, disable the timer interrupt and re-enable the falling edge interrupt.  If it is a zero, count it as a start bit and set the timer for a full bit time.  Thereafter, just read the bits for each timer interrupt until you get to the stop bit.  If the stop bit is not 1 when it's supposed to be, you'll probably want to signal a framing error.  If it is, the byte is probably good and can be stored.  Either way, disable the timer interrupt and re-enable the falling edge interrupt.  It's not ideal, but it should work, given the limitations.