summaryrefslogtreecommitdiff
path: root/src/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/ao_hmc5883.c18
-rw-r--r--src/drivers/ao_hmc5883.h2
-rw-r--r--src/drivers/ao_mpu6000.c39
-rw-r--r--src/drivers/ao_ms5607.c14
-rw-r--r--src/drivers/ao_rn4678.c606
-rw-r--r--src/drivers/ao_rn4678.h101
6 files changed, 748 insertions, 32 deletions
diff --git a/src/drivers/ao_hmc5883.c b/src/drivers/ao_hmc5883.c
index f668fb66..c33aa536 100644
--- a/src/drivers/ao_hmc5883.c
+++ b/src/drivers/ao_hmc5883.c
@@ -126,13 +126,15 @@ struct ao_hmc5883_sample ao_hmc5883_current;
static void
ao_hmc5883(void)
{
+ struct ao_hmc5883_sample sample;
ao_hmc5883_setup();
for (;;) {
- ao_hmc5883_sample(&ao_hmc5883_current);
- ao_arch_critical(
- AO_DATA_PRESENT(AO_DATA_HMC5883);
- AO_DATA_WAIT();
- );
+ ao_hmc5883_sample(&sample);
+ ao_arch_block_interrupts();
+ ao_hmc5883_current = sample;
+ AO_DATA_PRESENT(AO_DATA_HMC5883);
+ AO_DATA_WAIT();
+ ao_arch_release_interrupts();
}
}
@@ -141,10 +143,8 @@ static struct ao_task ao_hmc5883_task;
static void
ao_hmc5883_show(void)
{
- struct ao_data sample;
- ao_data_get(&sample);
- printf ("X: %d Y: %d Z: %d missed irq: %lu\n",
- sample.hmc5883.x, sample.hmc5883.y, sample.hmc5883.z, ao_hmc5883_missed_irq);
+ printf ("X: %d Z: %d Y: %d missed irq: %lu\n",
+ ao_hmc5883_current.x, ao_hmc5883_current.z, ao_hmc5883_current.y, ao_hmc5883_missed_irq);
}
static const struct ao_cmds ao_hmc5883_cmds[] = {
diff --git a/src/drivers/ao_hmc5883.h b/src/drivers/ao_hmc5883.h
index 78637b02..b90733df 100644
--- a/src/drivers/ao_hmc5883.h
+++ b/src/drivers/ao_hmc5883.h
@@ -77,7 +77,7 @@
#define HMC5883_ID_C 12
struct ao_hmc5883_sample {
- int16_t x, y, z;
+ int16_t x, z, y;
};
extern struct ao_hmc5883_sample ao_hmc5883_current;
diff --git a/src/drivers/ao_mpu6000.c b/src/drivers/ao_mpu6000.c
index 650407ad..81d3c16c 100644
--- a/src/drivers/ao_mpu6000.c
+++ b/src/drivers/ao_mpu6000.c
@@ -192,7 +192,7 @@ _ao_mpu6000_setup(void)
_ao_mpu6000_wait_alive();
/* Reset the whole chip */
-
+
_ao_mpu6000_reg_write(MPU6000_PWR_MGMT_1,
(1 << MPU6000_PWR_MGMT_1_DEVICE_RESET));
@@ -292,7 +292,7 @@ _ao_mpu6000_setup(void)
ao_delay(AO_MS_TO_TICKS(200));
_ao_mpu6000_sample(&normal_mode);
-
+
errors += ao_mpu6000_accel_check(normal_mode.accel_x, test_mode.accel_x);
errors += ao_mpu6000_accel_check(normal_mode.accel_y, test_mode.accel_y);
errors += ao_mpu6000_accel_check(normal_mode.accel_z, test_mode.accel_z);
@@ -315,7 +315,7 @@ _ao_mpu6000_setup(void)
/* Set sample rate divider to sample at 200Hz (v = gyro/rate - 1) */
_ao_mpu6000_reg_write(MPU6000_SMPRT_DIV,
1000 / 200 - 1);
-
+
ao_delay(AO_MS_TO_TICKS(100));
ao_mpu6000_configured = 1;
}
@@ -325,6 +325,7 @@ struct ao_mpu6000_sample ao_mpu6000_current;
static void
ao_mpu6000(void)
{
+ struct ao_mpu6000_sample sample;
/* ao_mpu6000_init already grabbed the SPI bus and mutex */
_ao_mpu6000_setup();
#if AO_MPU6000_SPI
@@ -335,14 +336,15 @@ ao_mpu6000(void)
#if AO_MPU6000_SPI
ao_mpu6000_spi_get();
#endif
- _ao_mpu6000_sample(&ao_mpu6000_current);
+ _ao_mpu6000_sample(&sample);
#if AO_MPU6000_SPI
ao_mpu6000_spi_put();
-#endif
- ao_arch_critical(
- AO_DATA_PRESENT(AO_DATA_MPU6000);
- AO_DATA_WAIT();
- );
+#endif
+ ao_arch_block_interrupts();
+ ao_mpu6000_current = sample;
+ AO_DATA_PRESENT(AO_DATA_MPU6000);
+ AO_DATA_WAIT();
+ ao_arch_release_interrupts();
}
}
@@ -351,16 +353,13 @@ static struct ao_task ao_mpu6000_task;
static void
ao_mpu6000_show(void)
{
- struct ao_data sample;
-
- ao_data_get(&sample);
printf ("Accel: %7d %7d %7d Gyro: %7d %7d %7d\n",
- sample.mpu6000.accel_x,
- sample.mpu6000.accel_y,
- sample.mpu6000.accel_z,
- sample.mpu6000.gyro_x,
- sample.mpu6000.gyro_y,
- sample.mpu6000.gyro_z);
+ ao_mpu6000_current.accel_x,
+ ao_mpu6000_current.accel_y,
+ ao_mpu6000_current.accel_z,
+ ao_mpu6000_current.gyro_x,
+ ao_mpu6000_current.gyro_y,
+ ao_mpu6000_current.gyro_z);
}
static const struct ao_cmds ao_mpu6000_cmds[] = {
@@ -374,7 +373,7 @@ ao_mpu6000_init(void)
ao_mpu6000_configured = 0;
ao_add_task(&ao_mpu6000_task, ao_mpu6000, "mpu6000");
-
+
#if AO_MPU6000_SPI
ao_spi_init_cs(AO_MPU6000_SPI_CS_PORT, (1 << AO_MPU6000_SPI_CS_PIN));
@@ -386,7 +385,7 @@ ao_mpu6000_init(void)
ao_cur_task = &ao_mpu6000_task;
ao_spi_get(AO_MPU6000_SPI_BUS, AO_SPI_SPEED_1MHz);
ao_cur_task = NULL;
-#endif
+#endif
ao_cmd_register(&ao_mpu6000_cmds[0]);
}
diff --git a/src/drivers/ao_ms5607.c b/src/drivers/ao_ms5607.c
index 261df67f..914e0c1b 100644
--- a/src/drivers/ao_ms5607.c
+++ b/src/drivers/ao_ms5607.c
@@ -191,17 +191,23 @@ ao_ms5607_sample(__xdata struct ao_ms5607_sample *sample)
#include "ao_ms5607_convert.c"
#endif
-#if HAS_TASK
+#ifndef HAS_MS5607_TASK
+#define HAS_MS5607_TASK HAS_TASK
+#endif
+
__xdata struct ao_ms5607_sample ao_ms5607_current;
+#if HAS_MS5607_TASK
static void
ao_ms5607(void)
{
+ struct ao_ms5607_sample sample;
ao_ms5607_setup();
for (;;)
{
- ao_ms5607_sample(&ao_ms5607_current);
+ ao_ms5607_sample(&sample);
ao_arch_block_interrupts();
+ ao_ms5607_current = sample;
AO_DATA_PRESENT(AO_DATA_MS5607);
AO_DATA_WAIT();
ao_arch_release_interrupts();
@@ -209,7 +215,9 @@ ao_ms5607(void)
}
__xdata struct ao_task ao_ms5607_task;
+#endif
+#if HAS_TASK
void
ao_ms5607_info(void)
{
@@ -248,6 +256,8 @@ ao_ms5607_init(void)
#if HAS_TASK
ao_cmd_register(&ao_ms5607_cmds[0]);
+#endif
+#if HAS_MS5607_TASK
ao_add_task(&ao_ms5607_task, ao_ms5607, "ms5607");
#endif
diff --git a/src/drivers/ao_rn4678.c b/src/drivers/ao_rn4678.c
new file mode 100644
index 00000000..98dc35b5
--- /dev/null
+++ b/src/drivers/ao_rn4678.c
@@ -0,0 +1,606 @@
+/*
+ * Copyright © 2017 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <ao.h>
+#include <ao_rn4678.h>
+#include <ao_exti.h>
+#include <stdarg.h>
+
+static uint8_t ao_rn_connected;
+
+#define AO_RN_DEBUG 0
+
+#if AO_RN_DEBUG
+static void ao_rn_dbg(char *format, ...) {
+ va_list a;
+ uint32_t irq = ao_arch_irqsave();
+ ao_arch_release_interrupts();
+ va_start(a, format);
+ vprintf(format, a);
+ va_end(a);
+ flush();
+ ao_arch_irqrestore(irq);
+}
+
+static char ao_rn_dir;
+
+static void
+ao_rn_log_char(char c, char dir)
+{
+ if (dir != ao_rn_dir) {
+ putchar(dir); putchar('\n');
+ ao_rn_dir = dir;
+ }
+ switch (c) {
+ case '\r':
+ putchar('\\'); putchar('r');
+ break;
+ case '\n':
+ putchar('\\'); putchar('n');
+ break;
+ default:
+ putchar(c);
+ }
+ flush();
+}
+
+static void
+ao_rn_log_out_char(char c)
+{
+ ao_rn_log_char(c, '}');
+}
+
+static void
+ao_rn_log_in_char(char c)
+{
+ ao_rn_log_char(c, '{');
+}
+
+static inline void
+ao_rn_putchar(char c)
+{
+ ao_rn_log_out_char(c);
+ ao_serial_rn_putchar(c);
+}
+
+static inline int
+_ao_rn_pollchar(void)
+{
+ int c = _ao_serial_rn_pollchar();
+
+ if (c != AO_READ_AGAIN) {
+ ao_arch_release_interrupts();
+ ao_rn_log_in_char((char) c);
+ ao_arch_block_interrupts();
+ }
+ return c;
+}
+#else
+#define ao_rn_dbg(fmt, ...)
+#define ao_rn_putchar(c) ao_serial_rn_putchar(c)
+#define _ao_rn_pollchar() _ao_serial_rn_pollchar()
+#endif
+
+/* For stdio, this skips all status messages *sigh* */
+
+#define STATUS_CHAR '%'
+
+static const char *status_strings[] = {
+ "RFCOMM_CLOSE",
+ "RFCOMM_OPEN",
+ "CONNECT",
+ "LCONNECT",
+ "DISCONN",
+ "BONDED",
+};
+
+#define NUM_STATUS_STRING (sizeof status_strings/sizeof status_strings[0])
+
+static char ao_rn_buffer[64];
+static int ao_rn_buf_cnt, ao_rn_buf_ptr;
+static int ao_rn_draining;
+static AO_TICK_TYPE ao_rn_buf_time;
+
+/* Well, this is annoying. The status strings from the RN4678 can't be
+ * disabled due to a firmware bug. So, this code finds those in the
+ * input and strips them out.
+ */
+int
+_ao_wrap_rn_pollchar(void)
+{
+ int c = AO_READ_AGAIN;
+ unsigned i;
+ int done = 0;
+
+ while (!done && !ao_rn_draining) {
+ c = _ao_serial_rn_pollchar();
+
+ if (c == AO_READ_AGAIN) {
+ if (ao_rn_buf_cnt && (ao_time() - ao_rn_buf_time) > AO_MS_TO_TICKS(1000)) {
+ ao_rn_draining = 1;
+ continue;
+ }
+ return AO_READ_AGAIN;
+ }
+
+ if (ao_rn_buf_cnt) {
+ /* buffering chars */
+
+ if (c == STATUS_CHAR) {
+ /* End of status string, drop it and carry on */
+ ao_rn_buffer[ao_rn_buf_cnt] = '\0';
+// ao_rn_dbg("discard %s\n", ao_rn_buffer);
+ ao_rn_buf_cnt = 0;
+ } else if (ao_rn_buf_cnt == sizeof(ao_rn_buffer)) {
+ /* If we filled the buffer, just give up */
+ ao_rn_draining = 1;
+ } else {
+ ao_rn_buffer[ao_rn_buf_cnt++] = c;
+ for (i = 0; i < NUM_STATUS_STRING; i++) {
+ int cmp = strlen(status_strings[i]);
+ if (cmp >= ao_rn_buf_cnt)
+ cmp = ao_rn_buf_cnt-1;
+ if (memcmp(ao_rn_buffer+1, status_strings[i], cmp) == 0)
+ break;
+ }
+ if (i == NUM_STATUS_STRING)
+ ao_rn_draining = 1;
+ }
+ } else if (c == STATUS_CHAR) {
+ ao_rn_buffer[0] = c;
+ ao_rn_buf_cnt = 1;
+ ao_rn_buf_ptr = 0;
+ ao_rn_buf_time = ao_time();
+ } else
+ done = 1;
+ }
+ if (ao_rn_draining) {
+ c = ao_rn_buffer[ao_rn_buf_ptr++] & 0xff;
+ if (ao_rn_buf_ptr == ao_rn_buf_cnt) {
+ ao_rn_buf_ptr = ao_rn_buf_cnt = 0;
+ ao_rn_draining = 0;
+ }
+ }
+ return c;
+}
+
+static void
+ao_rn_puts(char *s)
+{
+ char c;
+
+ while ((c = *s++))
+ ao_rn_putchar(c);
+}
+
+static void
+ao_rn_drain(void)
+{
+ int timeout = 0;
+
+// ao_rn_dbg("drain...\n");
+ ao_serial_rn_drain();
+ while (!timeout) {
+ ao_arch_block_interrupts();
+ while (_ao_rn_pollchar() == AO_READ_AGAIN) {
+ if (_ao_serial_rn_sleep_for(AO_MS_TO_TICKS(10))) {
+ timeout = 1;
+ break;
+ }
+ }
+ ao_arch_release_interrupts();
+ }
+// ao_rn_dbg("drain done\n");
+}
+
+static void
+ao_rn_send_cmd(char *cmd, char *param)
+{
+// ao_rn_dbg("send_cmd %s%s\n", cmd, param ? param : "");
+ ao_rn_drain();
+ ao_rn_puts(cmd);
+ if (param)
+ ao_rn_puts(param);
+ ao_rn_putchar('\r');
+}
+
+static int
+ao_rn_wait_char(AO_TICK_TYPE giveup_time)
+{
+ int c;
+
+ ao_arch_block_interrupts();
+ while ((c = _ao_rn_pollchar()) == AO_READ_AGAIN) {
+ AO_TICK_SIGNED delay = (AO_TICK_SIGNED) (giveup_time - ao_time());
+ if (delay < 0) {
+ ao_arch_release_interrupts();
+ return AO_READ_AGAIN;
+ }
+ _ao_serial_rn_sleep_for(delay);
+ }
+ ao_arch_release_interrupts();
+ return c;
+}
+
+static int
+ao_rn_wait_for(int timeout, char *match)
+{
+ char reply[AO_RN_MAX_REPLY_LEN + 1];
+ int match_len = strlen(match);
+ AO_TICK_TYPE giveup_time = ao_time() + timeout;
+ int c;
+
+// ao_rn_dbg("wait for %d, \"%s\"\n", timeout, match);
+ memset(reply, ' ', sizeof(reply));
+ while (memcmp(reply, match, match_len) != 0) {
+ c = ao_rn_wait_char(giveup_time);
+ if (c == AO_READ_AGAIN) {
+// ao_rn_dbg("\twait for timeout\n");
+ return AO_RN_TIMEOUT;
+ }
+ reply[match_len] = (char) c;
+ memmove(reply, reply+1, match_len);
+ reply[match_len] = '\0';
+// ao_rn_dbg("\tmatch now \"%s\"\n", reply);
+ }
+// ao_rn_dbg("\twait for ok\n");
+ return AO_RN_OK;
+}
+
+static int
+ao_rn_wait_line(AO_TICK_TYPE giveup_time, char *line, int len)
+{
+ char *l = line;
+
+// ao_rn_dbg("wait line\n");
+ for (;;) {
+ int c = ao_rn_wait_char(giveup_time);
+
+ /* timeout */
+ if (c == AO_READ_AGAIN) {
+// ao_rn_dbg("\twait line timeout\n");
+ return AO_RN_TIMEOUT;
+ }
+
+ /* done */
+ if (c == '\r') {
+ *l = '\0';
+// ao_rn_dbg("\twait line \"%s\"\n", line);
+ return AO_RN_OK;
+ }
+
+ if (c == '\n')
+ continue;
+
+ /* buffer overrun */
+ if (len <= 1)
+ return AO_RN_ERROR;
+
+ *l++ = (char) c;
+ len--;
+ }
+}
+
+static int
+ao_rn_wait_status(void)
+{
+ char message[AO_RN_MAX_REPLY_LEN];
+ AO_TICK_TYPE giveup_time = ao_time() + AO_RN_CMD_TIMEOUT;
+ int status;
+
+// ao_rn_dbg("wait status\n");
+ status = ao_rn_wait_line(giveup_time, message, sizeof (message));
+ if (status == AO_RN_OK)
+ if (strncmp(message, "AOK", 3) != 0)
+ status = AO_RN_ERROR;
+ return status;
+}
+
+static int
+ao_rn_set_name(void)
+{
+ char sn[8];
+ char *s = sn + 8;
+ int n;
+
+// ao_rn_dbg("set name...\n");
+ *--s = '\0';
+ n = ao_serial_number;
+ do {
+ *--s = '0' + n % 10;
+ } while (n /= 10);
+ ao_rn_send_cmd(AO_RN_SET_NAME_CMD "TeleBT-", s);
+ return ao_rn_wait_status();
+}
+
+static int
+ao_rn_get_name(char *name, int len)
+{
+// ao_rn_dbg("get name...\n");
+ ao_rn_send_cmd(AO_RN_GET_NAME_CMD, NULL);
+ return ao_rn_wait_line(ao_time() + AO_RN_CMD_TIMEOUT, name, len);
+}
+
+static void
+ao_rn_check_link(void)
+{
+ ao_rn_connected = 1 - ao_gpio_get(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN, foo);
+}
+
+static void
+ao_rn_isr(void)
+{
+ ao_rn_check_link();
+ ao_wakeup(&ao_rn_connected);
+}
+
+static void
+ao_bt_panic(int where)
+{
+ int i;
+ for (;;) {
+ for (i = 0; i < 50; i++) {
+ ao_led_toggle(AO_BT_LED);
+ ao_delay(AO_MS_TO_TICKS(10));
+ }
+ ao_led_off(AO_BT_LED);
+ ao_delay(AO_MS_TO_TICKS(500));
+ for (i = 0; i < where; i++) {
+ ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(200));
+ ao_delay(AO_MS_TO_TICKS(200));
+ }
+ }
+}
+
+static uint8_t ao_rn_stdio;
+
+/*
+ * Set the stdio echo for the bluetooth link
+ */
+void
+ao_rn_echo(uint8_t echo)
+{
+ ao_stdios[ao_rn_stdio].echo = echo;
+}
+
+static void
+ao_rn(void)
+{
+ int status = AO_RN_ERROR;
+ char name[17];
+ int i;
+
+ ao_rn_dbg("ao_rn top\n");
+
+ /* Select CMD mode after the device gets out of reset */
+ ao_gpio_set(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_CMD);
+
+ for (i = 0; i < 3; i++) {
+ ao_rn_dbg("reset device\n");
+
+ ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0);
+ ao_delay(AO_MS_TO_TICKS(100));
+
+ /* Reboot the RN4678 and wait for it to start talking */
+ ao_rn_drain();
+ ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 1);
+ status = ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, AO_RN_REBOOT_MSG);
+ if (status != AO_RN_OK) {
+ ao_rn_dbg("reboot failed\n");
+ continue;
+ }
+
+ /* After it reboots, it can take a moment before it responds
+ * to commands
+ */
+ status = ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, "CMD> ");
+
+ if (status == AO_RN_TIMEOUT) {
+ ao_rn_puts("$$$");
+ (void) ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, "CMD> ");
+ }
+
+ ao_rn_send_cmd(AO_RN_VERSION_CMD, NULL);
+ (void) ao_rn_wait_status();
+
+ /* Check to see if the name is already set and assume
+ * that the device is ready to go
+ */
+ status = ao_rn_get_name(name, sizeof (name));
+ if (status != AO_RN_OK) {
+ ao_rn_dbg("get name failed\n");
+ status = ao_rn_get_name(name, sizeof (name));
+ if (status != AO_RN_OK)
+ continue;
+ }
+
+ if (strncmp(name, "TeleBT-", 7) == 0) {
+ ao_rn_dbg("name is set\n");
+ status = AO_RN_OK;
+ break;
+ }
+
+ /* Make the command pin control command/data mode */
+ ao_rn_send_cmd(AO_RN_SET_COMMAND_PIN, NULL);
+ if (ao_rn_wait_status() != AO_RN_OK) {
+ ao_rn_dbg("set command pin failed\n");
+ continue;
+ }
+
+ ao_rn_send_cmd(AO_RN_SET_STATUS_STRING, AO_RN_STATUS_STRING_ENABLE);
+ if (ao_rn_wait_status() != AO_RN_OK) {
+ ao_rn_dbg("set status string\n");
+ continue;
+ }
+
+ /* Select 'fast' mode to ignore command sequence (more or less) */
+ ao_rn_send_cmd(AO_RN_SET_FAST_MODE, NULL);
+ if (ao_rn_wait_status() != AO_RN_OK) {
+ ao_rn_dbg("set fast mode failed\n");
+ continue;
+ }
+
+ /* Finally, set the name. Doing this last makes it possible to check
+ * if the whole sequence has been done
+ */
+ if (ao_rn_set_name() != AO_RN_OK) {
+ ao_rn_dbg("set name failed\n");
+ continue;
+ }
+
+ /* After we've configured the device, go back around and reboot it
+ * as that's how we get the new configuration to take effect
+ */
+ }
+ ao_rn_dbg("ao_rn status %d\n", status);
+
+ if (status != AO_RN_OK)
+ ao_bt_panic(4);
+
+ ao_gpio_set(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_DATA);
+
+ /* Wait for the hardware to finish sending messages, then clear the queue */
+ ao_delay(AO_MS_TO_TICKS(200));
+ ao_rn_drain();
+
+ ao_exti_enable(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN);
+
+#if AO_RN_DEBUG
+
+ /*
+ * Separate debug code when things aren't working. Just dump
+ * inbound bluetooth characters to stdout
+ */
+ for (;;) {
+ int c;
+
+ ao_arch_block_interrupts();
+ while ((c = _ao_rn_pollchar()) == AO_READ_AGAIN)
+ ao_sleep(&ao_serial_rn_rx_fifo);
+ ao_arch_release_interrupts();
+ }
+#else
+ ao_rn_stdio = ao_add_stdio(_ao_wrap_rn_pollchar,
+ ao_serial_rn_putchar,
+ NULL);
+
+ ao_rn_echo(0);
+ ao_rn_check_link();
+ /*
+ * Now just hang around and flash the blue LED when we've got
+ * a connection
+ */
+ for (;;) {
+ ao_arch_block_interrupts();
+ while (!ao_rn_connected)
+ ao_sleep(&ao_rn_connected);
+ ao_arch_release_interrupts();
+ while (ao_rn_connected) {
+ ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(20));
+ if (ao_rn_buf_cnt != 0)
+ ao_wakeup(&ao_stdin_ready);
+ ao_delay(AO_SEC_TO_TICKS(3));
+ }
+ }
+#endif
+}
+
+static struct ao_task ao_rn_task;
+
+static void
+ao_rn_factory(void)
+{
+ int i;
+ int v = 0;
+
+ /*
+ * Factory reset. Flip pin P3_1 5 times within the first five
+ * seconds of power-on
+ */
+
+ /* Select our target output pin */
+ ao_enable_output(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, foo, v);
+
+ /* Turn off the BT device using the SW_BTN pin */
+ printf("Power down BT\n"); flush();
+ ao_gpio_set(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 0);
+ ao_delay(AO_MS_TO_TICKS(1000));
+
+ /* And turn it back on */
+ printf("Power up BT\n"); flush();
+ ao_gpio_set(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 1);
+
+ /* Right after power on, poke P3_1 five times to force a
+ * factory reset
+ */
+ for (i = 0; i < 20; i++) {
+ v = 1-v;
+ ao_delay(AO_MS_TO_TICKS(50));
+ ao_gpio_set(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, foo, v);
+ ao_led_toggle(AO_BT_LED);
+ }
+
+ /* And let P3_1 float again */
+ ao_enable_input(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, AO_EXTI_MODE_PULL_NONE);
+
+ printf("Reboot BT\n"); flush();
+ ao_delay(AO_MS_TO_TICKS(100));
+ ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0);
+ ao_delay(AO_MS_TO_TICKS(100));
+ ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 1);
+}
+
+#if AO_RN_DEBUG
+static void
+ao_rn_send(void)
+{
+ int c;
+
+ while ((c = getchar()) != '~')
+ ao_rn_putchar(c);
+}
+#endif
+
+static const struct ao_cmds rn_cmds[] = {
+ { ao_rn_factory, "F\0Factory reset rn4678" },
+#if AO_RN_DEBUG
+ { ao_rn_send, "B\0Send data to rn4678. End with ~" },
+#endif
+ { 0 },
+};
+
+void
+ao_rn4678_init(void)
+{
+ (void) ao_rn_set_name;
+
+ ao_serial_rn_set_speed(AO_SERIAL_SPEED_115200);
+
+ /* Reset line */
+ ao_enable_output(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0);
+
+ /* SW_BTN */
+ ao_enable_output(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 1);
+
+ /* P3_7 command/data selector */
+ ao_enable_output(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_CMD);
+
+ ao_enable_input(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN, AO_EXTI_MODE_PULL_NONE);
+ ao_exti_setup(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN,
+ AO_EXTI_MODE_FALLING|AO_EXTI_MODE_RISING|AO_EXTI_PRIORITY_LOW,
+ ao_rn_isr);
+
+ ao_cmd_register(rn_cmds);
+ ao_add_task(&ao_rn_task, ao_rn, "bluetooth");
+}
diff --git a/src/drivers/ao_rn4678.h b/src/drivers/ao_rn4678.h
new file mode 100644
index 00000000..a4dcea38
--- /dev/null
+++ b/src/drivers/ao_rn4678.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2017 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef _AO_RN4678_H_
+#define _AO_RN4678_H_
+
+/* From the rn4678 pictail board
+
+ 1 SW_BTN 0-off/1-on 2 P2_0 1-app/0-test (boot time)
+ 3 P2_4 1-app/0-WF 4 EAN 0-app/1-WF (reboots)
+ 5 6 RTS 0
+ 7 8 P3_2 1 (NC)
+ 9 TXD 10 P3_3 1 (NC)
+ 11 RXD 12 P3_4 1 (NC)
+ 13 14
+ 15 16 P3_7 1 (NC)
+ 17 P0_5 1 (NC) 18 RST_N 1 run/0 reset
+ 19 WAKE_UP 1 run/0 btn 20
+ 21 P0_4 0 (NC) 22
+ 23 P1_5 1 (NC) 24 P3_1 1 (NC)
+ 25 CTS 0 26 3.3V
+ 27 28 GND
+
+
+ Interesting pins:
+
+ Not connected to microcontroller:
+
+ SW_BTN 0-off 1-on
+ P2_4 0 write-flash 1-app
+ WAKE_UP 0-stop 1-run
+ P2_0 0 test 1-run
+ EAN 1-WF (reboots) 0-run
+ RST_N 0 reset 1 run
+
+ Connected to microcontroller:
+
+ CTS mostly 0
+ RTS mostly 1
+
+ TXD
+ RXD
+
+ Other connections -- LDO33_O to VDD_IO
+
+*/
+
+#define AO_RN_REBOOT_MSG "REBOOT"
+
+#define AO_RN_CMD_TIMEOUT AO_MS_TO_TICKS(200)
+
+#define AO_RN_REBOOT_TIMEOUT AO_MS_TO_TICKS(2000)
+
+#define AO_RN_MAX_REPLY_LEN 10
+
+#define AO_RN_SET_NAME_CMD "SN,"
+#define AO_RN_GET_NAME_CMD "GN"
+
+#define AO_RN_SET_STATUS_STRING "so,"
+#define AO_RN_STATUS_STRING_DISABLE " "
+#define AO_RN_STATUS_STRING_ENABLE "%,%"
+
+#define AO_RN_REBOOT_CMD "R,1"
+
+#define AO_RN_VERSION_CMD "V"
+
+#define AO_RN_TIMEOUT -1
+#define AO_RN_ERROR 0
+#define AO_RN_OK 1
+
+#define AO_RN_SET_COMMAND_PIN "SX,07,0B"
+
+#define AO_RN_SET_AUTH_JUST_WORKS "SA,2"
+#define AO_RN_SET_FAST_MODE "SQ,9000"
+
+/* This pin is configured to control cmd/data mode */
+#define AO_RN_CMD_PORT AO_RN_P3_7_PORT
+#define AO_RN_CMD_PIN AO_RN_P3_7_PIN
+
+#define AO_RN_CMD_CMD 0
+#define AO_RN_CMD_DATA 1
+
+/* This pin indicates BT connection status */
+#define AO_RN_CONNECTED_PORT AO_RN_P1_5_PORT
+#define AO_RN_CONNECTED_PIN AO_RN_P1_5_PIN
+
+void
+ao_rn4678_init(void);
+
+#endif /* _AO_RN_H_ */