altos: Require sequencing through 'main' state before landing
[fw/altos] / src / ao_btm.c
1 /*
2  * Copyright © 2011 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include "ao.h"
19
20 int8_t                  ao_btm_stdio;
21 __xdata uint8_t         ao_btm_connected;
22
23 #define AO_BTM_MAX_REPLY        16
24 __xdata char            ao_btm_reply[AO_BTM_MAX_REPLY];
25
26 extern volatile __xdata struct ao_fifo  ao_usart1_rx_fifo;
27
28 /*
29  * Read a line of data from the serial port, truncating
30  * it after a few characters.
31  */
32
33 uint8_t
34 ao_btm_get_line(void)
35 {
36         uint8_t ao_btm_reply_len = 0;
37         char c;
38
39         for (;;) {
40
41                 while ((c = ao_serial_pollchar()) != AO_READ_AGAIN) {
42                         if (ao_btm_reply_len < sizeof (ao_btm_reply))
43                                 ao_btm_reply[ao_btm_reply_len++] = c;
44                         if (c == '\r' || c == '\n')
45                                 goto done;
46                 }
47                 for (c = 0; c < 10; c++) {
48                         ao_delay(AO_MS_TO_TICKS(10));
49                         if (!ao_fifo_empty(ao_usart1_rx_fifo))
50                                 break;
51                 }
52                 if (c == 10)
53                         goto done;
54         }
55 done:
56         for (c = ao_btm_reply_len; c < sizeof (ao_btm_reply);)
57                 ao_btm_reply[c++] = '\0';
58         return ao_btm_reply_len;
59 }
60
61 /*
62  * Drain the serial port completely
63  */
64 void
65 ao_btm_drain()
66 {
67         while (ao_btm_get_line())
68                 ;
69 }
70
71 /*
72  * Set the stdio echo for the bluetooth link
73  */
74 void
75 ao_btm_echo(uint8_t echo)
76 {
77         ao_stdios[ao_btm_stdio].echo = echo;
78 }
79
80 /*
81  * Delay between command charaters; the BT module
82  * can't keep up with 57600 baud
83  */
84
85 void
86 ao_btm_putchar(char c)
87 {
88         ao_serial_putchar(c);
89         ao_delay(1);
90 }
91
92 /*
93  * Wait for the bluetooth device to return
94  * status from the previously executed command
95  */
96 uint8_t
97 ao_btm_wait_reply(void)
98 {
99         for (;;) {
100                 ao_btm_get_line();
101                 if (!strncmp(ao_btm_reply, "OK", 2))
102                         return 1;
103                 if (!strncmp(ao_btm_reply, "ERROR", 5))
104                         return -1;
105                 if (ao_btm_reply[0] == '\0')
106                         return 0;
107         }
108 }
109
110 void
111 ao_btm_string(__code char *cmd)
112 {
113         char    c;
114
115         while (c = *cmd++)
116                 ao_btm_putchar(c);
117 }
118
119 uint8_t
120 ao_btm_cmd(__code char *cmd)
121 {
122         ao_btm_drain();
123         ao_btm_string(cmd);
124         return ao_btm_wait_reply();
125 }
126
127 uint8_t
128 ao_btm_set_name(void)
129 {
130         char    sn[8];
131         char    *s = sn + 8;
132         char    c;
133         int     n;
134         ao_btm_string("ATN=TeleBT-");
135         *--s = '\0';
136         *--s = '\r';
137         n = ao_serial_number;
138         do {
139                 *--s = '0' + n % 10;
140         } while (n /= 10);
141         while ((c = *s++))
142                 ao_btm_putchar(c);
143         return ao_btm_wait_reply();
144 }
145
146 uint8_t
147 ao_btm_try_speed(uint8_t speed)
148 {
149         ao_serial_set_speed(speed);
150         ao_btm_drain();
151         (void) ao_btm_cmd("\rATE0\rATQ0\r");
152         if (ao_btm_cmd("AT\r") == 1)
153                 return 1;
154         return 0;
155 }
156
157 /*
158  * A thread to initialize the bluetooth device and
159  * hang around to blink the LED when connected
160  */
161 void
162 ao_btm(void)
163 {
164         /*
165          * Wait for the bluetooth device to boot
166          */
167         ao_delay(AO_SEC_TO_TICKS(3));
168
169 #if HAS_BEEP
170         ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
171 #endif
172
173         /*
174          * The first time we connect, the BTM-180 comes up at 19200 baud.
175          * After that, it will remember and come up at 57600 baud. So, see
176          * if it is already running at 57600 baud, and if that doesn't work
177          * then tell it to switch to 57600 from 19200 baud.
178          */
179         while (!ao_btm_try_speed(AO_SERIAL_SPEED_57600)) {
180                 ao_delay(AO_SEC_TO_TICKS(1));
181                 if (ao_btm_try_speed(AO_SERIAL_SPEED_19200))
182                         ao_btm_cmd("ATL4\r");
183                 ao_delay(AO_SEC_TO_TICKS(1));
184         }
185
186         /* Disable echo */
187         ao_btm_cmd("ATE0\r");
188
189         /* Enable flow control */
190         ao_btm_cmd("ATC1\r");
191
192         /* Set the reported name to something we can find on the host */
193         ao_btm_set_name();
194
195         /* Turn off status reporting */
196         ao_btm_cmd("ATQ1\r");
197
198         ao_btm_stdio = ao_add_stdio(ao_serial_pollchar,
199                                     ao_serial_putchar,
200                                     NULL);
201         ao_btm_echo(0);
202
203         for (;;) {
204                 while (!ao_btm_connected)
205                         ao_sleep(&ao_btm_connected);
206                 while (ao_btm_connected) {
207                         ao_led_for(AO_LED_GREEN, AO_MS_TO_TICKS(20));
208                         ao_delay(AO_SEC_TO_TICKS(3));
209                 }
210         }
211 }
212
213 __xdata struct ao_task ao_btm_task;
214
215 #if BT_LINK_ON_P2
216 #define BT_PICTL_ICON   PICTL_P2ICON
217 #define BT_PIFG         P2IFG
218 #define BT_PDIR         P2DIR
219 #define BT_PINP         P2INP
220 #define BT_IEN2_PIE     IEN2_P2IE
221 #endif
222 #if BT_LINK_ON_P1
223 #define BT_PICTL_ICON   PICTL_P1ICON
224 #define BT_PIFG         P1IFG
225 #define BT_PDIR         P1DIR
226 #define BT_PINP         P1INP
227 #define BT_IEN2_PIE     IEN2_P1IE
228 #endif
229
230 void
231 ao_btm_check_link() __critical
232 {
233         /* Check the pin and configure the interrupt detector to wait for the
234          * pin to flip the other way
235          */
236         if (BT_LINK_PIN) {
237                 ao_btm_connected = 0;
238                 PICTL |= BT_PICTL_ICON;
239         } else {
240                 ao_btm_connected = 1;
241                 PICTL &= ~BT_PICTL_ICON;
242         }
243 }
244
245 void
246 ao_btm_isr(void)
247 #if BT_LINK_ON_P1
248         __interrupt 15
249 #endif
250 {
251 #if BT_LINK_ON_P1
252         P1IF = 0;
253 #endif
254         if (BT_PIFG & (1 << BT_LINK_PIN_INDEX)) {
255                 ao_btm_check_link();
256                 ao_wakeup(&ao_btm_connected);
257         }
258         BT_PIFG = 0;
259 }
260
261 void
262 ao_btm_init (void)
263 {
264         ao_serial_init();
265         ao_serial_set_speed(AO_SERIAL_SPEED_19200);
266
267 #if BT_LINK_ON_P1
268         /*
269          * Configure ser reset line
270          */
271
272         P1_6 = 0;
273         P1DIR |= (1 << 6);
274 #endif
275
276         /*
277          * Configure link status line
278          */
279
280         /* Set pin to input */
281         BT_PDIR &= ~(1 << BT_LINK_PIN_INDEX);
282
283         /* Set pin to tri-state */
284         BT_PINP |= (1 << BT_LINK_PIN_INDEX);
285
286         /* Enable interrupts */
287         IEN2 |= BT_IEN2_PIE;
288
289         /* Check current pin state */
290         ao_btm_check_link();
291
292 #if BT_LINK_ON_P2
293         /* Eable the pin interrupt */
294         PICTL |= PICTL_P2IEN;
295 #endif
296 #if BT_LINK_ON_P1
297         /* Enable pin interrupt */
298         P1IEN |= (1 << BT_LINK_PIN_INDEX);
299 #endif
300
301         ao_add_task(&ao_btm_task, ao_btm, "bt");
302 }