So you have an old microcomputer board that has a UART but no PIO. Maybe one like mine. No chance then of driving a couple of digital I/O lines unless you add a PIO chip?
Hmm, let's look at the data sheet for the Intel 8251, a typical UART of a bygone era. Here's part of the page describing the modem lines.

These lines were intended for handshaking between the UART and the data set. In the past this meant the modem, but these days it could be a PC talking to the microcomputer through a serial interface.
If you are don't require handshake, then DTR and RTS can be used as output lines, and DSR can be used as an input line. CTS is an input line, but is not directly readable by the CPU, it only has an effect on the transmit buffer.
Let me cut to the chase. It works.

And here's a short video of it in action. The test setup on the Arduino Uno uses pins 2-9 to drive the data pins of the UART, A0 to drive the Control/~Data line, A1 to drive the ~Read line, A2 to drive the ~Write line and A3 to drive the Reset line. On a MCU board these would already be part of the circuit and you just have to program the UART appropriately.
Here is the program:
//
// Drive 8251 UART with Arduino
// Pins 2-9 are connected to D0-D7
// Pins A0-A3 are connected as #define below
//
#define CNOTD A0
#define READ A1
#define WRITE A2
#define RESET A3
#define DTRBIT 0x02
#define RTSBIT 0x20
byte cmdinst = 0;
void init8251()
{
pinMode(CNOTD, OUTPUT);
pinMode(READ, OUTPUT);
pinMode(WRITE, OUTPUT);
pinMode(RESET, OUTPUT);
digitalWrite(CNOTD, HIGH);
digitalWrite(READ, HIGH);
digitalWrite(WRITE, HIGH);
digitalWrite(RESET, HIGH); // RESET
delay(1);
digitalWrite(RESET, LOW); // release
delay(1);
writecmd(0x4e); // mode: x16, 8 bit, 1 stop bit
writecmd(0x10); // command: error reset
}
void writecmd(byte b)
{
for (byte i = 2; i < 10; i++) {
pinMode(i, OUTPUT);
}
for (byte i = 2; i < 10; i++) {
digitalWrite(i, b & 0x1 ? HIGH : LOW);
b >>= 1;
}
digitalWrite(WRITE, LOW); // pulse /W
delayMicroseconds(1);
digitalWrite(WRITE, HIGH);
}
byte readstatus()
{
byte b = 0;
for (byte i = 2; i < 10; i++) {
pinMode(i, INPUT_PULLUP);
}
digitalWrite(READ, LOW); // lower /R
delayMicroseconds(1);
for (byte i = 2; i < 10; i++) {
b >>= 1;
if (digitalRead(i) == HIGH)
b |= 0x80;
}
digitalWrite(READ, HIGH); // raise /R
return (b);
}
inline void dtr0()
{
writecmd(cmdinst |= DTRBIT); // /DTR = 0
}
inline void dtr1()
{
writecmd(cmdinst &= ~DTRBIT); // /DTR = 1
}
inline void rts0()
{
writecmd(cmdinst |= RTSBIT); // /RTS = 0
}
inline void rts1()
{
writecmd(cmdinst &= ~RTSBIT); // /RTS = 1
}
// First test program
//
void blink()
{
dtr0(); // /DTR antiphase to /RTS
delay(500);
for (;;) {
dtr1();
rts0();
delay(500);
rts1();
dtr0();
delay(500);
}
}
/*
Second test program
Program for displaying counting from 0 - 9999 modified from
www.funwidelectronics.blogspot.in
This source code is modified from :
https://blog.3d-logic.com/2015/01/21/arduino-and-the-tm1637-4-digit-seven-segment-display/
Thanks for the code
*/
#define clk0 rts0
#define clk1 rts1
#define data0 dtr0
#define data1 dtr1
byte digits[] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f };
void start(void)
{
clk1();
data1();
delayMicroseconds(5);
data0();
clk0();
delayMicroseconds(5);
}
void stop(void)
{
clk0();
data0();
delayMicroseconds(5);
clk1();
data1();
delayMicroseconds(5);
}
bool writebyte(byte value)
{
for (byte i = 0; i < 8; value >>= 1, i++) {
clk0();
delayMicroseconds(5);
if (value & 0x1)
data1();
else
data0();
delayMicroseconds(5);
clk1();
delayMicroseconds(5);
}
// wait for ACK (not available in this implementation)
clk0();
delayMicroseconds(5);
//pinMode(data, INPUT);
clk1();
delayMicroseconds(5);
//bool ack = digitalRead(data) == 0;
//pinMode(data, OUTPUT);
return true;
}
void writenumber(byte first, byte second, byte third, byte fourth)
{
start();
writebyte(0x40);
stop();
start();
writebyte(0xc0);
writebyte(first);
writebyte(second);
writebyte(third);
writebyte(fourth);
stop();
}
void inittm1637()
{
start();
writebyte(0x89); // for changing the brightness (0x88-dim 0x8f-bright)
stop();
}
void tm1637()
{
for (;;) {
for (int j = 0; j < 10000; j++) {
writenumber(digits[j / 1000], digits[(j % 1000) / 100], digits[((j % 1000) % 100) / 10], digits[j % 10]);
delayMicroseconds(50);
}
}
}
void setup() {
// put your setup code here, to run once:
// Serial.begin(9600); // in case we need serial debugging
init8251();
inittm1637();
}
void loop() {
// put your main code here, to run repeatedly:
// blink();
tm1637();
}
A few things to note.
- There are actually 2 programs in this sketch. The first one is good old blink but alternating the LEDs, which I used to debug the basics. The second one drives the TM1637 serial display which I have used in a couple of previously published projects.
- The output pins can only be outputs so you cannot read the line. This means that any ack features of the driven peripheral can't work.
- I have not tested reading the DSR input pin, but I have confidence it will work.
- The pins are actually negated outputs so the bit manipulation is abstracted in routines. For example dtr0() actually sets the corresponding bit in the command register.
- You cannot read the state of the command register so a variable is needed to remember the current state.
- One little detail not visible: I needed pullup resistors to make the TM1637 accept the clock and data signals.
- The 8085 CPU in the corner is unused. It's for another project later.
It occurs to me that this could be a covert channel for sending data out of an MCU system. 😊
Ken Yap