0
\$\begingroup\$

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:
  nop ;no overloading processor
  jnb RDI,rwait
  ;RDAT = remote byte received
  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:
db 'Received: ',00h
ptestdone:
db 'This is a test that worked!',0Dh,0Ah,00h
END
\$\endgroup\$
  • \$\begingroup\$ Have you ever heard of I²C? \$\endgroup\$ – Dave Tweed Oct 15 '16 at 1:07
  • \$\begingroup\$ 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. \$\endgroup\$ – user116345 Oct 15 '16 at 1:52
  • \$\begingroup\$ 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. \$\endgroup\$ – Dave Tweed Oct 15 '16 at 2:01
  • \$\begingroup\$ 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. \$\endgroup\$ – user116345 Oct 15 '16 at 2:05
  • \$\begingroup\$ 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 \$\endgroup\$ – crowie Oct 15 '16 at 2:24
1
\$\begingroup\$
  • 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.

\$\endgroup\$
  • \$\begingroup\$ At this rate, I might as well buy the same crystal (same speed) for each microcontroller for best speed and i/o reliability. \$\endgroup\$ – user116345 Oct 15 '16 at 4:58
  • \$\begingroup\$ @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. \$\endgroup\$ – jonk Oct 15 '16 at 5:05
  • \$\begingroup\$ 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? \$\endgroup\$ – user116345 Oct 15 '16 at 5:31
  • \$\begingroup\$ @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. \$\endgroup\$ – jonk Oct 15 '16 at 5:33
  • \$\begingroup\$ @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. \$\endgroup\$ – jonk Oct 15 '16 at 5:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy