# bi-directional half-duplex communication between two AT89C2051's micros without serial port

I'm trying to do a direct two-way communication between two AT89C2051 microcontrollers with only two GPIO pins so that I have other GPIO lines free for other tasks.

P3.7 is the data wire, and P3.2 (INT0) is the control wire, and they are externally pulled high to VCC (of 5 volts) through 10K resistors.

One microcontroller has an 18.432Mhz crystal connected to it so I can connect it to my computer at a clean 9600bps baud. The other microcontroller is connected to a 20Mhz crystal (which is the highest speed lowest-priced compact crystal I ordered from the internet) for fast memory and wireless communication.

Initially I was going to reserve 4 GPIO pins for data communication but I wouldn't have any free for other tasks such as memory and LED manipulation.

This is my idea behind the two wires.

The sender loads P3.7 with the one bit and then sends P3.2 low then waits a bit for remote processing. This repeats until the whole byte is sent.

When the receiver receives data, the interrupt is supposed to be executed (from the remote sender making P3.2 low) and the byte received from P3.7 is stored in a temporary location until all bytes are received which a counter tracks. Once all are received, a flag is set.

The sending seems to be ok, but the receiving always stalls.

Do I need to adjust my timings or is there a better approach of doing half-duplex bi-directional communication with only two wires connected to the same pins (with pull-up resistors) on both micros?

Here is my code for reference. I have to remove the first line of code after programming the first chip to ensure the correct code is on each chip.

UNIT equ 1h ;1 = 8051 to interface to PC, 0 = 8051 to interface to radio.
RDATS EQU 40h ;remote data temp
RDAT EQU 42h
RDATCT EQU 41h
RDI EQU 0h ;remote-data-interrupt bit
CT EQU 1h ;carry temp - receive
CTS EQU 2h ;carry temp - send

org 0h
ljmp main
;INT 0 natural address is here (03h)
ljmp intZ
org 002Bh

;called
intZ:
clr EX0
ifdef UNIT
clr P3.4 ;This is supposed to execute on 8051 connected to PC but does not.
endif
mov CT,C
push ACC
mov C,P3.7
mov A,RDATS
RLC A
mov RDATS,A
inc RDATCT
mov A,RDATCT
cjne A,#8h,no8
setb RDI
mov RDAT,RDATS
mov RDATCT,#0h
no8:
jnb P3.2,$pop ACC mov C,CT ifdef UNIT setb P3.4 endif setb EX0 reti sendrdat: clr EX0 mov CTS,C mov R7,#8h push ACC sendrmore: clr C rlc A mov P3.7,C clr P3.2 nop nop setb P3.2 djnz R7,sendrmore pop ACC mov C,CTS setb EX0 ret serialin: clr RI jnb RI,$
mov A,SBUF
ret

serialout:
clr TI
mov SBUF,A
jnb TI,$ret pserial: rewr: clr A movc A,@A+DPTR jz exl acall serialout inc DPTR ajmp rewr exl: ret main: mov RDAT,#0h mov RDATS,#0h mov RDATCT,#0h clr RDI mov P3,#0FFh mov P1,#0FFh mov SP,#50h ;skip most ram so SP doesnt be wrecked mov scon,#50h mov tmod,#020h mov 087h,#80h ;pcon.7 mov TH1,#0F6h ;9600 baud on 18.432Mhz setb EX0 setb EA setb TR1 IFDEF UNIT ajmp PCmode ELSE ajmp RADmode ENDIF ;branch PCmode: clr RI jnb RI,$
mov DPTR,#ptest1
acall pserial
;send 0AAh to remote 8051
mov A,#0AAh
acall sendrdat
mov DPTR,#ptest2
acall pserial
; code stalls here forever.
; It should only stall until remote 8051 calls this one.
rwait:
jnb RDI,rwait
mov A,RDAT
push ACC
mov DPTR,#ptestr
acall pserial
pop ACC
acall serialout
mov A,#0Dh
acall serialout
mov A,#0Ah
acall serialout
mov DPTR,#ptestdone
acall pserial
sjmp $RADmode: ;radio mode... only obey commands at this time nop jnb RDI,RADmode clr RDI mov A,RDAT cjne A,#0AAh,nosig ;AAh=get signature = 41h (A) mov A,#041h acall sendrdat nosig: ajmp RADmode sjmp$

ptest1:
db 'Sending data to unit 2...',0Dh,0Ah,00h
ptest2:
db 'Receiving data from unit 2...',0Dh,0Ah,00h
ptestr:
ptestdone:
db 'This is a test that worked!',0Dh,0Ah,00h
END

• Have you ever heard of I²C? – Dave Tweed Oct 15 '16 at 1:07
• I read up about it and It seems I'm using the protocol to an extent. If possible, I want to enable as much multitasking as possible in the IC's instead of stalling for the full 8 bits. – user116345 Oct 15 '16 at 1:52
• The point is, I²C is a completely static, synchronous protocol that lots of people understand. You'll get a lot more help if you stick to an existing standard rather than inventing your own. You can run I²C as slow or fast as you like, and since it is entirely driven by edges, even a bit-banged implementation can be done entirely in interrupts, with no busy-wait loops at all. Plus, there's no way I'm going to wade through that mess of poorly-formatted, uncommented assembly code. I've written a lot of 8051 code in my time (C and assembly), and I'd be ashamed to show code like that in public. – Dave Tweed Oct 15 '16 at 2:01
• I could write substantially worse code. Anyways, I'm using the interrupt line as a trigger to start the reception of a byte. I'm gonna look for better code examples to see where I'm wrong but I'm still open to answers. – user116345 Oct 15 '16 at 2:05
• So why don't you use the onboard UART? I googled the datasheet for that chip and it seems to come in a 20 pin package... plenty of gpio – crowie Oct 15 '16 at 2:24

• I want to enable as much multitasking as possible

That just means that you will be using interrupts in some fashion.

The alternative, salting your code with short snippets of identical polling code placed strategically to simulate periodic events is as ugly as it gets. So you don't ever do it. I've seen it done before. And it's terrible and will make your code for all practical purposes unmaintainable.

• until all bytes are received which a counter tracks
• half-duplex bi-directional communication

These suggest to me that you actually may want a master/slave relationship, but where either can become the master for the period of the communication. It can be done by having only one master, ever. But the way you write suggests you would either like a protocol without a specific master (such as the asynchronous protocol used in RS-232, for example) or else a means by which either can become a master for the purposes of a burst of communication.

• 18.432Mhz crystal
• 20Mhz crystal

With two different processors, even if they are running at the so-called same clock rate, will start out with some phase difference which also drifts around relative to each other. Nothing is perfect in life. But with two different clock rates, one thing I also look at is the GCD. In this case, that's 32000. This means one option might be to use a divisor of 576 on your $18.432\:\textrm{Mhz}$ micro and a divisor of 625 on your $20.000\:\textrm{Mhz}$. That would get the bit timing, if you used a protocol where that mattered, into the similar ballparks. You'd still have phase error and drifting. But if you chose to use an asynchronous RS-232 like protocol, you could probably manage that after a fashion at a bit rate of $32\:\textrm{kHz}$.

But I also think Dave is right, too. Using something like $\textrm{I}^2\textrm{C}$ has its advantages over dinking around with too much over-sampling (you may need 2X over-sampling or more, regardless, since this is software; but this varies a bit with the hardware interrupt modes you can use, too) and getting your bit timings zeroed in.

The tradeoffs are now something like this:

1. Use multi-master $\textrm{I}^2\textrm{C}$. This takes a little extra effort in the code for master and slave to achieve the multi-master parts. But there is a standard for this too and plenty of example code to examine, I'm sure. The limitation here is that your communications are half-duplex using two lines. But you've already accepted this. And arbitration issues might have you scrambling for those GCD divisors I mentioned above.

2. Use a copy of the asynchronous format (start bit, N data bits, and let's say two stop bits) used by RS-232 and commonly found in UART hardware. The advantage here is that you get full-duplex communications. But you've indicated you don't need that. You'd probably want to use those GCD divisors here, too, to get the bit timing close enough to each other.

3. Use $\textrm{I}^2\textrm{C}$ in simple master-slave mode. Slaves can not push any data to the master. Instead, the master would have to ask the slave if there was something to send back. But you could also go this route. It would spare you arbitration issues, too.

In all cases you will have interrupts involved and either, (A) use state machines; or, (B) use an operating system with threads (cooperative ones, if not pre-emptive.)

It doesn't take much, not even on the 8051, to pony up a simple cooperative set of threads. (Yes, I've done it on a SiLabs C8051F061 and using SDCC and not Keil.) It really cleans up code and makes it very maintainable, because it avoids the chain of state transitions.

But a clearly documented set of state transitions isn't bad, either. Most with any experience using state machines can follow the clear documentation and see which code performs which step. So I think either approach would be fine.

• they are externally pulled high to VCC (of 5 volts) through 10K resistors.

So this is good as $\textrm{I}^2\textrm{C}$ wants that and asynchronous protocols can work with that.

Why don't you do some googling on $\textrm{I}^2\textrm{C}$ state machines, read through them until you feel you understand them, and also read through the documentatation available from this $\textrm{I}^2\textrm{C}$ documentation that is available to you. Similarly, consider an asynchronous protocol, as well.

One thing in your favor is that you do have the ability to get similar bit times on each device. This doesn't fix phase differences or drift. But a little bit of over-sampling can address that. Or you can just leave them free-run and work through the issues of that process.

Chances are, there are many different examples of code readily available on the web to cover you here, too. I'd recommend doing some searching there, as well. You are the one who needs this. So you should be doing the up-front work here. Not asking us to do that for you.

That said, if you get yourself educated about these methods and can narrow down your questions to something specific, I'm sure you'll get some wise advice. But right now, you are just throwing code against a wall to see what sticks. And I think we'd like you to do your own work getting up to speed on existing methods which are tried and true, before you start asking anyone to come up with something new for you or something based only on your own (apparently quite limited) thinking about this. If you are experienced, you can indeed draft up your own protocol and make it work -- first time, too, out of the box. But you aren't experienced. So you should first work from the benefit of others.

• At this rate, I might as well buy the same crystal (same speed) for each microcontroller for best speed and i/o reliability. – user116345 Oct 15 '16 at 4:58
• @Mike You could consider driving them both from the same crystal module. They'd be in phase as well as running at the same clock rate. – jonk Oct 15 '16 at 5:05
• I never knew such a setup is possible. I already made one circuit board where each micro has its own crystal. How do I wire the xtal pins of each microcontroller so both use the same crystal without blowing either part up? – user116345 Oct 15 '16 at 5:31
• @Mike That depends on the specific micro. Many can have one of their clock input pins driven externally (one is actually an output, one an input, in a class-A oscillator scheme.) You should follow guidance, though. Most manufacturers will talk about how this can be done for their product. If not, do a search on the web. If that doesn't work, consider calling up the local rep for the micro and ask them to get their field engineer to call you so you can ask about it. They'll know. – jonk Oct 15 '16 at 5:33
• @Mike Look at the datasheet for the AT89C2051. You will see the two clock lines clearly labeled, one as output and one as input. Figure 5-2 seems to cover the case. – jonk Oct 15 '16 at 5:42