I'm trying to figure out the best approach to transfer high-speed data one way between two microcontrollers (with their own crystals of the same speed) of the same variety (between AT89C4051 and AT89S52) from an electrical standpoint.

I have them wired up as follows:

AT89S52 P0 is connected to AT89C4051 P1
AT89S52 P1.1 is connected to AT89C4051 P3.7

The AT89C4051 has 6 bytes of data that the AT89S52 needs, and the AT89S52 always initiates the download.

My options are as follows:

** OPTION 1. Transfer byte-wide. **

Code in AT89C4051 (transmitter) will be this:

jb P3.7,$  ;Wait for falling edge
mov P1,@R1 ;send out byte
inc R1     ;Increment pointer
jnb P3.7,$ ;Wait for rising edge
mov P1,@R1
inc R1        
;and this code (minus first line) repeats twice for remaining bytes

And the AT89S52 will have this code:

mov P0,#0FFh ;Make ports accept data
clr P1.1 ;lower line to get first byte
mov @R0,P1 ;Load next byte in
inc R0     ;increment pointer
nop        ;waste cycles to let AT89C4051
nop        ;be ready for next byte. Is this wait time enough under worst
           ;case scenarios???
setb P1.1  ;raise line to get next byte
mov @R0,P1 ;Load next byte in
inc R0     ;increment pointer
nop        ;waste cycles to let AT89C4051
nop        ;be ready for next byte
;and this code (minus first line) repeats twice for remaining bytes

That is my 8-bit approach which seems fast, but I didn't have enough pins free to use one for an acknowledgement pin as the rest are used.

** OPTION 2. Transfer nibble-wide **

Code in AT89C4051 (transmitter) will be this:

mov R1,#DATALOCATION ;pointer = start of data
jb P3.7,$   ;Wait for falling edge (but this means stalls which I don't like)
mov A,@R1   ;get byte
orl A,#0F0h ;and accept lower nibble. 
clr ACC.7   ;Make P1.7 our ack bit (ack=0)
mov P1,A    ;return data in lower nibble with P1.7=0 as ack
jnb P3.7,$  ;Wait for rising edge
mov A,@R1   ;get byte
orl A,#0Fh  ;and accept high nibble. (ack=1)
swap A      ;and put it in our low nibble slot
mov P1,A    ;return data in lower nibble with P1.7=1 as ack
inc R1
;and this code (minus first line) repeats 5x for remaining bytes

Code in AT89S52 (receiver) will be like this:

mov R0,#DATALOCATION ;pointer = start of data
mov A,@R0 ;Load byte
orl A,#0F0h ;set our nibble and make rest of lines high to receive ack
mov P1,A  ;and show it
clr P1.1  ;lower clock
jb P1.7,$ ;wait till remote is ready
mov A,@R0 ;Load byte again
orl A,#0Fh ;set our nibble and make rest of lines high to receive ack
swap A    ;swap nibbles so we get right nibble
mov P1,A  ;show data
setb P1.1 ;raise clock
jnb P1.7,$ ;wait till remote is ready
inc R0
;and this code (minus first line) repeats 5x for remaining bytes

which is best?

But the part that makes me concerned is timing and hardware.

The micros are connected no more than 10cm away from each other and all PCB traces are 12mils wide with 12mils clearance. When I run any microcontroller circuit, if my hand touches both leads of the crystal, then the speed of operation seems to vary (probably because human resistance affects crystal frequency?)

Given all environments the micros can be exposed to (except water), which of my two ideas is best to ensure I get data at the highest speed possible? and I only have 9 I/O lines to play with here.

So do I resort to the nibble method and wait for acknowledgements even if the response takes a while? or am I safe to use the byte method?

Remember, we have to assume worst-case scenarios. fingers touching crystal area (as a test), weak batteries, etc. because the last thing I want to happen is data loss.

For clarification, each crystal lead is connected to 33pF ceramic capacitors which are also grounded. (I'm using the standard microcontroller crystal setup). and my ground planes are large.


As requested, I included the important connections. Both crystals are 22.1184Mhz. The capacitor and resistor connected to the reset pin is 47nF and 100K. All other capacitors are 33pF.


I also included a basic timing diagram. I had to use paint to draw the lines in because I have no program on my computer to do professional diagrams.


  • \$\begingroup\$ How often does this happen, and how high-speed does it need to be? \$\endgroup\$
    – BeB00
    Commented Aug 29, 2018 at 16:56
  • \$\begingroup\$ I think this question is actually independent of the processor instruction set. Can you provide timing diagrams instead of code? A block diagram would also be better than your written description of the connections. \$\endgroup\$ Commented Aug 29, 2018 at 17:06
  • \$\begingroup\$ I think timing diagrams are more of what I need based on all environments the micro is exposed to. But the crystals on both are 22.1184Mhz and many instructions execute at 1/12th of a clock speed (equals 1 cycle)(about 0.54uS) but some need 2 cycles to execute. But I'm asking this question more in terms of hardware. Like for example, would nearby interference affect how my code works? I completely understand the execution time of each instruction in itself. \$\endgroup\$ Commented Aug 29, 2018 at 17:16
  • \$\begingroup\$ This happens very frequently and this fragment alone should execute in under 50uS (ideal) but no more than 100uS. Just as an example of timing, I want to have this executed no more than the time required to transmit a byte down the serial line at 56kbps with a typical 8N1 uart setting. and Elliot, I included images for your reference. \$\endgroup\$ Commented Aug 29, 2018 at 17:42
  • 1
    \$\begingroup\$ Can you run one crystal output to both chips? I foresee clock de-synchronization using separate clocks. \$\endgroup\$
    – rdtsc
    Commented Aug 29, 2018 at 17:51

3 Answers 3


First of all, you need to put your project in an enclosure that prevents human fingers (or anything else) from getting so close to the crystal that it has a major effect on the frequency.

Once you have the clocks under control, the first method should be fine, assuming that you have accounted for the worst-case delays through the I/O synchronizers on both chips. Keep in mind that although the two processors are at the same nominal frequency, their actual frequencies will vary slightly and the phase relationship between them can be anything at all.

You'll probably need a few more nop instructions on the master side (the one generating the clock). Also, those nop instructions should come before the instruction that reads the data — this gives the slave procesor the time it needs to recognize the clock transition and actually put the data on the 8-bit bus.

  • \$\begingroup\$ Yes I agree with your 1st statement. That of course I will do. Now that's two people telling me frequencies from each crystal can differ slightly. Because of the differences and my bad luck, I think I'm safer to use the nibble approach with acks rather than reprogramming the chip hundreds of times with the same program only to add a nop for each new time it is programmed. \$\endgroup\$ Commented Aug 29, 2018 at 20:21
  • \$\begingroup\$ I will also wait to see if anyone else offers me answers here that help before I just accept an answer. \$\endgroup\$ Commented Aug 29, 2018 at 20:22
  • \$\begingroup\$ Why would you need to reprogram the chip "hundreds of times"? You should be able to work out the required number of nops from the documentation in one go. \$\endgroup\$
    – Dave Tweed
    Commented Aug 29, 2018 at 20:34
  • \$\begingroup\$ well maybe not hundreds of times but I could be inaccurate because of clock drift which probably wont be listed in the documentation. \$\endgroup\$ Commented Aug 30, 2018 at 15:08
  • \$\begingroup\$ The point is, you can easily calculate the worst-case conditions, and allow a margin beyond that. There's no need to adjust anything if conditions change, as long as they don't exceed your original worst case. \$\endgroup\$
    – Dave Tweed
    Commented Aug 30, 2018 at 15:10

OK, let's see if I got this right. You are trying to achieve high-speed data transfer and you are doing bit-banging? Furthermore, you are literally counting CPU ticks for each operation, meaning your MCU is running at 100% load continuously.

There are only two possible outcomes here. Either you run out of RAM for incoming data, or you have to stop somewhere and process it. I'll assume it is the latter, and the processing takes much longer than communication. Let's say at least 5 times longer.

Now, funny thing about baud rates is that the volume of data transferred at 1 Mbps 1/6 of the time is exactly the same as the volume transferred at 170 kbps continuously. You seem to understand this, as in your comment "serial line at 56kbps with a typical 8N1".

Furthermore, by using hardware communication you can do data transfer and processing simultaneously. So, why don't you use UART hardware available in both MCUs and perfectly capable of higher speeds than you quote?

Now, just in case you need those UARTs for some other use, take a look at application note 3524 from Microchip. It is bit-banging implementation of SPI that is supposed to achieve 2Mbps transmission (at 32Mhz clock).

  • \$\begingroup\$ I already am using the hardware UART for data transfer and I have an interrupt specifically for it. Except transfer and processing simultaneously is impossible. Even all my 8051 emulators agree with me because once the byte is received/sent, my program that has to prepare the correct bytes that the serial routine needs is interrupted and therefore garbage will be processed because my transmit routine transmits the next byte once the last one is transmitted. \$\endgroup\$ Commented Aug 30, 2018 at 15:07
  • \$\begingroup\$ I don't see the use of UART hardware in your code samples. Of course transfer and processing simultaneously is possible, that is how embedded software works. Once the byte is sent all your program has to do is move next byte into transmit buffer, that's just a few OP codes. Then it can continue preparing data while the transmission is happening. If you don't have data ready, you do not send anything. On receive side you push received byte into buffer and continue processing. If no data in the buffer you simply wait for it. Why some "garbage" will be processed? \$\endgroup\$
    – Maple
    Commented Aug 30, 2018 at 15:40

Another option you might consider is to copy the way PonyLink does its low level signalling.

This is a serial link between FPGAs, but you could implement it by bit-banging on a microcontroller. It works in a clever and unusual way:

Taking into account the tolerance of the two MCU's crystals, what's the shortest pulse that the sender can generate that the receiver is guaranteed to be able to detect? We'll call this one bit. If it's a high pulse, then we'll say it's a single 1 bit, and if it was a low pulse, then we'll say it was a single 0 bit.

Now, what's the next longest pulse that the receiver is guaranteed to detect as longer than the shortest pulse. We'll consider this to be two bits. Again, if it's a high pulse, then consider it to be two 1 bits (11), and if it was a low pulse, then consider it to be two 0 bits (00).

And so we proceed. Longer and longer pulses used to represent longer strings of bits.

PonyLink also uses 10b8b encoding. This way you can guarantee that there is never a string of more than 5 bits of the same value, so that the receiver can frequently see an edge to re-synchronize on.

You don't need to implement the whole complexity of the full PonyLink protocol, but the low level signalling I described is very interesting, and might be able to help you here. Read the "Low-level signalling" section of the protocol.txt document.

There's even a Python script that you can use. Tell it the two clock speeds and their tolerance, and it will calculate the pulse lengths you need to make this work.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.