Bringing WiFi to the Cidco Mailstation
1/*
2 * WiFiStation
3 * Copyright (c) 2021 joshua stein <jcs@jcs.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include "MCP23S18.h"
19#include "wifistation.h"
20
21/* millis to consider a read/write timed out */
22#define MAILSTATION_TIMEOUT 2500
23
24/*
25 * ESP8266 default SPI pins:
26 *
27 * GPIO 12: MISO
28 * GPIO 13: MOSI
29 * GPIO 14: SCK
30 *
31 * Custom pins will be:
32 * GPIO 4: CS
33 * GPIO 16: Reset
34 */
35
36#define GPIO_CS 4
37#define GPIO_RESET 16
38
39/* these are pins on the MCP23S18 */
40const int pData0 = 0;
41const int pData1 = 1;
42const int pData2 = 2;
43const int pData3 = 3;
44const int pData4 = 4;
45const int pData5 = 5;
46const int pData6 = 6;
47const int pData7 = 7;
48
49const int pBusy = 8; /* input from mailstation pin 1 (strobe) */
50const int pAck = 9; /* input from mailstation pin 14 (linefeed) */
51
52const int pLineFeed = 10; /* output to mailstation pin 10 (ack) */
53const int pStrobe = 11; /* output to mailstation pin 11 (busy) */
54
55MCP23S18 mcp;
56
57/* cache data pin direction */
58int data_mode = -1;
59
60void
61ms_init(void)
62{
63 /* reset the MCP23S18 */
64 pinMode(GPIO_RESET, OUTPUT);
65 digitalWrite(GPIO_RESET, LOW);
66 delay(100);
67 digitalWrite(GPIO_RESET, HIGH);
68 delay(100);
69
70 mcp.begin(GPIO_CS);
71
72 /* data lines will flip between input/output, start in output mode */
73 ms_datadir(OUTPUT);
74
75 /* strobe (control) */
76 mcp.pinMode(pStrobe, OUTPUT);
77 mcp.pullUp(pStrobe, HIGH);
78 mcp.digitalWrite(pStrobe, LOW);
79
80 /* linefeed (control) */
81 mcp.pinMode(pLineFeed, OUTPUT);
82 mcp.pullUp(pLineFeed, HIGH);
83 mcp.digitalWrite(pLineFeed, LOW);
84
85 /* ack (status) */
86 mcp.pinMode(pAck, INPUT);
87 mcp.pullUp(pAck, LOW);
88
89 /* busy (status) */
90 mcp.pinMode(pBusy, INPUT);
91 mcp.pullUp(pBusy, LOW);
92}
93
94void
95ms_setup(void)
96{
97 uint8_t iocon;
98
99 for (int i = 0; i < 10; i++) {
100 ms_init();
101 iocon = mcp.readRegister(MCP23S18_IOCON);
102
103 if (iocon == 0xff || iocon == 0x0) {
104 error_flash();
105 ms_init();
106 } else
107 break;
108 }
109}
110
111void
112ms_datadir(uint8_t which)
113{
114 if (data_mode == which)
115 return;
116
117 mcp.bankPinMode(0, which);
118 mcp.bankPullUp(0, which == OUTPUT ? HIGH : LOW);
119 data_mode = which;
120}
121
122int
123ms_read(void)
124{
125 unsigned long t;
126 char c;
127
128 /* sender raises strobe (busy) to signal a write */
129 if (mcp.digitalRead(pBusy) == LOW)
130 return -1;
131
132 /* but when both lines are high, something's not right */
133 if (mcp.digitalRead(pAck) == HIGH) {
134 /*
135 * Both pins will be raised during a reboot, so in that case
136 * stop talking.
137 */
138 mailstation_alive = false;
139 return -1;
140 }
141
142 ms_datadir(INPUT);
143
144 /* raise linefeed (ack) to signal ready to receive */
145 mcp.digitalWrite(pLineFeed, HIGH);
146
147 /* sender sees raised ack and writes data */
148
149 /* sender lowers strobe (busy) once data is ready */
150 t = millis();
151 while (mcp.digitalRead(pBusy) == HIGH) {
152 if (millis() - t > MAILSTATION_TIMEOUT) {
153 mcp.digitalWrite(pLineFeed, LOW);
154 error_flash();
155 return -1;
156 }
157 ESP.wdtFeed();
158 }
159
160 c = mcp.readGPIO(0);
161
162 /* lower linefeed (ack) when we're done reading */
163 mcp.digitalWrite(pLineFeed, LOW);
164
165 return c;
166}
167
168uint16_t
169ms_status(void)
170{
171 return mcp.readGPIOAB();
172}
173
174int
175ms_write(char c)
176{
177 unsigned long t;
178
179 ms_datadir(OUTPUT);
180
181 /* raise strobe (busy on receiver) to signal write */
182 mcp.digitalWrite(pStrobe, HIGH);
183
184 /* wait for receiver to raise ack (linefeed on receiver) */
185 t = millis();
186 while (mcp.digitalRead(pAck) == LOW &&
187 mcp.digitalRead(pLineFeed) == LOW) {
188 if (millis() - t > MAILSTATION_TIMEOUT) {
189 mcp.digitalWrite(pStrobe, LOW);
190 error_flash();
191 mailstation_alive = false;
192 return -1;
193 }
194 ESP.wdtFeed();
195 }
196
197 /* ack is high, write data */
198 ms_writedata(c);
199
200 /* lower strobe (busy on receiver) to indicate we're done writing */
201 mcp.digitalWrite(pStrobe, LOW);
202
203 /* wait for receiver to read and lower ack (busy on their end) */
204 t = millis();
205 while (mcp.digitalRead(pAck) == HIGH) {
206 if (millis() - t > MAILSTATION_TIMEOUT) {
207 error_flash();
208 return -1;
209 }
210 ESP.wdtFeed();
211 }
212
213 return 0;
214}
215
216int
217ms_print(String str)
218{
219 size_t len = str.length();
220 int ret;
221
222 for (size_t i = 0; i < len; i++)
223 if ((ret = ms_write(str.charAt(i))) != 0)
224 return ret;
225
226 return 0;
227}
228
229int
230ms_print(char *string)
231{
232 size_t len = strlen(string);
233 int ret;
234
235 for (size_t i = 0; i < len; i++)
236 if ((ret = ms_write(string[i])) != 0)
237 return ret;
238
239 return 0;
240}
241
242void
243ms_writedata(char c)
244{
245 mcp.writeGPIO(0, c);
246}