summaryrefslogtreecommitdiff
path: root/src/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/ao_25lc1024.c240
-rw-r--r--src/drivers/ao_25lc1024.h41
-rw-r--r--src/drivers/ao_74hc497.c55
-rw-r--r--src/drivers/ao_74hc497.h27
-rw-r--r--src/drivers/ao_at45db161d.c318
-rw-r--r--src/drivers/ao_at45db161d.h45
-rw-r--r--src/drivers/ao_btm.c388
-rw-r--r--src/drivers/ao_button.c94
-rw-r--r--src/drivers/ao_button.h24
-rw-r--r--src/drivers/ao_cc1120.c1045
-rw-r--r--src/drivers/ao_cc1120.h494
-rw-r--r--src/drivers/ao_cc1120_CC1120.h210
-rw-r--r--src/drivers/ao_companion.c143
-rw-r--r--src/drivers/ao_event.c77
-rw-r--r--src/drivers/ao_event.h41
-rw-r--r--src/drivers/ao_gps_sirf.c448
-rw-r--r--src/drivers/ao_gps_skytraq.c505
-rw-r--r--src/drivers/ao_hmc5883.c170
-rw-r--r--src/drivers/ao_hmc5883.h85
-rw-r--r--src/drivers/ao_lcd.c150
-rw-r--r--src/drivers/ao_lco_cmd.c189
-rw-r--r--src/drivers/ao_lco_cmd.h24
-rw-r--r--src/drivers/ao_lco_func.c70
-rw-r--r--src/drivers/ao_lco_func.h32
-rw-r--r--src/drivers/ao_m25.c388
-rw-r--r--src/drivers/ao_mma655x.c279
-rw-r--r--src/drivers/ao_mma655x.h85
-rw-r--r--src/drivers/ao_mpu6000.c286
-rw-r--r--src/drivers/ao_mpu6000.h161
-rw-r--r--src/drivers/ao_ms5607.c280
-rw-r--r--src/drivers/ao_ms5607.h74
-rw-r--r--src/drivers/ao_packet.c176
-rw-r--r--src/drivers/ao_packet_master.c156
-rw-r--r--src/drivers/ao_packet_slave.c66
-rw-r--r--src/drivers/ao_pad.c356
-rw-r--r--src/drivers/ao_pad.h75
-rw-r--r--src/drivers/ao_pca9922.c86
-rw-r--r--src/drivers/ao_pyro_slave.c63
-rw-r--r--src/drivers/ao_quadrature.c134
-rw-r--r--src/drivers/ao_quadrature.h32
-rw-r--r--src/drivers/ao_radio_master.c314
-rw-r--r--src/drivers/ao_radio_slave.c131
-rw-r--r--src/drivers/ao_radio_spi.h58
-rw-r--r--src/drivers/ao_science_slave.c66
-rw-r--r--src/drivers/ao_seven_segment.c220
-rw-r--r--src/drivers/ao_seven_segment.h34
46 files changed, 8435 insertions, 0 deletions
diff --git a/src/drivers/ao_25lc1024.c b/src/drivers/ao_25lc1024.c
new file mode 100644
index 00000000..fac0a430
--- /dev/null
+++ b/src/drivers/ao_25lc1024.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+#include "ao_25lc1024.h"
+
+#define EE_BLOCK_SIZE ((uint16_t) (256))
+#define EE_BLOCK_SHIFT 8
+#define EE_DEVICE_SIZE ((uint32_t) 128 * (uint32_t) 1024)
+
+/* Total bytes of available storage */
+__pdata uint32_t ao_storage_total;
+
+/* Block size - device is erased in these units. At least 256 bytes */
+__pdata uint32_t ao_storage_block;
+
+/* Byte offset of config block. Will be ao_storage_block bytes long */
+__pdata uint32_t ao_storage_config;
+
+/* Storage unit size - device reads and writes must be within blocks of this size. Usually 256 bytes. */
+__pdata uint16_t ao_storage_unit;
+
+/*
+ * Using SPI on USART 0, with P1_2 as the chip select
+ */
+
+#define EE_CS_PORT P1
+#define EE_CS P1_2
+#define EE_CS_PIN 2
+
+static __xdata uint8_t ao_ee_mutex;
+
+#define ao_ee_delay() do { \
+ _asm nop _endasm; \
+ _asm nop _endasm; \
+ _asm nop _endasm; \
+} while(0)
+
+#define ao_ee_cs_low() ao_spi_get_bit(EE_CS_PORT, EE_CS_PIN, EE_CS, AO_EE_SPI_BUS, AO_SPI_SPEED_FAST)
+
+#define ao_ee_cs_high() ao_spi_put_bit(EE_CS_PORT, EE_CS_PIN, EE_CS, AO_EE_SPI_BUS)
+
+struct ao_ee_instruction {
+ uint8_t instruction;
+ uint8_t address[3];
+} __xdata ao_ee_instruction;
+
+static void
+ao_ee_write_enable(void)
+{
+ ao_ee_cs_low();
+ ao_ee_instruction.instruction = EE_WREN;
+ ao_spi_send(&ao_ee_instruction, 1, AO_EE_SPI_BUS);
+ ao_ee_cs_high();
+}
+
+static uint8_t
+ao_ee_rdsr(void)
+{
+ ao_ee_cs_low();
+ ao_ee_instruction.instruction = EE_RDSR;
+ ao_spi_send(&ao_ee_instruction, 1, AO_EE_SPI_BUS);
+ ao_spi_recv(&ao_ee_instruction, 1, AO_EE_SPI_BUS);
+ ao_ee_cs_high();
+ return ao_ee_instruction.instruction;
+}
+
+static void
+ao_ee_wrsr(uint8_t status)
+{
+ ao_ee_cs_low();
+ ao_ee_instruction.instruction = EE_WRSR;
+ ao_ee_instruction.address[0] = status;
+ ao_spi_send(&ao_ee_instruction, 2, AO_EE_SPI_BUS);
+ ao_ee_cs_high();
+}
+
+#define EE_BLOCK_NONE 0xffff
+
+static __xdata uint8_t ao_ee_data[EE_BLOCK_SIZE];
+static __pdata uint16_t ao_ee_block = EE_BLOCK_NONE;
+static __pdata uint8_t ao_ee_block_dirty;
+
+/* Write the current block to the EEPROM */
+static void
+ao_ee_write_block(void)
+{
+ uint8_t status;
+
+ status = ao_ee_rdsr();
+ if (status & (EE_STATUS_BP0|EE_STATUS_BP1|EE_STATUS_WPEN)) {
+ status &= ~(EE_STATUS_BP0|EE_STATUS_BP1|EE_STATUS_WPEN);
+ ao_ee_wrsr(status);
+ }
+ ao_ee_write_enable();
+ ao_ee_cs_low();
+ ao_ee_instruction.instruction = EE_WRITE;
+ ao_ee_instruction.address[0] = ao_ee_block >> 8;
+ ao_ee_instruction.address[1] = ao_ee_block;
+ ao_ee_instruction.address[2] = 0;
+ ao_spi_send(&ao_ee_instruction, 4, AO_EE_SPI_BUS);
+ ao_spi_send(ao_ee_data, EE_BLOCK_SIZE, AO_EE_SPI_BUS);
+ ao_ee_cs_high();
+ for (;;) {
+ uint8_t status = ao_ee_rdsr();
+ if ((status & EE_STATUS_WIP) == 0)
+ break;
+ }
+}
+
+/* Read the current block from the EEPROM */
+static void
+ao_ee_read_block(void)
+{
+ ao_ee_cs_low();
+ ao_ee_instruction.instruction = EE_READ;
+ ao_ee_instruction.address[0] = ao_ee_block >> 8;
+ ao_ee_instruction.address[1] = ao_ee_block;
+ ao_ee_instruction.address[2] = 0;
+ ao_spi_send(&ao_ee_instruction, 4, AO_EE_SPI_BUS);
+ ao_spi_recv(ao_ee_data, EE_BLOCK_SIZE, AO_EE_SPI_BUS);
+ ao_ee_cs_high();
+}
+
+static void
+ao_ee_flush_internal(void)
+{
+ if (ao_ee_block_dirty) {
+ ao_ee_write_block();
+ ao_ee_block_dirty = 0;
+ }
+}
+
+static void
+ao_ee_fill(uint16_t block)
+{
+ if (block != ao_ee_block) {
+ ao_ee_flush_internal();
+ ao_ee_block = block;
+ ao_ee_read_block();
+ }
+}
+
+uint8_t
+ao_storage_device_write(uint32_t pos, __xdata void *buf, uint16_t len) __reentrant
+{
+ uint16_t block = (uint16_t) (pos >> EE_BLOCK_SHIFT);
+
+ /* Transfer the data */
+ ao_mutex_get(&ao_ee_mutex); {
+ if (len != EE_BLOCK_SIZE)
+ ao_ee_fill(block);
+ else {
+ ao_ee_flush_internal();
+ ao_ee_block = block;
+ }
+ ao_xmemcpy(ao_ee_data + (uint16_t) (pos & 0xff), buf, len);
+ ao_ee_block_dirty = 1;
+ } ao_mutex_put(&ao_ee_mutex);
+ return 1;
+}
+
+uint8_t
+ao_storage_device_read(uint32_t pos, __xdata void *buf, uint16_t len) __reentrant
+{
+ uint16_t block = (uint16_t) (pos >> EE_BLOCK_SHIFT);
+
+ /* Transfer the data */
+ ao_mutex_get(&ao_ee_mutex); {
+ ao_ee_fill(block);
+ ao_xmemcpy(buf, ao_ee_data + (uint16_t) (pos & 0xff), len);
+ } ao_mutex_put(&ao_ee_mutex);
+ return 1;
+}
+
+void
+ao_storage_flush(void) __reentrant
+{
+ ao_mutex_get(&ao_ee_mutex); {
+ ao_ee_flush_internal();
+ } ao_mutex_put(&ao_ee_mutex);
+}
+
+uint8_t
+ao_storage_erase(uint32_t pos) __reentrant
+{
+ ao_mutex_get(&ao_ee_mutex); {
+ ao_ee_flush_internal();
+ ao_ee_block = (uint16_t) (pos >> EE_BLOCK_SHIFT);
+ ao_xmemset(ao_ee_data, 0xff, EE_BLOCK_SIZE);
+ ao_ee_block_dirty = 1;
+ } ao_mutex_put(&ao_ee_mutex);
+ return 1;
+}
+
+static void
+ee_store(void) __reentrant
+{
+}
+
+void
+ao_storage_setup(void)
+{
+ if (ao_storage_total == 0) {
+ ao_storage_total = EE_DEVICE_SIZE;
+ ao_storage_block = EE_BLOCK_SIZE;
+ ao_storage_config = EE_DEVICE_SIZE - EE_BLOCK_SIZE;
+ ao_storage_unit = EE_BLOCK_SIZE;
+ }
+}
+
+void
+ao_storage_device_info(void) __reentrant
+{
+}
+
+/*
+ * To initialize the chip, set up the CS line and
+ * the SPI interface
+ */
+void
+ao_storage_device_init(void)
+{
+ /* set up CS */
+ ao_enable_output(EE_CS_PORT, EE_CS_PIN, EE_CS, 1);
+}
diff --git a/src/drivers/ao_25lc1024.h b/src/drivers/ao_25lc1024.h
new file mode 100644
index 00000000..44e52387
--- /dev/null
+++ b/src/drivers/ao_25lc1024.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+/* Defines for the 25LC1024 1Mbit SPI Bus Serial EEPROM */
+
+#ifndef _25LC1024_H_
+#define _25LC1024_H_
+
+#define EE_READ 0x03
+#define EE_WRITE 0x02
+#define EE_WREN 0x06
+#define EE_WRDI 0x04
+#define EE_RDSR 0x05
+#define EE_WRSR 0x01
+#define EE_PE 0x42
+#define EE_SE 0xd8
+#define EE_CE 0xc7
+#define EE_RDID 0xab
+#define EE_DPD 0xb9
+
+#define EE_STATUS_WIP (1 << 0)
+#define EE_STATUS_WEL (1 << 1)
+#define EE_STATUS_BP0 (1 << 2)
+#define EE_STATUS_BP1 (1 << 3)
+#define EE_STATUS_WPEN (1 << 7)
+
+#endif /* _25LC1024_H_ */
diff --git a/src/drivers/ao_74hc497.c b/src/drivers/ao_74hc497.c
new file mode 100644
index 00000000..4c13ee71
--- /dev/null
+++ b/src/drivers/ao_74hc497.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+/*
+ * 74HC597 driver.
+ * Reads a single byte from the shift register
+ */
+
+#include <ao.h>
+#include <ao_74hc497.h>
+
+uint8_t
+ao_74hc497_read(void)
+{
+ static __xdata state;
+ ao_spi_get_bit(AO_74HC497_CS_PORT, AO_74HC497_CS_PIN, AO_74HC497_CS, AO_74HC497_SPI_BUS, AO_SPI_SPEED_FAST);
+ ao_spi_recv(&state, 1, AO_74HC497_SPI_BUS);
+ ao_spi_put_bit(AO_74HC497_CS_PORT, AO_74HC497_CS_PIN, AO_74HC497_CS, AO_74HC497_SPI_BUS);
+ return state;
+}
+
+static void
+ao_74hc497_cmd(void)
+{
+ uint8_t v;
+
+ v = ao_74hc497_read();
+ printf ("Switches: 0x%02x\n", v);
+}
+
+static const struct ao_cmds ao_74hc497_cmds[] = {
+ { ao_74hc497_cmd, "L\0Show 74hc497" },
+ { 0, NULL }
+};
+
+void
+ao_74hc497_init(void)
+{
+ ao_enable_output(AO_74HC497_CS_PORT, AO_74HC497_CS_PIN, AO_74HC497_CS, 1);
+ ao_cmd_register(&ao_74hc497_cmds[0]);
+}
diff --git a/src/drivers/ao_74hc497.h b/src/drivers/ao_74hc497.h
new file mode 100644
index 00000000..6df7bcae
--- /dev/null
+++ b/src/drivers/ao_74hc497.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_74HC497_H_
+#define _AO_74HC497_H_
+
+uint8_t
+ao_74hc497_read(void);
+
+void
+ao_74hc497_init(void);
+
+#endif /* _AO_74HC497_H_ */
diff --git a/src/drivers/ao_at45db161d.c b/src/drivers/ao_at45db161d.c
new file mode 100644
index 00000000..e7e74153
--- /dev/null
+++ b/src/drivers/ao_at45db161d.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+#include "ao_at45db161d.h"
+
+/* Total bytes of available storage */
+__pdata uint32_t ao_storage_total;
+
+/* Block size - device is erased in these units. At least 256 bytes */
+__pdata uint32_t ao_storage_block;
+
+/* Byte offset of config block. Will be ao_storage_block bytes long */
+__pdata uint32_t ao_storage_config;
+
+/* Storage unit size - device reads and writes must be within blocks of this size. Usually 256 bytes. */
+__pdata uint16_t ao_storage_unit;
+
+#define FLASH_CS P1_1
+#define FLASH_CS_INDEX 1
+
+#define FLASH_BLOCK_SIZE_MAX 512
+
+__xdata uint8_t ao_flash_mutex;
+
+#define ao_flash_delay() do { \
+ _asm nop _endasm; \
+ _asm nop _endasm; \
+ _asm nop _endasm; \
+} while(0)
+
+#define ao_flash_cs_low() ao_spi_get_bit(FLASH_CS_PORT, FLASH_CS_PIN, FLASH_CS, AO_FLASH_SPI_BUS, AO_SPI_SPEED_FAST)
+
+#define ao_flash_cs_high() ao_spi_put_bit(FLASH_CS_PORT, FLASH_CS_PIN, FLASH_CS, AO_FLASH_SPI_BUS)
+
+struct ao_flash_instruction {
+ uint8_t instruction;
+ uint8_t address[3];
+} __xdata ao_flash_instruction;
+
+static void
+ao_flash_set_pagesize_512(void)
+{
+ ao_flash_cs_low();
+ ao_flash_instruction.instruction = FLASH_SET_CONFIG;
+ ao_flash_instruction.address[0] = FLASH_SET_512_BYTE_0;
+ ao_flash_instruction.address[1] = FLASH_SET_512_BYTE_1;
+ ao_flash_instruction.address[2] = FLASH_SET_512_BYTE_2;
+ ao_spi_send(&ao_flash_instruction, 4, AO_FLASH_SPI_BUS);
+ ao_flash_cs_high();
+}
+
+
+static uint8_t
+ao_flash_read_status(void)
+{
+ ao_flash_cs_low();
+ ao_flash_instruction.instruction = FLASH_READ_STATUS;
+ ao_spi_send(&ao_flash_instruction, 1, AO_FLASH_SPI_BUS);
+ ao_spi_recv(&ao_flash_instruction, 1, AO_FLASH_SPI_BUS);
+ ao_flash_cs_high();
+ return ao_flash_instruction.instruction;
+}
+
+#define FLASH_BLOCK_NONE 0xffff
+
+static __xdata uint8_t ao_flash_data[FLASH_BLOCK_SIZE_MAX];
+static __pdata uint16_t ao_flash_block = FLASH_BLOCK_NONE;
+static __pdata uint8_t ao_flash_block_dirty;
+static __pdata uint8_t ao_flash_write_pending;
+static __pdata uint8_t ao_flash_setup_done;
+static __pdata uint8_t ao_flash_block_shift;
+static __pdata uint16_t ao_flash_block_size;
+static __pdata uint16_t ao_flash_block_mask;
+
+void
+ao_storage_setup(void) __reentrant
+{
+ uint8_t status;
+
+ if (ao_flash_setup_done)
+ return;
+
+ ao_mutex_get(&ao_flash_mutex);
+ if (ao_flash_setup_done) {
+ ao_mutex_put(&ao_flash_mutex);
+ return;
+ }
+
+ /* On first use, check to see if the flash chip has
+ * been programmed to use 512 byte pages. If not, do so.
+ * And then, because the flash part must be power cycled
+ * for that change to take effect, panic.
+ */
+ status = ao_flash_read_status();
+
+ if (!(status & FLASH_STATUS_PAGESIZE_512)) {
+ ao_flash_set_pagesize_512();
+ ao_panic(AO_PANIC_FLASH);
+ }
+
+ switch (status & 0x3c) {
+
+ /* AT45DB321D */
+ case 0x34:
+ ao_flash_block_shift = 9;
+ ao_storage_total = ((uint32_t) 4 * (uint32_t) 1024 * (uint32_t) 1024);
+ break;
+
+ /* AT45DB161D */
+ case 0x2c:
+ ao_flash_block_shift = 9;
+ ao_storage_total = ((uint32_t) 2 * (uint32_t) 1024 * (uint32_t) 1024);
+ break;
+
+ /* AT45DB081D */
+ case 0x24:
+ ao_flash_block_shift = 8;
+ ao_storage_total = ((uint32_t) 1024 * (uint32_t) 1024);
+ break;
+
+ /* AT45DB041D */
+ case 0x1c:
+ ao_flash_block_shift = 8;
+ ao_storage_total = ((uint32_t) 512 * (uint32_t) 1024);
+ break;
+
+ /* AT45DB021D */
+ case 0x14:
+ ao_flash_block_shift = 8;
+ ao_storage_total = ((uint32_t) 256 * (uint32_t) 1024);
+ break;
+
+ /* AT45DB011D */
+ case 0x0c:
+ ao_flash_block_shift = 8;
+ ao_storage_total = ((uint32_t) 128 * (uint32_t) 1024);
+ break;
+
+ default:
+ ao_panic(AO_PANIC_FLASH);
+ }
+ ao_flash_block_size = 1 << ao_flash_block_shift;
+ ao_flash_block_mask = ao_flash_block_size - 1;
+
+ ao_storage_block = ao_flash_block_size;
+ ao_storage_config = ao_storage_total - ao_storage_block;
+ ao_storage_unit = ao_flash_block_size;
+
+ ao_flash_setup_done = 1;
+ ao_mutex_put(&ao_flash_mutex);
+}
+
+static void
+ao_flash_wait_write(void)
+{
+ if (ao_flash_write_pending) {
+ for (;;) {
+ uint8_t status = ao_flash_read_status();
+ if ((status & FLASH_STATUS_RDY))
+ break;
+ }
+ ao_flash_write_pending = 0;
+ }
+}
+
+/* Write the current block to the FLASHPROM */
+static void
+ao_flash_write_block(void)
+{
+ ao_flash_wait_write();
+ ao_flash_cs_low();
+ ao_flash_instruction.instruction = FLASH_WRITE;
+
+ /* 13/14 block bits + 9/8 byte bits (always 0) */
+ ao_flash_instruction.address[0] = ao_flash_block >> (16 - ao_flash_block_shift);
+ ao_flash_instruction.address[1] = ao_flash_block << (ao_flash_block_shift - 8);
+ ao_flash_instruction.address[2] = 0;
+ ao_spi_send(&ao_flash_instruction, 4, AO_FLASH_SPI_BUS);
+ ao_spi_send(ao_flash_data, ao_storage_block, AO_FLASH_SPI_BUS);
+ ao_flash_cs_high();
+ ao_flash_write_pending = 1;
+}
+
+/* Read the current block from the FLASHPROM */
+static void
+ao_flash_read_block(void)
+{
+ ao_flash_wait_write();
+ ao_flash_cs_low();
+ ao_flash_instruction.instruction = FLASH_READ;
+
+ /* 13/14 block bits + 9/8 byte bits (always 0) */
+ ao_flash_instruction.address[0] = ao_flash_block >> (16 - ao_flash_block_shift);
+ ao_flash_instruction.address[1] = ao_flash_block << (ao_flash_block_shift - 8);
+ ao_flash_instruction.address[2] = 0;
+ ao_spi_send(&ao_flash_instruction, 4, AO_FLASH_SPI_BUS);
+ ao_spi_recv(ao_flash_data, ao_flash_block_size, AO_FLASH_SPI_BUS);
+ ao_flash_cs_high();
+}
+
+static void
+ao_flash_flush_internal(void)
+{
+ if (ao_flash_block_dirty) {
+ ao_flash_write_block();
+ ao_flash_block_dirty = 0;
+ }
+}
+
+static void
+ao_flash_fill(uint16_t block)
+{
+ if (block != ao_flash_block) {
+ ao_flash_flush_internal();
+ ao_flash_block = block;
+ ao_flash_read_block();
+ }
+}
+
+uint8_t
+ao_storage_device_write(uint32_t pos, __xdata void *buf, uint16_t len) __reentrant
+{
+ uint16_t block = (uint16_t) (pos >> ao_flash_block_shift);
+
+ /* Transfer the data */
+ ao_mutex_get(&ao_flash_mutex); {
+ if (len != ao_flash_block_size)
+ ao_flash_fill(block);
+ else {
+ ao_flash_flush_internal();
+ ao_flash_block = block;
+ }
+ ao_xmemcpy(ao_flash_data + (uint16_t) (pos & ao_flash_block_mask),
+ buf,
+ len);
+ ao_flash_block_dirty = 1;
+ } ao_mutex_put(&ao_flash_mutex);
+ return 1;
+}
+
+uint8_t
+ao_storage_device_read(uint32_t pos, __xdata void *buf, uint16_t len) __reentrant
+{
+ uint16_t block = (uint16_t) (pos >> ao_flash_block_shift);
+
+ /* Transfer the data */
+ ao_mutex_get(&ao_flash_mutex); {
+ ao_flash_fill(block);
+ ao_xmemcpy(buf,
+ ao_flash_data + (uint16_t) (pos & ao_flash_block_mask),
+ len);
+ } ao_mutex_put(&ao_flash_mutex);
+ return 1;
+}
+
+void
+ao_storage_flush(void) __reentrant
+{
+ ao_mutex_get(&ao_flash_mutex); {
+ ao_flash_flush_internal();
+ } ao_mutex_put(&ao_flash_mutex);
+}
+
+uint8_t
+ao_storage_erase(uint32_t pos) __reentrant
+{
+ ao_mutex_get(&ao_flash_mutex); {
+ ao_flash_flush_internal();
+ ao_flash_block = (uint16_t) (pos >> ao_flash_block_shift);
+ ao_xmemset(ao_flash_data, 0xff, ao_flash_block_size);
+ ao_flash_block_dirty = 1;
+ } ao_mutex_put(&ao_flash_mutex);
+ return 1;
+}
+
+void
+ao_storage_device_info(void) __reentrant
+{
+ uint8_t status;
+
+ ao_storage_setup();
+ ao_mutex_get(&ao_flash_mutex); {
+ status = ao_flash_read_status();
+ printf ("Flash status: 0x%02x\n", status);
+ printf ("Flash block shift: %d\n", ao_flash_block_shift);
+ printf ("Flash block size: %d\n", ao_flash_block_size);
+ printf ("Flash block mask: %d\n", ao_flash_block_mask);
+ printf ("Flash device size: %ld\n", ao_storage_total);
+ } ao_mutex_put(&ao_flash_mutex);
+}
+
+/*
+ * To initialize the chip, set up the CS line and
+ * the SPI interface
+ */
+void
+ao_storage_device_init(void)
+{
+ /* set up CS */
+ FLASH_CS = 1;
+ P1DIR |= (1 << FLASH_CS_INDEX);
+ P1SEL &= ~(1 << FLASH_CS_INDEX);
+}
diff --git a/src/drivers/ao_at45db161d.h b/src/drivers/ao_at45db161d.h
new file mode 100644
index 00000000..9ee6f1b6
--- /dev/null
+++ b/src/drivers/ao_at45db161d.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2010 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+/* Defines for the Atmel AT45DB161D 16Mbit SPI Bus DataFlash® */
+
+#ifndef _AT45DB161D_H_
+#define _AT45DB161D_H_
+
+/*
+ * We reserve the last block on the device for
+ * configuration space. Writes and reads in this
+ * area return errors.
+ */
+
+
+#define FLASH_READ 0x03
+#define FLASH_WRITE 0x82
+#define FLASH_PAGE_ERASE 0x81
+#define FLASH_READ_STATUS 0xd7
+#define FLASH_SET_CONFIG 0x3d
+
+#define FLASH_SET_512_BYTE_0 0x2a
+#define FLASH_SET_512_BYTE_1 0x80
+#define FLASH_SET_512_BYTE_2 0xa6
+
+#define FLASH_STATUS_RDY (1 << 7)
+#define FLASH_STATUS_COMP (1 << 6)
+#define FLASH_STATUS_PROTECT (1 << 1)
+#define FLASH_STATUS_PAGESIZE_512 (1 << 0)
+
+#endif /* _AT45DB161D_H_ */
diff --git a/src/drivers/ao_btm.c b/src/drivers/ao_btm.c
new file mode 100644
index 00000000..f193ac8e
--- /dev/null
+++ b/src/drivers/ao_btm.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright © 2011 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+
+#ifndef ao_serial_btm_getchar
+#define ao_serial_btm_putchar ao_serial1_putchar
+#define ao_serial_btm_pollchar ao_serial1_pollchar
+#define ao_serial_btm_set_speed ao_serial1_set_speed
+#define ao_serial_btm_drain ao_serial1_drain
+#endif
+
+int8_t ao_btm_stdio;
+__xdata uint8_t ao_btm_connected;
+
+#define BT_DEBUG 0
+
+#if BT_DEBUG
+__xdata char ao_btm_buffer[256];
+int ao_btm_ptr;
+char ao_btm_dir;
+
+static void
+ao_btm_add_char(char c)
+{
+ if (ao_btm_ptr < sizeof (ao_btm_buffer))
+ ao_btm_buffer[ao_btm_ptr++] = c;
+}
+
+static void
+ao_btm_log_char(char c, char dir)
+{
+ if (dir != ao_btm_dir) {
+ ao_btm_add_char(dir);
+ ao_btm_dir = dir;
+ }
+ ao_btm_add_char(c);
+}
+
+static void
+ao_btm_log_out_char(char c)
+{
+ ao_btm_log_char(c, '>');
+}
+
+static void
+ao_btm_log_in_char(char c)
+{
+ ao_btm_log_char(c, '<');
+}
+
+/*
+ * Dump everything received from the bluetooth device during startup
+ */
+static void
+ao_btm_dump(void)
+{
+ int i;
+ char c;
+
+ for (i = 0; i < ao_btm_ptr; i++) {
+ c = ao_btm_buffer[i];
+ if (c < ' ' && c != '\n')
+ printf("\\%03o", ((int) c) & 0xff);
+ else
+ putchar(ao_btm_buffer[i]);
+ }
+ putchar('\n');
+}
+
+static void
+ao_btm_speed(void)
+{
+ ao_cmd_decimal();
+ if (ao_cmd_lex_u32 == 57600)
+ ao_serial_btm_set_speed(AO_SERIAL_SPEED_57600);
+ else if (ao_cmd_lex_u32 == 19200)
+ ao_serial_btm_set_speed(AO_SERIAL_SPEED_19200);
+ else
+ ao_cmd_status = ao_cmd_syntax_error;
+}
+
+__code struct ao_cmds ao_btm_cmds[] = {
+ { ao_btm_dump, "d\0Dump btm buffer." },
+ { ao_btm_speed, "s <19200,57600>\0Set btm serial speed." },
+ { 0, NULL },
+};
+
+#define ao_btm_log_init() ao_cmd_register(&ao_btm_cmds[0])
+
+#else
+#define ao_btm_log_in_char(c)
+#define ao_btm_log_out_char(c)
+#define ao_btm_log_init()
+#endif
+
+#define AO_BTM_MAX_REPLY 16
+__xdata char ao_btm_reply[AO_BTM_MAX_REPLY];
+
+/*
+ * Read a line of data from the serial port, truncating
+ * it after a few characters.
+ */
+
+uint8_t
+ao_btm_get_line(void)
+{
+ uint8_t ao_btm_reply_len = 0;
+ char c;
+
+ for (;;) {
+
+ while ((c = ao_serial_btm_pollchar()) != AO_READ_AGAIN) {
+ ao_btm_log_in_char(c);
+ if (ao_btm_reply_len < sizeof (ao_btm_reply))
+ ao_btm_reply[ao_btm_reply_len++] = c;
+ if (c == '\r' || c == '\n')
+ goto done;
+ }
+ for (c = 0; c < 10; c++) {
+ ao_delay(AO_MS_TO_TICKS(10));
+ if (!ao_fifo_empty(ao_serial1_rx_fifo))
+ break;
+ }
+ if (c == 10)
+ goto done;
+ }
+done:
+ for (c = ao_btm_reply_len; c < sizeof (ao_btm_reply);)
+ ao_btm_reply[c++] = '\0';
+ return ao_btm_reply_len;
+}
+
+/*
+ * Drain the serial port completely
+ */
+void
+ao_btm_drain()
+{
+ while (ao_btm_get_line())
+ ;
+}
+
+/*
+ * Set the stdio echo for the bluetooth link
+ */
+void
+ao_btm_echo(uint8_t echo)
+{
+ ao_stdios[ao_btm_stdio].echo = echo;
+}
+
+/*
+ * Delay between command charaters; the BT module
+ * can't keep up with 57600 baud
+ */
+
+void
+ao_btm_putchar(char c)
+{
+ ao_btm_log_out_char(c);
+ ao_serial_btm_putchar(c);
+ ao_delay(1);
+}
+
+/*
+ * Wait for the bluetooth device to return
+ * status from the previously executed command
+ */
+uint8_t
+ao_btm_wait_reply(void)
+{
+ for (;;) {
+ ao_btm_get_line();
+ if (!strncmp(ao_btm_reply, "OK", 2))
+ return 1;
+ if (!strncmp(ao_btm_reply, "ERROR", 5))
+ return -1;
+ if (ao_btm_reply[0] == '\0')
+ return 0;
+ }
+}
+
+void
+ao_btm_string(__code char *cmd)
+{
+ char c;
+
+ while (c = *cmd++)
+ ao_btm_putchar(c);
+}
+
+uint8_t
+ao_btm_cmd(__code char *cmd)
+{
+ ao_btm_drain();
+ ao_btm_string(cmd);
+ return ao_btm_wait_reply();
+}
+
+uint8_t
+ao_btm_set_name(void)
+{
+ char sn[8];
+ char *s = sn + 8;
+ char c;
+ int n;
+ ao_btm_string("ATN=TeleBT-");
+ *--s = '\0';
+ *--s = '\r';
+ n = ao_serial_number;
+ do {
+ *--s = '0' + n % 10;
+ } while (n /= 10);
+ while ((c = *s++))
+ ao_btm_putchar(c);
+ return ao_btm_wait_reply();
+}
+
+uint8_t
+ao_btm_try_speed(uint8_t speed)
+{
+ ao_serial_btm_set_speed(speed);
+ ao_serial_btm_drain();
+ (void) ao_btm_cmd("\rATE0\rATQ0\r");
+ if (ao_btm_cmd("AT\r") == 1)
+ return 1;
+ return 0;
+}
+
+/*
+ * A thread to initialize the bluetooth device and
+ * hang around to blink the LED when connected
+ */
+void
+ao_btm(void)
+{
+ /*
+ * Wait for the bluetooth device to boot
+ */
+ ao_delay(AO_SEC_TO_TICKS(3));
+
+ /*
+ * The first time we connect, the BTM-180 comes up at 19200 baud.
+ * After that, it will remember and come up at 57600 baud. So, see
+ * if it is already running at 57600 baud, and if that doesn't work
+ * then tell it to switch to 57600 from 19200 baud.
+ */
+ while (!ao_btm_try_speed(AO_SERIAL_SPEED_57600)) {
+ ao_delay(AO_SEC_TO_TICKS(1));
+ if (ao_btm_try_speed(AO_SERIAL_SPEED_19200))
+ ao_btm_cmd("ATL4\r");
+ ao_delay(AO_SEC_TO_TICKS(1));
+ }
+
+ /* Disable echo */
+ ao_btm_cmd("ATE0\r");
+
+ /* Enable flow control */
+ ao_btm_cmd("ATC1\r");
+
+ /* Set the reported name to something we can find on the host */
+ ao_btm_set_name();
+
+ /* Turn off status reporting */
+ ao_btm_cmd("ATQ1\r");
+
+ ao_btm_stdio = ao_add_stdio(ao_serial_btm_pollchar,
+ ao_serial_btm_putchar,
+ NULL);
+ ao_btm_echo(0);
+
+ for (;;) {
+ while (!ao_btm_connected)
+ ao_sleep(&ao_btm_connected);
+ while (ao_btm_connected) {
+ ao_led_for(AO_LED_GREEN, AO_MS_TO_TICKS(20));
+ ao_delay(AO_SEC_TO_TICKS(3));
+ }
+ }
+}
+
+__xdata struct ao_task ao_btm_task;
+
+#if BT_LINK_ON_P2
+#define BT_PICTL_ICON PICTL_P2ICON
+#define BT_PIFG P2IFG
+#define BT_PDIR P2DIR
+#define BT_PINP P2INP
+#define BT_IEN2_PIE IEN2_P2IE
+#endif
+#if BT_LINK_ON_P1
+#define BT_PICTL_ICON PICTL_P1ICON
+#define BT_PIFG P1IFG
+#define BT_PDIR P1DIR
+#define BT_PINP P1INP
+#define BT_IEN2_PIE IEN2_P1IE
+#endif
+
+void
+ao_btm_check_link() __critical
+{
+ /* Check the pin and configure the interrupt detector to wait for the
+ * pin to flip the other way
+ */
+ if (BT_LINK_PIN) {
+ ao_btm_connected = 0;
+ PICTL |= BT_PICTL_ICON;
+ } else {
+ ao_btm_connected = 1;
+ PICTL &= ~BT_PICTL_ICON;
+ }
+}
+
+void
+ao_btm_isr(void)
+#if BT_LINK_ON_P1
+ __interrupt 15
+#endif
+{
+#if BT_LINK_ON_P1
+ P1IF = 0;
+#endif
+ if (BT_PIFG & (1 << BT_LINK_PIN_INDEX)) {
+ ao_btm_check_link();
+ ao_wakeup(&ao_btm_connected);
+ }
+ BT_PIFG = 0;
+}
+
+void
+ao_btm_init (void)
+{
+ ao_serial_init();
+
+ ao_serial_btm_set_speed(AO_SERIAL_SPEED_19200);
+
+#if BT_LINK_ON_P1
+ /*
+ * Configure ser reset line
+ */
+
+ P1_6 = 0;
+ P1DIR |= (1 << 6);
+#endif
+
+ /*
+ * Configure link status line
+ */
+
+ /* Set pin to input */
+ BT_PDIR &= ~(1 << BT_LINK_PIN_INDEX);
+
+ /* Set pin to tri-state */
+ BT_PINP |= (1 << BT_LINK_PIN_INDEX);
+
+ /* Enable interrupts */
+ IEN2 |= BT_IEN2_PIE;
+
+ /* Check current pin state */
+ ao_btm_check_link();
+
+#if BT_LINK_ON_P2
+ /* Eable the pin interrupt */
+ PICTL |= PICTL_P2IEN;
+#endif
+#if BT_LINK_ON_P1
+ /* Enable pin interrupt */
+ P1IEN |= (1 << BT_LINK_PIN_INDEX);
+#endif
+
+ ao_add_task(&ao_btm_task, ao_btm, "bt");
+ ao_btm_log_init();
+}
diff --git a/src/drivers/ao_button.c b/src/drivers/ao_button.c
new file mode 100644
index 00000000..a507c909
--- /dev/null
+++ b/src/drivers/ao_button.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_button.h>
+#include <ao_exti.h>
+#if AO_EVENT
+#include <ao_event.h>
+#define ao_button_queue(b,v) ao_event_put_isr(AO_EVENT_BUTTON, b, v)
+#else
+#define ao_button_queue(b,v)
+#endif
+
+static uint8_t ao_button[AO_BUTTON_COUNT];
+static AO_TICK_TYPE ao_button_time[AO_BUTTON_COUNT];
+
+#define AO_DEBOUNCE AO_MS_TO_TICKS(20)
+
+#define port(q) AO_BUTTON_ ## q ## _PORT
+#define bit(q) AO_BUTTON_ ## q
+#define pin(q) AO_BUTTON_ ## q ## _PIN
+
+static void
+ao_button_do(uint8_t b, uint8_t v)
+{
+ /* Debounce */
+ if ((AO_TICK_SIGNED) (ao_tick_count - ao_button_time[b]) < AO_DEBOUNCE)
+ return;
+
+ /* pins are inverted */
+ v = !v;
+ if (ao_button[b] != v) {
+ ao_button[b] = v;
+ ao_button_time[b] = ao_tick_count;
+ ao_button_queue(b, v);
+ ao_wakeup(&ao_button[b]);
+ }
+}
+
+#define ao_button_update(b) ao_button_do(b, ao_gpio_get(port(b), bit(b), pin(b)))
+
+static void
+ao_button_isr(void)
+{
+#if AO_BUTTON_COUNT > 0
+ ao_button_update(0);
+#endif
+#if AO_BUTTON_COUNT > 1
+ ao_button_update(1);
+#endif
+#if AO_BUTTON_COUNT > 2
+ ao_button_update(2);
+#endif
+#if AO_BUTTON_COUNT > 3
+ ao_button_update(3);
+#endif
+#if AO_BUTTON_COUNT > 4
+ ao_button_update(4);
+#endif
+}
+
+#define init(b) do { \
+ ao_enable_port(port(b)); \
+ \
+ ao_exti_setup(port(b), bit(b), \
+ AO_BUTTON_MODE|AO_EXTI_MODE_FALLING|AO_EXTI_MODE_RISING|AO_EXTI_PRIORITY_MED, \
+ ao_button_isr); \
+ ao_exti_enable(port(b), bit(b)); \
+ } while (0)
+
+void
+ao_button_init(void)
+{
+#if AO_BUTTON_COUNT > 0
+ init(0);
+#endif
+#if AO_BUTTON_COUNT > 1
+ init(1);
+#endif
+}
diff --git a/src/drivers/ao_button.h b/src/drivers/ao_button.h
new file mode 100644
index 00000000..ce349d65
--- /dev/null
+++ b/src/drivers/ao_button.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_BUTTON_H_
+#define _AO_BUTTON_H_
+
+void
+ao_button_init(void);
+
+#endif /* _AO_BUTTON_H_ */
diff --git a/src/drivers/ao_cc1120.c b/src/drivers/ao_cc1120.c
new file mode 100644
index 00000000..2f9c296f
--- /dev/null
+++ b/src/drivers/ao_cc1120.c
@@ -0,0 +1,1045 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_cc1120.h>
+#include <ao_exti.h>
+#include <ao_fec.h>
+#include <ao_packet.h>
+
+uint8_t ao_radio_wake;
+uint8_t ao_radio_mutex;
+uint8_t ao_radio_abort;
+uint8_t ao_radio_in_recv;
+
+#define CC1120_DEBUG AO_FEC_DEBUG
+#define CC1120_TRACE 0
+
+extern const uint32_t ao_radio_cal;
+
+#define FOSC 32000000
+
+#define ao_radio_select() ao_spi_get_mask(AO_CC1120_SPI_CS_PORT,(1 << AO_CC1120_SPI_CS_PIN),AO_CC1120_SPI_BUS,AO_SPI_SPEED_1MHz)
+#define ao_radio_deselect() ao_spi_put_mask(AO_CC1120_SPI_CS_PORT,(1 << AO_CC1120_SPI_CS_PIN),AO_CC1120_SPI_BUS)
+#define ao_radio_spi_send(d,l) ao_spi_send((d), (l), AO_CC1120_SPI_BUS)
+#define ao_radio_spi_send_fixed(d,l) ao_spi_send_fixed((d), (l), AO_CC1120_SPI_BUS)
+#define ao_radio_spi_recv(d,l) ao_spi_recv((d), (l), AO_CC1120_SPI_BUS)
+#define ao_radio_duplex(o,i,l) ao_spi_duplex((o), (i), (l), AO_CC1120_SPI_BUS)
+
+static uint8_t
+ao_radio_reg_read(uint16_t addr)
+{
+ uint8_t data[2];
+ uint8_t d;
+
+#if CC1120_TRACE
+ printf("\t\tao_radio_reg_read (%04x): ", addr); flush();
+#endif
+ if (CC1120_IS_EXTENDED(addr)) {
+ data[0] = ((1 << CC1120_READ) |
+ (0 << CC1120_BURST) |
+ CC1120_EXTENDED);
+ data[1] = addr;
+ d = 2;
+ } else {
+ data[0] = ((1 << CC1120_READ) |
+ (0 << CC1120_BURST) |
+ addr);
+ d = 1;
+ }
+ ao_radio_select();
+ ao_radio_spi_send(data, d);
+ ao_radio_spi_recv(data, 1);
+ ao_radio_deselect();
+#if CC1120_TRACE
+ printf (" %02x\n", data[0]);
+#endif
+ return data[0];
+}
+
+static void
+ao_radio_reg_write(uint16_t addr, uint8_t value)
+{
+ uint8_t data[3];
+ uint8_t d;
+
+#if CC1120_TRACE
+ printf("\t\tao_radio_reg_write (%04x): %02x\n", addr, value);
+#endif
+ if (CC1120_IS_EXTENDED(addr)) {
+ data[0] = ((0 << CC1120_READ) |
+ (0 << CC1120_BURST) |
+ CC1120_EXTENDED);
+ data[1] = addr;
+ d = 2;
+ } else {
+ data[0] = ((0 << CC1120_READ) |
+ (0 << CC1120_BURST) |
+ addr);
+ d = 1;
+ }
+ data[d] = value;
+ ao_radio_select();
+ ao_radio_spi_send(data, d+1);
+ ao_radio_deselect();
+}
+
+static void
+ao_radio_burst_read_start (uint16_t addr)
+{
+ uint8_t data[2];
+ uint8_t d;
+
+ if (CC1120_IS_EXTENDED(addr)) {
+ data[0] = ((1 << CC1120_READ) |
+ (1 << CC1120_BURST) |
+ CC1120_EXTENDED);
+ data[1] = addr;
+ d = 2;
+ } else {
+ data[0] = ((1 << CC1120_READ) |
+ (1 << CC1120_BURST) |
+ addr);
+ d = 1;
+ }
+ ao_radio_select();
+ ao_radio_spi_send(data, d);
+}
+
+static void
+ao_radio_burst_read_stop (void)
+{
+ ao_radio_deselect();
+}
+
+
+static uint8_t
+ao_radio_strobe(uint8_t addr)
+{
+ uint8_t in;
+
+#if CC1120_TRACE
+ printf("\t\tao_radio_strobe (%02x): ", addr); flush();
+#endif
+ ao_radio_select();
+ ao_radio_duplex(&addr, &in, 1);
+ ao_radio_deselect();
+#if CC1120_TRACE
+ printf("%02x\n", in); flush();
+#endif
+ return in;
+}
+
+static uint8_t
+ao_radio_fifo_read(uint8_t *data, uint8_t len)
+{
+ uint8_t addr = ((1 << CC1120_READ) |
+ (1 << CC1120_BURST) |
+ CC1120_FIFO);
+ uint8_t status;
+
+ ao_radio_select();
+ ao_radio_duplex(&addr, &status, 1);
+ ao_radio_spi_recv(data, len);
+ ao_radio_deselect();
+ return status;
+}
+
+static uint8_t
+ao_radio_fifo_write_start(void)
+{
+ uint8_t addr = ((0 << CC1120_READ) |
+ (1 << CC1120_BURST) |
+ CC1120_FIFO);
+ uint8_t status;
+
+ ao_radio_select();
+ ao_radio_duplex(&addr, &status, 1);
+ return status;
+}
+
+static inline uint8_t ao_radio_fifo_write_stop(uint8_t status) {
+ ao_radio_deselect();
+ return status;
+}
+
+static uint8_t
+ao_radio_fifo_write(uint8_t *data, uint8_t len)
+{
+ uint8_t status = ao_radio_fifo_write_start();
+ ao_radio_spi_send(data, len);
+ return ao_radio_fifo_write_stop(status);
+}
+
+static uint8_t
+ao_radio_fifo_write_fixed(uint8_t data, uint8_t len)
+{
+ uint8_t status = ao_radio_fifo_write_start();
+ ao_radio_spi_send_fixed(data, len);
+ return ao_radio_fifo_write_stop(status);
+}
+
+static uint8_t
+ao_radio_tx_fifo_space(void)
+{
+ return CC1120_FIFO_SIZE - ao_radio_reg_read(CC1120_NUM_TXBYTES);
+}
+
+static uint8_t
+ao_radio_status(void)
+{
+ return ao_radio_strobe (CC1120_SNOP);
+}
+
+void
+ao_radio_recv_abort(void)
+{
+ ao_radio_abort = 1;
+ ao_wakeup(&ao_radio_wake);
+}
+
+#define ao_radio_rdf_value 0x55
+
+static uint8_t
+ao_radio_marc_status(void)
+{
+ return ao_radio_reg_read(CC1120_MARC_STATUS1);
+}
+
+static void
+ao_radio_tx_isr(void)
+{
+ ao_exti_disable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN);
+ ao_radio_wake = 1;
+ ao_wakeup(&ao_radio_wake);
+}
+
+static void
+ao_radio_start_tx(void)
+{
+ ao_exti_set_callback(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN, ao_radio_tx_isr);
+ ao_exti_enable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN);
+ ao_radio_strobe(CC1120_STX);
+}
+
+static void
+ao_radio_idle(void)
+{
+ for (;;) {
+ uint8_t state = ao_radio_strobe(CC1120_SIDLE);
+ if ((state >> CC1120_STATUS_STATE) == CC1120_STATUS_STATE_IDLE)
+ break;
+ }
+}
+
+/*
+ * Packet deviation is 20.5kHz
+ *
+ * fdev = fosc >> 24 * (256 + dev_m) << dev_e
+ *
+ * 32e6Hz / (2 ** 24) * (256 + 80) * (2 ** 5) = 20508Hz
+ */
+
+#define PACKET_DEV_E 5
+#define PACKET_DEV_M 80
+
+/*
+ * For our packet data, set the symbol rate to 38360 Baud
+ *
+ * (2**20 + DATARATE_M) * 2 ** DATARATE_E
+ * Rdata = -------------------------------------- * fosc
+ * 2 ** 39
+ *
+ *
+ * DATARATE_M = 239914
+ * DATARATE_E = 9
+ */
+#define PACKET_DRATE_E 9
+#define PACKET_DRATE_M 239914
+
+static const uint16_t packet_setup[] = {
+ CC1120_DEVIATION_M, PACKET_DEV_M,
+ CC1120_MODCFG_DEV_E, ((CC1120_MODCFG_DEV_E_MODEM_MODE_NORMAL << CC1120_MODCFG_DEV_E_MODEM_MODE) |
+ (CC1120_MODCFG_DEV_E_MOD_FORMAT_2_GFSK << CC1120_MODCFG_DEV_E_MOD_FORMAT) |
+ (PACKET_DEV_E << CC1120_MODCFG_DEV_E_DEV_E)),
+ CC1120_DRATE2, ((PACKET_DRATE_E << CC1120_DRATE2_DATARATE_E) |
+ (((PACKET_DRATE_M >> 16) & CC1120_DRATE2_DATARATE_M_19_16_MASK) << CC1120_DRATE2_DATARATE_M_19_16)),
+ CC1120_DRATE1, ((PACKET_DRATE_M >> 8) & 0xff),
+ CC1120_DRATE0, ((PACKET_DRATE_M >> 0) & 0xff),
+ CC1120_PKT_CFG2, ((CC1120_PKT_CFG2_CCA_MODE_ALWAYS_CLEAR << CC1120_PKT_CFG2_CCA_MODE) |
+ (CC1120_PKT_CFG2_PKT_FORMAT_NORMAL << CC1120_PKT_CFG2_PKT_FORMAT)),
+ CC1120_PKT_CFG1, ((0 << CC1120_PKT_CFG1_WHITE_DATA) |
+ (CC1120_PKT_CFG1_ADDR_CHECK_CFG_NONE << CC1120_PKT_CFG1_ADDR_CHECK_CFG) |
+ (CC1120_PKT_CFG1_CRC_CFG_DISABLED << CC1120_PKT_CFG1_CRC_CFG) |
+ (0 << CC1120_PKT_CFG1_APPEND_STATUS)),
+ CC1120_PKT_CFG0, ((0 << CC1120_PKT_CFG0_RESERVED7) |
+ (CC1120_PKT_CFG0_LENGTH_CONFIG_FIXED << CC1120_PKT_CFG0_LENGTH_CONFIG) |
+ (0 << CC1120_PKT_CFG0_PKG_BIT_LEN) |
+ (0 << CC1120_PKT_CFG0_UART_MODE_EN) |
+ (0 << CC1120_PKT_CFG0_UART_SWAP_EN)),
+};
+
+static const uint16_t packet_tx_setup[] = {
+ CC1120_PKT_CFG2, ((CC1120_PKT_CFG2_CCA_MODE_ALWAYS_CLEAR << CC1120_PKT_CFG2_CCA_MODE) |
+ (CC1120_PKT_CFG2_PKT_FORMAT_NORMAL << CC1120_PKT_CFG2_PKT_FORMAT)),
+ CC1120_IOCFG2, CC1120_IOCFG_GPIO_CFG_RX0TX1_CFG,
+};
+
+static const uint16_t packet_rx_setup[] = {
+ CC1120_PKT_CFG2, ((CC1120_PKT_CFG2_CCA_MODE_ALWAYS_CLEAR << CC1120_PKT_CFG2_CCA_MODE) |
+ (CC1120_PKT_CFG2_PKT_FORMAT_SYNCHRONOUS_SERIAL << CC1120_PKT_CFG2_PKT_FORMAT)),
+ CC1120_IOCFG2, CC1120_IOCFG_GPIO_CFG_CLKEN_SOFT,
+};
+
+/*
+ * RDF deviation is 5kHz
+ *
+ * fdev = fosc >> 24 * (256 + dev_m) << dev_e
+ *
+ * 32e6Hz / (2 ** 24) * (256 + 71) * (2 ** 3) = 4989
+ */
+
+#define RDF_DEV_E 3
+#define RDF_DEV_M 71
+#define RDF_PACKET_LEN 50
+
+/*
+ * For our RDF beacon, set the symbol rate to 2kBaud (for a 1kHz tone)
+ *
+ * (2**20 - DATARATE_M) * 2 ** DATARATE_E
+ * Rdata = -------------------------------------- * fosc
+ * 2 ** 39
+ *
+ * DATARATE_M = 511705
+ * DATARATE_E = 6
+ *
+ * To make the tone last for 200ms, we need 2000 * .2 = 400 bits or 50 bytes
+ */
+#define RDF_DRATE_E 5
+#define RDF_DRATE_M 25166
+#define RDF_PACKET_LEN 50
+
+static const uint16_t rdf_setup[] = {
+ CC1120_DEVIATION_M, RDF_DEV_M,
+ CC1120_MODCFG_DEV_E, ((CC1120_MODCFG_DEV_E_MODEM_MODE_NORMAL << CC1120_MODCFG_DEV_E_MODEM_MODE) |
+ (CC1120_MODCFG_DEV_E_MOD_FORMAT_2_GFSK << CC1120_MODCFG_DEV_E_MOD_FORMAT) |
+ (RDF_DEV_E << CC1120_MODCFG_DEV_E_DEV_E)),
+ CC1120_DRATE2, ((RDF_DRATE_E << CC1120_DRATE2_DATARATE_E) |
+ (((RDF_DRATE_M >> 16) & CC1120_DRATE2_DATARATE_M_19_16_MASK) << CC1120_DRATE2_DATARATE_M_19_16)),
+ CC1120_DRATE1, ((RDF_DRATE_M >> 8) & 0xff),
+ CC1120_DRATE0, ((RDF_DRATE_M >> 0) & 0xff),
+ CC1120_PKT_CFG2, ((CC1120_PKT_CFG2_CCA_MODE_ALWAYS_CLEAR << CC1120_PKT_CFG2_CCA_MODE) |
+ (CC1120_PKT_CFG2_PKT_FORMAT_NORMAL << CC1120_PKT_CFG2_PKT_FORMAT)),
+ CC1120_PKT_CFG1, ((0 << CC1120_PKT_CFG1_WHITE_DATA) |
+ (CC1120_PKT_CFG1_ADDR_CHECK_CFG_NONE << CC1120_PKT_CFG1_ADDR_CHECK_CFG) |
+ (CC1120_PKT_CFG1_CRC_CFG_DISABLED << CC1120_PKT_CFG1_CRC_CFG) |
+ (0 << CC1120_PKT_CFG1_APPEND_STATUS)),
+ CC1120_PKT_CFG0, ((0 << CC1120_PKT_CFG0_RESERVED7) |
+ (CC1120_PKT_CFG0_LENGTH_CONFIG_FIXED << CC1120_PKT_CFG0_LENGTH_CONFIG) |
+ (0 << CC1120_PKT_CFG0_PKG_BIT_LEN) |
+ (0 << CC1120_PKT_CFG0_UART_MODE_EN) |
+ (0 << CC1120_PKT_CFG0_UART_SWAP_EN)),
+};
+
+static uint8_t ao_radio_mode;
+
+#define AO_RADIO_MODE_BITS_PACKET 1
+#define AO_RADIO_MODE_BITS_PACKET_TX 2
+#define AO_RADIO_MODE_BITS_TX_BUF 4
+#define AO_RADIO_MODE_BITS_TX_FINISH 8
+#define AO_RADIO_MODE_BITS_PACKET_RX 16
+#define AO_RADIO_MODE_BITS_RDF 32
+
+#define AO_RADIO_MODE_NONE 0
+#define AO_RADIO_MODE_PACKET_TX_BUF (AO_RADIO_MODE_BITS_PACKET | AO_RADIO_MODE_BITS_PACKET_TX | AO_RADIO_MODE_BITS_TX_BUF)
+#define AO_RADIO_MODE_PACKET_TX_FINISH (AO_RADIO_MODE_BITS_PACKET | AO_RADIO_MODE_BITS_PACKET_TX | AO_RADIO_MODE_BITS_TX_FINISH)
+#define AO_RADIO_MODE_PACKET_RX (AO_RADIO_MODE_BITS_PACKET | AO_RADIO_MODE_BITS_PACKET_RX)
+#define AO_RADIO_MODE_RDF (AO_RADIO_MODE_BITS_RDF | AO_RADIO_MODE_BITS_TX_FINISH)
+
+static void
+ao_radio_set_mode(uint8_t new_mode)
+{
+ uint8_t changes;
+ int i;
+
+ if (new_mode == ao_radio_mode)
+ return;
+
+ changes = new_mode & (~ao_radio_mode);
+ if (changes & AO_RADIO_MODE_BITS_PACKET)
+ for (i = 0; i < sizeof (packet_setup) / sizeof (packet_setup[0]); i += 2)
+ ao_radio_reg_write(packet_setup[i], packet_setup[i+1]);
+
+ if (changes & AO_RADIO_MODE_BITS_PACKET_TX)
+ for (i = 0; i < sizeof (packet_tx_setup) / sizeof (packet_tx_setup[0]); i += 2)
+ ao_radio_reg_write(packet_tx_setup[i], packet_tx_setup[i+1]);
+
+ if (changes & AO_RADIO_MODE_BITS_TX_BUF)
+ ao_radio_reg_write(CC1120_IOCFG2, CC1120_IOCFG_GPIO_CFG_TXFIFO_THR);
+
+ if (changes & AO_RADIO_MODE_BITS_TX_FINISH)
+ ao_radio_reg_write(CC1120_IOCFG2, CC1120_IOCFG_GPIO_CFG_RX0TX1_CFG);
+
+ if (changes & AO_RADIO_MODE_BITS_PACKET_RX)
+ for (i = 0; i < sizeof (packet_rx_setup) / sizeof (packet_rx_setup[0]); i += 2)
+ ao_radio_reg_write(packet_rx_setup[i], packet_rx_setup[i+1]);
+
+ if (changes & AO_RADIO_MODE_BITS_RDF)
+ for (i = 0; i < sizeof (rdf_setup) / sizeof (rdf_setup[0]); i += 2)
+ ao_radio_reg_write(rdf_setup[i], rdf_setup[i+1]);
+ ao_radio_mode = new_mode;
+}
+
+static const uint16_t radio_setup[] = {
+#include "ao_cc1120_CC1120.h"
+};
+
+static uint8_t ao_radio_configured = 0;
+
+static void
+ao_radio_setup(void)
+{
+ int i;
+
+ ao_radio_strobe(CC1120_SRES);
+
+ for (i = 0; i < sizeof (radio_setup) / sizeof (radio_setup[0]); i += 2)
+ ao_radio_reg_write(radio_setup[i], radio_setup[i+1]);
+
+ ao_radio_mode = 0;
+
+ ao_config_get();
+
+ ao_radio_configured = 1;
+}
+
+static void
+ao_radio_get(uint8_t len)
+{
+ static uint32_t last_radio_setting;
+ static uint8_t last_len;
+
+ ao_mutex_get(&ao_radio_mutex);
+ if (!ao_radio_configured)
+ ao_radio_setup();
+ if (ao_config.radio_setting != last_radio_setting) {
+ ao_radio_reg_write(CC1120_FREQ2, ao_config.radio_setting >> 16);
+ ao_radio_reg_write(CC1120_FREQ1, ao_config.radio_setting >> 8);
+ ao_radio_reg_write(CC1120_FREQ0, ao_config.radio_setting);
+ last_radio_setting = ao_config.radio_setting;
+ }
+ if (len != last_len) {
+ ao_radio_reg_write(CC1120_PKT_LEN, len);
+ last_len = len;
+ }
+}
+
+#define ao_radio_put() ao_mutex_put(&ao_radio_mutex)
+
+static void
+ao_rdf_start(uint8_t len)
+{
+ ao_radio_abort = 0;
+ ao_radio_get(len);
+
+ ao_radio_set_mode(AO_RADIO_MODE_RDF);
+ ao_radio_wake = 0;
+
+}
+
+static void
+ao_rdf_run(void)
+{
+ ao_radio_start_tx();
+
+ cli();
+ while (!ao_radio_wake && !ao_radio_abort)
+ ao_sleep(&ao_radio_wake);
+ sei();
+ if (!ao_radio_wake)
+ ao_radio_idle();
+ ao_radio_put();
+}
+
+void
+ao_radio_rdf(void)
+{
+ ao_rdf_start(AO_RADIO_RDF_LEN);
+
+ ao_radio_fifo_write_fixed(ao_radio_rdf_value, AO_RADIO_RDF_LEN);
+
+ ao_rdf_run();
+}
+
+void
+ao_radio_continuity(uint8_t c)
+{
+ uint8_t i;
+ uint8_t status;
+
+ ao_rdf_start(AO_RADIO_CONT_TOTAL_LEN);
+
+ status = ao_radio_fifo_write_start();
+ for (i = 0; i < 3; i++) {
+ ao_radio_spi_send_fixed(0x00, AO_RADIO_CONT_PAUSE_LEN);
+ if (i < c)
+ ao_radio_spi_send_fixed(ao_radio_rdf_value, AO_RADIO_CONT_TONE_LEN);
+ else
+ ao_radio_spi_send_fixed(0x00, AO_RADIO_CONT_TONE_LEN);
+ }
+ ao_radio_spi_send_fixed(0x00, AO_RADIO_CONT_PAUSE_LEN);
+ status = ao_radio_fifo_write_stop(status);
+ (void) status;
+ ao_rdf_run();
+}
+
+void
+ao_radio_rdf_abort(void)
+{
+ ao_radio_abort = 1;
+ ao_wakeup(&ao_radio_wake);
+}
+
+static void
+ao_radio_test_cmd(void)
+{
+ uint8_t mode = 2;
+ uint8_t radio_on;
+ ao_cmd_white();
+ if (ao_cmd_lex_c != '\n') {
+ ao_cmd_decimal();
+ mode = (uint8_t) ao_cmd_lex_u32;
+ }
+ mode++;
+ if ((mode & 2) && !radio_on) {
+#if HAS_MONITOR
+ ao_monitor_disable();
+#endif
+#if PACKET_HAS_SLAVE
+ ao_packet_slave_stop();
+#endif
+ ao_radio_get(0xff);
+ ao_radio_strobe(CC1120_STX);
+#if CC1120_TRACE
+ { int t;
+ for (t = 0; t < 10; t++) {
+ printf ("status: %02x\n", ao_radio_status());
+ ao_delay(AO_MS_TO_TICKS(100));
+ }
+ }
+#endif
+ radio_on = 1;
+ }
+ if (mode == 3) {
+ printf ("Hit a character to stop..."); flush();
+ getchar();
+ putchar('\n');
+ }
+ if ((mode & 1) && radio_on) {
+ ao_radio_idle();
+ ao_radio_put();
+ radio_on = 0;
+#if HAS_MONITOR
+ ao_monitor_enable();
+#endif
+ }
+}
+
+void
+ao_radio_send(const void *d, uint8_t size)
+{
+ uint8_t marc_status;
+ static uint8_t encode[256];
+ uint8_t *e = encode;
+ uint8_t encode_len;
+ uint8_t this_len;
+ uint8_t started = 0;
+ uint8_t fifo_space;
+
+ encode_len = ao_fec_encode(d, size, encode);
+
+ ao_radio_get(encode_len);
+
+ started = 0;
+ fifo_space = CC1120_FIFO_SIZE;
+ while (encode_len) {
+ this_len = encode_len;
+
+ if (this_len > fifo_space) {
+ this_len = fifo_space;
+ ao_radio_set_mode(AO_RADIO_MODE_PACKET_TX_BUF);
+ } else {
+ ao_radio_set_mode(AO_RADIO_MODE_PACKET_TX_FINISH);
+ }
+
+ ao_radio_fifo_write(e, this_len);
+ e += this_len;
+ encode_len -= this_len;
+
+ if (!started) {
+ ao_radio_start_tx();
+ started = 1;
+ } else {
+ ao_exti_enable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN);
+ }
+
+ do {
+ ao_radio_wake = 0;
+ cli();
+ while (!ao_radio_wake)
+ ao_sleep(&ao_radio_wake);
+ sei();
+ if (!encode_len)
+ break;
+ fifo_space = ao_radio_tx_fifo_space();
+ } while (!fifo_space);
+ }
+ ao_radio_put();
+}
+
+#define AO_RADIO_MAX_RECV 90
+
+static uint8_t rx_data[(AO_RADIO_MAX_RECV + 4) * 2 * 8];
+static uint16_t rx_data_count;
+static uint16_t rx_data_consumed;
+static uint16_t rx_data_cur;
+static uint8_t rx_ignore;
+static uint8_t rx_waiting;
+
+#if AO_PROFILE
+static uint32_t rx_start_tick, rx_packet_tick, rx_done_tick, rx_last_done_tick;
+
+uint32_t ao_rx_start_tick, ao_rx_packet_tick, ao_rx_done_tick, ao_rx_last_done_tick;
+
+#include <ao_profile.h>
+#endif
+
+static void
+ao_radio_rx_isr(void)
+{
+ uint8_t d;
+
+ d = stm_spi2.dr;
+ stm_spi2.dr = 0;
+ if (rx_ignore == 0) {
+ if (rx_data_cur >= rx_data_count)
+ ao_exti_disable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN);
+ else
+ rx_data[rx_data_cur++] = d;
+ if (rx_waiting && rx_data_cur - rx_data_consumed >= AO_FEC_DECODE_BLOCK) {
+#if AO_PROFILE
+ if (!rx_packet_tick)
+ rx_packet_tick = ao_profile_tick();
+ if (rx_data_cur < rx_data_count)
+ return;
+#endif
+ rx_waiting = 0;
+ ao_wakeup(&ao_radio_wake);
+ }
+ } else {
+ --rx_ignore;
+ }
+}
+
+static uint16_t
+ao_radio_rx_wait(void)
+{
+ cli();
+ rx_waiting = 1;
+ while (rx_data_cur - rx_data_consumed < AO_FEC_DECODE_BLOCK &&
+ !ao_radio_abort) {
+ ao_sleep(&ao_radio_wake);
+ }
+ rx_waiting = 0;
+ sei();
+ if (ao_radio_abort)
+ return 0;
+ rx_data_consumed += AO_FEC_DECODE_BLOCK;
+#if AO_PROFILE
+ return rx_data_cur - rx_data_consumed;
+#endif
+ return AO_FEC_DECODE_BLOCK;
+}
+
+uint8_t
+ao_radio_recv(__xdata void *d, uint8_t size)
+{
+ uint8_t len;
+ uint16_t i;
+ uint8_t rssi;
+ uint8_t ret;
+ static int been_here = 0;
+
+ size -= 2; /* status bytes */
+ if (size > AO_RADIO_MAX_RECV) {
+ ao_delay(AO_SEC_TO_TICKS(1));
+ return 0;
+ }
+#if AO_PROFILE
+ rx_start_tick = ao_profile_tick();
+ rx_packet_tick = 0;
+#endif
+ len = size + 2; /* CRC bytes */
+ len += 1 + ~(len & 1); /* 1 or two pad bytes */
+ len *= 2; /* 1/2 rate convolution */
+ rx_data_count = len * 8; /* bytes to bits */
+ rx_data_cur = 0;
+ rx_data_consumed = 0;
+ rx_ignore = 2;
+
+ ao_radio_abort = 0;
+ ao_radio_in_recv = 1;
+ /* configure interrupt pin */
+ ao_radio_get(len);
+ ao_radio_set_mode(AO_RADIO_MODE_PACKET_RX);
+
+ ao_radio_wake = 0;
+
+ stm_spi2.cr2 = 0;
+
+ /* clear any RXNE */
+ (void) stm_spi2.dr;
+
+ ao_exti_set_callback(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN, ao_radio_rx_isr);
+ ao_exti_enable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN);
+
+ ao_radio_strobe(CC1120_SRX);
+
+ ao_radio_burst_read_start(CC1120_SOFT_RX_DATA_OUT);
+
+ ret = ao_fec_decode(rx_data, rx_data_count, d, size + 2, ao_radio_rx_wait);
+
+ ao_radio_burst_read_stop();
+
+ ao_radio_strobe(CC1120_SIDLE);
+
+ /* Convert from 'real' rssi to cc1111-style values */
+
+ rssi = AO_RADIO_FROM_RSSI(ao_radio_reg_read(CC1120_RSSI1));
+
+ ao_radio_put();
+
+ /* Store the received RSSI value; the crc-OK byte is already done */
+
+ ((uint8_t *) d)[size] = (uint8_t) rssi;
+
+ ao_radio_in_recv = 0;
+
+ if (ao_radio_abort)
+ ao_delay(1);
+
+#if AO_PROFILE
+ rx_last_done_tick = rx_done_tick;
+ rx_done_tick = ao_profile_tick();
+
+ ao_rx_start_tick = rx_start_tick;
+ ao_rx_packet_tick = rx_packet_tick;
+ ao_rx_done_tick = rx_done_tick;
+ ao_rx_last_done_tick = rx_last_done_tick;
+#endif
+
+ return ret;
+}
+
+
+#if CC1120_DEBUG
+static char *cc1120_state_name[] = {
+ [CC1120_STATUS_STATE_IDLE] = "IDLE",
+ [CC1120_STATUS_STATE_RX] = "RX",
+ [CC1120_STATUS_STATE_TX] = "TX",
+ [CC1120_STATUS_STATE_FSTXON] = "FSTXON",
+ [CC1120_STATUS_STATE_CALIBRATE] = "CALIBRATE",
+ [CC1120_STATUS_STATE_SETTLING] = "SETTLING",
+ [CC1120_STATUS_STATE_RX_FIFO_ERROR] = "RX_FIFO_ERROR",
+ [CC1120_STATUS_STATE_TX_FIFO_ERROR] = "TX_FIFO_ERROR",
+};
+
+struct ao_cc1120_reg {
+ uint16_t addr;
+ char *name;
+};
+
+const static struct ao_cc1120_reg ao_cc1120_reg[] = {
+ { .addr = CC1120_IOCFG3, .name = "IOCFG3" },
+ { .addr = CC1120_IOCFG2, .name = "IOCFG2" },
+ { .addr = CC1120_IOCFG1, .name = "IOCFG1" },
+ { .addr = CC1120_IOCFG0, .name = "IOCFG0" },
+ { .addr = CC1120_SYNC3, .name = "SYNC3" },
+ { .addr = CC1120_SYNC2, .name = "SYNC2" },
+ { .addr = CC1120_SYNC1, .name = "SYNC1" },
+ { .addr = CC1120_SYNC0, .name = "SYNC0" },
+ { .addr = CC1120_SYNC_CFG1, .name = "SYNC_CFG1" },
+ { .addr = CC1120_SYNC_CFG0, .name = "SYNC_CFG0" },
+ { .addr = CC1120_DEVIATION_M, .name = "DEVIATION_M" },
+ { .addr = CC1120_MODCFG_DEV_E, .name = "MODCFG_DEV_E" },
+ { .addr = CC1120_DCFILT_CFG, .name = "DCFILT_CFG" },
+ { .addr = CC1120_PREAMBLE_CFG1, .name = "PREAMBLE_CFG1" },
+ { .addr = CC1120_PREAMBLE_CFG0, .name = "PREAMBLE_CFG0" },
+ { .addr = CC1120_FREQ_IF_CFG, .name = "FREQ_IF_CFG" },
+ { .addr = CC1120_IQIC, .name = "IQIC" },
+ { .addr = CC1120_CHAN_BW, .name = "CHAN_BW" },
+ { .addr = CC1120_MDMCFG1, .name = "MDMCFG1" },
+ { .addr = CC1120_MDMCFG0, .name = "MDMCFG0" },
+ { .addr = CC1120_DRATE2, .name = "DRATE2" },
+ { .addr = CC1120_DRATE1, .name = "DRATE1" },
+ { .addr = CC1120_DRATE0, .name = "DRATE0" },
+ { .addr = CC1120_AGC_REF, .name = "AGC_REF" },
+ { .addr = CC1120_AGC_CS_THR, .name = "AGC_CS_THR" },
+ { .addr = CC1120_AGC_GAIN_ADJUST, .name = "AGC_GAIN_ADJUST" },
+ { .addr = CC1120_AGC_CFG3, .name = "AGC_CFG3" },
+ { .addr = CC1120_AGC_CFG2, .name = "AGC_CFG2" },
+ { .addr = CC1120_AGC_CFG1, .name = "AGC_CFG1" },
+ { .addr = CC1120_AGC_CFG0, .name = "AGC_CFG0" },
+ { .addr = CC1120_FIFO_CFG, .name = "FIFO_CFG" },
+ { .addr = CC1120_DEV_ADDR, .name = "DEV_ADDR" },
+ { .addr = CC1120_SETTLING_CFG, .name = "SETTLING_CFG" },
+ { .addr = CC1120_FS_CFG, .name = "FS_CFG" },
+ { .addr = CC1120_WOR_CFG1, .name = "WOR_CFG1" },
+ { .addr = CC1120_WOR_CFG0, .name = "WOR_CFG0" },
+ { .addr = CC1120_WOR_EVENT0_MSB, .name = "WOR_EVENT0_MSB" },
+ { .addr = CC1120_WOR_EVENT0_LSB, .name = "WOR_EVENT0_LSB" },
+ { .addr = CC1120_PKT_CFG2, .name = "PKT_CFG2" },
+ { .addr = CC1120_PKT_CFG1, .name = "PKT_CFG1" },
+ { .addr = CC1120_PKT_CFG0, .name = "PKT_CFG0" },
+ { .addr = CC1120_RFEND_CFG1, .name = "RFEND_CFG1" },
+ { .addr = CC1120_RFEND_CFG0, .name = "RFEND_CFG0" },
+ { .addr = CC1120_PA_CFG2, .name = "PA_CFG2" },
+ { .addr = CC1120_PA_CFG1, .name = "PA_CFG1" },
+ { .addr = CC1120_PA_CFG0, .name = "PA_CFG0" },
+ { .addr = CC1120_PKT_LEN, .name = "PKT_LEN" },
+ { .addr = CC1120_IF_MIX_CFG, .name = "IF_MIX_CFG" },
+ { .addr = CC1120_FREQOFF_CFG, .name = "FREQOFF_CFG" },
+ { .addr = CC1120_TOC_CFG, .name = "TOC_CFG" },
+ { .addr = CC1120_MARC_SPARE, .name = "MARC_SPARE" },
+ { .addr = CC1120_ECG_CFG, .name = "ECG_CFG" },
+ { .addr = CC1120_SOFT_TX_DATA_CFG, .name = "SOFT_TX_DATA_CFG" },
+ { .addr = CC1120_EXT_CTRL, .name = "EXT_CTRL" },
+ { .addr = CC1120_RCCAL_FINE, .name = "RCCAL_FINE" },
+ { .addr = CC1120_RCCAL_COARSE, .name = "RCCAL_COARSE" },
+ { .addr = CC1120_RCCAL_OFFSET, .name = "RCCAL_OFFSET" },
+ { .addr = CC1120_FREQOFF1, .name = "FREQOFF1" },
+ { .addr = CC1120_FREQOFF0, .name = "FREQOFF0" },
+ { .addr = CC1120_FREQ2, .name = "FREQ2" },
+ { .addr = CC1120_FREQ1, .name = "FREQ1" },
+ { .addr = CC1120_FREQ0, .name = "FREQ0" },
+ { .addr = CC1120_IF_ADC2, .name = "IF_ADC2" },
+ { .addr = CC1120_IF_ADC1, .name = "IF_ADC1" },
+ { .addr = CC1120_IF_ADC0, .name = "IF_ADC0" },
+ { .addr = CC1120_FS_DIG1, .name = "FS_DIG1" },
+ { .addr = CC1120_FS_DIG0, .name = "FS_DIG0" },
+ { .addr = CC1120_FS_CAL3, .name = "FS_CAL3" },
+ { .addr = CC1120_FS_CAL2, .name = "FS_CAL2" },
+ { .addr = CC1120_FS_CAL1, .name = "FS_CAL1" },
+ { .addr = CC1120_FS_CAL0, .name = "FS_CAL0" },
+ { .addr = CC1120_FS_CHP, .name = "FS_CHP" },
+ { .addr = CC1120_FS_DIVTWO, .name = "FS_DIVTWO" },
+ { .addr = CC1120_FS_DSM1, .name = "FS_DSM1" },
+ { .addr = CC1120_FS_DSM0, .name = "FS_DSM0" },
+ { .addr = CC1120_FS_DVC1, .name = "FS_DVC1" },
+ { .addr = CC1120_FS_DVC0, .name = "FS_DVC0" },
+ { .addr = CC1120_FS_LBI, .name = "FS_LBI" },
+ { .addr = CC1120_FS_PFD, .name = "FS_PFD" },
+ { .addr = CC1120_FS_PRE, .name = "FS_PRE" },
+ { .addr = CC1120_FS_REG_DIV_CML, .name = "FS_REG_DIV_CML" },
+ { .addr = CC1120_FS_SPARE, .name = "FS_SPARE" },
+ { .addr = CC1120_FS_VCO4, .name = "FS_VCO4" },
+ { .addr = CC1120_FS_VCO3, .name = "FS_VCO3" },
+ { .addr = CC1120_FS_VCO2, .name = "FS_VCO2" },
+ { .addr = CC1120_FS_VCO1, .name = "FS_VCO1" },
+ { .addr = CC1120_FS_VCO0, .name = "FS_VCO0" },
+ { .addr = CC1120_GBIAS6, .name = "GBIAS6" },
+ { .addr = CC1120_GBIAS5, .name = "GBIAS5" },
+ { .addr = CC1120_GBIAS4, .name = "GBIAS4" },
+ { .addr = CC1120_GBIAS3, .name = "GBIAS3" },
+ { .addr = CC1120_GBIAS2, .name = "GBIAS2" },
+ { .addr = CC1120_GBIAS1, .name = "GBIAS1" },
+ { .addr = CC1120_GBIAS0, .name = "GBIAS0" },
+ { .addr = CC1120_IFAMP, .name = "IFAMP" },
+ { .addr = CC1120_LNA, .name = "LNA" },
+ { .addr = CC1120_RXMIX, .name = "RXMIX" },
+ { .addr = CC1120_XOSC5, .name = "XOSC5" },
+ { .addr = CC1120_XOSC4, .name = "XOSC4" },
+ { .addr = CC1120_XOSC3, .name = "XOSC3" },
+ { .addr = CC1120_XOSC2, .name = "XOSC2" },
+ { .addr = CC1120_XOSC1, .name = "XOSC1" },
+ { .addr = CC1120_XOSC0, .name = "XOSC0" },
+ { .addr = CC1120_ANALOG_SPARE, .name = "ANALOG_SPARE" },
+ { .addr = CC1120_PA_CFG3, .name = "PA_CFG3" },
+ { .addr = CC1120_WOR_TIME1, .name = "WOR_TIME1" },
+ { .addr = CC1120_WOR_TIME0, .name = "WOR_TIME0" },
+ { .addr = CC1120_WOR_CAPTURE1, .name = "WOR_CAPTURE1" },
+ { .addr = CC1120_WOR_CAPTURE0, .name = "WOR_CAPTURE0" },
+ { .addr = CC1120_BIST, .name = "BIST" },
+ { .addr = CC1120_DCFILTOFFSET_I1, .name = "DCFILTOFFSET_I1" },
+ { .addr = CC1120_DCFILTOFFSET_I0, .name = "DCFILTOFFSET_I0" },
+ { .addr = CC1120_DCFILTOFFSET_Q1, .name = "DCFILTOFFSET_Q1" },
+ { .addr = CC1120_DCFILTOFFSET_Q0, .name = "DCFILTOFFSET_Q0" },
+ { .addr = CC1120_IQIE_I1, .name = "IQIE_I1" },
+ { .addr = CC1120_IQIE_I0, .name = "IQIE_I0" },
+ { .addr = CC1120_IQIE_Q1, .name = "IQIE_Q1" },
+ { .addr = CC1120_IQIE_Q0, .name = "IQIE_Q0" },
+ { .addr = CC1120_RSSI1, .name = "RSSI1" },
+ { .addr = CC1120_RSSI0, .name = "RSSI0" },
+ { .addr = CC1120_MARCSTATE, .name = "MARCSTATE" },
+ { .addr = CC1120_LQI_VAL, .name = "LQI_VAL" },
+ { .addr = CC1120_PQT_SYNC_ERR, .name = "PQT_SYNC_ERR" },
+ { .addr = CC1120_DEM_STATUS, .name = "DEM_STATUS" },
+ { .addr = CC1120_FREQOFF_EST1, .name = "FREQOFF_EST1" },
+ { .addr = CC1120_FREQOFF_EST0, .name = "FREQOFF_EST0" },
+ { .addr = CC1120_AGC_GAIN3, .name = "AGC_GAIN3" },
+ { .addr = CC1120_AGC_GAIN2, .name = "AGC_GAIN2" },
+ { .addr = CC1120_AGC_GAIN1, .name = "AGC_GAIN1" },
+ { .addr = CC1120_AGC_GAIN0, .name = "AGC_GAIN0" },
+ { .addr = CC1120_SOFT_RX_DATA_OUT, .name = "SOFT_RX_DATA_OUT" },
+ { .addr = CC1120_SOFT_TX_DATA_IN, .name = "SOFT_TX_DATA_IN" },
+ { .addr = CC1120_ASK_SOFT_RX_DATA, .name = "ASK_SOFT_RX_DATA" },
+ { .addr = CC1120_RNDGEN, .name = "RNDGEN" },
+ { .addr = CC1120_MAGN2, .name = "MAGN2" },
+ { .addr = CC1120_MAGN1, .name = "MAGN1" },
+ { .addr = CC1120_MAGN0, .name = "MAGN0" },
+ { .addr = CC1120_ANG1, .name = "ANG1" },
+ { .addr = CC1120_ANG0, .name = "ANG0" },
+ { .addr = CC1120_CHFILT_I2, .name = "CHFILT_I2" },
+ { .addr = CC1120_CHFILT_I1, .name = "CHFILT_I1" },
+ { .addr = CC1120_CHFILT_I0, .name = "CHFILT_I0" },
+ { .addr = CC1120_CHFILT_Q2, .name = "CHFILT_Q2" },
+ { .addr = CC1120_CHFILT_Q1, .name = "CHFILT_Q1" },
+ { .addr = CC1120_CHFILT_Q0, .name = "CHFILT_Q0" },
+ { .addr = CC1120_GPIO_STATUS, .name = "GPIO_STATUS" },
+ { .addr = CC1120_FSCAL_CTRL, .name = "FSCAL_CTRL" },
+ { .addr = CC1120_PHASE_ADJUST, .name = "PHASE_ADJUST" },
+ { .addr = CC1120_PARTNUMBER, .name = "PARTNUMBER" },
+ { .addr = CC1120_PARTVERSION, .name = "PARTVERSION" },
+ { .addr = CC1120_SERIAL_STATUS, .name = "SERIAL_STATUS" },
+ { .addr = CC1120_RX_STATUS, .name = "RX_STATUS" },
+ { .addr = CC1120_TX_STATUS, .name = "TX_STATUS" },
+ { .addr = CC1120_MARC_STATUS1, .name = "MARC_STATUS1" },
+ { .addr = CC1120_MARC_STATUS0, .name = "MARC_STATUS0" },
+ { .addr = CC1120_PA_IFAMP_TEST, .name = "PA_IFAMP_TEST" },
+ { .addr = CC1120_FSRF_TEST, .name = "FSRF_TEST" },
+ { .addr = CC1120_PRE_TEST, .name = "PRE_TEST" },
+ { .addr = CC1120_PRE_OVR, .name = "PRE_OVR" },
+ { .addr = CC1120_ADC_TEST, .name = "ADC_TEST" },
+ { .addr = CC1120_DVC_TEST, .name = "DVC_TEST" },
+ { .addr = CC1120_ATEST, .name = "ATEST" },
+ { .addr = CC1120_ATEST_LVDS, .name = "ATEST_LVDS" },
+ { .addr = CC1120_ATEST_MODE, .name = "ATEST_MODE" },
+ { .addr = CC1120_XOSC_TEST1, .name = "XOSC_TEST1" },
+ { .addr = CC1120_XOSC_TEST0, .name = "XOSC_TEST0" },
+ { .addr = CC1120_RXFIRST, .name = "RXFIRST" },
+ { .addr = CC1120_TXFIRST, .name = "TXFIRST" },
+ { .addr = CC1120_RXLAST, .name = "RXLAST" },
+ { .addr = CC1120_TXLAST, .name = "TXLAST" },
+ { .addr = CC1120_NUM_TXBYTES, .name = "NUM_TXBYTES" },
+ { .addr = CC1120_NUM_RXBYTES, .name = "NUM_RXBYTES" },
+ { .addr = CC1120_FIFO_NUM_TXBYTES, .name = "FIFO_NUM_TXBYTES" },
+ { .addr = CC1120_FIFO_NUM_RXBYTES, .name = "FIFO_NUM_RXBYTES" },
+};
+
+#define AO_NUM_CC1120_REG (sizeof ao_cc1120_reg / sizeof ao_cc1120_reg[0])
+
+static void ao_radio_show(void) {
+ uint8_t status = ao_radio_status();
+ int i;
+
+ ao_radio_get(0xff);
+ status = ao_radio_status();
+ printf ("Status: %02x\n", status);
+ printf ("CHIP_RDY: %d\n", (status >> CC1120_STATUS_CHIP_RDY) & 1);
+ printf ("STATE: %s\n", cc1120_state_name[(status >> CC1120_STATUS_STATE) & CC1120_STATUS_STATE_MASK]);
+ printf ("MARC: %02x\n", ao_radio_marc_status());
+
+ for (i = 0; i < AO_NUM_CC1120_REG; i++)
+ printf ("\t%02x %-20.20s\n", ao_radio_reg_read(ao_cc1120_reg[i].addr), ao_cc1120_reg[i].name);
+ ao_radio_put();
+}
+
+static void ao_radio_beep(void) {
+ ao_radio_rdf(RDF_PACKET_LEN);
+}
+
+static void ao_radio_packet(void) {
+ static const uint8_t packet[] = {
+#if 1
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+#else
+ 3, 1, 2, 3
+#endif
+ };
+
+ ao_radio_send(packet, sizeof (packet));
+}
+
+void
+ao_radio_test_recv()
+{
+ uint8_t bytes[34];
+ uint8_t b;
+
+ if (ao_radio_recv(bytes, 34)) {
+ if (bytes[33] & 0x80)
+ printf ("CRC OK");
+ else
+ printf ("CRC BAD");
+ printf (" RSSI %d", AO_RSSI_FROM_RADIO(bytes[32]));
+ for (b = 0; b < 32; b++)
+ printf (" %02x", bytes[b]);
+ printf ("\n");
+ }
+}
+
+#endif
+
+static const struct ao_cmds ao_radio_cmds[] = {
+ { ao_radio_test_cmd, "C <1 start, 0 stop, none both>\0Radio carrier test" },
+#if CC1120_DEBUG
+ { ao_radio_show, "R\0Show CC1120 status" },
+ { ao_radio_beep, "b\0Emit an RDF beacon" },
+ { ao_radio_packet, "p\0Send a test packet" },
+ { ao_radio_test_recv, "q\0Recv a test packet" },
+#endif
+ { 0, NULL }
+};
+
+void
+ao_radio_init(void)
+{
+ int i;
+
+ ao_radio_configured = 0;
+ ao_spi_init_cs (AO_CC1120_SPI_CS_PORT, (1 << AO_CC1120_SPI_CS_PIN));
+
+ AO_CC1120_SPI_CS_PORT->bsrr = ((uint32_t) (1 << AO_CC1120_SPI_CS_PIN));
+ for (i = 0; i < 10000; i++) {
+ if ((SPI_2_PORT->idr & (1 << SPI_2_MISO_PIN)) == 0)
+ break;
+ }
+ AO_CC1120_SPI_CS_PORT->bsrr = (1 << AO_CC1120_SPI_CS_PIN);
+ if (i == 10000)
+ ao_panic(AO_PANIC_SELF_TEST_CC1120);
+
+ /* Enable the EXTI interrupt for the appropriate pin */
+ ao_enable_port(AO_CC1120_INT_PORT);
+ ao_exti_setup(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN,
+ AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_HIGH,
+ ao_radio_tx_isr);
+
+ ao_cmd_register(&ao_radio_cmds[0]);
+}
diff --git a/src/drivers/ao_cc1120.h b/src/drivers/ao_cc1120.h
new file mode 100644
index 00000000..60b9621e
--- /dev/null
+++ b/src/drivers/ao_cc1120.h
@@ -0,0 +1,494 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_CC1120_H_
+#define _AO_CC1120_H_
+
+#define CC1120_READ (7)
+#define CC1120_BURST (6)
+
+/* Register space */
+#define CC1120_IOCFG3 0x00
+#define CC1120_IOCFG_GPIO_ATRAN 7
+#define CC1120_IOCFG_GPIO_INV 6
+#define CC1120_IOCFG_GPIO_CFG 0
+#define CC1120_IOCFG_GPIO_CFG_RXFIFO_THR 0
+#define CC1120_IOCFG_GPIO_CFG_RXFIFO_THR_PKT 1
+#define CC1120_IOCFG_GPIO_CFG_TXFIFO_THR 2
+#define CC1120_IOCFG_GPIO_CFG_TXFIFO_THR_PKT 3
+#define CC1120_IOCFG_GPIO_CFG_RXFIFO_OVERFLOW 4
+#define CC1120_IOCFG_GPIO_CFG_TXFIFO_UNDERFLOW 5
+#define CC1120_IOCFG_GPIO_CFG_PKT_SYNC_RXTX 6
+#define CC1120_IOCFG_GPIO_CFG_CRC_OK 7
+#define CC1120_IOCFG_GPIO_CFG_SERIAL_CLK 8
+#define CC1120_IOCFG_GPIO_CFG_SERIAL_RX 9
+#define CC1120_IOCFG_GPIO_CFG_PQT_REACHED 11
+#define CC1120_IOCFG_GPIO_CFG_PQT_VALID 12
+#define CC1120_IOCFG_GPIO_CFG_RSSI_VALID 13
+#define CC1120_IOCFG_GPIO3_CFG_RSSI_UPDATE 14
+#define CC1120_IOCFG_GPIO2_CFG_RSSI_UPDATE 14
+#define CC1120_IOCFG_GPIO1_CFG_AGC_HOLD 14
+#define CC1120_IOCFG_GPIO0_CFG_AGC_UPDATE 14
+#define CC1120_IOCFG_GPIO3_CFG_CGA_STATUS 15
+#define CC1120_IOCFG_GPIO2_CFG_TXONCCA_DONE 15
+#define CC1120_IOCFG_GPIO1_CFG_CCA_STATUS 15
+#define CC1120_IOCFG_GPIO0_CFG_TXONCCA_FAILED 15
+#define CC1120_IOCFG_GPIO_CFG_CARRIER_SENSE_VALID 16
+#define CC1120_IOCFG_GPIO_CFG_CARRIER_SENSE 17
+#define CC1120_IOCFG_GPIO3_CFG_DSSS_CLK 18
+#define CC1120_IOCFG_GPIO2_CFG_DSSS_DATA0 18
+#define CC1120_IOCFG_GPIO1_CFG_DSSS_CLK 18
+#define CC1120_IOCFG_GPIO0_CFG_DSSS_DATA1 18
+#define CC1120_IOCFG_GPIO_CFG_PKT_CRC_OK 19
+#define CC1120_IOCFG_GPIO_CFG_MARC_MCU_WAKEUP 20
+#define CC1120_IOCFG_GPIO_CFG_SYNC_LOW0_HIGH1 21
+#define CC1120_IOCFG_GPIO_CFG_LNA_PA_REG_PD 23
+#define CC1120_IOCFG_GPIO_CFG_LNA_PD 24
+#define CC1120_IOCFG_GPIO_CFG_PA_RD 25
+#define CC1120_IOCFG_GPIO_CFG_RX0TX1_CFG 26
+#define CC1120_IOCFG_GPIO_CFG_IMAGE_FOUND 28
+#define CC1120_IOCFG_GPIO_CFG_CLKEN_SOFT 29
+#define CC1120_IOCFG_GPIO_CFG_SOFT_TX_DATA_CLK 30
+#define CC1120_IOCFG_GPIO_CFG_RSSI_STEP_FOUND 33
+#define CC1120_IOCFG_GPIO_CFG_RSSI_STEP_EVENT 34
+#define CC1120_IOCFG_GPIO_CFG_ANTENNA_SELECT 36
+#define CC1120_IOCFG_GPIO_CFG_MARC_2PIN_STATUS1 37
+#define CC1120_IOCFG_GPIO_CFG_MARC_2PIN_STATUS0 38
+#define CC1120_IOCFG_GPIO2_CFG_TXFIFO_OVERFLOW 39
+#define CC1120_IOCFG_GPIO0_CFG_RXFIFO_UNDERFLOW 39
+#define CC1120_IOCFG_GPIO3_CFG_MAGN_VALID 40
+#define CC1120_IOCFG_GPIO2_CFG_CHFILT_VALID 40
+#define CC1120_IOCFG_GPIO1_CFG_RCC_CAL_VALID 40
+#define CC1120_IOCFG_GPIO0_CFG_CHFILTER_STARTUP_VALID 40
+#define CC1120_IOCFG_GPIO3_CFG_COLLISION_FOUND 41
+#define CC1120_IOCFG_GPIO2_CFG_SYNC_EVENT 41
+#define CC1120_IOCFG_GPIO1_CFG_COLLISION_FOUND 41
+#define CC1120_IOCFG_GPIO0_CFG_COLLISION_EVENT 41
+#define CC1120_IOCFG_GPIO_CFG_PA_RAMP_UP 42
+#define CC1120_IOCFG_GPIO3_CFG_CRC_FAILED 43
+#define CC1120_IOCFG_GPIO2_CFG_LENGTH_FAILED 43
+#define CC1120_IOCFG_GPIO1_CFG_ADDR_FAILED 43
+#define CC1120_IOCFG_GPIO0_CFG_UART_FRAMING_ERROR 43
+#define CC1120_IOCFG_GPIO_CFG_AGC_STABLE_GAIN 44
+#define CC1120_IOCFG_GPIO_CFG_AGC_UPDATE 45
+#define CC1120_IOCFG_GPIO3_CFG_ADC_CLOCK 46
+#define CC1120_IOCFG_GPIO2_CFG_ADC_Q_DATA_SAMPLE 46
+#define CC1120_IOCFG_GPIO1_CFG_ADC_CLOCK 46
+#define CC1120_IOCFG_GPIO0_CFG_ADC_I_DATA_SAMPLE 46
+#define CC1120_IOCFG_GPIO_CFG_HIGHZ 48
+#define CC1120_IOCFG_GPIO_CFG_EXT_CLOCK 49
+#define CC1120_IOCFG_GPIO_CFG_CHIP_RDY 50
+#define CC1120_IOCFG_GPIO_CFG_HW0 51
+#define CC1120_IOCFG_GPIO_CFG_CLOCK_32K 54
+#define CC1120_IOCFG_GPIO_CFG_WOR_EVENT0 55
+#define CC1120_IOCFG_GPIO_CFG_WOR_EVENT1 56
+#define CC1120_IOCFG_GPIO_CFG_WOR_EVENT2 57
+#define CC1120_IOCFG_GPIO_CFG_XOSC_STABLE 59
+#define CC1120_IOCFG_GPIO_CFG_EXT_OSC_EN 60
+#define CC1120_IOCFG_GPIO_CFG_MASK 0x3f
+
+#define CC1120_IOCFG3 0x00
+#define CC1120_IOCFG2 0x01
+#define CC1120_IOCFG1 0x02
+#define CC1120_IOCFG0 0x03
+#define CC1120_SYNC3 0x04
+#define CC1120_SYNC2 0x05
+#define CC1120_SYNC1 0x06
+#define CC1120_SYNC0 0x07
+#define CC1120_SYNC_CFG1 0x08
+#define CC1120_SYNC_CFG1_DEM_CFG 5
+#define CC1120_SYNC_CFG1_DEM_CFG_PQT_GATING_DISABLED 0
+#define CC1120_SYNC_CFG1_DEM_CFG_PQT_GATING_ENABLED 2
+#define CC1120_SYNC_CFG1_DEM_CFG_MASK 0x7
+
+#define CC1120_SYNC_CFG1_SYNC_THR 0
+#define CC1120_SYNC_CFG1_SYNC_MASK 0x1f
+
+#define CC1120_SYNC_CFG0 0x09
+#define CC1120_SYNC_CFG0_SYNC_MODE 2
+#define CC1120_SYNC_CFG0_SYNC_MODE_NONE 0
+#define CC1120_SYNC_CFG0_SYNC_MODE_11_BITS 1
+#define CC1120_SYNC_CFG0_SYNC_MODE_16_BITS 2
+#define CC1120_SYNC_CFG0_SYNC_MODE_18_BITS 3
+#define CC1120_SYNC_CFG0_SYNC_MODE_24_BITS 4
+#define CC1120_SYNC_CFG0_SYNC_MODE_32_BITS 5
+#define CC1120_SYNC_CFG0_SYNC_MODE_16H_BITS 6
+#define CC1120_SYNC_CFG0_SYNC_MODE_16D_BITS 7
+#define CC1120_SYNC_CFG0_SYNC_MODE_MASK 7
+#define CC1120_SYNC_CFG0_SYNC_NUM_ERROR 0
+#define CC1120_SYNC_CFG0_SYNC_NUM_ERROR_0 0
+#define CC1120_SYNC_CFG0_SYNC_NUM_ERROR_2 1
+#define CC1120_SYNC_CFG0_SYNC_NUM_ERROR_DISABLED 3
+#define CC1120_SYNC_CFG0_SYNC_NUM_ERROR_MASK 3
+
+#define CC1120_DEVIATION_M 0x0a
+#define CC1120_MODCFG_DEV_E 0x0b
+#define CC1120_MODCFG_DEV_E_MODEM_MODE 6
+#define CC1120_MODCFG_DEV_E_MODEM_MODE_NORMAL 0
+#define CC1120_MODCFG_DEV_E_MODEM_MODE_DSSS_REPEAT 1
+#define CC1120_MODCFG_DEV_E_MODEM_MODE_DSSS_PN 2
+#define CC1120_MODCFG_DEV_E_MODEM_MODE_MASK 3
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT 3
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_2_FSK 0
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_2_GFSK 1
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_ASK_OOK 3
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_4_FSK 4
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_4_GFSK 5
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_SC_MSK_UNSHAPED 6
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_SC_MSK_SHAPED 7
+#define CC1120_MODCFG_DEV_E_MOD_FORMAT_MASK 7
+#define CC1120_MODCFG_DEV_E_DEV_E 0
+#define CC1120_MODCFG_DEV_E_DEV_E_MASK 7
+
+#define CC1120_DCFILT_CFG 0x0c
+#define CC1120_PREAMBLE_CFG1 0x0d
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE 2
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_NONE 0
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_0_5_BYTE 1
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_1_BYTE 2
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_1_5_BYTE 3
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_2_BYTES 4
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_3_BYTES 5
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_4_BYTES 6
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_5_BYTES 7
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_6_BYTES 8
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_7_BYTES 9
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_8_BYTES 10
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_12_BYTES 11
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_24_BYTES 12
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_30_BYTES 13
+#define CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_MASK 0xf
+
+#define CC1120_PREAMBLE_CFG1_PREAMBLE_WORD 0
+#define CC1120_PREAMBLE_CFG1_PREAMBLE_WORD_AA 0
+#define CC1120_PREAMBLE_CFG1_PREAMBLE_WORD_55 1
+#define CC1120_PREAMBLE_CFG1_PREAMBLE_WORD_33 2
+#define CC1120_PREAMBLE_CFG1_PREAMBLE_WORD_CC 3
+#define CC1120_PREAMBLE_CFG1_PREAMBLE_WORD_MASK 3
+
+#define CC1120_PREAMBLE_CFG0 0x0e
+#define CC1120_PREAMBLE_CFG0_PQT_EN 5
+#define CC1120_PREAMBLE_CFG0_PQT_VALID_TIMEOUT 4
+#define CC1120_PREAMBLE_CFG0_PQT 0
+#define CC1120_PREAMBLE_CFG0_PQT_MASK 0xf
+
+#define CC1120_FREQ_IF_CFG 0x0f
+#define CC1120_IQIC 0x10
+#define CC1120_CHAN_BW 0x11
+#define CC1120_MDMCFG1 0x12
+#define CC1120_MDMCFG1_CARRIER_SENSE_GATE 7
+#define CC1120_MDMCFG1_FIFO_EN 6
+#define CC1120_MDMCFG1_MANCHESTER_EN 5
+#define CC1120_MDMCFG1_INVERT_DATA_EN 4
+#define CC1120_MDMCFG1_COLLISION_DETECT_EN 3
+#define CC1120_MDMCFG1_DVGA_GAIN 1
+#define CC1120_MDMCFG1_DVGA_GAIN_0 0
+#define CC1120_MDMCFG1_DVGA_GAIN_3 1
+#define CC1120_MDMCFG1_DVGA_GAIN_6 2
+#define CC1120_MDMCFG1_DVGA_GAIN_9 3
+#define CC1120_MDMCFG1_DVGA_GAIN_MASK 3
+#define CC1120_MDMCFG1_SINGLE_ADC_EN 0
+
+#define CC1120_MDMCFG0 0x13
+#define CC1120_DRATE2 0x14
+#define CC1120_DRATE2_DATARATE_E 4
+#define CC1120_DRATE2_DATARATE_E_MASK 0xf
+#define CC1120_DRATE2_DATARATE_M_19_16 0
+#define CC1120_DRATE2_DATARATE_M_19_16_MASK 0xf
+
+#define CC1120_DRATE1 0x15
+#define CC1120_DRATE0 0x16
+#define CC1120_AGC_REF 0x17
+#define CC1120_AGC_CS_THR 0x18
+#define CC1120_AGC_GAIN_ADJUST 0x19
+#define CC1120_AGC_CFG3 0x1a
+#define CC1120_AGC_CFG2 0x1b
+#define CC1120_AGC_CFG1 0x1c
+#define CC1120_AGC_CFG0 0x1d
+#define CC1120_FIFO_CFG 0x1e
+#define CC1120_FIFO_CFG_CRC_AUTOFLUSH 7
+#define CC1120_FIFO_CFG_FIFO_THR 0
+#define CC1120_DEV_ADDR 0x1f
+#define CC1120_SETTLING_CFG 0x20
+#define CC1120_SETTLING_CFG_FS_AUTOCAL 3
+#define CC1120_SETTLING_CFG_FS_AUTOCAL_NEVER 0
+#define CC1120_SETTLING_CFG_FS_AUTOCAL_IDLE_TO_ON 1
+#define CC1120_SETTLING_CFG_FS_AUTOCAL_ON_TO_IDLE 2
+#define CC1120_SETTLING_CFG_FS_AUTOCAL_EVERY_4TH_TIME 3
+#define CC1120_SETTLING_CFG_FS_AUTOCAL_MASK 3
+#define CC1120_SETTLING_CFG_LOCK_TIME 1
+#define CC1120_SETTLING_CFG_LOCK_TIME_50_20 0
+#define CC1120_SETTLING_CFG_LOCK_TIME_70_30 1
+#define CC1120_SETTLING_CFG_LOCK_TIME_100_40 2
+#define CC1120_SETTLING_CFG_LOCK_TIME_150_60 3
+#define CC1120_SETTLING_CFG_LOCK_TIME_MASK 3
+#define CC1120_SETTLING_CFG_FSREG_TIME 0
+#define CC1120_SETTLING_CFG_FSREG_TIME_30 0
+#define CC1120_SETTLING_CFG_FSREG_TIME_60 1
+#define CC1120_SETTLING_CFG_FSREG_TIME_MASK 1
+
+#define CC1120_FS_CFG 0x21
+#define CC1120_FS_CFG_LOCK_EN 4
+#define CC1120_FS_CFG_FSD_BANDSELECT 0
+#define CC1120_FS_CFG_FSD_BANDSELECT_820_960 2
+#define CC1120_FS_CFG_FSD_BANDSELECT_410_480 4
+#define CC1120_FS_CFG_FSD_BANDSELECT_273_320 6
+#define CC1120_FS_CFG_FSD_BANDSELECT_205_240 8
+#define CC1120_FS_CFG_FSD_BANDSELECT_164_192 10
+#define CC1120_FS_CFG_FSD_BANDSELECT_136_160 11
+#define CC1120_FS_CFG_FSD_BANDSELECT_MASK 0xf
+
+#define CC1120_WOR_CFG1 0x22
+#define CC1120_WOR_CFG0 0x23
+#define CC1120_WOR_EVENT0_MSB 0x24
+#define CC1120_WOR_EVENT0_LSB 0x25
+#define CC1120_PKT_CFG2 0x26
+#define CC1120_PKT_CFG2_CCA_MODE 2
+#define CC1120_PKT_CFG2_CCA_MODE_ALWAYS_CLEAR 0
+#define CC1120_PKT_CFG2_CCA_MODE_RSSI_THRESHOLD 1
+#define CC1120_PKT_CFG2_CCA_MODE_NOT_RECEIVING 2
+#define CC1120_PKT_CFG2_CCA_MODE_RSSI_OR_NOT 3
+#define CC1120_PKT_CFG2_CCA_MODE_RSSI_AND_ETSI_LBT 4
+#define CC1120_PKT_CFG2_CCA_MODE_MASK 7
+#define CC1120_PKT_CFG2_PKT_FORMAT 0
+#define CC1120_PKT_CFG2_PKT_FORMAT_NORMAL 0
+#define CC1120_PKT_CFG2_PKT_FORMAT_SYNCHRONOUS_SERIAL 1
+#define CC1120_PKT_CFG2_PKT_FORMAT_RANDOM 2
+#define CC1120_PKT_CFG2_PKT_FORMAT_TRANSPARENT_SERIAL 3
+#define CC1120_PKT_CFG2_PKT_FORMAT_MASK 3
+
+#define CC1120_PKT_CFG1 0x27
+#define CC1120_PKT_CFG1_WHITE_DATA 6
+#define CC1120_PKT_CFG1_ADDR_CHECK_CFG 4
+#define CC1120_PKT_CFG1_ADDR_CHECK_CFG_NONE 0
+#define CC1120_PKT_CFG1_ADDR_CHECK_CFG_CHECK 1
+#define CC1120_PKT_CFG1_ADDR_CHECK_CFG_00_BROADCAST 2
+#define CC1120_PKT_CFG1_ADDR_CHECK_CFG_00_FF_BROADCAST 3
+#define CC1120_PKT_CFG1_ADDR_CHECK_CFG_MASK 3
+#define CC1120_PKT_CFG1_CRC_CFG 2
+#define CC1120_PKT_CFG1_CRC_CFG_DISABLED 0
+#define CC1120_PKT_CFG1_CRC_CFG_CRC16_INIT_ONES 1
+#define CC1120_PKT_CFG1_CRC_CFG_CRC16_INIT_ZEROS 2
+#define CC1120_PKT_CFG1_CRC_CFG_MASK 3
+#define CC1120_PKT_CFG1_BYTE_SWAP_EN 1
+#define CC1120_PKT_CFG1_APPEND_STATUS 0
+
+#define CC1120_PKT_CFG0 0x28
+#define CC1120_PKT_CFG0_RESERVED7 7
+#define CC1120_PKT_CFG0_LENGTH_CONFIG 5
+#define CC1120_PKT_CFG0_LENGTH_CONFIG_FIXED 0
+#define CC1120_PKT_CFG0_LENGTH_CONFIG_VARIABLE 1
+#define CC1120_PKT_CFG0_LENGTH_CONFIG_INFINITE 2
+#define CC1120_PKT_CFG0_LENGTH_CONFIG_VARIABLE_5LSB 3
+#define CC1120_PKT_CFG0_LENGTH_CONFIG_MASK 3
+#define CC1120_PKT_CFG0_PKG_BIT_LEN 2
+#define CC1120_PKT_CFG0_PKG_BIT_LEN_MASK 7
+#define CC1120_PKT_CFG0_UART_MODE_EN 1
+#define CC1120_PKT_CFG0_UART_SWAP_EN 0
+
+#define CC1120_RFEND_CFG1 0x29
+#define CC1120_RFEND_CFG0 0x2a
+#define CC1120_PA_CFG2 0x2b
+#define CC1120_PA_CFG1 0x2c
+#define CC1120_PA_CFG0 0x2d
+#define CC1120_PKT_LEN 0x2e
+
+#define CC1120_EXTENDED 0x2f
+
+/* Command strobes */
+#define CC1120_SRES 0x30
+#define CC1120_SFSTXON 0x31
+#define CC1120_SXOFF 0x32
+#define CC1120_SCAL 0x33
+#define CC1120_SRX 0x34
+#define CC1120_STX 0x35
+#define CC1120_SIDLE 0x36
+#define CC1120_SAFC 0x37
+#define CC1120_SWOR 0x38
+#define CC1120_SPWD 0x39
+#define CC1120_SFRX 0x3a
+#define CC1120_SFTX 0x3b
+#define CC1120_SWORRST 0x3c
+#define CC1120_SNOP 0x3d
+
+#define CC1120_DIRECT_FIFO 0x3e
+#define CC1120_FIFO 0x3f
+
+#define CC1120_FIFO_SIZE 128
+
+/* Extended register space */
+
+#define CC1120_EXTENDED_BIT 0x8000
+
+#define CC1120_IS_EXTENDED(r) ((r) & CC1120_EXTENDED_BIT)
+
+#define CC1120_IF_MIX_CFG (CC1120_EXTENDED_BIT | 0x00)
+#define CC1120_FREQOFF_CFG (CC1120_EXTENDED_BIT | 0x01)
+#define CC1120_TOC_CFG (CC1120_EXTENDED_BIT | 0x02)
+#define CC1120_MARC_SPARE (CC1120_EXTENDED_BIT | 0x03)
+#define CC1120_ECG_CFG (CC1120_EXTENDED_BIT | 0x04)
+#define CC1120_SOFT_TX_DATA_CFG (CC1120_EXTENDED_BIT | 0x05)
+#define CC1120_EXT_CTRL (CC1120_EXTENDED_BIT | 0x06)
+#define CC1120_RCCAL_FINE (CC1120_EXTENDED_BIT | 0x07)
+#define CC1120_RCCAL_COARSE (CC1120_EXTENDED_BIT | 0x08)
+#define CC1120_RCCAL_OFFSET (CC1120_EXTENDED_BIT | 0x09)
+#define CC1120_FREQOFF1 (CC1120_EXTENDED_BIT | 0x0A)
+#define CC1120_FREQOFF0 (CC1120_EXTENDED_BIT | 0x0B)
+#define CC1120_FREQ2 (CC1120_EXTENDED_BIT | 0x0C)
+#define CC1120_FREQ1 (CC1120_EXTENDED_BIT | 0x0D)
+#define CC1120_FREQ0 (CC1120_EXTENDED_BIT | 0x0E)
+#define CC1120_IF_ADC2 (CC1120_EXTENDED_BIT | 0x0F)
+#define CC1120_IF_ADC1 (CC1120_EXTENDED_BIT | 0x10)
+#define CC1120_IF_ADC0 (CC1120_EXTENDED_BIT | 0x11)
+#define CC1120_FS_DIG1 (CC1120_EXTENDED_BIT | 0x12)
+#define CC1120_FS_DIG0 (CC1120_EXTENDED_BIT | 0x13)
+#define CC1120_FS_CAL3 (CC1120_EXTENDED_BIT | 0x14)
+#define CC1120_FS_CAL2 (CC1120_EXTENDED_BIT | 0x15)
+#define CC1120_FS_CAL1 (CC1120_EXTENDED_BIT | 0x16)
+#define CC1120_FS_CAL0 (CC1120_EXTENDED_BIT | 0x17)
+#define CC1120_FS_CHP (CC1120_EXTENDED_BIT | 0x18)
+#define CC1120_FS_DIVTWO (CC1120_EXTENDED_BIT | 0x19)
+#define CC1120_FS_DSM1 (CC1120_EXTENDED_BIT | 0x1A)
+#define CC1120_FS_DSM0 (CC1120_EXTENDED_BIT | 0x1B)
+#define CC1120_FS_DVC1 (CC1120_EXTENDED_BIT | 0x1C)
+#define CC1120_FS_DVC0 (CC1120_EXTENDED_BIT | 0x1D)
+#define CC1120_FS_LBI (CC1120_EXTENDED_BIT | 0x1E)
+#define CC1120_FS_PFD (CC1120_EXTENDED_BIT | 0x1F)
+#define CC1120_FS_PRE (CC1120_EXTENDED_BIT | 0x20)
+#define CC1120_FS_REG_DIV_CML (CC1120_EXTENDED_BIT | 0x21)
+#define CC1120_FS_SPARE (CC1120_EXTENDED_BIT | 0x22)
+#define CC1120_FS_VCO4 (CC1120_EXTENDED_BIT | 0x23)
+#define CC1120_FS_VCO3 (CC1120_EXTENDED_BIT | 0x24)
+#define CC1120_FS_VCO2 (CC1120_EXTENDED_BIT | 0x25)
+#define CC1120_FS_VCO1 (CC1120_EXTENDED_BIT | 0x26)
+#define CC1120_FS_VCO0 (CC1120_EXTENDED_BIT | 0x27)
+#define CC1120_GBIAS6 (CC1120_EXTENDED_BIT | 0x28)
+#define CC1120_GBIAS5 (CC1120_EXTENDED_BIT | 0x29)
+#define CC1120_GBIAS4 (CC1120_EXTENDED_BIT | 0x2A)
+#define CC1120_GBIAS3 (CC1120_EXTENDED_BIT | 0x2B)
+#define CC1120_GBIAS2 (CC1120_EXTENDED_BIT | 0x2C)
+#define CC1120_GBIAS1 (CC1120_EXTENDED_BIT | 0x2D)
+#define CC1120_GBIAS0 (CC1120_EXTENDED_BIT | 0x2E)
+#define CC1120_IFAMP (CC1120_EXTENDED_BIT | 0x2F)
+#define CC1120_LNA (CC1120_EXTENDED_BIT | 0x30)
+#define CC1120_RXMIX (CC1120_EXTENDED_BIT | 0x31)
+#define CC1120_XOSC5 (CC1120_EXTENDED_BIT | 0x32)
+#define CC1120_XOSC4 (CC1120_EXTENDED_BIT | 0x33)
+#define CC1120_XOSC3 (CC1120_EXTENDED_BIT | 0x34)
+#define CC1120_XOSC2 (CC1120_EXTENDED_BIT | 0x35)
+#define CC1120_XOSC1 (CC1120_EXTENDED_BIT | 0x36)
+#define CC1120_XOSC0 (CC1120_EXTENDED_BIT | 0x37)
+#define CC1120_ANALOG_SPARE (CC1120_EXTENDED_BIT | 0x38)
+#define CC1120_PA_CFG3 (CC1120_EXTENDED_BIT | 0x39)
+#define CC1120_WOR_TIME1 (CC1120_EXTENDED_BIT | 0x64)
+#define CC1120_WOR_TIME0 (CC1120_EXTENDED_BIT | 0x65)
+#define CC1120_WOR_CAPTURE1 (CC1120_EXTENDED_BIT | 0x66)
+#define CC1120_WOR_CAPTURE0 (CC1120_EXTENDED_BIT | 0x67)
+#define CC1120_BIST (CC1120_EXTENDED_BIT | 0x68)
+#define CC1120_DCFILTOFFSET_I1 (CC1120_EXTENDED_BIT | 0x69)
+#define CC1120_DCFILTOFFSET_I0 (CC1120_EXTENDED_BIT | 0x6A)
+#define CC1120_DCFILTOFFSET_Q1 (CC1120_EXTENDED_BIT | 0x6B)
+#define CC1120_DCFILTOFFSET_Q0 (CC1120_EXTENDED_BIT | 0x6C)
+#define CC1120_IQIE_I1 (CC1120_EXTENDED_BIT | 0x6D)
+#define CC1120_IQIE_I0 (CC1120_EXTENDED_BIT | 0x6E)
+#define CC1120_IQIE_Q1 (CC1120_EXTENDED_BIT | 0x6f)
+#define CC1120_IQIE_Q0 (CC1120_EXTENDED_BIT | 0x70)
+#define CC1120_RSSI1 (CC1120_EXTENDED_BIT | 0x71)
+#define CC1120_RSSI0 (CC1120_EXTENDED_BIT | 0x72)
+#define CC1120_MARCSTATE (CC1120_EXTENDED_BIT | 0x73)
+#define CC1120_LQI_VAL (CC1120_EXTENDED_BIT | 0x74)
+#define CC1120_PQT_SYNC_ERR (CC1120_EXTENDED_BIT | 0x75)
+#define CC1120_DEM_STATUS (CC1120_EXTENDED_BIT | 0x76)
+#define CC1120_FREQOFF_EST1 (CC1120_EXTENDED_BIT | 0x77)
+#define CC1120_FREQOFF_EST0 (CC1120_EXTENDED_BIT | 0x78)
+#define CC1120_AGC_GAIN3 (CC1120_EXTENDED_BIT | 0x79)
+#define CC1120_AGC_GAIN2 (CC1120_EXTENDED_BIT | 0x7a)
+#define CC1120_AGC_GAIN1 (CC1120_EXTENDED_BIT | 0x7b)
+#define CC1120_AGC_GAIN0 (CC1120_EXTENDED_BIT | 0x7c)
+#define CC1120_SOFT_RX_DATA_OUT (CC1120_EXTENDED_BIT | 0x7d)
+#define CC1120_SOFT_TX_DATA_IN (CC1120_EXTENDED_BIT | 0x7e)
+#define CC1120_ASK_SOFT_RX_DATA (CC1120_EXTENDED_BIT | 0x7f)
+#define CC1120_RNDGEN (CC1120_EXTENDED_BIT | 0x80)
+#define CC1120_MAGN2 (CC1120_EXTENDED_BIT | 0x81)
+#define CC1120_MAGN1 (CC1120_EXTENDED_BIT | 0x82)
+#define CC1120_MAGN0 (CC1120_EXTENDED_BIT | 0x83)
+#define CC1120_ANG1 (CC1120_EXTENDED_BIT | 0x84)
+#define CC1120_ANG0 (CC1120_EXTENDED_BIT | 0x85)
+#define CC1120_CHFILT_I2 (CC1120_EXTENDED_BIT | 0x86)
+#define CC1120_CHFILT_I1 (CC1120_EXTENDED_BIT | 0x87)
+#define CC1120_CHFILT_I0 (CC1120_EXTENDED_BIT | 0x88)
+#define CC1120_CHFILT_Q2 (CC1120_EXTENDED_BIT | 0x89)
+#define CC1120_CHFILT_Q1 (CC1120_EXTENDED_BIT | 0x8a)
+#define CC1120_CHFILT_Q0 (CC1120_EXTENDED_BIT | 0x8b)
+#define CC1120_GPIO_STATUS (CC1120_EXTENDED_BIT | 0x8c)
+#define CC1120_FSCAL_CTRL (CC1120_EXTENDED_BIT | 0x8d)
+#define CC1120_PHASE_ADJUST (CC1120_EXTENDED_BIT | 0x8e)
+#define CC1120_PARTNUMBER (CC1120_EXTENDED_BIT | 0x8f)
+#define CC1120_PARTVERSION (CC1120_EXTENDED_BIT | 0x90)
+#define CC1120_SERIAL_STATUS (CC1120_EXTENDED_BIT | 0x91)
+#define CC1120_RX_STATUS (CC1120_EXTENDED_BIT | 0x92)
+#define CC1120_TX_STATUS (CC1120_EXTENDED_BIT | 0x93)
+#define CC1120_MARC_STATUS1 (CC1120_EXTENDED_BIT | 0x94)
+# define CC1120_MARC_STATUS1_NO_FAILURE 0
+# define CC1120_MARC_STATUS1_RX_TIMEOUT 1
+# define CC1120_MARC_STATUS1_RX_TERMINATION 2
+# define CC1120_MARC_STATUS1_EWOR_SYNC_LOST 3
+# define CC1120_MARC_STATUS1_MAXIMUM_LENGTH 4
+# define CC1120_MARC_STATUS1_ADDRESS 5
+# define CC1120_MARC_STATUS1_CRC 6
+# define CC1120_MARC_STATUS1_TX_FIFO_OVERFLOW 7
+# define CC1120_MARC_STATUS1_TX_FIFO_UNDERFLOW 8
+# define CC1120_MARC_STATUS1_RX_FIFO_OVERFLOW 9
+# define CC1120_MARC_STATUS1_RX_FIFO_UNDERFLOW 10
+# define CC1120_MARC_STATUS1_TX_ON_CCA_FAILED 11
+# define CC1120_MARC_STATUS1_TX_FINISHED 0x40
+# define CC1120_MARC_STATUS1_RX_FINISHED 0x80
+#define CC1120_MARC_STATUS0 (CC1120_EXTENDED_BIT | 0x95)
+#define CC1120_PA_IFAMP_TEST (CC1120_EXTENDED_BIT | 0x96)
+#define CC1120_FSRF_TEST (CC1120_EXTENDED_BIT | 0x97)
+#define CC1120_PRE_TEST (CC1120_EXTENDED_BIT | 0x98)
+#define CC1120_PRE_OVR (CC1120_EXTENDED_BIT | 0x99)
+#define CC1120_ADC_TEST (CC1120_EXTENDED_BIT | 0x9a)
+#define CC1120_DVC_TEST (CC1120_EXTENDED_BIT | 0x9b)
+#define CC1120_ATEST (CC1120_EXTENDED_BIT | 0x9c)
+#define CC1120_ATEST_LVDS (CC1120_EXTENDED_BIT | 0x9d)
+#define CC1120_ATEST_MODE (CC1120_EXTENDED_BIT | 0x9e)
+#define CC1120_XOSC_TEST1 (CC1120_EXTENDED_BIT | 0x9f)
+#define CC1120_XOSC_TEST0 (CC1120_EXTENDED_BIT | 0xa0)
+#define CC1120_RXFIRST (CC1120_EXTENDED_BIT | 0xd2)
+#define CC1120_TXFIRST (CC1120_EXTENDED_BIT | 0xd3)
+#define CC1120_RXLAST (CC1120_EXTENDED_BIT | 0xd4)
+#define CC1120_TXLAST (CC1120_EXTENDED_BIT | 0xd5)
+#define CC1120_NUM_TXBYTES (CC1120_EXTENDED_BIT | 0xd6)
+#define CC1120_NUM_RXBYTES (CC1120_EXTENDED_BIT | 0xd7)
+#define CC1120_FIFO_NUM_TXBYTES (CC1120_EXTENDED_BIT | 0xd8)
+#define CC1120_FIFO_NUM_RXBYTES (CC1120_EXTENDED_BIT | 0xd9)
+
+/* Status byte */
+#define CC1120_STATUS_CHIP_RDY 7
+#define CC1120_STATUS_STATE 4
+#define CC1120_STATUS_STATE_IDLE 0
+#define CC1120_STATUS_STATE_RX 1
+#define CC1120_STATUS_STATE_TX 2
+#define CC1120_STATUS_STATE_FSTXON 3
+#define CC1120_STATUS_STATE_CALIBRATE 4
+#define CC1120_STATUS_STATE_SETTLING 5
+#define CC1120_STATUS_STATE_RX_FIFO_ERROR 6
+#define CC1120_STATUS_STATE_TX_FIFO_ERROR 7
+#define CC1120_STATUS_STATE_MASK 7
+
+#endif /* _AO_CC1120_H_ */
diff --git a/src/drivers/ao_cc1120_CC1120.h b/src/drivers/ao_cc1120_CC1120.h
new file mode 100644
index 00000000..44cca938
--- /dev/null
+++ b/src/drivers/ao_cc1120_CC1120.h
@@ -0,0 +1,210 @@
+/* RX filter BW = 100.000000 */
+/* Address config = No address check */
+/* Packet length = 255 */
+/* Symbol rate = 38.3606 */
+/* PA ramping = false */
+/* Carrier frequency = 434.549988 */
+/* Bit rate = 38.3606 */
+/* Whitening = true */
+/* Manchester enable = false */
+/* Modulation format = 2-GFSK */
+/* Packet length mode = Variable */
+/* Device address = 0 */
+/* TX power = 15 */
+/* Deviation = 20.507812 */
+/***************************************************************
+ * SmartRF Studio(tm) Export
+ *
+ * Radio register settings specifed with address, value
+ *
+ * RF device: CC1120
+ *
+ ***************************************************************/
+
+ CC1120_SYNC3, 0xD3, /* Sync Word Configuration [31:24] */
+ CC1120_SYNC2, 0x91, /* Sync Word Configuration [23:16] */
+ CC1120_SYNC1, 0xD3, /* Sync Word Configuration [15:8] */
+ CC1120_SYNC0, 0x91, /* Sync Word Configuration [7:0] */
+
+ CC1120_SYNC_CFG1, /* Sync Word Detection Configuration */
+ (CC1120_SYNC_CFG1_DEM_CFG_PQT_GATING_ENABLED << CC1120_SYNC_CFG1_DEM_CFG) |
+ (0x07 << CC1120_SYNC_CFG1_SYNC_THR),
+ CC1120_SYNC_CFG0,
+ (CC1120_SYNC_CFG0_SYNC_MODE_16_BITS << CC1120_SYNC_CFG0_SYNC_MODE) |
+ (CC1120_SYNC_CFG0_SYNC_NUM_ERROR_2 << CC1120_SYNC_CFG0_SYNC_NUM_ERROR),
+ CC1120_DCFILT_CFG, 0x1c, /* Digital DC Removal Configuration */
+ CC1120_PREAMBLE_CFG1, /* Preamble Length Configuration */
+ (CC1120_PREAMBLE_CFG1_NUM_PREAMBLE_4_BYTES << CC1120_PREAMBLE_CFG1_NUM_PREAMBLE) |
+ (CC1120_PREAMBLE_CFG1_PREAMBLE_WORD_AA << CC1120_PREAMBLE_CFG1_PREAMBLE_WORD),
+ CC1120_PREAMBLE_CFG0,
+ (1 << CC1120_PREAMBLE_CFG0_PQT_EN) |
+ (0x6 << CC1120_PREAMBLE_CFG0_PQT),
+ CC1120_FREQ_IF_CFG, 0x40, /* RX Mixer Frequency Configuration */
+ CC1120_IQIC, 0x46, /* Digital Image Channel Compensation Configuration */
+ CC1120_CHAN_BW, 0x02, /* Channel Filter Configuration */
+
+ CC1120_MDMCFG1, /* General Modem Parameter Configuration */
+ (0 << CC1120_MDMCFG1_CARRIER_SENSE_GATE) |
+ (1 << CC1120_MDMCFG1_FIFO_EN) |
+ (0 << CC1120_MDMCFG1_MANCHESTER_EN) |
+ (0 << CC1120_MDMCFG1_INVERT_DATA_EN) |
+ (0 << CC1120_MDMCFG1_COLLISION_DETECT_EN) |
+ (CC1120_MDMCFG1_DVGA_GAIN_9 << CC1120_MDMCFG1_DVGA_GAIN) |
+ (0 << CC1120_MDMCFG1_SINGLE_ADC_EN),
+ CC1120_MDMCFG0, 0x05, /* General Modem Parameter Configuration */
+
+ CC1120_AGC_REF, 0x20, /* AGC Reference Level Configuration */
+ CC1120_AGC_CS_THR, 0x19, /* Carrier Sense Threshold Configuration */
+ CC1120_AGC_GAIN_ADJUST, 0x00, /* RSSI Offset Configuration */
+ CC1120_AGC_CFG3, 0x91, /* AGC Configuration */
+ CC1120_AGC_CFG2, 0x20, /* AGC Configuration */
+ CC1120_AGC_CFG1, 0xa9, /* AGC Configuration */
+ CC1120_AGC_CFG0, 0xcf, /* AGC Configuration */
+ CC1120_FIFO_CFG, /* FIFO Configuration */
+ (0 << CC1120_FIFO_CFG_CRC_AUTOFLUSH) |
+ (0x40 << CC1120_FIFO_CFG_FIFO_THR),
+ CC1120_DEV_ADDR, 0x00, /* Device Address Configuration */
+ CC1120_SETTLING_CFG, /* Frequency Synthesizer Calibration and Settling Configuration */
+ (CC1120_SETTLING_CFG_FS_AUTOCAL_IDLE_TO_ON << CC1120_SETTLING_CFG_FS_AUTOCAL) |
+ (CC1120_SETTLING_CFG_LOCK_TIME_50_20 << CC1120_SETTLING_CFG_LOCK_TIME) |
+ (CC1120_SETTLING_CFG_FSREG_TIME_60 << CC1120_SETTLING_CFG_FSREG_TIME),
+ CC1120_FS_CFG, /* Frequency Synthesizer Configuration */
+ (1 << CC1120_FS_CFG_LOCK_EN) |
+ (CC1120_FS_CFG_FSD_BANDSELECT_410_480 << CC1120_FS_CFG_FSD_BANDSELECT),
+ CC1120_WOR_CFG1, 0x08, /* eWOR Configuration, Reg 1 */
+ CC1120_WOR_CFG0, 0x21, /* eWOR Configuration, Reg 0 */
+ CC1120_WOR_EVENT0_MSB, 0x00, /* Event 0 Configuration */
+ CC1120_WOR_EVENT0_LSB, 0x00, /* Event 0 Configuration */
+#if 0
+ CC1120_PKT_CFG2, 0x04, /* Packet Configuration, Reg 2 */
+ CC1120_PKT_CFG1, 0x45, /* Packet Configuration, Reg 1 */
+ CC1120_PKT_CFG0, 0x00, /* Packet Configuration, Reg 0 */
+#endif
+ CC1120_RFEND_CFG1, 0x0f, /* RFEND Configuration, Reg 1 */
+ CC1120_RFEND_CFG0, 0x00, /* RFEND Configuration, Reg 0 */
+ // CC1120_PA_CFG2, 0x3f, /* Power Amplifier Configuration, Reg 2 */
+ CC1120_PA_CFG2, 0x3f, /* Power Amplifier Configuration, Reg 2 */
+ CC1120_PA_CFG1, 0x56, /* Power Amplifier Configuration, Reg 1 */
+ CC1120_PA_CFG0, 0x7b, /* Power Amplifier Configuration, Reg 0 */
+ CC1120_PKT_LEN, 0xff, /* Packet Length Configuration */
+ CC1120_IF_MIX_CFG, 0x00, /* IF Mix Configuration */
+ CC1120_FREQOFF_CFG, 0x22, /* Frequency Offset Correction Configuration */
+ CC1120_TOC_CFG, 0x0b, /* Timing Offset Correction Configuration */
+ CC1120_MARC_SPARE, 0x00, /* MARC Spare */
+ CC1120_ECG_CFG, 0x00, /* External Clock Frequency Configuration */
+ CC1120_SOFT_TX_DATA_CFG, 0x00, /* Soft TX Data Configuration */
+ CC1120_EXT_CTRL, 0x00, /* External Control Configuration */
+ CC1120_RCCAL_FINE, 0x00, /* RC Oscillator Calibration (fine) */
+ CC1120_RCCAL_COARSE, 0x00, /* RC Oscillator Calibration (coarse) */
+ CC1120_RCCAL_OFFSET, 0x00, /* RC Oscillator Calibration Clock Offset */
+ CC1120_FREQOFF1, 0x00, /* Frequency Offset (MSB) */
+ CC1120_FREQOFF0, 0x00, /* Frequency Offset (LSB) */
+ CC1120_IF_ADC2, 0x02, /* Analog to Digital Converter Configuration, Reg 2 */
+ CC1120_IF_ADC1, 0xa6, /* Analog to Digital Converter Configuration, Reg 1 */
+ CC1120_IF_ADC0, 0x04, /* Analog to Digital Converter Configuration, Reg 0 */
+ CC1120_FS_DIG1, 0x00, /* */
+ CC1120_FS_DIG0, 0x5f, /* */
+ CC1120_FS_CAL3, 0x00, /* */
+ CC1120_FS_CAL2, 0x20, /* */
+ CC1120_FS_CAL1, 0x40, /* */
+ CC1120_FS_CAL0, 0x0e, /* */
+ CC1120_FS_CHP, 0x28, /* Charge Pump Configuration */
+ CC1120_FS_DIVTWO, 0x03, /* Divide by 2 */
+ CC1120_FS_DSM1, 0x00, /* Digital Synthesizer Module Configuration, Reg 1 */
+ CC1120_FS_DSM0, 0x33, /* Digital Synthesizer Module Configuration, Reg 0 */
+ CC1120_FS_DVC1, 0xff, /* Divider Chain Configuration, Reg 1 */
+ CC1120_FS_DVC0, 0x17, /* Divider Chain Configuration, Reg 0 */
+ CC1120_FS_LBI, 0x00, /* Local Bias Configuration */
+ CC1120_FS_PFD, 0x50, /* Phase Frequency Detector Configuration */
+ CC1120_FS_PRE, 0x6e, /* Prescaler Configuration */
+ CC1120_FS_REG_DIV_CML, 0x14, /* */
+ CC1120_FS_SPARE, 0xac, /* */
+ CC1120_FS_VCO4, 0x14, /* VCO Configuration, Reg 4 */
+ CC1120_FS_VCO3, 0x00, /* VCO Configuration, Reg 3 */
+ CC1120_FS_VCO2, 0x00, /* VCO Configuration, Reg 2 */
+ CC1120_FS_VCO1, 0x00, /* VCO Configuration, Reg 1 */
+ CC1120_FS_VCO0, 0xb4, /* VCO Configuration, Reg 0 */
+ CC1120_GBIAS6, 0x00, /* Global Bias Configuration, Reg 6 */
+ CC1120_GBIAS5, 0x02, /* Global Bias Configuration, Reg 5 */
+ CC1120_GBIAS4, 0x00, /* Global Bias Configuration, Reg 4 */
+ CC1120_GBIAS3, 0x00, /* Global Bias Configuration, Reg 3 */
+ CC1120_GBIAS2, 0x10, /* Global Bias Configuration, Reg 2 */
+ CC1120_GBIAS1, 0x00, /* Global Bias Configuration, Reg 1 */
+ CC1120_GBIAS0, 0x00, /* Global Bias Configuration, Reg 0 */
+ CC1120_IFAMP, 0x01, /* Intermediate Frequency Amplifier Configuration */
+ CC1120_LNA, 0x01, /* Low Noise Amplifier Configuration */
+ CC1120_RXMIX, 0x01, /* RX Mixer Configuration */
+ CC1120_XOSC5, 0x0e, /* Crystal Oscillator Configuration, Reg 5 */
+ CC1120_XOSC4, 0xa0, /* Crystal Oscillator Configuration, Reg 4 */
+ CC1120_XOSC3, 0x03, /* Crystal Oscillator Configuration, Reg 3 */
+ CC1120_XOSC2, 0x04, /* Crystal Oscillator Configuration, Reg 2 */
+ CC1120_XOSC1, 0x01, /* Crystal Oscillator Configuration, Reg 1 */
+ CC1120_XOSC0, 0x00, /* Crystal Oscillator Configuration, Reg 0 */
+ CC1120_ANALOG_SPARE, 0x00, /* */
+ CC1120_PA_CFG3, 0x00, /* Power Amplifier Configuration, Reg 3 */
+ CC1120_WOR_TIME1, 0x00, /* eWOR Timer Status (MSB) */
+ CC1120_WOR_TIME0, 0x00, /* eWOR Timer Status (LSB) */
+ CC1120_WOR_CAPTURE1, 0x00, /* eWOR Timer Capture (MSB) */
+ CC1120_WOR_CAPTURE0, 0x00, /* eWOR Timer Capture (LSB) */
+ CC1120_BIST, 0x00, /* MARC BIST */
+ CC1120_DCFILTOFFSET_I1, 0x00, /* DC Filter Offset I (MSB) */
+ CC1120_DCFILTOFFSET_I0, 0x00, /* DC Filter Offset I (LSB) */
+ CC1120_DCFILTOFFSET_Q1, 0x00, /* DC Filter Offset Q (MSB) */
+ CC1120_DCFILTOFFSET_Q0, 0x00, /* DC Filter Offset Q (LSB) */
+ CC1120_IQIE_I1, 0x00, /* IQ Imbalance Value I (MSB) */
+ CC1120_IQIE_I0, 0x00, /* IQ Imbalance Value I (LSB) */
+ CC1120_IQIE_Q1, 0x00, /* IQ Imbalance Value Q (MSB) */
+ CC1120_IQIE_Q0, 0x00, /* IQ Imbalance Value Q (LSB) */
+ CC1120_RSSI1, 0x80, /* Received Signal Strength Indicator (MSB) */
+ CC1120_RSSI0, 0x00, /* Received Signal Strength Indicator (LSB) */
+ CC1120_MARCSTATE, 0x41, /* MARC State */
+ CC1120_LQI_VAL, 0x00, /* Link Quality Indicator Value */
+ CC1120_PQT_SYNC_ERR, 0xff, /* Preamble and Sync Word Error */
+ CC1120_DEM_STATUS, 0x00, /* Demodulator Status */
+ CC1120_FREQOFF_EST1, 0x00, /* Frequency Offset Estimate (MSB) */
+ CC1120_FREQOFF_EST0, 0x00, /* Frequency Offset Estimate (LSB) */
+ CC1120_AGC_GAIN3, 0x00, /* AGC Gain, Reg 3 */
+ CC1120_AGC_GAIN2, 0xd1, /* AGC Gain, Reg 2 */
+ CC1120_AGC_GAIN1, 0x00, /* AGC Gain, Reg 1 */
+ CC1120_AGC_GAIN0, 0x3f, /* AGC Gain, Reg 0 */
+ CC1120_SOFT_RX_DATA_OUT, 0x00, /* Soft Decision Symbol Data */
+ CC1120_SOFT_TX_DATA_IN, 0x00, /* Soft TX Data Input Register */
+ CC1120_ASK_SOFT_RX_DATA, 0x30, /* AGC ASK Soft Decision Output */
+ CC1120_RNDGEN, 0x7f, /* Random Number Value */
+ CC1120_MAGN2, 0x00, /* Signal Magnitude after CORDIC [16] */
+ CC1120_MAGN1, 0x00, /* Signal Magnitude after CORDIC [15:8] */
+ CC1120_MAGN0, 0x00, /* Signal Magnitude after CORDIC [7:0] */
+ CC1120_ANG1, 0x00, /* Signal Angular after CORDIC [9:8] */
+ CC1120_ANG0, 0x00, /* Signal Angular after CORDIC [7:0] */
+ CC1120_CHFILT_I2, 0x08, /* Channel Filter Data Real Part [18:16] */
+ CC1120_CHFILT_I1, 0x00, /* Channel Filter Data Real Part [15:8] */
+ CC1120_CHFILT_I0, 0x00, /* Channel Filter Data Real Part [7:0] */
+ CC1120_CHFILT_Q2, 0x00, /* Channel Filter Data Imaginary Part [18:16] */
+ CC1120_CHFILT_Q1, 0x00, /* Channel Filter Data Imaginary Part [15:8] */
+ CC1120_CHFILT_Q0, 0x00, /* Channel Filter Data Imaginary Part [7:0] */
+ CC1120_GPIO_STATUS, 0x00, /* GPIO Status */
+ CC1120_FSCAL_CTRL, 0x01, /* */
+ CC1120_PHASE_ADJUST, 0x00, /* */
+ CC1120_PARTNUMBER, 0x00, /* Part Number */
+ CC1120_PARTVERSION, 0x00, /* Part Revision */
+ CC1120_SERIAL_STATUS, 0x00, /* Serial Status */
+ CC1120_RX_STATUS, 0x01, /* RX Status */
+ CC1120_TX_STATUS, 0x00, /* TX Status */
+ CC1120_MARC_STATUS1, 0x00, /* MARC Status, Reg 1 */
+ CC1120_MARC_STATUS0, 0x00, /* MARC Status, Reg 0 */
+ CC1120_PA_IFAMP_TEST, 0x00, /* */
+ CC1120_FSRF_TEST, 0x00, /* */
+ CC1120_PRE_TEST, 0x00, /* */
+ CC1120_PRE_OVR, 0x00, /* */
+ CC1120_ADC_TEST, 0x00, /* ADC Test */
+ CC1120_DVC_TEST, 0x0b, /* DVC Test */
+ CC1120_ATEST, 0x40, /* */
+ CC1120_ATEST_LVDS, 0x00, /* */
+ CC1120_ATEST_MODE, 0x00, /* */
+ CC1120_XOSC_TEST1, 0x3c, /* */
+ CC1120_XOSC_TEST0, 0x00, /* */
+ CC1120_RXFIRST, 0x00, /* RX FIFO Pointer (first entry) */
+ CC1120_TXFIRST, 0x00, /* TX FIFO Pointer (first entry) */
+ CC1120_RXLAST, 0x00, /* RX FIFO Pointer (last entry) */
+ CC1120_TXLAST, 0x00, /* TX FIFO Pointer (last entry) */
+
diff --git a/src/drivers/ao_companion.c b/src/drivers/ao_companion.c
new file mode 100644
index 00000000..c749adea
--- /dev/null
+++ b/src/drivers/ao_companion.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright © 2011 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_companion.h>
+
+#ifdef MEGAMETRUM
+#define ao_spi_slow(b)
+#define ao_spi_fast(b)
+#endif
+
+#define COMPANION_SELECT() do { \
+ ao_spi_get_bit(AO_COMPANION_CS_PORT, \
+ AO_COMPANION_CS_PIN, \
+ AO_COMPANION_CS, \
+ AO_COMPANION_SPI_BUS, \
+ AO_SPI_SPEED_200kHz); \
+ } while (0)
+
+#define COMPANION_DESELECT() do { \
+ ao_spi_put_bit(AO_COMPANION_CS_PORT, \
+ AO_COMPANION_CS_PIN, \
+ AO_COMPANION_CS, \
+ AO_COMPANION_SPI_BUS); \
+ } while (0)
+
+__xdata struct ao_companion_command ao_companion_command;
+__xdata struct ao_companion_setup ao_companion_setup;
+
+__xdata uint16_t ao_companion_data[AO_COMPANION_MAX_CHANNELS];
+__pdata uint8_t ao_companion_running;
+__xdata uint8_t ao_companion_mutex;
+
+static void
+ao_companion_send_command(uint8_t command)
+{
+ ao_companion_command.command = command;
+ ao_companion_command.flight_state = ao_flight_state;
+ ao_companion_command.tick = ao_time();
+ ao_companion_command.serial = ao_serial_number;
+ ao_companion_command.flight = ao_flight_number;
+ ao_companion_command.accel = ao_accel;
+ ao_companion_command.speed = ao_speed;
+ ao_companion_command.height = ao_height;
+ ao_companion_command.motor_number = ao_motor_number;
+ ao_spi_send(&ao_companion_command, sizeof (ao_companion_command), AO_COMPANION_SPI_BUS);
+}
+
+static uint8_t
+ao_companion_get_setup(void)
+{
+ COMPANION_SELECT();
+ ao_companion_send_command(AO_COMPANION_SETUP);
+ ao_spi_recv(&ao_companion_setup, sizeof (ao_companion_setup), AO_COMPANION_SPI_BUS);
+ COMPANION_DESELECT();
+ return (ao_companion_setup.board_id ==
+ (uint16_t) ~ao_companion_setup.board_id_inverse);
+}
+
+static void
+ao_companion_get_data(void)
+{
+ COMPANION_SELECT();
+ ao_companion_send_command(AO_COMPANION_FETCH);
+ ao_mutex_get(&ao_companion_mutex);
+ ao_spi_recv(&ao_companion_data, ao_companion_setup.channels * 2, AO_COMPANION_SPI_BUS);
+ ao_mutex_put(&ao_companion_mutex);
+ COMPANION_DESELECT();
+}
+
+static void
+ao_companion_notify(void)
+{
+ COMPANION_SELECT();
+ ao_companion_send_command(AO_COMPANION_NOTIFY);
+ COMPANION_DESELECT();
+}
+
+void
+ao_companion(void)
+{
+ uint8_t i;
+ while (!ao_flight_number)
+ ao_sleep(&ao_flight_number);
+ for (i = 0; i < 10; i++) {
+ ao_delay(AO_SEC_TO_TICKS(1));
+ if ((ao_companion_running = ao_companion_get_setup()))
+ break;
+ }
+ while (ao_companion_running) {
+ ao_alarm(ao_companion_setup.update_period);
+ if (ao_sleep(DATA_TO_XDATA(&ao_flight_state)))
+ ao_companion_get_data();
+ else
+ ao_companion_notify();
+ }
+ ao_exit();
+}
+
+void
+ao_companion_status(void) __reentrant
+{
+ uint8_t i;
+ printf("Companion running: %d\n", ao_companion_running);
+ if (!ao_companion_running)
+ return;
+ printf("device: %d\n", ao_companion_setup.board_id);
+ printf("update period: %d\n", ao_companion_setup.update_period);
+ printf("channels: %d\n", ao_companion_setup.channels);
+ printf("data:");
+ for(i = 0; i < ao_companion_setup.channels; i++)
+ printf(" %5u", ao_companion_data[i]);
+ printf("\n");
+}
+
+__code struct ao_cmds ao_companion_cmds[] = {
+ { ao_companion_status, "L\0Companion link status" },
+ { 0, NULL },
+};
+
+static __xdata struct ao_task ao_companion_task;
+
+void
+ao_companion_init(void)
+{
+ ao_enable_output(AO_COMPANION_CS_PORT, AO_COMPANION_CS_PIN, AO_COMPANION_CS, 1);
+ ao_cmd_register(&ao_companion_cmds[0]);
+ ao_add_task(&ao_companion_task, ao_companion, "companion");
+}
diff --git a/src/drivers/ao_event.c b/src/drivers/ao_event.c
new file mode 100644
index 00000000..440ef2de
--- /dev/null
+++ b/src/drivers/ao_event.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_event.h>
+
+#define AO_EVENT_QUEUE 64
+
+#define ao_event_queue_next(n) (((n) + 1) & (AO_EVENT_QUEUE - 1))
+#define ao_event_queue_prev(n) (((n) - 1) & (AO_EVENT_QUEUE - 1))
+#define ao_event_queue_empty() (ao_event_queue_insert == ao_event_queue_remove)
+#define ao_event_queue_full() (ao_event_queue_next(ao_event_queue_insert) == ao_event_queue_remove)
+
+/*
+ * Whether a sequence of events from the same device should be collapsed
+ */
+#define ao_event_can_collapse(type) ((type) == AO_EVENT_QUADRATURE)
+
+struct ao_event ao_event_queue[AO_EVENT_QUEUE];
+uint8_t ao_event_queue_insert;
+uint8_t ao_event_queue_remove;
+
+
+uint8_t
+ao_event_get(struct ao_event *ev)
+{
+ ao_arch_critical(
+ while (ao_event_queue_empty())
+ ao_sleep(&ao_event_queue);
+ *ev = ao_event_queue[ao_event_queue_remove];
+ ao_event_queue_remove = ao_event_queue_next(ao_event_queue_remove);
+ );
+}
+
+/* called with interrupts disabled */
+void
+ao_event_put_isr(uint8_t type, uint8_t unit, uint32_t value)
+{
+ if (!ao_event_queue_full()) {
+
+ if (ao_event_can_collapse(type) && !ao_event_queue_empty()) {
+ uint8_t prev = ao_event_queue_prev(ao_event_queue_insert);
+
+ if (ao_event_queue[prev].type == type &&
+ ao_event_queue[prev].unit == unit)
+ ao_event_queue_insert = prev;
+ }
+ ao_event_queue[ao_event_queue_insert] = (struct ao_event) {
+ .type = type,
+ .unit = unit,
+ .tick = ao_tick_count,
+ .value = value
+ };
+ ao_event_queue_insert = ao_event_queue_next(ao_event_queue_insert);
+ ao_wakeup(&ao_event_queue);
+ }
+}
+
+void
+ao_event_put(uint8_t type, uint8_t unit, uint32_t value)
+{
+ ao_arch_critical(ao_event_put_isr(type, unit, value););
+}
diff --git a/src/drivers/ao_event.h b/src/drivers/ao_event.h
new file mode 100644
index 00000000..25c49c35
--- /dev/null
+++ b/src/drivers/ao_event.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_EVENT_H_
+#define _AO_EVENT_H_
+
+#define AO_EVENT_NONE 0
+#define AO_EVENT_QUADRATURE 1
+#define AO_EVENT_BUTTON 2
+
+struct ao_event {
+ uint8_t type;
+ uint8_t unit;
+ uint16_t tick;
+ uint32_t value;
+};
+
+uint8_t
+ao_event_get(struct ao_event *ev);
+
+void
+ao_event_put_isr(uint8_t type, uint8_t unit, uint32_t value);
+
+void
+ao_event_put(uint8_t type, uint8_t unit, uint32_t value);
+
+#endif /* _AO_EVENT_H_ */
diff --git a/src/drivers/ao_gps_sirf.c b/src/drivers/ao_gps_sirf.c
new file mode 100644
index 00000000..91fc948b
--- /dev/null
+++ b/src/drivers/ao_gps_sirf.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef AO_GPS_TEST
+#include "ao.h"
+#endif
+
+__xdata uint8_t ao_gps_mutex;
+__pdata uint16_t ao_gps_tick;
+__xdata struct ao_telemetry_location ao_gps_data;
+__xdata struct ao_telemetry_satellite ao_gps_tracking_data;
+
+static const char ao_gps_set_nmea[] = "\r\n$PSRF100,0,57600,8,1,0*37\r\n";
+
+const char ao_gps_config[] = {
+
+ 0xa0, 0xa2, 0x00, 0x0e, /* length: 14 bytes */
+ 136, /* mode control */
+ 0, 0, /* reserved */
+ 0, /* degraded mode (allow 1-SV navigation) */
+ 0, 0, /* reserved */
+ 0, 0, /* user specified altitude */
+ 2, /* alt hold mode (disabled, require 3d fixes) */
+ 0, /* alt hold source (use last computed altitude) */
+ 0, /* reserved */
+ 10, /* Degraded time out (10 sec) */
+ 10, /* Dead Reckoning time out (10 sec) */
+ 0, /* Track smoothing (disabled) */
+ 0x00, 0x8e, 0xb0, 0xb3,
+
+ 0xa0, 0xa2, 0x00, 0x08, /* length: 8 bytes */
+ 166, /* Set message rate */
+ 2, /* enable/disable all messages */
+ 0, /* message id (ignored) */
+ 0, /* update rate (0 = disable) */
+ 0, 0, 0, 0, /* reserved */
+ 0x00, 0xa8, 0xb0, 0xb3,
+
+ 0xa0, 0xa2, 0x00, 0x02, /* length: 2 bytes */
+ 143, /* static navigation */
+ 0, /* disable */
+ 0x00, 0x8f, 0xb0, 0xb3,
+};
+
+#define NAV_TYPE_GPS_FIX_TYPE_MASK (7 << 0)
+#define NAV_TYPE_NO_FIX (0 << 0)
+#define NAV_TYPE_SV_KF (1 << 0)
+#define NAV_TYPE_2_SV_KF (2 << 0)
+#define NAV_TYPE_3_SV_KF (3 << 0)
+#define NAV_TYPE_4_SV_KF (4 << 0)
+#define NAV_TYPE_2D_LEAST_SQUARES (5 << 0)
+#define NAV_TYPE_3D_LEAST_SQUARES (6 << 0)
+#define NAV_TYPE_DR (7 << 0)
+#define NAV_TYPE_TRICKLE_POWER (1 << 3)
+#define NAV_TYPE_ALTITUDE_HOLD_MASK (3 << 4)
+#define NAV_TYPE_ALTITUDE_HOLD_NONE (0 << 4)
+#define NAV_TYPE_ALTITUDE_HOLD_KF (1 << 4)
+#define NAV_TYPE_ALTITUDE_HOLD_USER (2 << 4)
+#define NAV_TYPE_ALTITUDE_HOLD_ALWAYS (3 << 4)
+#define NAV_TYPE_DOP_LIMIT_EXCEEDED (1 << 6)
+#define NAV_TYPE_DGPS_APPLIED (1 << 7)
+#define NAV_TYPE_SENSOR_DR (1 << 8)
+#define NAV_TYPE_OVERDETERMINED (1 << 9)
+#define NAV_TYPE_DR_TIMEOUT_EXCEEDED (1 << 10)
+#define NAV_TYPE_FIX_MI_EDIT (1 << 11)
+#define NAV_TYPE_INVALID_VELOCITY (1 << 12)
+#define NAV_TYPE_ALTITUDE_HOLD_DISABLED (1 << 13)
+#define NAV_TYPE_DR_ERROR_STATUS_MASK (3 << 14)
+#define NAV_TYPE_DR_ERROR_STATUS_GPS_ONLY (0 << 14)
+#define NAV_TYPE_DR_ERROR_STATUS_DR_FROM_GPS (1 << 14)
+#define NAV_TYPE_DR_ERROR_STATUS_DR_SENSOR_ERROR (2 << 14)
+#define NAV_TYPE_DR_ERROR_STATUS_DR_IN_TEST (3 << 14)
+
+struct sirf_geodetic_nav_data {
+ uint16_t nav_type;
+ uint16_t utc_year;
+ uint8_t utc_month;
+ uint8_t utc_day;
+ uint8_t utc_hour;
+ uint8_t utc_minute;
+ uint16_t utc_second;
+ int32_t lat;
+ int32_t lon;
+ int32_t alt_msl;
+ uint16_t ground_speed;
+ uint16_t course;
+ int16_t climb_rate;
+ uint32_t h_error;
+ uint32_t v_error;
+ uint8_t num_sv;
+ uint8_t hdop;
+};
+
+static __xdata struct sirf_geodetic_nav_data ao_sirf_data;
+
+struct sirf_measured_sat_data {
+ uint8_t svid;
+ uint8_t c_n_1;
+};
+
+struct sirf_measured_tracker_data {
+ int16_t gps_week;
+ uint32_t gps_tow;
+ uint8_t channels;
+ struct sirf_measured_sat_data sats[12];
+};
+
+static __xdata struct sirf_measured_tracker_data ao_sirf_tracker_data;
+
+static __pdata uint16_t ao_sirf_cksum;
+static __pdata uint16_t ao_sirf_len;
+
+#ifndef ao_sirf_getchar
+#define ao_sirf_getchar ao_serial1_getchar
+#define ao_sirf_putchar ao_serial1_putchar
+#define ao_sirf_set_speed ao_serial1_set_speed
+#endif
+
+#define ao_sirf_byte() ((uint8_t) ao_sirf_getchar())
+
+static uint8_t data_byte(void)
+{
+ uint8_t c = ao_sirf_byte();
+ --ao_sirf_len;
+ ao_sirf_cksum += c;
+ return c;
+}
+
+static char __xdata *sirf_target;
+
+static void sirf_u16(uint8_t offset)
+{
+ uint16_t __xdata *ptr = (uint16_t __xdata *) (sirf_target + offset);
+ uint16_t val;
+
+ val = data_byte() << 8;
+ val |= data_byte ();
+ *ptr = val;
+}
+
+static void sirf_u8(uint8_t offset)
+{
+ uint8_t __xdata *ptr = (uint8_t __xdata *) (sirf_target + offset);
+ uint8_t val;
+
+ val = data_byte ();
+ *ptr = val;
+}
+
+static void sirf_u32(uint8_t offset) __reentrant
+{
+ uint32_t __xdata *ptr = (uint32_t __xdata *) (sirf_target + offset);
+ uint32_t val;
+
+ val = ((uint32_t) data_byte ()) << 24;
+ val |= ((uint32_t) data_byte ()) << 16;
+ val |= ((uint32_t) data_byte ()) << 8;
+ val |= ((uint32_t) data_byte ());
+ *ptr = val;
+}
+
+static void sirf_discard(uint8_t len)
+{
+ while (len--)
+ data_byte();
+}
+
+#define SIRF_END 0
+#define SIRF_DISCARD 1
+#define SIRF_U8 2
+#define SIRF_U16 3
+#define SIRF_U32 4
+#define SIRF_U8X10 5
+
+struct sirf_packet_parse {
+ uint8_t type;
+ uint8_t offset;
+};
+
+static void
+ao_sirf_parse(void __xdata *target, const struct sirf_packet_parse *parse) __reentrant
+{
+ uint8_t i, offset, j;
+
+ sirf_target = target;
+ for (i = 0; ; i++) {
+ offset = parse[i].offset;
+ switch (parse[i].type) {
+ case SIRF_END:
+ return;
+ case SIRF_DISCARD:
+ sirf_discard(offset);
+ break;
+ case SIRF_U8:
+ sirf_u8(offset);
+ break;
+ case SIRF_U16:
+ sirf_u16(offset);
+ break;
+ case SIRF_U32:
+ sirf_u32(offset);
+ break;
+ case SIRF_U8X10:
+ for (j = 10; j--;)
+ sirf_u8(offset++);
+ break;
+ }
+ }
+}
+
+static const struct sirf_packet_parse geodetic_nav_data_packet[] = {
+ { SIRF_DISCARD, 2 }, /* 1 nav valid */
+ { SIRF_U16, offsetof(struct sirf_geodetic_nav_data, nav_type) }, /* 3 */
+ { SIRF_DISCARD, 6 }, /* 5 week number, time of week */
+ { SIRF_U16, offsetof(struct sirf_geodetic_nav_data, utc_year) }, /* 11 */
+ { SIRF_U8, offsetof(struct sirf_geodetic_nav_data, utc_month) }, /* 13 */
+ { SIRF_U8, offsetof(struct sirf_geodetic_nav_data, utc_day) }, /* 14 */
+ { SIRF_U8, offsetof(struct sirf_geodetic_nav_data, utc_hour) }, /* 15 */
+ { SIRF_U8, offsetof(struct sirf_geodetic_nav_data, utc_minute) }, /* 16 */
+ { SIRF_U16, offsetof(struct sirf_geodetic_nav_data, utc_second) }, /* 17 */
+ { SIRF_DISCARD, 4 }, /* satellite id list */ /* 19 */
+ { SIRF_U32, offsetof(struct sirf_geodetic_nav_data, lat) }, /* 23 */
+ { SIRF_U32, offsetof(struct sirf_geodetic_nav_data, lon) }, /* 27 */
+ { SIRF_DISCARD, 4 }, /* altitude from ellipsoid */ /* 31 */
+ { SIRF_U32, offsetof(struct sirf_geodetic_nav_data, alt_msl) }, /* 35 */
+ { SIRF_DISCARD, 1 }, /* map datum */ /* 39 */
+ { SIRF_U16, offsetof(struct sirf_geodetic_nav_data, ground_speed) }, /* 40 */
+ { SIRF_U16, offsetof(struct sirf_geodetic_nav_data, course) }, /* 42 */
+ { SIRF_DISCARD, 2 }, /* magnetic variation */ /* 44 */
+ { SIRF_U16, offsetof(struct sirf_geodetic_nav_data, climb_rate) }, /* 46 */
+ { SIRF_DISCARD, 2 }, /* turn rate */ /* 48 */
+ { SIRF_U32, offsetof(struct sirf_geodetic_nav_data, h_error) }, /* 50 */
+ { SIRF_U32, offsetof(struct sirf_geodetic_nav_data, v_error) }, /* 54 */
+ { SIRF_DISCARD, 30 }, /* time error, h_vel error, clock_bias,
+ clock bias error, clock drift,
+ clock drift error, distance,
+ distance error, heading error */ /* 58 */
+ { SIRF_U8, offsetof(struct sirf_geodetic_nav_data, num_sv) }, /* 88 */
+ { SIRF_U8, offsetof(struct sirf_geodetic_nav_data, hdop) }, /* 89 */
+ { SIRF_DISCARD, 1 }, /* additional mode info */ /* 90 */
+ { SIRF_END, 0 }, /* 91 */
+};
+
+static void
+ao_sirf_parse_41(void) __reentrant
+{
+ ao_sirf_parse(&ao_sirf_data, geodetic_nav_data_packet);
+}
+
+static const struct sirf_packet_parse measured_tracker_data_packet[] = {
+ { SIRF_U16, offsetof (struct sirf_measured_tracker_data, gps_week) }, /* 1 week */
+ { SIRF_U32, offsetof (struct sirf_measured_tracker_data, gps_tow) }, /* 3 time of week */
+ { SIRF_U8, offsetof (struct sirf_measured_tracker_data, channels) }, /* 7 channels */
+ { SIRF_END, 0 },
+};
+
+static const struct sirf_packet_parse measured_sat_data_packet[] = {
+ { SIRF_U8, offsetof (struct sirf_measured_sat_data, svid) }, /* 0 SV id */
+ { SIRF_DISCARD, 4 }, /* 1 azimuth, 2 elevation, 3 state */
+ { SIRF_U8, offsetof (struct sirf_measured_sat_data, c_n_1) }, /* C/N0 1 */
+ { SIRF_DISCARD, 9 }, /* C/N0 2-10 */
+ { SIRF_END, 0 },
+};
+
+static void
+ao_sirf_parse_4(void) __reentrant
+{
+ uint8_t i;
+ ao_sirf_parse(&ao_sirf_tracker_data, measured_tracker_data_packet);
+ for (i = 0; i < 12; i++)
+ ao_sirf_parse(&ao_sirf_tracker_data.sats[i], measured_sat_data_packet);
+}
+
+static void
+ao_gps_setup(void) __reentrant
+{
+ uint8_t i, k;
+ ao_sirf_set_speed(AO_SERIAL_SPEED_4800);
+ for (i = 0; i < 64; i++)
+ ao_sirf_putchar(0x00);
+ for (k = 0; k < 3; k++)
+ for (i = 0; i < sizeof (ao_gps_set_nmea); i++)
+ ao_sirf_putchar(ao_gps_set_nmea[i]);
+ ao_sirf_set_speed(AO_SERIAL_SPEED_57600);
+ for (i = 0; i < 64; i++)
+ ao_sirf_putchar(0x00);
+}
+
+static const char ao_gps_set_message_rate[] = {
+ 0xa0, 0xa2, 0x00, 0x08,
+ 166,
+ 0,
+};
+
+void
+ao_sirf_set_message_rate(uint8_t msg, uint8_t rate) __reentrant
+{
+ uint16_t cksum = 0x00a6;
+ uint8_t i;
+
+ for (i = 0; i < sizeof (ao_gps_set_message_rate); i++)
+ ao_sirf_putchar(ao_gps_set_message_rate[i]);
+ ao_sirf_putchar(msg);
+ ao_sirf_putchar(rate);
+ cksum = 0xa6 + msg + rate;
+ for (i = 0; i < 4; i++)
+ ao_sirf_putchar(0);
+ ao_sirf_putchar((cksum >> 8) & 0x7f);
+ ao_sirf_putchar(cksum & 0xff);
+ ao_sirf_putchar(0xb0);
+ ao_sirf_putchar(0xb3);
+}
+
+static const uint8_t sirf_disable[] = {
+ 2,
+ 9,
+ 10,
+ 27,
+ 50,
+ 52,
+};
+
+void
+ao_gps(void) __reentrant
+{
+ uint8_t i, k;
+ uint16_t cksum;
+
+ ao_gps_setup();
+ for (k = 0; k < 5; k++)
+ {
+ for (i = 0; i < sizeof (ao_gps_config); i++)
+ ao_sirf_putchar(ao_gps_config[i]);
+ for (i = 0; i < sizeof (sirf_disable); i++)
+ ao_sirf_set_message_rate(sirf_disable[i], 0);
+ ao_sirf_set_message_rate(41, 1);
+ ao_sirf_set_message_rate(4, 1);
+ }
+ for (;;) {
+ /* Locate the begining of the next record */
+ while (ao_sirf_byte() != (uint8_t) 0xa0)
+ ;
+ if (ao_sirf_byte() != (uint8_t) 0xa2)
+ continue;
+
+ /* Length */
+ ao_sirf_len = ao_sirf_byte() << 8;
+ ao_sirf_len |= ao_sirf_byte();
+ if (ao_sirf_len > 1023)
+ continue;
+
+ ao_sirf_cksum = 0;
+
+ /* message ID */
+ i = data_byte (); /* 0 */
+
+ switch (i) {
+ case 41:
+ if (ao_sirf_len < 90)
+ break;
+ ao_sirf_parse_41();
+ break;
+ case 4:
+ if (ao_sirf_len < 187)
+ break;
+ ao_sirf_parse_4();
+ break;
+ }
+ if (ao_sirf_len != 0)
+ continue;
+
+ /* verify checksum and end sequence */
+ ao_sirf_cksum &= 0x7fff;
+ cksum = ao_sirf_byte() << 8;
+ cksum |= ao_sirf_byte();
+ if (ao_sirf_cksum != cksum)
+ continue;
+ if (ao_sirf_byte() != (uint8_t) 0xb0)
+ continue;
+ if (ao_sirf_byte() != (uint8_t) 0xb3)
+ continue;
+
+ switch (i) {
+ case 41:
+ ao_mutex_get(&ao_gps_mutex);
+ ao_gps_tick = ao_time();
+ ao_gps_data.hour = ao_sirf_data.utc_hour;
+ ao_gps_data.minute = ao_sirf_data.utc_minute;
+ ao_gps_data.second = ao_sirf_data.utc_second / 1000;
+ ao_gps_data.flags = ((ao_sirf_data.num_sv << AO_GPS_NUM_SAT_SHIFT) & AO_GPS_NUM_SAT_MASK) | AO_GPS_RUNNING;
+ if ((ao_sirf_data.nav_type & NAV_TYPE_GPS_FIX_TYPE_MASK) >= NAV_TYPE_4_SV_KF)
+ ao_gps_data.flags |= AO_GPS_VALID;
+ ao_gps_data.latitude = ao_sirf_data.lat;
+ ao_gps_data.longitude = ao_sirf_data.lon;
+ ao_gps_data.altitude = ao_sirf_data.alt_msl / 100;
+ ao_gps_data.ground_speed = ao_sirf_data.ground_speed;
+ ao_gps_data.course = ao_sirf_data.course / 200;
+ ao_gps_data.hdop = ao_sirf_data.hdop;
+ ao_gps_data.climb_rate = ao_sirf_data.climb_rate;
+ ao_gps_data.flags |= AO_GPS_COURSE_VALID;
+#if 0
+ if (ao_sirf_data.h_error > 6553500)
+ ao_gps_data.h_error = 65535;
+ else
+ ao_gps_data.h_error = ao_sirf_data.h_error / 100;
+ if (ao_sirf_data.v_error > 6553500)
+ ao_gps_data.v_error = 65535;
+ else
+ ao_gps_data.v_error = ao_sirf_data.v_error / 100;
+#endif
+ ao_mutex_put(&ao_gps_mutex);
+ ao_wakeup(&ao_gps_data);
+ break;
+ case 4:
+ ao_mutex_get(&ao_gps_mutex);
+ ao_gps_tracking_data.channels = ao_sirf_tracker_data.channels;
+ for (i = 0; i < 12; i++) {
+ ao_gps_tracking_data.sats[i].svid = ao_sirf_tracker_data.sats[i].svid;
+ ao_gps_tracking_data.sats[i].c_n_1 = ao_sirf_tracker_data.sats[i].c_n_1;
+ }
+ ao_mutex_put(&ao_gps_mutex);
+ ao_wakeup(&ao_gps_tracking_data);
+ break;
+ }
+ }
+}
+
+__xdata struct ao_task ao_gps_task;
+
+void
+ao_gps_init(void)
+{
+ ao_add_task(&ao_gps_task, ao_gps, "gps");
+}
diff --git a/src/drivers/ao_gps_skytraq.c b/src/drivers/ao_gps_skytraq.c
new file mode 100644
index 00000000..d80da97c
--- /dev/null
+++ b/src/drivers/ao_gps_skytraq.c
@@ -0,0 +1,505 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef AO_GPS_TEST
+#include "ao.h"
+#endif
+
+#ifndef ao_gps_getchar
+#define ao_gps_getchar ao_serial1_getchar
+#endif
+
+#ifndef ao_gps_putchar
+#define ao_gps_putchar ao_serial1_putchar
+#endif
+
+#ifndef ao_gps_set_speed
+#define ao_gps_set_speed ao_serial1_set_speed
+#endif
+
+__xdata uint8_t ao_gps_mutex;
+static __data char ao_gps_char;
+static __data uint8_t ao_gps_cksum;
+static __data uint8_t ao_gps_error;
+
+__pdata uint16_t ao_gps_tick;
+__xdata struct ao_telemetry_location ao_gps_data;
+__xdata struct ao_telemetry_satellite ao_gps_tracking_data;
+
+static __pdata uint16_t ao_gps_next_tick;
+static __pdata struct ao_telemetry_location ao_gps_next;
+static __pdata uint8_t ao_gps_date_flags;
+static __pdata struct ao_telemetry_satellite ao_gps_tracking_next;
+
+#define STQ_S 0xa0, 0xa1
+#define STQ_E 0x0d, 0x0a
+#define SKYTRAQ_MSG_2(id,a,b) \
+ STQ_S, 0, 3, id, a,b, (id^a^b), STQ_E
+#define SKYTRAQ_MSG_3(id,a,b,c) \
+ STQ_S, 0, 4, id, a,b,c, (id^a^b^c), STQ_E
+#define SKYTRAQ_MSG_8(id,a,b,c,d,e,f,g,h) \
+ STQ_S, 0, 9, id, a,b,c,d,e,f,g,h, (id^a^b^c^d^e^f^g^h), STQ_E
+#define SKYTRAQ_MSG_14(id,a,b,c,d,e,f,g,h,i,j,k,l,m,n) \
+ STQ_S, 0,15, id, a,b,c,d,e,f,g,h,i,j,k,l,m,n, \
+ (id^a^b^c^d^e^f^g^h^i^j^k^l^m^n), STQ_E
+
+static __code uint8_t ao_gps_config[] = {
+ SKYTRAQ_MSG_8(0x08, 1, 0, 1, 0, 1, 0, 0, 0), /* configure nmea */
+ /* gga interval */
+ /* gsa interval */
+ /* gsv interval */
+ /* gll interval */
+ /* rmc interval */
+ /* vtg interval */
+ /* zda interval */
+ /* attributes (0 = update to sram, 1 = update flash too) */
+
+ SKYTRAQ_MSG_2(0x3c, 0x00, 0x00), /* configure navigation mode */
+ /* 0 = car, 1 = pedestrian */
+ /* 0 = update to sram, 1 = update sram + flash */
+};
+
+static void
+ao_gps_lexchar(void)
+{
+ char c;
+ if (ao_gps_error)
+ c = '\n';
+ else
+ c = ao_gps_getchar();
+ ao_gps_cksum ^= c;
+ ao_gps_char = c;
+}
+
+void
+ao_gps_skip_field(void)
+{
+ for (;;) {
+ char c = ao_gps_char;
+ if (c == ',' || c == '*' || c == '\n')
+ break;
+ ao_gps_lexchar();
+ }
+}
+
+void
+ao_gps_skip_sep(void)
+{
+ char c = ao_gps_char;
+ if (c == ',' || c == '.' || c == '*')
+ ao_gps_lexchar();
+}
+
+__data static uint8_t ao_gps_num_width;
+
+static int16_t
+ao_gps_decimal(uint8_t max_width)
+{
+ int16_t v;
+ uint8_t neg = 0;
+
+ ao_gps_skip_sep();
+ if (ao_gps_char == '-') {
+ neg = 1;
+ ao_gps_lexchar();
+ }
+ v = 0;
+ ao_gps_num_width = 0;
+ while (ao_gps_num_width < max_width) {
+ uint8_t c = ao_gps_char;
+ if (c < (uint8_t) '0' || (uint8_t) '9' < c)
+ break;
+ v = v * 10 + (uint8_t) (c - (uint8_t) '0');
+ ao_gps_num_width++;
+ ao_gps_lexchar();
+ }
+ if (neg)
+ v = -v;
+ return v;
+}
+
+static uint8_t
+ao_gps_hex(void)
+{
+ uint8_t v;
+
+ ao_gps_skip_sep();
+ v = 0;
+ ao_gps_num_width = 0;
+ while (ao_gps_num_width < 2) {
+ uint8_t c = ao_gps_char;
+ uint8_t d;
+ if ((uint8_t) '0' <= c && c <= (uint8_t) '9')
+ d = - '0';
+ else if ((uint8_t) 'A' <= c && c <= (uint8_t) 'F')
+ d = - 'A' + 10;
+ else if ((uint8_t) 'a' <= c && c <= (uint8_t) 'f')
+ d = - 'a' + 10;
+ else
+ break;
+ v = (v << 4) | (c + d);
+ ao_gps_num_width++;
+ ao_gps_lexchar();
+ }
+ return v;
+}
+
+static int32_t
+ao_gps_parse_pos(uint8_t deg_width) __reentrant
+{
+ static __pdata uint16_t d;
+ static __pdata uint8_t m;
+ static __pdata uint16_t f;
+ char c;
+
+ d = ao_gps_decimal(deg_width);
+ m = ao_gps_decimal(2);
+ c = ao_gps_char;
+ if (c == '.') {
+ f = ao_gps_decimal(4);
+ while (ao_gps_num_width < 4) {
+ f *= 10;
+ ao_gps_num_width++;
+ }
+ } else {
+ f = 0;
+ if (c != ',')
+ ao_gps_error = 1;
+ }
+ return d * 10000000l + (m * 10000l + f) * 50 / 3;
+}
+
+static uint8_t
+ao_gps_parse_flag(char no_c, char yes_c)
+{
+ uint8_t ret = 0;
+ ao_gps_skip_sep();
+ if (ao_gps_char == yes_c)
+ ret = 1;
+ else if (ao_gps_char == no_c)
+ ret = 0;
+ else
+ ao_gps_error = 1;
+ ao_gps_lexchar();
+ return ret;
+}
+
+static void
+ao_nmea_finish(void)
+{
+ char c;
+ /* Skip remaining fields */
+ for (;;) {
+ c = ao_gps_char;
+ if (c == '*' || c == '\n' || c == '\r')
+ break;
+ ao_gps_lexchar();
+ ao_gps_skip_field();
+ }
+ if (c == '*') {
+ uint8_t cksum = ao_gps_cksum ^ '*';
+ if (cksum != ao_gps_hex())
+ ao_gps_error = 1;
+ } else
+ ao_gps_error = 1;
+}
+
+static void
+ao_nmea_gga(void)
+{
+ uint8_t i;
+
+ /* Now read the data into the gps data record
+ *
+ * $GPGGA,025149.000,4528.1723,N,12244.2480,W,1,05,2.0,103.5,M,-19.5,M,,0000*66
+ *
+ * Essential fix data
+ *
+ * 025149.000 time (02:51:49.000 GMT)
+ * 4528.1723,N Latitude 45°28.1723' N
+ * 12244.2480,W Longitude 122°44.2480' W
+ * 1 Fix quality:
+ * 0 = invalid
+ * 1 = GPS fix (SPS)
+ * 2 = DGPS fix
+ * 3 = PPS fix
+ * 4 = Real Time Kinematic
+ * 5 = Float RTK
+ * 6 = estimated (dead reckoning)
+ * 7 = Manual input mode
+ * 8 = Simulation mode
+ * 05 Number of satellites (5)
+ * 2.0 Horizontal dilution
+ * 103.5,M Altitude, 103.5M above msl
+ * -19.5,M Height of geoid above WGS84 ellipsoid
+ * ? time in seconds since last DGPS update
+ * 0000 DGPS station ID
+ * *66 checksum
+ */
+
+ ao_gps_next_tick = ao_time();
+ ao_gps_next.flags = AO_GPS_RUNNING | ao_gps_date_flags;
+ ao_gps_next.hour = ao_gps_decimal(2);
+ ao_gps_next.minute = ao_gps_decimal(2);
+ ao_gps_next.second = ao_gps_decimal(2);
+ ao_gps_skip_field(); /* skip seconds fraction */
+
+ ao_gps_next.latitude = ao_gps_parse_pos(2);
+ if (ao_gps_parse_flag('N', 'S'))
+ ao_gps_next.latitude = -ao_gps_next.latitude;
+ ao_gps_next.longitude = ao_gps_parse_pos(3);
+ if (ao_gps_parse_flag('E', 'W'))
+ ao_gps_next.longitude = -ao_gps_next.longitude;
+
+ i = ao_gps_decimal(0xff);
+ if (i == 1)
+ ao_gps_next.flags |= AO_GPS_VALID;
+
+ i = ao_gps_decimal(0xff) << AO_GPS_NUM_SAT_SHIFT;
+ if (i > AO_GPS_NUM_SAT_MASK)
+ i = AO_GPS_NUM_SAT_MASK;
+ ao_gps_next.flags |= i;
+
+ ao_gps_lexchar();
+ i = ao_gps_decimal(0xff);
+ if (i <= 50) {
+ i = (uint8_t) 5 * i;
+ if (ao_gps_char == '.')
+ i = (i + ((uint8_t) ao_gps_decimal(1) >> 1));
+ } else
+ i = 255;
+ ao_gps_next.hdop = i;
+ ao_gps_skip_field();
+
+ ao_gps_next.altitude = ao_gps_decimal(0xff);
+ ao_gps_skip_field(); /* skip any fractional portion */
+
+ ao_nmea_finish();
+
+ if (!ao_gps_error) {
+ ao_mutex_get(&ao_gps_mutex);
+ ao_gps_tick = ao_gps_next_tick;
+ ao_xmemcpy(&ao_gps_data, PDATA_TO_XDATA(&ao_gps_next), sizeof (ao_gps_data));
+ ao_mutex_put(&ao_gps_mutex);
+ ao_wakeup(&ao_gps_data);
+ }
+}
+
+static void
+ao_nmea_gsv(void)
+{
+ char c;
+ uint8_t i;
+ uint8_t done;
+ /* Now read the data into the GPS tracking data record
+ *
+ * $GPGSV,3,1,12,05,54,069,45,12,44,061,44,21,07,184,46,22,78,289,47*72<CR><LF>
+ *
+ * Satellites in view data
+ *
+ * 3 Total number of GSV messages
+ * 1 Sequence number of current GSV message
+ * 12 Total sats in view (0-12)
+ * 05 SVID
+ * 54 Elevation
+ * 069 Azimuth
+ * 45 C/N0 in dB
+ * ... other SVIDs
+ * 72 checksum
+ */
+ c = ao_gps_decimal(1); /* total messages */
+ i = ao_gps_decimal(1); /* message sequence */
+ if (i == 1) {
+ ao_gps_tracking_next.channels = 0;
+ }
+ done = (uint8_t) c == i;
+ ao_gps_lexchar();
+ ao_gps_skip_field(); /* sats in view */
+ while (ao_gps_char != '*' && ao_gps_char != '\n' && ao_gps_char != '\r') {
+ i = ao_gps_tracking_next.channels;
+ c = ao_gps_decimal(2); /* SVID */
+ if (i < AO_MAX_GPS_TRACKING)
+ ao_gps_tracking_next.sats[i].svid = c;
+ ao_gps_lexchar();
+ ao_gps_skip_field(); /* elevation */
+ ao_gps_lexchar();
+ ao_gps_skip_field(); /* azimuth */
+ c = ao_gps_decimal(2); /* C/N0 */
+ if (i < AO_MAX_GPS_TRACKING) {
+ if ((ao_gps_tracking_next.sats[i].c_n_1 = c) != 0)
+ ao_gps_tracking_next.channels = i + 1;
+ }
+ }
+
+ ao_nmea_finish();
+
+ if (ao_gps_error)
+ ao_gps_tracking_next.channels = 0;
+ else if (done) {
+ ao_mutex_get(&ao_gps_mutex);
+ ao_xmemcpy(&ao_gps_tracking_data, PDATA_TO_XDATA(&ao_gps_tracking_next), sizeof(ao_gps_tracking_data));
+ ao_mutex_put(&ao_gps_mutex);
+ ao_wakeup(&ao_gps_tracking_data);
+ }
+}
+
+static void
+ao_nmea_rmc(void)
+{
+ char a, c;
+ uint8_t i;
+ /* Parse the RMC record to read out the current date */
+
+ /* $GPRMC,111636.932,A,2447.0949,N,12100.5223,E,000.0,000.0,030407,,,A*61
+ *
+ * Recommended Minimum Specific GNSS Data
+ *
+ * 111636.932 UTC time 11:16:36.932
+ * A Data Valid (V = receiver warning)
+ * 2447.0949 Latitude
+ * N North/south indicator
+ * 12100.5223 Longitude
+ * E East/west indicator
+ * 000.0 Speed over ground
+ * 000.0 Course over ground
+ * 030407 UTC date (ddmmyy format)
+ * A Mode indicator:
+ * N = data not valid
+ * A = autonomous mode
+ * D = differential mode
+ * E = estimated (dead reckoning) mode
+ * M = manual input mode
+ * S = simulator mode
+ * 61 checksum
+ */
+ ao_gps_skip_field();
+ for (i = 0; i < 8; i++) {
+ ao_gps_lexchar();
+ ao_gps_skip_field();
+ }
+ a = ao_gps_decimal(2);
+ c = ao_gps_decimal(2);
+ i = ao_gps_decimal(2);
+
+ ao_nmea_finish();
+
+ if (!ao_gps_error) {
+ ao_gps_next.year = i;
+ ao_gps_next.month = c;
+ ao_gps_next.day = a;
+ ao_gps_date_flags = AO_GPS_DATE_VALID;
+ }
+}
+
+#define ao_skytraq_sendstruct(s) ao_skytraq_sendbytes((s), sizeof(s))
+
+static void
+ao_skytraq_sendbytes(__code uint8_t *b, uint8_t l)
+{
+ while (l--) {
+ uint8_t c = *b++;
+ if (c == 0xa0)
+ ao_delay(AO_MS_TO_TICKS(500));
+ ao_gps_putchar(c);
+ }
+}
+
+static void
+ao_gps_nmea_parse(void)
+{
+ uint8_t a, b, c;
+
+ ao_gps_cksum = 0;
+ ao_gps_error = 0;
+
+ ao_gps_lexchar();
+ if (ao_gps_char != 'G')
+ return;
+ ao_gps_lexchar();
+ if (ao_gps_char != 'P')
+ return;
+
+ ao_gps_lexchar();
+ a = ao_gps_char;
+ ao_gps_lexchar();
+ b = ao_gps_char;
+ ao_gps_lexchar();
+ c = ao_gps_char;
+ ao_gps_lexchar();
+
+ if (ao_gps_char != ',')
+ return;
+
+ if (a == (uint8_t) 'G' && b == (uint8_t) 'G' && c == (uint8_t) 'A') {
+ ao_nmea_gga();
+ } else if (a == (uint8_t) 'G' && b == (uint8_t) 'S' && c == (uint8_t) 'V') {
+ ao_nmea_gsv();
+ } else if (a == (uint8_t) 'R' && b == (uint8_t) 'M' && c == (uint8_t) 'C') {
+ ao_nmea_rmc();
+ }
+}
+
+void
+ao_gps(void) __reentrant
+{
+ ao_gps_set_speed(AO_SERIAL_SPEED_9600);
+
+ /* give skytraq time to boot in case of cold start */
+ ao_delay(AO_MS_TO_TICKS(2000));
+
+ ao_skytraq_sendstruct(ao_gps_config);
+
+ for (;;) {
+ /* Locate the begining of the next record */
+ if (ao_gps_getchar() == '$') {
+ ao_gps_nmea_parse();
+ }
+ }
+}
+
+__xdata struct ao_task ao_gps_task;
+
+static void
+gps_dump(void) __reentrant
+{
+ uint8_t i;
+ ao_mutex_get(&ao_gps_mutex);
+ printf ("Date: %02d/%02d/%02d\n", ao_gps_data.year, ao_gps_data.month, ao_gps_data.day);
+ printf ("Time: %02d:%02d:%02d\n", ao_gps_data.hour, ao_gps_data.minute, ao_gps_data.second);
+ printf ("Lat/Lon: %ld %ld\n", (long) ao_gps_data.latitude, (long) ao_gps_data.longitude);
+ printf ("Alt: %d\n", ao_gps_data.altitude);
+ printf ("Flags: 0x%x\n", ao_gps_data.flags);
+ printf ("Sats: %d", ao_gps_tracking_data.channels);
+ for (i = 0; i < ao_gps_tracking_data.channels; i++)
+ printf (" %d %d",
+ ao_gps_tracking_data.sats[i].svid,
+ ao_gps_tracking_data.sats[i].c_n_1);
+ printf ("\ndone\n");
+ ao_mutex_put(&ao_gps_mutex);
+}
+
+__code struct ao_cmds ao_gps_cmds[] = {
+ { gps_dump, "g\0Display GPS" },
+ { 0, NULL },
+};
+
+void
+ao_gps_init(void)
+{
+ ao_add_task(&ao_gps_task, ao_gps, "gps");
+ ao_cmd_register(&ao_gps_cmds[0]);
+}
diff --git a/src/drivers/ao_hmc5883.c b/src/drivers/ao_hmc5883.c
new file mode 100644
index 00000000..ade6c263
--- /dev/null
+++ b/src/drivers/ao_hmc5883.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_hmc5883.h>
+#include <ao_exti.h>
+
+#if HAS_HMC5883
+
+static uint8_t ao_hmc5883_configured;
+
+static uint8_t ao_hmc5883_addr;
+
+static void
+ao_hmc5883_reg_write(uint8_t addr, uint8_t data)
+{
+ uint8_t d[2];
+
+ d[0] = addr;
+ d[1] = data;
+ ao_i2c_get(AO_HMC5883_I2C_INDEX);
+ ao_i2c_start(AO_HMC5883_I2C_INDEX, HMC5883_ADDR_WRITE);
+ ao_i2c_send(d, 2, AO_HMC5883_I2C_INDEX, TRUE);
+ ao_i2c_put(AO_HMC5883_I2C_INDEX);
+ ao_hmc5883_addr = addr + 1;
+}
+
+static void
+ao_hmc5883_read(uint8_t addr, uint8_t *data, uint8_t len)
+{
+ ao_i2c_get(AO_HMC5883_I2C_INDEX);
+ if (addr != ao_hmc5883_addr) {
+ ao_i2c_start(AO_HMC5883_I2C_INDEX, HMC5883_ADDR_WRITE);
+ ao_i2c_send(&addr, 1, AO_HMC5883_I2C_INDEX, FALSE);
+ }
+ ao_i2c_start(AO_HMC5883_I2C_INDEX, HMC5883_ADDR_READ);
+ ao_i2c_recv(data, len, AO_HMC5883_I2C_INDEX, TRUE);
+ ao_i2c_put(AO_HMC5883_I2C_INDEX);
+ ao_hmc5883_addr = 0xff;
+}
+
+static uint8_t ao_hmc5883_done;
+
+static void
+ao_hmc5883_isr(void)
+{
+ ao_exti_disable(AO_HMC5883_INT_PORT, AO_HMC5883_INT_PIN);
+ ao_hmc5883_done = 1;
+ ao_wakeup(&ao_hmc5883_done);
+}
+
+static uint32_t ao_hmc5883_missed_irq;
+
+void
+ao_hmc5883_sample(struct ao_hmc5883_sample *sample)
+{
+ uint16_t *d = (uint16_t *) sample;
+ int i = sizeof (*sample) / 2;
+ uint8_t single = HMC5883_MODE_SINGLE;
+
+ ao_hmc5883_done = 0;
+ ao_exti_enable(AO_HMC5883_INT_PORT, AO_HMC5883_INT_PIN);
+ ao_hmc5883_reg_write(HMC5883_MODE, HMC5883_MODE_SINGLE);
+
+ ao_alarm(AO_MS_TO_TICKS(10));
+ cli();
+ while (!ao_hmc5883_done)
+ if (ao_sleep(&ao_hmc5883_done))
+ ++ao_hmc5883_missed_irq;
+ sei();
+ ao_clear_alarm();
+
+ ao_hmc5883_read(HMC5883_X_MSB, (uint8_t *) sample, sizeof (struct ao_hmc5883_sample));
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ /* byte swap */
+ while (i--) {
+ uint16_t t = *d;
+ *d++ = (t >> 8) | (t << 8);
+ }
+#endif
+}
+
+static uint8_t
+ao_hmc5883_setup(void)
+{
+ uint8_t d;
+ uint8_t present;
+
+ if (ao_hmc5883_configured)
+ return 1;
+
+ ao_i2c_get(AO_HMC5883_I2C_INDEX);
+ present = ao_i2c_start(AO_HMC5883_I2C_INDEX, HMC5883_ADDR_READ);
+ ao_i2c_recv(&d, 1, AO_HMC5883_I2C_INDEX, TRUE);
+ ao_i2c_put(AO_HMC5883_I2C_INDEX);
+
+ if (!present)
+ ao_panic(AO_PANIC_SELF_TEST);
+
+ ao_hmc5883_reg_write(HMC5883_CONFIG_A,
+ (HMC5883_CONFIG_A_MA_8 << HMC5883_CONFIG_A_MA) |
+ (HMC5883_CONFIG_A_DO_15 << HMC5883_CONFIG_A_DO) |
+ (HMC5883_CONFIG_A_MS_NORMAL << HMC5883_CONFIG_A_MS));
+
+ ao_hmc5883_reg_write(HMC5883_CONFIG_B,
+ (HMC5883_CONFIG_B_GN_1_3 << HMC5883_CONFIG_B_GN));
+
+ ao_hmc5883_configured = 1;
+ return 1;
+}
+
+static void
+ao_hmc5883(void)
+{
+ ao_hmc5883_setup();
+ for (;;) {
+ ao_hmc5883_sample((struct ao_hmc5883_sample *) &ao_data_ring[ao_data_head].hmc5883);
+ ao_arch_critical(
+ AO_DATA_PRESENT(AO_DATA_HMC5883);
+ AO_DATA_WAIT();
+ );
+ }
+}
+
+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);
+}
+
+static const struct ao_cmds ao_hmc5883_cmds[] = {
+ { ao_hmc5883_show, "M\0Show HMC5883 status" },
+ { 0, NULL }
+};
+
+void
+ao_hmc5883_init(void)
+{
+ ao_hmc5883_configured = 0;
+
+ ao_enable_port(AO_HMC5883_INT_PORT);
+ ao_exti_setup(AO_HMC5883_INT_PORT,
+ AO_HMC5883_INT_PIN,
+ AO_EXTI_MODE_FALLING | AO_EXTI_MODE_PULL_UP,
+ ao_hmc5883_isr);
+
+ ao_add_task(&ao_hmc5883_task, ao_hmc5883, "hmc5883");
+ ao_cmd_register(&ao_hmc5883_cmds[0]);
+}
+
+#endif
diff --git a/src/drivers/ao_hmc5883.h b/src/drivers/ao_hmc5883.h
new file mode 100644
index 00000000..55690978
--- /dev/null
+++ b/src/drivers/ao_hmc5883.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_HMC5883_H_
+#define _AO_HMC5883_H_
+
+#define HMC5883_ADDR_WRITE 0x3c
+#define HMC5883_ADDR_READ 0x3d
+
+#define HMC5883_CONFIG_A 0
+
+#define HMC5883_CONFIG_A_MA 5
+#define HMC5883_CONFIG_A_MA_1 0
+#define HMC5883_CONFIG_A_MA_2 1
+#define HMC5883_CONFIG_A_MA_4 2
+#define HMC5883_CONFIG_A_MA_8 3
+#define HMC5883_CONFIG_A_MA_MASK 3
+
+#define HMC5883_CONFIG_A_DO 2
+#define HMC5883_CONFIG_A_DO_0_75 0
+#define HMC5883_CONFIG_A_DO_1_5 1
+#define HMC5883_CONFIG_A_DO_3 2
+#define HMC5883_CONFIG_A_DO_7_5 3
+#define HMC5883_CONFIG_A_DO_15 4
+#define HMC5883_CONFIG_A_DO_30 5
+#define HMC5883_CONFIG_A_DO_75 6
+#define HMC5883_CONFIG_A_DO_MASK 7
+
+#define HMC5883_CONFIG_A_MS 0
+#define HMC5883_CONFIG_A_MS_NORMAL 0
+#define HMC5883_CONFIG_A_MS_POSITIVE_BIAS 1
+#define HMC5883_CONFIG_A_MS_NEGATIVE_BIAS 2
+#define HMC5883_CONFIG_A_MS_MASK 3
+
+#define HMC5883_CONFIG_B 1
+
+#define HMC5883_CONFIG_B_GN 5
+#define HMC5883_CONFIG_B_GN_0_88 0
+#define HMC5883_CONFIG_B_GN_1_3 1
+#define HMC5883_CONFIG_B_GN_1_9 2
+#define HMC5883_CONFIG_B_GN_2_5 3
+#define HMC5883_CONFIG_B_GN_4_0 4
+#define HMC5883_CONFIG_B_GN_4_7 5
+#define HMC5883_CONFIG_B_GN_5_6 6
+#define HMC5883_CONFIG_B_GN_8_1 7
+#define HMC5883_CONFIG_B_GN_MASK 7
+
+#define HMC5883_MODE 2
+#define HMC5883_MODE_CONTINUOUS 0
+#define HMC5883_MODE_SINGLE 1
+#define HMC5883_MODE_IDLE 2
+
+#define HMC5883_X_MSB 3
+#define HMC5883_X_LSB 4
+#define HMC5883_Y_MSB 5
+#define HMC5883_Y_LSB 6
+#define HMC5883_Z_MSB 7
+#define HMC5883_Z_LSB 8
+#define HMC5883_STATUS 9
+#define HMC5883_ID_A 10
+#define HMC5883_ID_B 11
+#define HMC5883_ID_C 12
+
+struct ao_hmc5883_sample {
+ int16_t x, y, z;
+};
+
+void
+ao_hmc5883_init(void);
+
+#endif /* _AO_HMC5883_H_ */
diff --git a/src/drivers/ao_lcd.c b/src/drivers/ao_lcd.c
new file mode 100644
index 00000000..6def0c80
--- /dev/null
+++ b/src/drivers/ao_lcd.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright © 2011 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+
+static uint16_t ao_lcd_time = 3;
+
+static __xdata uint8_t ao_lcd_mutex;
+
+static void
+ao_lcd_delay(void)
+{
+ volatile uint16_t count;
+
+ for (count = 0; count < ao_lcd_time; count++)
+ ;
+}
+
+static void
+ao_lcd_send_ins(uint8_t ins)
+{
+// printf("send ins %02x\n", ins);
+// ao_lcd_wait_idle();
+// ao_delay(1);
+ ao_lcd_delay();
+ ao_lcd_port_put_nibble(0, ins >> 4);
+ ao_lcd_port_put_nibble(0, ins & 0xf);
+}
+
+static void
+ao_lcd_put_byte(uint8_t c)
+{
+// printf ("send data %02x\n", c);
+// ao_lcd_wait_idle();
+ ao_lcd_delay();
+ ao_lcd_port_put_nibble(1, c >> 4);
+ ao_lcd_port_put_nibble(1, c & 0x0f);
+}
+
+void
+ao_lcd_putstring(char *string)
+{
+ char c;
+
+ ao_mutex_get(&ao_lcd_mutex);
+ while ((c = (uint8_t) *string++))
+ ao_lcd_put_byte((uint8_t) c);
+ ao_mutex_put(&ao_lcd_mutex);
+}
+
+#define AO_LCD_POWER_CONTROL 0x54
+
+void
+ao_lcd_contrast_set(uint8_t contrast)
+{
+ ao_mutex_get(&ao_lcd_mutex);
+ ao_lcd_send_ins(AO_LCD_POWER_CONTROL | ((contrast >> 4) & 0x3));
+ ao_lcd_send_ins(0x70 | (contrast & 0xf));
+ ao_mutex_put(&ao_lcd_mutex);
+}
+
+void
+ao_lcd_cursor_on(void)
+{
+ ao_mutex_get(&ao_lcd_mutex);
+ ao_lcd_send_ins(0x08 | 0x04 | 0x02 | 0x01);
+ ao_mutex_put(&ao_lcd_mutex);
+}
+
+void
+ao_lcd_cursor_off(void)
+{
+ ao_mutex_get(&ao_lcd_mutex);
+ ao_lcd_send_ins(0x08 | 0x04);
+ ao_mutex_put(&ao_lcd_mutex);
+}
+
+void
+ao_lcd_clear(void)
+{
+ ao_mutex_get(&ao_lcd_mutex);
+ ao_lcd_send_ins(0x01);
+ ao_delay(1);
+ /* Entry mode */
+ ao_lcd_send_ins(0x04 | 0x02);
+ ao_mutex_put(&ao_lcd_mutex);
+}
+
+void
+ao_lcd_goto(uint8_t addr)
+{
+ ao_mutex_get(&ao_lcd_mutex);
+ ao_lcd_send_ins(0x80 | addr);
+ ao_lcd_send_ins(0x04 | 0x02);
+ ao_mutex_put(&ao_lcd_mutex);
+}
+
+void
+ao_lcd_start(void)
+{
+ /* get to 4bit mode */
+ ao_lcd_port_put_nibble(0, 0x3);
+ ao_lcd_port_put_nibble(0, 0x3);
+ ao_lcd_port_put_nibble(0, 0x3);
+ ao_lcd_port_put_nibble(0, 0x2);
+
+ /* function set */
+ ao_lcd_send_ins(0x28);
+ /* function set, instruction table 1 */
+ ao_lcd_send_ins(0x29);
+
+ /* freq set */
+ ao_lcd_send_ins(0x14);
+
+ /* Power/icon/contrast control*/
+ ao_lcd_send_ins(AO_LCD_POWER_CONTROL);
+
+ /* Follower control */
+ ao_lcd_send_ins(0x6d);
+ ao_delay(AO_MS_TO_TICKS(200));
+
+ /* contrast set */
+ ao_lcd_contrast_set(0x18);
+
+ /* Display on */
+ ao_lcd_send_ins(0x08 | 0x04);
+
+ /* Clear */
+ ao_lcd_clear();
+}
+
+void
+ao_lcd_init(void)
+{
+ ao_lcd_port_init();
+}
diff --git a/src/drivers/ao_lco_cmd.c b/src/drivers/ao_lco_cmd.c
new file mode 100644
index 00000000..9c35b324
--- /dev/null
+++ b/src/drivers/ao_lco_cmd.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_pad.h>
+#include <ao_lco_cmd.h>
+#include <ao_lco_func.h>
+#include <ao_radio_cmac.h>
+
+static __pdata uint16_t lco_box;
+static __pdata uint8_t lco_channels;
+static __pdata uint16_t tick_offset;
+
+static void
+lco_args(void) __reentrant
+{
+ ao_cmd_decimal();
+ lco_box = ao_cmd_lex_i;
+ ao_cmd_hex();
+ lco_channels = ao_cmd_lex_i;
+}
+
+static struct ao_pad_query ao_pad_query;
+static uint16_t tick_offset;
+
+static int8_t
+lco_query(void)
+{
+ uint8_t i;
+ int8_t r = AO_RADIO_CMAC_OK;
+
+ for (i = 0; i < 10; i++) {
+ printf ("."); flush();
+ r = ao_lco_query(lco_box, &ao_pad_query, &tick_offset);
+ if (r == AO_RADIO_CMAC_OK)
+ break;
+ }
+ printf("\n"); flush();
+ return r;
+}
+
+static void
+lco_arm(void)
+{
+ ao_lco_arm(lco_box, lco_channels, tick_offset);
+}
+
+static void
+lco_ignite(void)
+{
+ ao_lco_ignite(lco_box, lco_channels, tick_offset);
+}
+
+static void
+lco_report_cmd(void) __reentrant
+{
+ int8_t r;
+ uint8_t c;
+
+ lco_args();
+ if (ao_cmd_status != ao_cmd_success)
+ return;
+ r = lco_query();
+ switch (r) {
+ case AO_RADIO_CMAC_OK:
+ switch (ao_pad_query.arm_status) {
+ case AO_PAD_ARM_STATUS_ARMED:
+ printf ("Armed: ");
+ break;
+ case AO_PAD_ARM_STATUS_DISARMED:
+ printf("Disarmed: ");
+ break;
+ case AO_PAD_ARM_STATUS_UNKNOWN:
+ default:
+ printf("Unknown: ");
+ break;
+ }
+ for (c = 0; c < AO_PAD_MAX_CHANNELS; c++) {
+ if (ao_pad_query.channels & (1 << c)) {
+ printf (" pad %d ", c);
+ switch (ao_pad_query.igniter_status[c]) {
+ default:
+ printf("unknown, ");
+ break;
+ case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN:
+ printf("bad-open, ");
+ break;
+ case AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN:
+ printf("good-igniter, ");
+ break;
+ case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED:
+ printf("bad-closed, ");
+ break;
+ }
+ }
+ }
+ printf("Rssi: %d\n", ao_radio_cmac_rssi);
+ break;
+ default:
+ printf("Error %d\n", r);
+ break;
+ }
+}
+
+static void
+lco_fire_cmd(void) __reentrant
+{
+ static __xdata struct ao_pad_command command;
+ uint8_t secs;
+ uint8_t i;
+ int8_t r;
+
+ lco_args();
+ ao_cmd_decimal();
+ secs = ao_cmd_lex_i;
+ if (ao_cmd_status != ao_cmd_success)
+ return;
+ r = lco_query();
+ if (r != AO_RADIO_CMAC_OK) {
+ printf("query failed %d\n", r);
+ return;
+ }
+
+ for (i = 0; i < 4; i++) {
+ printf("arm %d\n", i); flush();
+ lco_arm();
+ }
+
+ secs = secs * 10 - 5;
+ if (secs > 100)
+ secs = 100;
+ for (i = 0; i < secs; i++) {
+ printf("fire %d\n", i); flush();
+ lco_ignite();
+ ao_delay(AO_MS_TO_TICKS(100));
+ }
+}
+
+static void
+lco_arm_cmd(void) __reentrant
+{
+ uint8_t i;
+ int8_t r;
+ lco_args();
+ r = lco_query();
+ if (r != AO_RADIO_CMAC_OK) {
+ printf("query failed %d\n", r);
+ return;
+ }
+ for (i = 0; i < 4; i++)
+ lco_arm();
+}
+
+static void
+lco_ignite_cmd(void) __reentrant
+{
+ uint8_t i;
+ lco_args();
+ for (i = 0; i < 4; i++)
+ lco_ignite();
+}
+
+static __code struct ao_cmds ao_lco_cmds[] = {
+ { lco_report_cmd, "l <box> <channel>\0Get remote status" },
+ { lco_fire_cmd, "F <box> <channel> <secs>\0Fire remote igniters" },
+ { lco_arm_cmd, "a <box> <channel>\0Arm remote igniter" },
+ { lco_ignite_cmd, "i <box> <channel>\0Pulse remote igniter" },
+ { 0, NULL },
+};
+
+void
+ao_lco_cmd_init(void)
+{
+ ao_cmd_register(&ao_lco_cmds[0]);
+}
diff --git a/src/drivers/ao_lco_cmd.h b/src/drivers/ao_lco_cmd.h
new file mode 100644
index 00000000..c55448cd
--- /dev/null
+++ b/src/drivers/ao_lco_cmd.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_LCO_CMD_H_
+#define _AO_LCO_CMD_H_
+
+void
+ao_lco_cmd_init(void);
+
+#endif /* _AO_LCO_CMD_H_ */
diff --git a/src/drivers/ao_lco_func.c b/src/drivers/ao_lco_func.c
new file mode 100644
index 00000000..99e58b76
--- /dev/null
+++ b/src/drivers/ao_lco_func.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_pad.h>
+#include <ao_radio_cmac.h>
+#include <ao_lco_func.h>
+
+static __xdata struct ao_pad_command command;
+static __xdata uint8_t ao_lco_mutex;
+
+int8_t
+ao_lco_query(uint16_t box, struct ao_pad_query *query, uint16_t *tick_offset)
+{
+ uint8_t i;
+ int8_t r;
+ uint16_t sent_time;
+
+ ao_mutex_get(&ao_lco_mutex);
+ command.tick = ao_time() - *tick_offset;
+ command.box = box;
+ command.cmd = AO_LAUNCH_QUERY;
+ command.channels = 0;
+ ao_radio_cmac_send(&command, sizeof (command));
+ sent_time = ao_time();
+ r = ao_radio_cmac_recv(query, sizeof (*query), AO_MS_TO_TICKS(20));
+ if (r == AO_RADIO_CMAC_OK)
+ *tick_offset = sent_time - query->tick;
+ ao_mutex_put(&ao_lco_mutex);
+ return r;
+}
+
+void
+ao_lco_arm(uint16_t box, uint8_t channels, uint16_t tick_offset)
+{
+ ao_mutex_get(&ao_lco_mutex);
+ command.tick = ao_time() - tick_offset;
+ command.box = box;
+ command.cmd = AO_LAUNCH_ARM;
+ command.channels = channels;
+ ao_radio_cmac_send(&command, sizeof (command));
+ ao_mutex_put(&ao_lco_mutex);
+}
+
+void
+ao_lco_ignite(uint16_t box, uint8_t channels, uint16_t tick_offset)
+{
+ ao_mutex_get(&ao_lco_mutex);
+ command.tick = ao_time() - tick_offset;
+ command.box = box;
+ command.cmd = AO_LAUNCH_FIRE;
+ command.channels = channels;
+ ao_radio_cmac_send(&command, sizeof (command));
+ ao_mutex_put(&ao_lco_mutex);
+}
+
diff --git a/src/drivers/ao_lco_func.h b/src/drivers/ao_lco_func.h
new file mode 100644
index 00000000..dccf602a
--- /dev/null
+++ b/src/drivers/ao_lco_func.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_LCO_FUNC_H_
+#define _AO_LCO_FUNC_H_
+
+#include <ao_pad.h>
+
+int8_t
+ao_lco_query(uint16_t box, struct ao_pad_query *query, uint16_t *tick_offset);
+
+void
+ao_lco_arm(uint16_t box, uint8_t channels, uint16_t tick_offset);
+
+void
+ao_lco_ignite(uint16_t box, uint8_t channels, uint16_t tick_offset);
+
+#endif /* _AO_LCO_FUNC_H_ */
diff --git a/src/drivers/ao_m25.c b/src/drivers/ao_m25.c
new file mode 100644
index 00000000..9603c1de
--- /dev/null
+++ b/src/drivers/ao_m25.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright © 2010 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+
+/* Total bytes of available storage */
+__pdata uint32_t ao_storage_total;
+
+/* Block size - device is erased in these units. At least 256 bytes */
+__pdata uint32_t ao_storage_block;
+
+/* Byte offset of config block. Will be ao_storage_block bytes long */
+__pdata uint32_t ao_storage_config;
+
+/* Storage unit size - device reads and writes must be within blocks of this size. Usually 256 bytes. */
+__pdata uint16_t ao_storage_unit;
+
+/*
+ * Each flash chip is arranged in 64kB sectors; the
+ * chip cannot erase in units smaller than that.
+ *
+ * Writing happens in units of 256 byte pages and
+ * can only change bits from 1 to 0. So, you can rewrite
+ * the same contents, or append to an existing page easily enough
+ */
+
+#define M25_WREN 0x06 /* Write Enable */
+#define M25_WRDI 0x04 /* Write Disable */
+#define M25_RDID 0x9f /* Read Identification */
+#define M25_RDSR 0x05 /* Read Status Register */
+#define M25_WRSR 0x01 /* Write Status Register */
+#define M25_READ 0x03 /* Read Data Bytes */
+#define M25_FAST_READ 0x0b /* Read Data Bytes at Higher Speed */
+#define M25_PP 0x02 /* Page Program */
+#define M25_SE 0xd8 /* Sector Erase */
+#define M25_BE 0xc7 /* Bulk Erase */
+#define M25_DP 0xb9 /* Deep Power-down */
+
+/* RDID response */
+#define M25_MANUF_OFFSET 0
+#define M25_MEMORY_TYPE_OFFSET 1
+#define M25_CAPACITY_OFFSET 2
+#define M25_UID_OFFSET 3
+#define M25_CFI_OFFSET 4
+#define M25_RDID_LEN 4 /* that's all we need */
+
+#define M25_CAPACITY_128KB 0x11
+#define M25_CAPACITY_256KB 0x12
+#define M25_CAPACITY_512KB 0x13
+#define M25_CAPACITY_1MB 0x14
+#define M25_CAPACITY_2MB 0x15
+
+/*
+ * Status register bits
+ */
+
+#define M25_STATUS_SRWD (1 << 7) /* Status register write disable */
+#define M25_STATUS_BP_MASK (7 << 2) /* Block protect bits */
+#define M25_STATUS_BP_SHIFT (2)
+#define M25_STATUS_WEL (1 << 1) /* Write enable latch */
+#define M25_STATUS_WIP (1 << 0) /* Write in progress */
+
+/*
+ * On teleterra, the m25 chip select pins are
+ * wired on P0_0 through P0_3.
+ */
+
+#if M25_MAX_CHIPS > 1
+static uint8_t ao_m25_size[M25_MAX_CHIPS]; /* number of sectors in each chip */
+static uint8_t ao_m25_pin[M25_MAX_CHIPS]; /* chip select pin for each chip */
+static uint8_t ao_m25_numchips; /* number of chips detected */
+#endif
+static uint8_t ao_m25_total; /* total sectors available */
+static uint8_t ao_m25_wip; /* write in progress */
+
+static __xdata uint8_t ao_m25_mutex;
+
+/*
+ * This little array is abused to send and receive data. A particular
+ * caution -- the read and write addresses are written into the last
+ * three bytes of the array by ao_m25_set_page_address and then the
+ * first byte is used by ao_m25_wait_wip and ao_m25_write_enable, neither
+ * of which touch those last three bytes.
+ */
+
+static __xdata uint8_t ao_m25_instruction[4];
+
+#if HAS_BOOT_RADIO
+extern uint8_t ao_radio_in_recv;
+
+static void ao_boot_radio(void) {
+ if (ao_radio_in_recv)
+ ao_radio_recv_abort();
+}
+#else
+#define ao_boot_radio()
+#endif
+
+#define M25_SELECT(cs) do { ao_boot_radio(); ao_spi_get_mask(AO_M25_SPI_CS_PORT,cs,AO_M25_SPI_BUS, AO_SPI_SPEED_FAST); } while (0)
+#define M25_DESELECT(cs) ao_spi_put_mask(AO_M25_SPI_CS_PORT,cs,AO_M25_SPI_BUS)
+
+#define M25_BLOCK_SHIFT 16
+#define M25_BLOCK 65536L
+#define M25_POS_TO_SECTOR(pos) ((uint8_t) ((pos) >> M25_BLOCK_SHIFT))
+#define M25_SECTOR_TO_POS(sector) (((uint32_t) (sector)) << M25_BLOCK_SHIFT)
+
+/*
+ * Block until the specified chip is done writing
+ */
+static void
+ao_m25_wait_wip(uint8_t cs)
+{
+ if (ao_m25_wip & cs) {
+ M25_SELECT(cs);
+ ao_m25_instruction[0] = M25_RDSR;
+ ao_spi_send(ao_m25_instruction, 1, AO_M25_SPI_BUS);
+ do {
+ ao_spi_recv(ao_m25_instruction, 1, AO_M25_SPI_BUS);
+ } while (ao_m25_instruction[0] & M25_STATUS_WIP);
+ M25_DESELECT(cs);
+ ao_m25_wip &= ~cs;
+ }
+}
+
+/*
+ * Set the write enable latch so that page program and sector
+ * erase commands will work. Also mark the chip as busy writing
+ * so that future operations will block until the WIP bit goes off
+ */
+static void
+ao_m25_write_enable(uint8_t cs)
+{
+ M25_SELECT(cs);
+ ao_m25_instruction[0] = M25_WREN;
+ ao_spi_send(&ao_m25_instruction, 1, AO_M25_SPI_BUS);
+ M25_DESELECT(cs);
+ ao_m25_wip |= cs;
+}
+
+
+/*
+ * Returns the number of 64kB sectors
+ */
+static uint8_t
+ao_m25_read_capacity(uint8_t cs)
+{
+ uint8_t capacity;
+ M25_SELECT(cs);
+ ao_m25_instruction[0] = M25_RDID;
+ ao_spi_send(ao_m25_instruction, 1, AO_M25_SPI_BUS);
+ ao_spi_recv(ao_m25_instruction, M25_RDID_LEN, AO_M25_SPI_BUS);
+ M25_DESELECT(cs);
+
+ /* Check to see if the chip is present */
+ if (ao_m25_instruction[0] == 0xff)
+ return 0;
+ capacity = ao_m25_instruction[M25_CAPACITY_OFFSET];
+
+ /* Sanity check capacity number */
+ if (capacity < 0x11 || 0x1f < capacity)
+ return 0;
+ return 1 << (capacity - 0x10);
+}
+
+static uint8_t
+ao_m25_set_address(uint32_t pos)
+{
+ uint8_t chip;
+#if M25_MAX_CHIPS > 1
+ uint8_t size;
+
+ for (chip = 0; chip < ao_m25_numchips; chip++) {
+ size = ao_m25_size[chip];
+ if (M25_POS_TO_SECTOR(pos) < size)
+ break;
+ pos -= M25_SECTOR_TO_POS(size);
+ }
+ if (chip == ao_m25_numchips)
+ return 0xff;
+
+ chip = ao_m25_pin[chip];
+#else
+ chip = AO_M25_SPI_CS_MASK;
+#endif
+ ao_m25_wait_wip(chip);
+
+ ao_m25_instruction[1] = pos >> 16;
+ ao_m25_instruction[2] = pos >> 8;
+ ao_m25_instruction[3] = pos;
+ return chip;
+}
+
+/*
+ * Scan the possible chip select lines
+ * to see which flash chips are connected
+ */
+static uint8_t
+ao_m25_scan(void)
+{
+#if M25_MAX_CHIPS > 1
+ uint8_t pin, size;
+#endif
+
+ if (ao_m25_total)
+ return 1;
+
+#if M25_MAX_CHIPS > 1
+ ao_m25_numchips = 0;
+ for (pin = 1; pin != 0; pin <<= 1) {
+ if (AO_M25_SPI_CS_MASK & pin) {
+ size = ao_m25_read_capacity(pin);
+ if (size != 0) {
+ ao_m25_size[ao_m25_numchips] = size;
+ ao_m25_pin[ao_m25_numchips] = pin;
+ ao_m25_total += size;
+ ao_m25_numchips++;
+ }
+ }
+ }
+#else
+ ao_m25_total = ao_m25_read_capacity(AO_M25_SPI_CS_MASK);
+#endif
+ if (!ao_m25_total)
+ return 0;
+ ao_storage_total = M25_SECTOR_TO_POS(ao_m25_total);
+ ao_storage_block = M25_BLOCK;
+ ao_storage_config = ao_storage_total - M25_BLOCK;
+ ao_storage_unit = 256;
+ return 1;
+}
+
+/*
+ * Erase the specified sector
+ */
+uint8_t
+ao_storage_erase(uint32_t pos) __reentrant
+{
+ uint8_t cs;
+
+ if (pos >= ao_storage_total || pos + ao_storage_block > ao_storage_total)
+ return 0;
+
+ ao_mutex_get(&ao_m25_mutex);
+ ao_m25_scan();
+
+ cs = ao_m25_set_address(pos);
+
+ ao_m25_wait_wip(cs);
+ ao_m25_write_enable(cs);
+
+ ao_m25_instruction[0] = M25_SE;
+ M25_SELECT(cs);
+ ao_spi_send(ao_m25_instruction, 4, AO_M25_SPI_BUS);
+ M25_DESELECT(cs);
+ ao_m25_wip |= cs;
+
+ ao_mutex_put(&ao_m25_mutex);
+ return 1;
+}
+
+/*
+ * Write to flash
+ */
+uint8_t
+ao_storage_device_write(uint32_t pos, __xdata void *d, uint16_t len) __reentrant
+{
+ uint8_t cs;
+
+ if (pos >= ao_storage_total || pos + len > ao_storage_total)
+ return 0;
+
+ ao_mutex_get(&ao_m25_mutex);
+ ao_m25_scan();
+
+ cs = ao_m25_set_address(pos);
+ ao_m25_write_enable(cs);
+
+ ao_m25_instruction[0] = M25_PP;
+ M25_SELECT(cs);
+ ao_spi_send(ao_m25_instruction, 4, AO_M25_SPI_BUS);
+ ao_spi_send(d, len, AO_M25_SPI_BUS);
+ M25_DESELECT(cs);
+
+ ao_mutex_put(&ao_m25_mutex);
+ return 1;
+}
+
+/*
+ * Read from flash
+ */
+uint8_t
+ao_storage_device_read(uint32_t pos, __xdata void *d, uint16_t len) __reentrant
+{
+ uint8_t cs;
+
+ if (pos >= ao_storage_total || pos + len > ao_storage_total)
+ return 0;
+ ao_mutex_get(&ao_m25_mutex);
+ ao_m25_scan();
+
+ cs = ao_m25_set_address(pos);
+
+ /* No need to use the FAST_READ as we're running at only 8MHz */
+ ao_m25_instruction[0] = M25_READ;
+ M25_SELECT(cs);
+ ao_spi_send(ao_m25_instruction, 4, AO_M25_SPI_BUS);
+ ao_spi_recv(d, len, AO_M25_SPI_BUS);
+ M25_DESELECT(cs);
+
+ ao_mutex_put(&ao_m25_mutex);
+ return 1;
+}
+
+void
+ao_storage_flush(void) __reentrant
+{
+}
+
+void
+ao_storage_setup(void)
+{
+ ao_mutex_get(&ao_m25_mutex);
+ ao_m25_scan();
+ ao_mutex_put(&ao_m25_mutex);
+}
+
+void
+ao_storage_device_info(void) __reentrant
+{
+ uint8_t cs;
+#if M25_MAX_CHIPS > 1
+ uint8_t chip;
+#endif
+
+ ao_mutex_get(&ao_m25_mutex);
+ ao_m25_scan();
+ ao_mutex_put(&ao_m25_mutex);
+
+#if M25_MAX_CHIPS > 1
+ printf ("Detected chips %d size %d\n", ao_m25_numchips, ao_m25_total);
+ for (chip = 0; chip < ao_m25_numchips; chip++)
+ printf ("Flash chip %d select %02x size %d\n",
+ chip, ao_m25_pin[chip], ao_m25_size[chip]);
+#else
+ printf ("Detected chips 1 size %d\n", ao_m25_total);
+#endif
+
+ printf ("Available chips:\n");
+ for (cs = 1; cs != 0; cs <<= 1) {
+ if ((AO_M25_SPI_CS_MASK & cs) == 0)
+ continue;
+
+ ao_mutex_get(&ao_m25_mutex);
+ M25_SELECT(cs);
+ ao_m25_instruction[0] = M25_RDID;
+ ao_spi_send(ao_m25_instruction, 1, AO_M25_SPI_BUS);
+ ao_spi_recv(ao_m25_instruction, M25_RDID_LEN, AO_M25_SPI_BUS);
+ M25_DESELECT(cs);
+
+ printf ("Select %02x manf %02x type %02x cap %02x uid %02x\n",
+ cs,
+ ao_m25_instruction[M25_MANUF_OFFSET],
+ ao_m25_instruction[M25_MEMORY_TYPE_OFFSET],
+ ao_m25_instruction[M25_CAPACITY_OFFSET],
+ ao_m25_instruction[M25_UID_OFFSET]);
+ ao_mutex_put(&ao_m25_mutex);
+ }
+}
+
+void
+ao_storage_device_init(void)
+{
+ ao_spi_init_cs (AO_M25_SPI_CS_PORT, AO_M25_SPI_CS_MASK);
+}
diff --git a/src/drivers/ao_mma655x.c b/src/drivers/ao_mma655x.c
new file mode 100644
index 00000000..06422206
--- /dev/null
+++ b/src/drivers/ao_mma655x.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_mma655x.h>
+
+#if HAS_MMA655X
+
+#if 1
+#define PRINTD(...) do { printf ("\r%5u %s: ", ao_tick_count, __func__); printf(__VA_ARGS__); } while(0)
+#else
+#define PRINTD(...)
+#endif
+
+static uint8_t mma655x_configured;
+
+uint8_t ao_mma655x_spi_index = AO_MMA655X_SPI_INDEX;
+
+static void
+ao_mma655x_start(void) {
+ ao_spi_get_bit(AO_MMA655X_CS_PORT,
+ AO_MMA655X_CS_PIN,
+ AO_MMA655X_CS,
+ AO_MMA655X_SPI_INDEX,
+ AO_SPI_SPEED_FAST);
+}
+
+static void
+ao_mma655x_stop(void) {
+ ao_spi_put_bit(AO_MMA655X_CS_PORT,
+ AO_MMA655X_CS_PIN,
+ AO_MMA655X_CS,
+ AO_MMA655X_SPI_INDEX);
+}
+
+static void
+ao_mma655x_restart(void) {
+ uint8_t i;
+ ao_gpio_set(AO_MMA655X_CS_PORT, AO_MMA655X_CS_PIN, AO_MMA655X_CS, 1);
+
+ /* Emperical testing on STM32L151 at 32MHz for this delay amount */
+ for (i = 0; i < 9; i++)
+ ao_arch_nop();
+ ao_gpio_set(AO_MMA655X_CS_PORT, AO_MMA655X_CS_PIN, AO_MMA655X_CS, 0);
+}
+
+static uint8_t
+ao_parity(uint8_t v)
+{
+ uint8_t p;
+ /* down to four bits */
+ p = (v ^ (v >> 4)) & 0xf;
+
+ /* Cute lookup hack -- 0x6996 encodes the sixteen
+ * even parity values in order.
+ */
+ p = (~0x6996 >> p) & 1;
+ return p;
+}
+
+static void
+ao_mma655x_cmd(uint8_t d[2])
+{
+ ao_mma655x_start();
+ PRINTD("\tSEND %02x %02x\n", d[0], d[1]);
+ ao_spi_duplex(d, d, 2, AO_MMA655X_SPI_INDEX);
+ PRINTD("\t\tRECV %02x %02x\n", d[0], d[1]);
+ ao_mma655x_stop();
+}
+
+static uint8_t
+ao_mma655x_reg_read(uint8_t addr)
+{
+ uint8_t d[2];
+ ao_mma655x_start();
+ d[0] = addr | (ao_parity(addr) << 7);
+ d[1] = 0;
+ ao_spi_send(&d, 2, AO_MMA655X_SPI_INDEX);
+ ao_mma655x_restart();
+
+ /* Send a dummy read of 00 to clock out the SPI data */
+ d[0] = 0x80;
+ d[1] = 0x00;
+ ao_spi_duplex(&d, &d, 2, AO_MMA655X_SPI_INDEX);
+ ao_mma655x_stop();
+ return d[1];
+}
+
+static void
+ao_mma655x_reg_write(uint8_t addr, uint8_t value)
+{
+ uint8_t d[2];
+
+ addr |= (1 << 6); /* write mode */
+ d[0] = addr | (ao_parity(addr^value) << 7);
+ d[1] = value;
+ ao_mma655x_start();
+ ao_spi_send(d, 2, AO_MMA655X_SPI_INDEX);
+ ao_mma655x_stop();
+
+ addr &= ~(1 << 6);
+ PRINTD("write %x %x = %x\n",
+ addr, value, ao_mma655x_reg_read(addr));
+}
+
+static uint16_t
+ao_mma655x_value(void)
+{
+ uint8_t d[2];
+ uint16_t v;
+
+ d[0] = ((0 << 6) | /* Axis selection (X) */
+ (1 << 5) | /* Acceleration operation */
+ (1 << 4)); /* Raw data */
+ d[1] = ((1 << 3) | /* must be one */
+ (1 << 2) | /* Unsigned data */
+ (0 << 1) | /* Arm disabled */
+ (1 << 0)); /* Odd parity */
+ ao_mma655x_start();
+ PRINTD("value SEND %02x %02x\n", d[0], d[1]);
+ ao_spi_send(d, 2, AO_MMA655X_SPI_INDEX);
+ ao_mma655x_restart();
+ d[0] = 0x80;
+ d[1] = 0x00;
+ ao_spi_duplex(d, d, 2, AO_MMA655X_SPI_INDEX);
+ ao_mma655x_stop();
+ PRINTD("value RECV %02x %02x\n", d[0], d[1]);
+
+ v = (uint16_t) d[1] << 2;
+ v |= d[0] >> 6;
+ v |= (uint16_t) (d[0] & 3) << 10;
+ return v;
+}
+
+static void
+ao_mma655x_reset(void) {
+ ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
+ (0 << AO_MMA655X_DEVCTL_RES_1) |
+ (0 << AO_MMA655X_DEVCTL_RES_1));
+ ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
+ (1 << AO_MMA655X_DEVCTL_RES_1) |
+ (1 << AO_MMA655X_DEVCTL_RES_1));
+ ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
+ (0 << AO_MMA655X_DEVCTL_RES_1) |
+ (1 << AO_MMA655X_DEVCTL_RES_1));
+}
+
+#define DEVCFG_VALUE (\
+ (1 << AO_MMA655X_DEVCFG_OC) | /* Disable offset cancelation */ \
+ (1 << AO_MMA655X_DEVCFG_SD) | /* Receive unsigned data */ \
+ (0 << AO_MMA655X_DEVCFG_OFMON) | /* Disable offset monitor */ \
+ (AO_MMA655X_DEVCFG_A_CFG_DISABLE << AO_MMA655X_DEVCFG_A_CFG))
+
+#define AXISCFG_VALUE (\
+ (0 << AO_MMA655X_AXISCFG_LPF)) /* 100Hz 4-pole filter */
+
+
+static void
+ao_mma655x_setup(void)
+{
+ uint8_t v;
+ uint16_t a, a_st;
+ uint8_t stdefl;
+ uint8_t i;
+ uint8_t s0, s1, s2, s3;
+ uint8_t pn;
+ uint32_t lot;
+ uint16_t serial;
+
+
+ if (mma655x_configured)
+ return;
+ mma655x_configured = 1;
+ ao_delay(AO_MS_TO_TICKS(10)); /* Top */
+ ao_mma655x_reset();
+ ao_delay(AO_MS_TO_TICKS(10)); /* Top */
+ (void) ao_mma655x_reg_read(AO_MMA655X_DEVSTAT);
+ v = ao_mma655x_reg_read(AO_MMA655X_DEVSTAT);
+
+ /* Configure R/W register values.
+ * Most of them relate to the arming feature, which
+ * we don't use, so the only registers we need to
+ * write are DEVCFG and AXISCFG
+ */
+
+ ao_mma655x_reg_write(AO_MMA655X_DEVCFG,
+ DEVCFG_VALUE | (0 << AO_MMA655X_DEVCFG_ENDINIT));
+
+ /* Test X axis
+ */
+
+ ao_mma655x_reg_write(AO_MMA655X_AXISCFG,
+ AXISCFG_VALUE |
+ (1 << AO_MMA655X_AXISCFG_ST));
+ for (i = 0; i < 10; i++) {
+ a_st = ao_mma655x_value();
+ printf ("SELF-TEST %2d = %6d\n", i, a_st);
+ }
+
+ stdefl = ao_mma655x_reg_read(AO_MMA655X_STDEFL);
+
+ ao_mma655x_reg_write(AO_MMA655X_AXISCFG,
+ AXISCFG_VALUE |
+ (0 << AO_MMA655X_AXISCFG_ST));
+ a = ao_mma655x_value();
+
+ for (i = 0; i < 10; i++) {
+ a = ao_mma655x_value();
+ printf("NORMAL %2d = %6d\n", i, a);
+ }
+
+ ao_mma655x_reg_write(AO_MMA655X_DEVCFG,
+ DEVCFG_VALUE | (1 << AO_MMA655X_DEVCFG_ENDINIT));
+ s0 = ao_mma655x_reg_read(AO_MMA655X_SN0);
+ s1 = ao_mma655x_reg_read(AO_MMA655X_SN1);
+ s2 = ao_mma655x_reg_read(AO_MMA655X_SN2);
+ s3 = ao_mma655x_reg_read(AO_MMA655X_SN3);
+ lot = ((uint32_t) s3 << 24) | ((uint32_t) s2 << 16) |
+ ((uint32_t) s1 << 8) | ((uint32_t) s0);
+ serial = lot & 0x1fff;
+ lot >>= 12;
+ pn = ao_mma655x_reg_read(AO_MMA655X_PN);
+ printf ("MMA655X lot %d serial %d number %d\n", lot, serial, pn);
+
+}
+
+static void
+ao_mma655x_dump(void)
+{
+ ao_mma655x_setup();
+ printf ("MMA655X value %d\n", ao_mma655x_value());
+}
+
+__code struct ao_cmds ao_mma655x_cmds[] = {
+ { ao_mma655x_dump, "A\0Display MMA655X data" },
+ { 0, NULL },
+};
+
+static void
+ao_mma655x(void)
+{
+ ao_mma655x_setup();
+ for (;;) {
+ ao_data_ring[ao_data_head].mma655x = ao_mma655x_value();
+ ao_arch_critical(
+ AO_DATA_PRESENT(AO_DATA_MMA655X);
+ AO_DATA_WAIT();
+ );
+ }
+}
+
+static __xdata struct ao_task ao_mma655x_task;
+
+void
+ao_mma655x_init(void)
+{
+ mma655x_configured = 0;
+
+ ao_cmd_register(&ao_mma655x_cmds[0]);
+ ao_spi_init_cs(AO_MMA655X_CS_PORT, (1 << AO_MMA655X_CS_PIN));
+
+// ao_add_task(&ao_mma655x_task, ao_mma655x, "mma655x");
+}
+
+#endif
diff --git a/src/drivers/ao_mma655x.h b/src/drivers/ao_mma655x.h
new file mode 100644
index 00000000..9c0c59dc
--- /dev/null
+++ b/src/drivers/ao_mma655x.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_MMA655X_H_
+#define _AO_MMA655X_H_
+
+
+#define AO_MMA655X_SN0 0x00
+#define AO_MMA655X_SN1 0x01
+#define AO_MMA655X_SN2 0x02
+#define AO_MMA655X_SN3 0x03
+#define AO_MMA655X_SN3 0x03
+#define AO_MMA655X_STDEFL 0x04
+#define AO_MMA655X_FCTCFG 0x06
+# define AO_MMA655X_FCTCFG_STMAG 7
+
+#define AO_MMA655X_PN 0x08
+#define AO_MMA655X_DEVCTL 0x0a
+#define AO_MMA655X_DEVCTL_RES_1 7
+#define AO_MMA655X_DEVCTL_RES_0 6
+#define AO_MMA655X_DEVCTL_OCPHASE 4
+#define AO_MMA655X_DEVCTL_OCPHASE_MASK 3
+#define AO_MMA655X_DEVCTL_OFFCFG_EN 3
+
+#define AO_MMA655X_DEVCFG 0x0b
+#define AO_MMA655X_DEVCFG_OC 7
+#define AO_MMA655X_DEVCFG_ENDINIT 5
+#define AO_MMA655X_DEVCFG_SD 4
+#define AO_MMA655X_DEVCFG_OFMON 3
+#define AO_MMA655X_DEVCFG_A_CFG 0
+#define AO_MMA655X_DEVCFG_A_CFG_DISABLE 0
+#define AO_MMA655X_DEVCFG_A_CFG_PCM 1
+#define AO_MMA655X_DEVCFG_A_CFG_MOVING_AVG_HIGH 2
+#define AO_MMA655X_DEVCFG_A_CFG_MOVING_AVG_LOW 3
+#define AO_MMA655X_DEVCFG_A_CFG_COUNT_HIGH 4
+#define AO_MMA655X_DEVCFG_A_CFG_COUNT_LOW 5
+#define AO_MMA655X_DEVCFG_A_CFG_UNFILTERED_HIGH 6
+#define AO_MMA655X_DEVCFG_A_CFG_UNFILTERED_LOW 7
+#define AO_MMA655X_DEVCFG_A_CFG_MASK 7
+
+#define AO_MMA655X_AXISCFG 0x0c
+#define AO_MMA655X_AXISCFG_ST 7
+#define AO_MMA655X_AXISCFG_LPF 0
+#define AO_MMA655X_AXISCFG_LPF_MASK 0xf
+
+#define AO_MMA655X_ARMCFG 0x0e
+#define AO_MMA655X_ARMCFG_APS 4
+#define AO_MMA655X_ARMCFG_APS_MASK 3
+#define AO_MMA655X_ARMCFG_AWS_N 2
+#define AO_MMA655X_ARMCFG_AWS_N_MASK 3
+#define AO_MMA655X_ARMCFG_AWS_P 0
+#define AO_MMA655X_ARMCFG_AWS_P_MASK 3
+
+#define AO_MMA655X_ARMT_P 0x10
+#define AO_MMA655X_ARMT_N 0x12
+
+#define AO_MMA655X_DEVSTAT 0x14
+#define AO_MMA655X_DEVSTAT_IDE 6
+#define AO_MMA655X_DEVSTAT_DEVINIT 4
+#define AO_MMA655X_DEVSTAT_MISOERR 3
+#define AO_MMA655X_DEVSTAT_OFFSET 1
+#define AO_MMA655X_DEVSTAT_DEVRES 0
+
+#define AO_MMA655X_COUNT 0x15
+#define AO_MMA655X_OFFCORR 0x16
+
+
+void
+ao_mma655x_init(void);
+
+#endif /* _AO_MMA655X_H_ */
diff --git a/src/drivers/ao_mpu6000.c b/src/drivers/ao_mpu6000.c
new file mode 100644
index 00000000..b3e284e0
--- /dev/null
+++ b/src/drivers/ao_mpu6000.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_mpu6000.h>
+#include <ao_exti.h>
+
+static uint8_t ao_mpu6000_wake;
+static uint8_t ao_mpu6000_configured;
+
+static void
+ao_mpu6000_write(uint8_t addr, uint8_t *data, uint8_t len)
+{
+ ao_i2c_get(AO_MPU6000_I2C_INDEX);
+ ao_i2c_start(AO_MPU6000_I2C_INDEX, MPU6000_ADDR_WRITE);
+ ao_i2c_send(&addr, 1, AO_MPU6000_I2C_INDEX, FALSE);
+ ao_i2c_send(data, len, AO_MPU6000_I2C_INDEX, TRUE);
+ ao_i2c_put(AO_MPU6000_I2C_INDEX);
+}
+
+static void
+ao_mpu6000_reg_write(uint8_t addr, uint8_t value)
+{
+ uint8_t d[2] = { addr, value };
+ ao_i2c_get(AO_MPU6000_I2C_INDEX);
+ ao_i2c_start(AO_MPU6000_I2C_INDEX, MPU6000_ADDR_WRITE);
+ ao_i2c_send(d, 2, AO_MPU6000_I2C_INDEX, TRUE);
+ ao_i2c_put(AO_MPU6000_I2C_INDEX);
+}
+
+static void
+ao_mpu6000_read(uint8_t addr, void *data, uint8_t len)
+{
+ ao_i2c_get(AO_MPU6000_I2C_INDEX);
+ ao_i2c_start(AO_MPU6000_I2C_INDEX, MPU6000_ADDR_WRITE);
+ ao_i2c_send(&addr, 1, AO_MPU6000_I2C_INDEX, FALSE);
+ ao_i2c_start(AO_MPU6000_I2C_INDEX, MPU6000_ADDR_READ);
+ ao_i2c_recv(data, len, AO_MPU6000_I2C_INDEX, TRUE);
+ ao_i2c_put(AO_MPU6000_I2C_INDEX);
+}
+
+static uint8_t
+ao_mpu6000_reg_read(uint8_t addr)
+{
+ uint8_t value;
+ ao_i2c_get(AO_MPU6000_I2C_INDEX);
+ ao_i2c_start(AO_MPU6000_I2C_INDEX, MPU6000_ADDR_WRITE);
+ ao_i2c_send(&addr, 1, AO_MPU6000_I2C_INDEX, FALSE);
+ ao_i2c_start(AO_MPU6000_I2C_INDEX, MPU6000_ADDR_READ);
+ ao_i2c_recv(&value, 1, AO_MPU6000_I2C_INDEX, TRUE);
+ ao_i2c_put(AO_MPU6000_I2C_INDEX);
+ return value;
+}
+
+static void
+ao_mpu6000_sample(struct ao_mpu6000_sample *sample)
+{
+ uint16_t *d = (uint16_t *) sample;
+ int i = sizeof (*sample) / 2;
+
+ ao_mpu6000_read(MPU6000_ACCEL_XOUT_H, sample, sizeof (*sample));
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ /* byte swap */
+ while (i--) {
+ uint16_t t = *d;
+ *d++ = (t >> 8) | (t << 8);
+ }
+#endif
+}
+
+#define G 981 /* in cm/s² */
+
+static int16_t /* cm/s² */
+ao_mpu6000_accel(int16_t v)
+{
+ return (int16_t) ((v * (int32_t) (16.0 * 980.665 + 0.5)) / 32767);
+}
+
+static int16_t /* deg*10/s */
+ao_mpu6000_gyro(int16_t v)
+{
+ return (int16_t) ((v * (int32_t) 20000) / 32767);
+}
+
+static uint8_t
+ao_mpu6000_accel_check(int16_t normal, int16_t test, char *which)
+{
+ int16_t diff = test - normal;
+
+ if (diff < MPU6000_ST_ACCEL(16) / 2) {
+ return 1;
+ }
+ if (diff > MPU6000_ST_ACCEL(16) * 2) {
+ return 1;
+ }
+ return 0;
+}
+
+static uint8_t
+ao_mpu6000_gyro_check(int16_t normal, int16_t test, char *which)
+{
+ int16_t diff = test - normal;
+
+ if (diff < 0)
+ diff = -diff;
+ if (diff < MPU6000_ST_GYRO(2000) / 2) {
+ return 1;
+ }
+ if (diff > MPU6000_ST_GYRO(2000) * 2) {
+ return 1;
+ }
+ return 0;
+}
+
+static void
+ao_mpu6000_setup(void)
+{
+ struct ao_mpu6000_sample normal_mode, test_mode;
+ int errors =0;
+
+ if (ao_mpu6000_configured)
+ return;
+
+ /* Reset the whole chip */
+
+ ao_mpu6000_reg_write(MPU6000_PWR_MGMT_1,
+ (1 << MPU6000_PWR_MGMT_1_DEVICE_RESET));
+
+ /* Wait for it to reset. If we talk too quickly, it appears to get confused */
+ ao_delay(AO_MS_TO_TICKS(100));
+
+ /* Reset signal conditioning */
+ ao_mpu6000_reg_write(MPU6000_USER_CONTROL,
+ (0 << MPU6000_USER_CONTROL_FIFO_EN) |
+ (0 << MPU6000_USER_CONTROL_I2C_MST_EN) |
+ (0 << MPU6000_USER_CONTROL_I2C_IF_DIS) |
+ (0 << MPU6000_USER_CONTROL_FIFO_RESET) |
+ (0 << MPU6000_USER_CONTROL_I2C_MST_RESET) |
+ (1 << MPU6000_USER_CONTROL_SIG_COND_RESET));
+
+ while (ao_mpu6000_reg_read(MPU6000_USER_CONTROL) & (1 << MPU6000_USER_CONTROL_SIG_COND_RESET))
+ ao_yield();
+
+ /* Reset signal paths */
+ ao_mpu6000_reg_write(MPU6000_SIGNAL_PATH_RESET,
+ (1 << MPU6000_SIGNAL_PATH_RESET_GYRO_RESET) |
+ (1 << MPU6000_SIGNAL_PATH_RESET_ACCEL_RESET) |
+ (1 << MPU6000_SIGNAL_PATH_RESET_TEMP_RESET));
+
+ ao_mpu6000_reg_write(MPU6000_SIGNAL_PATH_RESET,
+ (0 << MPU6000_SIGNAL_PATH_RESET_GYRO_RESET) |
+ (0 << MPU6000_SIGNAL_PATH_RESET_ACCEL_RESET) |
+ (0 << MPU6000_SIGNAL_PATH_RESET_TEMP_RESET));
+
+ /* Select clocks, disable sleep */
+ ao_mpu6000_reg_write(MPU6000_PWR_MGMT_1,
+ (0 << MPU6000_PWR_MGMT_1_DEVICE_RESET) |
+ (0 << MPU6000_PWR_MGMT_1_SLEEP) |
+ (0 << MPU6000_PWR_MGMT_1_CYCLE) |
+ (0 << MPU6000_PWR_MGMT_1_TEMP_DIS) |
+ (MPU6000_PWR_MGMT_1_CLKSEL_PLL_X_AXIS << MPU6000_PWR_MGMT_1_CLKSEL));
+
+ /* Set sample rate divider to sample at full speed
+ ao_mpu6000_reg_write(MPU6000_SMPRT_DIV, 0);
+
+ /* Disable filtering */
+ ao_mpu6000_reg_write(MPU6000_CONFIG,
+ (MPU6000_CONFIG_EXT_SYNC_SET_DISABLED << MPU6000_CONFIG_EXT_SYNC_SET) |
+ (MPU6000_CONFIG_DLPF_CFG_260_256 << MPU6000_CONFIG_DLPF_CFG));
+
+ /* Configure accelerometer to +/-16G in self-test mode */
+ ao_mpu6000_reg_write(MPU6000_ACCEL_CONFIG,
+ (1 << MPU600_ACCEL_CONFIG_XA_ST) |
+ (1 << MPU600_ACCEL_CONFIG_YA_ST) |
+ (1 << MPU600_ACCEL_CONFIG_ZA_ST) |
+ (MPU600_ACCEL_CONFIG_AFS_SEL_16G << MPU600_ACCEL_CONFIG_AFS_SEL));
+
+ /* Configure gyro to +/- 2000°/s in self-test mode */
+ ao_mpu6000_reg_write(MPU6000_GYRO_CONFIG,
+ (1 << MPU600_GYRO_CONFIG_XG_ST) |
+ (1 << MPU600_GYRO_CONFIG_YG_ST) |
+ (1 << MPU600_GYRO_CONFIG_ZG_ST) |
+ (MPU600_GYRO_CONFIG_FS_SEL_2000 << MPU600_GYRO_CONFIG_FS_SEL));
+
+ ao_delay(AO_MS_TO_TICKS(200));
+ ao_mpu6000_sample(&test_mode);
+
+ /* Configure accelerometer to +/-16G */
+ ao_mpu6000_reg_write(MPU6000_ACCEL_CONFIG,
+ (0 << MPU600_ACCEL_CONFIG_XA_ST) |
+ (0 << MPU600_ACCEL_CONFIG_YA_ST) |
+ (0 << MPU600_ACCEL_CONFIG_ZA_ST) |
+ (MPU600_ACCEL_CONFIG_AFS_SEL_16G << MPU600_ACCEL_CONFIG_AFS_SEL));
+
+ /* Configure gyro to +/- 2000°/s */
+ ao_mpu6000_reg_write(MPU6000_GYRO_CONFIG,
+ (0 << MPU600_GYRO_CONFIG_XG_ST) |
+ (0 << MPU600_GYRO_CONFIG_YG_ST) |
+ (0 << MPU600_GYRO_CONFIG_ZG_ST) |
+ (MPU600_GYRO_CONFIG_FS_SEL_2000 << MPU600_GYRO_CONFIG_FS_SEL));
+
+ ao_delay(AO_MS_TO_TICKS(10));
+ ao_mpu6000_sample(&normal_mode);
+
+ errors += ao_mpu6000_accel_check(normal_mode.accel_x, test_mode.accel_x, "x");
+ errors += ao_mpu6000_accel_check(normal_mode.accel_y, test_mode.accel_y, "y");
+ errors += ao_mpu6000_accel_check(normal_mode.accel_z, test_mode.accel_z, "z");
+
+ errors += ao_mpu6000_gyro_check(normal_mode.gyro_x, test_mode.gyro_x, "x");
+ errors += ao_mpu6000_gyro_check(normal_mode.gyro_y, test_mode.gyro_y, "y");
+ errors += ao_mpu6000_gyro_check(normal_mode.gyro_z, test_mode.gyro_z, "z");
+
+ if (errors)
+ ao_panic(AO_PANIC_SELF_TEST_MPU6000);
+
+ /* Filter to about 100Hz, which also sets the gyro rate to 1000Hz */
+ ao_mpu6000_reg_write(MPU6000_CONFIG,
+ (MPU6000_CONFIG_EXT_SYNC_SET_DISABLED << MPU6000_CONFIG_EXT_SYNC_SET) |
+ (MPU6000_CONFIG_DLPF_CFG_94_98 << MPU6000_CONFIG_DLPF_CFG));
+
+ /* 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;
+}
+
+static void
+ao_mpu6000(void)
+{
+ ao_mpu6000_setup();
+ for (;;)
+ {
+ ao_mpu6000_sample((struct ao_mpu6000_sample *) &ao_data_ring[ao_data_head].mpu6000);
+ ao_arch_critical(
+ AO_DATA_PRESENT(AO_DATA_MPU6000);
+ AO_DATA_WAIT();
+ );
+ }
+}
+
+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);
+}
+
+static const struct ao_cmds ao_mpu6000_cmds[] = {
+ { ao_mpu6000_show, "I\0Show MPU6000 status" },
+ { 0, NULL }
+};
+
+void
+ao_mpu6000_init(void)
+{
+ ao_mpu6000_configured = 0;
+
+ ao_add_task(&ao_mpu6000_task, ao_mpu6000, "mpu6000");
+ ao_cmd_register(&ao_mpu6000_cmds[0]);
+}
diff --git a/src/drivers/ao_mpu6000.h b/src/drivers/ao_mpu6000.h
new file mode 100644
index 00000000..ca76b081
--- /dev/null
+++ b/src/drivers/ao_mpu6000.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_MPU6000_H_
+#define _AO_MPU6000_H_
+
+#define MPU6000_ADDR_WRITE 0xd0
+#define MPU6000_ADDR_READ 0xd1
+
+#define MPU6000_SMPRT_DIV 0x19
+
+#define MPU6000_CONFIG 0x1a
+
+#define MPU6000_CONFIG_EXT_SYNC_SET 3
+#define MPU6000_CONFIG_EXT_SYNC_SET_DISABLED 0
+#define MPU6000_CONFIG_EXT_SYNC_SET_TEMP_OUT_L 1
+#define MPU6000_CONFIG_EXT_SYNC_SET_GYRO_XOUT_L 2
+#define MPU6000_CONFIG_EXT_SYNC_SET_GYRO_YOUT_L 3
+#define MPU6000_CONFIG_EXT_SYNC_SET_GYRO_ZOUT_L 4
+#define MPU6000_CONFIG_EXT_SYNC_SET_ACCEL_XOUT_L 5
+#define MPU6000_CONFIG_EXT_SYNC_SET_ACCEL_YOUT_L 6
+#define MPU6000_CONFIG_EXT_SYNC_SET_ACCEL_ZOUT_L 7
+#define MPU6000_CONFIG_EXT_SYNC_SET_MASK 7
+
+#define MPU6000_CONFIG_DLPF_CFG 0
+#define MPU6000_CONFIG_DLPF_CFG_260_256 0
+#define MPU6000_CONFIG_DLPF_CFG_184_188 1
+#define MPU6000_CONFIG_DLPF_CFG_94_98 2
+#define MPU6000_CONFIG_DLPF_CFG_44_42 3
+#define MPU6000_CONFIG_DLPF_CFG_21_20 4
+#define MPU6000_CONFIG_DLPF_CFG_10_10 5
+#define MPU6000_CONFIG_DLPF_CFG_5_5 6
+#define MPU6000_CONFIG_DLPF_CFG_MASK 7
+
+#define MPU6000_GYRO_CONFIG 0x1b
+# define MPU600_GYRO_CONFIG_XG_ST 7
+# define MPU600_GYRO_CONFIG_YG_ST 6
+# define MPU600_GYRO_CONFIG_ZG_ST 5
+# define MPU600_GYRO_CONFIG_FS_SEL 3
+# define MPU600_GYRO_CONFIG_FS_SEL_250 0
+# define MPU600_GYRO_CONFIG_FS_SEL_500 1
+# define MPU600_GYRO_CONFIG_FS_SEL_1000 2
+# define MPU600_GYRO_CONFIG_FS_SEL_2000 3
+# define MPU600_GYRO_CONFIG_FS_SEL_MASK 3
+
+#define MPU6000_ACCEL_CONFIG 0x1c
+# define MPU600_ACCEL_CONFIG_XA_ST 7
+# define MPU600_ACCEL_CONFIG_YA_ST 6
+# define MPU600_ACCEL_CONFIG_ZA_ST 5
+# define MPU600_ACCEL_CONFIG_AFS_SEL 3
+# define MPU600_ACCEL_CONFIG_AFS_SEL_2G 0
+# define MPU600_ACCEL_CONFIG_AFS_SEL_4G 1
+# define MPU600_ACCEL_CONFIG_AFS_SEL_8G 2
+# define MPU600_ACCEL_CONFIG_AFS_SEL_16G 3
+# define MPU600_ACCEL_CONFIG_AFS_SEL_MASK 3
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF 0
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF_RESET 0
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF_5Hz 1
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF_2_5Hz 2
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF_1_25Hz 3
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF_0_63Hz 4
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF_HOLD 7
+# define MPU600_ACCEL_CONFIG_ACCEL_HPF_MASK 7
+
+#define MPU6000_INT_ENABLE 0x38
+#define MPU6000_INT_ENABLE_FF_EN 7
+#define MPU6000_INT_ENABLE_MOT_EN 6
+#define MPU6000_INT_ENABLE_ZMOT_EN 5
+#define MPU6000_INT_ENABLE_FIFO_OFLOW_EN 4
+#define MPU6000_INT_ENABLE_I2C_MST_INT_EN 3
+#define MPU6000_INT_ENABLE_DATA_RDY_EN 0
+
+#define MPU6000_INT_STATUS 0x3a
+#define MPU6000_INT_STATUS_FF_EN 7
+#define MPU6000_INT_STATUS_MOT_EN 6
+#define MPU6000_INT_STATUS_ZMOT_EN 5
+#define MPU6000_INT_STATUS_FIFO_OFLOW_EN 4
+#define MPU6000_INT_STATUS_I2C_MST_INT_EN 3
+#define MPU6000_INT_STATUS_DATA_RDY_EN 0
+
+#define MPU6000_ACCEL_XOUT_H 0x3b
+#define MPU6000_ACCEL_XOUT_L 0x3c
+#define MPU6000_ACCEL_YOUT_H 0x3d
+#define MPU6000_ACCEL_YOUT_L 0x3e
+#define MPU6000_ACCEL_ZOUT_H 0x3f
+#define MPU6000_ACCEL_ZOUT_L 0x40
+#define MPU6000_TEMP_H 0x41
+#define MPU6000_TEMP_L 0x42
+#define MPU6000_GYRO_XOUT_H 0x43
+#define MPU6000_GYRO_XOUT_L 0x44
+#define MPU6000_GYRO_YOUT_H 0x45
+#define MPU6000_GYRO_YOUT_L 0x46
+#define MPU6000_GYRO_ZOUT_H 0x47
+#define MPU6000_GYRO_ZOUT_L 0x48
+
+#define MPU6000_SIGNAL_PATH_RESET 0x68
+#define MPU6000_SIGNAL_PATH_RESET_GYRO_RESET 2
+#define MPU6000_SIGNAL_PATH_RESET_ACCEL_RESET 1
+#define MPU6000_SIGNAL_PATH_RESET_TEMP_RESET 0
+
+#define MPU6000_USER_CONTROL 0x6a
+#define MPU6000_USER_CONTROL_FIFO_EN 6
+#define MPU6000_USER_CONTROL_I2C_MST_EN 5
+#define MPU6000_USER_CONTROL_I2C_IF_DIS 4
+#define MPU6000_USER_CONTROL_FIFO_RESET 2
+#define MPU6000_USER_CONTROL_I2C_MST_RESET 1
+#define MPU6000_USER_CONTROL_SIG_COND_RESET 0
+
+#define MPU6000_PWR_MGMT_1 0x6b
+#define MPU6000_PWR_MGMT_1_DEVICE_RESET 7
+#define MPU6000_PWR_MGMT_1_SLEEP 6
+#define MPU6000_PWR_MGMT_1_CYCLE 5
+#define MPU6000_PWR_MGMT_1_TEMP_DIS 3
+#define MPU6000_PWR_MGMT_1_CLKSEL 0
+#define MPU6000_PWR_MGMT_1_CLKSEL_INTERNAL 0
+#define MPU6000_PWR_MGMT_1_CLKSEL_PLL_X_AXIS 1
+#define MPU6000_PWR_MGMT_1_CLKSEL_PLL_Y_AXIS 2
+#define MPU6000_PWR_MGMT_1_CLKSEL_PLL_Z_AXIS 3
+#define MPU6000_PWR_MGMT_1_CLKSEL_PLL_EXTERNAL_32K 4
+#define MPU6000_PWR_MGMT_1_CLKSEL_PLL_EXTERNAL_19M 5
+#define MPU6000_PWR_MGMT_1_CLKSEL_STOP 7
+#define MPU6000_PWR_MGMT_1_CLKSEL_MASK 7
+
+#define MPU6000_PWR_MGMT_2 0x6c
+
+#define MPU6000_WHO_AM_I 0x75
+
+/* Self test acceleration is approximately 0.5g */
+#define MPU6000_ST_ACCEL(full_scale) (32767 / ((full_scale) * 2))
+
+/* Self test gyro is approximately 50°/s */
+#define MPU6000_ST_GYRO(full_scale) ((int16_t) (((int32_t) 32767 * (int32_t) 50) / (full_scale)))
+
+struct ao_mpu6000_sample {
+ int16_t accel_x;
+ int16_t accel_y;
+ int16_t accel_z;
+ int16_t temp;
+ int16_t gyro_x;
+ int16_t gyro_y;
+ int16_t gyro_z;
+};
+
+void
+ao_mpu6000_init(void);
+
+#endif /* _AO_MPU6000_H_ */
diff --git a/src/drivers/ao_ms5607.c b/src/drivers/ao_ms5607.c
new file mode 100644
index 00000000..704b4583
--- /dev/null
+++ b/src/drivers/ao_ms5607.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_exti.h>
+#include "ao_ms5607.h"
+
+#if HAS_MS5607
+
+static struct ao_ms5607_prom ms5607_prom;
+static uint8_t ms5607_configured;
+
+static void
+ao_ms5607_start(void) {
+ ao_spi_get(AO_MS5607_SPI_INDEX,AO_SPI_SPEED_FAST);
+ stm_gpio_set(AO_MS5607_CS_PORT, AO_MS5607_CS_PIN, 0);
+}
+
+static void
+ao_ms5607_stop(void) {
+ stm_gpio_set(AO_MS5607_CS_PORT, AO_MS5607_CS_PIN, 1);
+ ao_spi_put(AO_MS5607_SPI_INDEX);
+}
+
+static void
+ao_ms5607_reset(void) {
+ uint8_t cmd;
+
+ cmd = AO_MS5607_RESET;
+ ao_ms5607_start();
+ ao_spi_send(&cmd, 1, AO_MS5607_SPI_INDEX);
+ ao_delay(AO_MS_TO_TICKS(10));
+ ao_ms5607_stop();
+}
+
+static uint8_t
+ao_ms5607_crc(uint8_t *prom)
+{
+ uint8_t crc_byte = prom[15];
+ uint8_t cnt;
+ uint16_t n_rem = 0;
+ uint16_t crc_read;
+ uint8_t n_bit;
+
+ prom[15] = 0;
+ for (cnt = 0; cnt < 16; cnt++) {
+ n_rem ^= prom[cnt];
+ for (n_bit = 8; n_bit > 0; n_bit--) {
+ if (n_rem & 0x8000)
+ n_rem = (n_rem << 1) ^ 0x3000;
+ else
+ n_rem = (n_rem << 1);
+ }
+ }
+ n_rem = (n_rem >> 12) & 0xf;
+ prom[15] = crc_byte;
+ return n_rem;
+}
+
+static void
+ao_ms5607_prom_read(struct ao_ms5607_prom *prom)
+{
+ uint8_t addr;
+ uint8_t crc;
+ uint16_t *r;
+
+ r = (uint16_t *) prom;
+ for (addr = 0; addr < 8; addr++) {
+ uint8_t cmd = AO_MS5607_PROM_READ(addr);
+ ao_ms5607_start();
+ ao_spi_send(&cmd, 1, AO_MS5607_SPI_INDEX);
+ ao_spi_recv(r, 2, AO_MS5607_SPI_INDEX);
+ ao_ms5607_stop();
+ r++;
+ }
+ crc = ao_ms5607_crc((uint8_t *) prom);
+ if (crc != (((uint8_t *) prom)[15] & 0xf)) {
+ printf ("MS5607 PROM CRC error (computed %x actual %x)\n",
+ crc, (((uint8_t *) prom)[15] & 0xf));
+ flush();
+// ao_panic(AO_PANIC_SELF_TEST_MS5607);
+ }
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ /* Byte swap */
+ r = (uint16_t *) prom;
+ for (addr = 0; addr < 8; addr++) {
+ uint16_t t = *r;
+ *r++ = (t << 8) | (t >> 8);
+ }
+#endif
+}
+
+static void
+ao_ms5607_setup(void)
+{
+ if (ms5607_configured)
+ return;
+ ms5607_configured = 1;
+ ao_ms5607_reset();
+ ao_ms5607_prom_read(&ms5607_prom);
+}
+
+static uint8_t ao_ms5607_done;
+
+static void
+ao_ms5607_isr(void)
+{
+ ao_exti_disable(AO_MS5607_MISO_PORT, AO_MS5607_MISO_PIN);
+ ao_ms5607_done = 1;
+ ao_wakeup(&ao_ms5607_done);
+}
+
+static uint32_t
+ao_ms5607_get_sample(uint8_t cmd) {
+ uint8_t reply[3];
+ uint8_t read;
+ uint16_t now;
+
+ ao_ms5607_done = 0;
+
+ ao_ms5607_start();
+ ao_spi_send(&cmd, 1, AO_MS5607_SPI_INDEX);
+ ao_exti_enable(AO_MS5607_MISO_PORT, AO_MS5607_MISO_PIN);
+#if AO_MS5607_PRIVATE_PINS
+ ao_spi_put(AO_MS5607_SPI_INDEX);
+#endif
+ cli();
+ while (!ao_ms5607_done)
+ ao_sleep(&ao_ms5607_done);
+ sei();
+#if AO_MS5607_PRIVATE_PINS
+ stm_gpio_set(AO_MS5607_CS_PORT, AO_MS5607_CS_PIN, 1);
+#else
+ ao_ms5607_stop();
+#endif
+
+ ao_ms5607_start();
+ read = AO_MS5607_ADC_READ;
+ ao_spi_send(&read, 1, AO_MS5607_SPI_INDEX);
+ ao_spi_recv(&reply, 3, AO_MS5607_SPI_INDEX);
+ ao_ms5607_stop();
+
+ return ((uint32_t) reply[0] << 16) | ((uint32_t) reply[1] << 8) | (uint32_t) reply[2];
+}
+
+void
+ao_ms5607_sample(struct ao_ms5607_sample *sample)
+{
+ sample->pres = ao_ms5607_get_sample(AO_MS5607_CONVERT_D1_2048);
+ sample->temp = ao_ms5607_get_sample(AO_MS5607_CONVERT_D2_2048);
+}
+
+void
+ao_ms5607_convert(struct ao_ms5607_sample *sample, struct ao_ms5607_value *value)
+{
+ uint8_t addr;
+ int32_t dT;
+ int32_t TEMP;
+ int64_t OFF;
+ int64_t SENS;
+ int32_t P;
+
+ dT = sample->temp - ((int32_t) ms5607_prom.tref << 8);
+
+ TEMP = 2000 + (((int64_t) dT * ms5607_prom.tempsens) >> 23);
+
+ OFF = ((int64_t) ms5607_prom.off << 17) + (((int64_t) ms5607_prom.tco * dT) >> 6);
+
+ SENS = ((int64_t) ms5607_prom.sens << 16) + (((int64_t) ms5607_prom.tcs * dT) >> 7);
+
+ if (TEMP < 2000) {
+ int32_t T2 = ((int64_t) dT * (int64_t) dT) >> 31;
+ int32_t TEMPM = TEMP - 2000;
+ int64_t OFF2 = (61 * (int64_t) TEMPM * (int64_t) TEMPM) >> 4;
+ int64_t SENS2 = 2 * (int64_t) TEMPM * (int64_t) TEMPM;
+ if (TEMP < 1500) {
+ int32_t TEMPP = TEMP + 1500;
+ int64_t TEMPP2 = TEMPP * TEMPP;
+ OFF2 = OFF2 + 15 * TEMPP2;
+ SENS2 = SENS2 + 8 * TEMPP2;
+ }
+ TEMP -= T2;
+ OFF -= OFF2;
+ SENS -= SENS2;
+ }
+
+ value->pres = ((((int64_t) sample->pres * SENS) >> 21) - OFF) >> 15;
+ value->temp = TEMP;
+}
+
+static void
+ao_ms5607(void)
+{
+ ao_ms5607_setup();
+ for (;;)
+ {
+ ao_ms5607_sample((struct ao_ms5607_sample *) &ao_data_ring[ao_data_head].ms5607_raw);
+ ao_arch_critical(
+ AO_DATA_PRESENT(AO_DATA_MS5607);
+ AO_DATA_WAIT();
+ );
+ }
+}
+
+__xdata struct ao_task ao_ms5607_task;
+
+void
+ao_ms5607_info(void)
+{
+ printf ("ms5607 reserved: %u\n", ms5607_prom.reserved);
+ printf ("ms5607 sens: %u\n", ms5607_prom.sens);
+ printf ("ms5607 off: %u\n", ms5607_prom.off);
+ printf ("ms5607 tcs: %u\n", ms5607_prom.tcs);
+ printf ("ms5607 tco: %u\n", ms5607_prom.tco);
+ printf ("ms5607 tref: %u\n", ms5607_prom.tref);
+ printf ("ms5607 tempsens: %u\n", ms5607_prom.tempsens);
+ printf ("ms5607 crc: %u\n", ms5607_prom.crc);
+}
+
+static void
+ao_ms5607_dump(void)
+{
+ struct ao_ms5607_sample sample;
+ struct ao_ms5607_value value;
+
+ ao_ms5607_setup();
+ ao_ms5607_sample(&sample);
+ ao_ms5607_convert(&sample, &value);
+ printf ("Pressure: %8u %8d\n", sample.pres, value.pres);
+ printf ("Temperature: %8u %8d\n", sample.temp, value.temp);
+ printf ("Altitude: %ld\n", ao_pa_to_altitude(value.pres));
+}
+
+__code struct ao_cmds ao_ms5607_cmds[] = {
+ { ao_ms5607_dump, "B\0Display MS5607 data" },
+ { 0, NULL },
+};
+
+void
+ao_ms5607_init(void)
+{
+ ms5607_configured = 0;
+ ao_cmd_register(&ao_ms5607_cmds[0]);
+ ao_spi_init_cs(AO_MS5607_CS_PORT, (1 << AO_MS5607_CS_PIN));
+
+// ao_add_task(&ao_ms5607_task, ao_ms5607, "ms5607");
+
+ /* Configure the MISO pin as an interrupt; when the
+ * conversion is complete, the MS5607 will raise this
+ * pin as a signal
+ */
+ ao_exti_setup(AO_MS5607_MISO_PORT,
+ AO_MS5607_MISO_PIN,
+ AO_EXTI_MODE_RISING,
+ ao_ms5607_isr);
+
+ /* Reset the pin from INPUT to ALTERNATE so that SPI works
+ * This needs an abstraction at some point...
+ */
+ stm_moder_set(AO_MS5607_MISO_PORT,
+ AO_MS5607_MISO_PIN,
+ STM_MODER_ALTERNATE);
+}
+
+#endif
diff --git a/src/drivers/ao_ms5607.h b/src/drivers/ao_ms5607.h
new file mode 100644
index 00000000..e9c364d9
--- /dev/null
+++ b/src/drivers/ao_ms5607.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_MS5607_H_
+#define _AO_MS5607_H_
+
+#define AO_MS5607_RESET 0x1e
+
+#define AO_MS5607_CONVERT_D1_256 0x40
+#define AO_MS5607_CONVERT_D1_512 0x42
+#define AO_MS5607_CONVERT_D1_1024 0x44
+#define AO_MS5607_CONVERT_D1_2048 0x46
+#define AO_MS5607_CONVERT_D1_4096 0x48
+
+#define AO_MS5607_CONVERT_D2_256 0x50
+#define AO_MS5607_CONVERT_D2_512 0x52
+#define AO_MS5607_CONVERT_D2_1024 0x54
+#define AO_MS5607_CONVERT_D2_2048 0x56
+#define AO_MS5607_CONVERT_D2_4096 0x58
+
+#define AO_MS5607_ADC_READ 0x00
+#define AO_MS5607_PROM_READ(ad) (0xA0 | ((ad) << 1))
+
+struct ao_ms5607_prom {
+ uint16_t reserved;
+ uint16_t sens;
+ uint16_t off;
+ uint16_t tcs;
+ uint16_t tco;
+ uint16_t tref;
+ uint16_t tempsens;
+ uint16_t crc;
+};
+
+struct ao_ms5607_sample {
+ uint32_t pres; /* raw 24 bit sensor */
+ uint32_t temp; /* raw 24 bit sensor */
+};
+
+struct ao_ms5607_value {
+ int32_t pres; /* in Pa * 10 */
+ int32_t temp; /* in °C * 100 */
+};
+
+void
+ao_ms5607_init(void);
+
+void
+ao_ms5607_info(void);
+
+void
+ao_ms5607_sample(struct ao_ms5607_sample *sample);
+
+void
+ao_ms5607_convert(struct ao_ms5607_sample *sample, struct ao_ms5607_value *value);
+
+void
+ao_ms5607_get_prom(struct ao_ms5607_prom *prom);
+
+#endif /* _AO_MS5607_H_ */
diff --git a/src/drivers/ao_packet.c b/src/drivers/ao_packet.c
new file mode 100644
index 00000000..d813b25f
--- /dev/null
+++ b/src/drivers/ao_packet.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+
+__xdata struct ao_packet_recv ao_rx_packet;
+__xdata struct ao_packet ao_tx_packet;
+__pdata uint8_t ao_packet_rx_len, ao_packet_rx_used, ao_packet_tx_used;
+
+static __xdata char tx_data[AO_PACKET_MAX];
+static __xdata char rx_data[AO_PACKET_MAX];
+static __pdata uint8_t rx_seq;
+
+__xdata struct ao_task ao_packet_task;
+__xdata uint8_t ao_packet_enable;
+
+#if PACKET_HAS_MASTER
+__xdata uint8_t ao_packet_master_sleeping;
+__xdata uint8_t ao_packet_last_rssi;
+#endif
+
+void
+ao_packet_send(void)
+{
+#ifdef AO_LED_RED
+ ao_led_on(AO_LED_RED);
+#endif
+ /* If any tx data is pending then copy it into the tx packet */
+ if (ao_packet_tx_used && ao_tx_packet.len == 0) {
+ ao_xmemcpy(&ao_tx_packet.d, tx_data, ao_packet_tx_used);
+ ao_tx_packet.len = ao_packet_tx_used;
+ ao_tx_packet.seq++;
+ ao_packet_tx_used = 0;
+ ao_wakeup(&tx_data);
+ }
+ ao_radio_send(&ao_tx_packet, sizeof (ao_tx_packet));
+#ifdef AO_LED_RED
+ ao_led_off(AO_LED_RED);
+#endif
+}
+
+uint8_t
+ao_packet_recv(void)
+{
+ uint8_t dma_done;
+
+#ifdef AO_LED_GREEN
+ ao_led_on(AO_LED_GREEN);
+#endif
+ dma_done = ao_radio_recv(&ao_rx_packet, sizeof (struct ao_packet_recv));
+#ifdef AO_LED_GREEN
+ ao_led_off(AO_LED_GREEN);
+#endif
+#if AO_PROFILE
+ {
+ extern uint32_t ao_rx_start_tick, ao_rx_packet_tick, ao_rx_done_tick, ao_rx_last_done_tick;
+ extern uint32_t ao_fec_decode_start, ao_fec_decode_end;
+
+ printf ("between packet: %d\n", ao_rx_start_tick - ao_rx_last_done_tick);
+ printf ("receive start delay: %d\n", ao_rx_packet_tick - ao_rx_start_tick);
+ printf ("decode time: %d\n", ao_fec_decode_end - ao_fec_decode_start);
+ printf ("rx cleanup: %d\n\n", ao_rx_done_tick - ao_fec_decode_end);
+ flush();
+ }
+#endif
+
+ /* Check to see if we got a valid packet */
+ if (!dma_done)
+ return 0;
+ if (!(ao_rx_packet.status & AO_RADIO_STATUS_CRC_OK))
+ return 0;
+
+#if PACKET_HAS_MASTER
+ ao_packet_last_rssi = ao_rx_packet.rssi;
+#endif
+ /* Accept packets with matching call signs, or any packet if
+ * our callsign hasn't been configured
+ */
+ if (ao_xmemcmp(ao_rx_packet.packet.callsign,
+ ao_config.callsign,
+ AO_MAX_CALLSIGN) != 0 &&
+ ao_xmemcmp(ao_config.callsign, CODE_TO_XDATA("N0CALL"), 7) != 0)
+ return 0;
+
+ /* SYN packets carry no data */
+ if (ao_rx_packet.packet.len == AO_PACKET_SYN) {
+ rx_seq = ao_rx_packet.packet.seq;
+ ao_tx_packet.seq = ao_rx_packet.packet.ack;
+ ao_tx_packet.ack = rx_seq;
+ } else if (ao_rx_packet.packet.len) {
+
+ /* Check for incoming data at the next sequence and
+ * for an empty data buffer
+ */
+ if (ao_rx_packet.packet.seq == (uint8_t) (rx_seq + (uint8_t) 1) &&
+ ao_packet_rx_used == ao_packet_rx_len) {
+
+ /* Copy data to the receive data buffer and set up the
+ * offsets
+ */
+ ao_xmemcpy(rx_data, ao_rx_packet.packet.d, ao_rx_packet.packet.len);
+ ao_packet_rx_used = 0;
+ ao_packet_rx_len = ao_rx_packet.packet.len;
+
+ /* Mark the sequence that we've received to
+ * let the sender know when we return a packet
+ */
+ rx_seq = ao_rx_packet.packet.seq;
+ ao_tx_packet.ack = rx_seq;
+
+ /* Poke anyone looking for received data */
+ ao_wakeup(&ao_stdin_ready);
+ }
+ }
+
+ /* If the other side has seen the latest data we queued,
+ * wake up any task waiting to send data and let them go again
+ */
+ if (ao_rx_packet.packet.ack == ao_tx_packet.seq) {
+ ao_tx_packet.len = 0;
+ ao_wakeup(&ao_tx_packet);
+ }
+ return 1;
+}
+
+#if PACKET_HAS_MASTER
+void
+ao_packet_flush(void)
+{
+ /* If there is data to send, and this is the master,
+ * then poke the master to send all queued data
+ */
+ if (ao_packet_tx_used && ao_packet_master_sleeping)
+ ao_wakeup(&ao_packet_master_sleeping);
+}
+#endif /* PACKET_HAS_MASTER */
+
+void
+ao_packet_putchar(char c) __reentrant
+{
+ while (ao_packet_tx_used == AO_PACKET_MAX && ao_packet_enable) {
+#if PACKET_HAS_MASTER
+ ao_packet_flush();
+#endif
+ ao_sleep(&tx_data);
+ }
+
+ if (ao_packet_enable)
+ tx_data[ao_packet_tx_used++] = c;
+}
+
+char
+ao_packet_pollchar(void) __critical
+{
+ if (!ao_packet_enable)
+ return AO_READ_AGAIN;
+
+ if (ao_packet_rx_used == ao_packet_rx_len)
+ return AO_READ_AGAIN;
+
+ return rx_data[ao_packet_rx_used++];
+}
diff --git a/src/drivers/ao_packet_master.c b/src/drivers/ao_packet_master.c
new file mode 100644
index 00000000..e97a6648
--- /dev/null
+++ b/src/drivers/ao_packet_master.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+
+static char
+ao_packet_getchar(void) __critical
+{
+ char c;
+ while ((c = ao_packet_pollchar()) == AO_READ_AGAIN) {
+ if (!ao_packet_enable)
+ break;
+ if (ao_packet_master_sleeping)
+ ao_wakeup(&ao_packet_master_sleeping);
+ flush();
+ ao_sleep(&ao_stdin_ready);
+ }
+ return c;
+}
+
+static void
+ao_packet_echo(void) __reentrant
+{
+ char c;
+ while (ao_packet_enable) {
+ c = ao_packet_getchar();
+ if (c != AO_READ_AGAIN)
+ putchar(c);
+ }
+ ao_exit();
+}
+
+static __xdata struct ao_task ao_packet_echo_task;
+static __xdata uint16_t ao_packet_master_delay;
+static __xdata uint16_t ao_packet_master_time;
+
+#define AO_PACKET_MASTER_DELAY_SHORT AO_MS_TO_TICKS(100)
+#define AO_PACKET_MASTER_DELAY_LONG AO_MS_TO_TICKS(1000)
+#define AO_PACKET_MASTER_DELAY_TIMEOUT AO_MS_TO_TICKS(2000)
+
+static void
+ao_packet_master_busy(void)
+{
+ ao_packet_master_delay = AO_PACKET_MASTER_DELAY_SHORT;
+ ao_packet_master_time = ao_time();
+}
+
+static void
+ao_packet_master_check_busy(void)
+{
+ int16_t idle;
+ if (ao_packet_master_delay != AO_PACKET_MASTER_DELAY_SHORT)
+ return;
+ idle = (int16_t) (ao_time() - ao_packet_master_time);
+
+ if (idle > AO_PACKET_MASTER_DELAY_TIMEOUT)
+ ao_packet_master_delay = AO_PACKET_MASTER_DELAY_LONG;
+}
+
+void
+ao_packet_master(void)
+{
+ ao_config_get();
+ ao_tx_packet.addr = ao_serial_number;
+ ao_tx_packet.len = AO_PACKET_SYN;
+ ao_packet_master_time = ao_time();
+ ao_packet_master_delay = AO_PACKET_MASTER_DELAY_SHORT;
+ while (ao_packet_enable) {
+ uint8_t r;
+ ao_xmemcpy(ao_tx_packet.callsign, ao_config.callsign, AO_MAX_CALLSIGN);
+ ao_packet_send();
+ if (ao_tx_packet.len)
+ ao_packet_master_busy();
+ ao_packet_master_check_busy();
+ ao_alarm(ao_packet_master_delay);
+ r = ao_packet_recv();
+ ao_clear_alarm();
+ if (r) {
+ /* if we can transmit data, do so */
+ if (ao_packet_tx_used && ao_tx_packet.len == 0)
+ continue;
+ if (ao_rx_packet.packet.len)
+ ao_packet_master_busy();
+ ao_packet_master_sleeping = 1;
+ ao_alarm(ao_packet_master_delay);
+ ao_sleep(&ao_packet_master_sleeping);
+ ao_clear_alarm();
+ ao_packet_master_sleeping = 0;
+ }
+ }
+ ao_exit();
+}
+
+static void
+ao_packet_forward(void) __reentrant
+{
+ char c;
+ ao_packet_enable = 1;
+ ao_cmd_white();
+
+ flush();
+#if HAS_MONITOR
+ ao_monitor_disable();
+#endif
+ ao_add_task(&ao_packet_task, ao_packet_master, "master");
+ ao_add_task(&ao_packet_echo_task, ao_packet_echo, "echo");
+ while ((c = getchar()) != '~') {
+ if (c == '\r') c = '\n';
+ ao_packet_putchar(c);
+ }
+
+ /* Wait for a second if there is any pending data */
+ for (c = 0; (ao_packet_tx_used || ao_tx_packet.len) && c < 10; c++)
+ ao_delay(AO_MS_TO_TICKS(100));
+ ao_packet_enable = 0;
+ while (ao_packet_echo_task.wchan || ao_packet_task.wchan) {
+ ao_radio_recv_abort();
+ ao_wakeup(&ao_stdin_ready);
+ ao_delay(AO_MS_TO_TICKS(10));
+ }
+#if HAS_MONITOR
+ ao_monitor_enable();
+#endif
+}
+
+static void
+ao_packet_signal(void)
+{
+ printf ("RSSI: %d\n", AO_RSSI_FROM_RADIO(ao_packet_last_rssi));
+}
+
+__code struct ao_cmds ao_packet_master_cmds[] = {
+ { ao_packet_forward, "p\0Remote packet link." },
+ { ao_packet_signal, "s\0Report signal strength." },
+ { 0, NULL },
+};
+
+void
+ao_packet_master_init(void)
+{
+ ao_cmd_register(&ao_packet_master_cmds[0]);
+}
diff --git a/src/drivers/ao_packet_slave.c b/src/drivers/ao_packet_slave.c
new file mode 100644
index 00000000..fd5d443e
--- /dev/null
+++ b/src/drivers/ao_packet_slave.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2009 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+
+void
+ao_packet_slave(void)
+{
+ ao_tx_packet.addr = ao_serial_number;
+ ao_tx_packet.len = AO_PACKET_SYN;
+ while (ao_packet_enable) {
+ if (ao_packet_recv()) {
+ ao_xmemcpy(&ao_tx_packet.callsign, &ao_rx_packet.packet.callsign, AO_MAX_CALLSIGN);
+#if HAS_FLIGHT
+ ao_flight_force_idle = TRUE;
+#endif
+ ao_packet_send();
+ }
+ }
+ ao_exit();
+}
+
+void
+ao_packet_slave_start(void)
+{
+ if (!ao_packet_enable) {
+ ao_packet_enable = 1;
+ ao_add_task(&ao_packet_task, ao_packet_slave, "slave");
+ }
+}
+
+void
+ao_packet_slave_stop(void)
+{
+ if (ao_packet_enable) {
+ ao_packet_enable = 0;
+ while (ao_packet_task.wchan) {
+ ao_radio_recv_abort();
+ ao_delay(AO_MS_TO_TICKS(10));
+ }
+ }
+}
+
+void
+ao_packet_slave_init(uint8_t enable)
+{
+ ao_add_stdio(ao_packet_pollchar,
+ ao_packet_putchar,
+ NULL);
+ if (enable)
+ ao_packet_slave_start();
+}
diff --git a/src/drivers/ao_pad.c b/src/drivers/ao_pad.c
new file mode 100644
index 00000000..55e6289d
--- /dev/null
+++ b/src/drivers/ao_pad.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_pad.h>
+#include <ao_74hc497.h>
+#include <ao_radio_cmac.h>
+
+static __xdata uint8_t ao_pad_ignite;
+static __xdata struct ao_pad_command command;
+static __xdata struct ao_pad_query query;
+static __pdata uint8_t ao_pad_armed;
+static __pdata uint16_t ao_pad_arm_time;
+static __pdata uint8_t ao_pad_box;
+static __xdata uint8_t ao_pad_disabled;
+
+#define DEBUG 1
+
+#if DEBUG
+static __pdata uint8_t ao_pad_debug;
+#define PRINTD(...) (ao_pad_debug ? (printf(__VA_ARGS__), 0) : 0)
+#define FLUSHD() (ao_pad_debug ? (flush(), 0) : 0)
+#else
+#define PRINTD(...)
+#define FLUSHD()
+#endif
+
+static void
+ao_pad_run(void)
+{
+ for (;;) {
+ while (!ao_pad_ignite)
+ ao_sleep(&ao_pad_ignite);
+ /*
+ * Actually set the pad bits
+ */
+ AO_PAD_PORT = (AO_PAD_PORT & (~AO_PAD_ALL_PINS)) | ao_pad_ignite;
+ while (ao_pad_ignite) {
+ ao_pad_ignite = 0;
+ ao_delay(AO_PAD_FIRE_TIME);
+ }
+ AO_PAD_PORT &= ~(AO_PAD_ALL_PINS);
+ }
+}
+
+#define AO_PAD_ARM_BEEP_INTERVAL 200
+
+static void
+ao_pad_monitor(void)
+{
+ uint8_t c;
+ uint8_t sample;
+ __pdata uint8_t prev = 0, cur = 0;
+ __pdata uint8_t beeping = 0;
+ __xdata struct ao_data *packet;
+ __pdata uint16_t arm_beep_time = 0;
+
+ sample = ao_data_head;
+ for (;;) {
+ __pdata int16_t pyro;
+ ao_arch_critical(
+ while (sample == ao_data_head)
+ ao_sleep((void *) DATA_TO_XDATA(&ao_data_head));
+ );
+
+ packet = &ao_data_ring[sample];
+ sample = ao_data_ring_next(sample);
+
+ pyro = packet->adc.pyro;
+
+#define VOLTS_TO_PYRO(x) ((int16_t) ((x) * 27.0 / 127.0 / 3.3 * 32767.0))
+
+ cur = 0;
+ if (pyro > VOLTS_TO_PYRO(10)) {
+ query.arm_status = AO_PAD_ARM_STATUS_ARMED;
+ cur |= AO_LED_ARMED;
+ } else if (pyro < VOLTS_TO_PYRO(5)) {
+ query.arm_status = AO_PAD_ARM_STATUS_DISARMED;
+ arm_beep_time = 0;
+ } else {
+ if ((ao_time() % 100) < 50)
+ cur |= AO_LED_ARMED;
+ query.arm_status = AO_PAD_ARM_STATUS_UNKNOWN;
+ arm_beep_time = 0;
+ }
+
+ for (c = 0; c < AO_PAD_NUM; c++) {
+ int16_t sense = packet->adc.sense[c];
+ uint8_t status = AO_PAD_IGNITER_STATUS_UNKNOWN;
+
+ /*
+ * pyro is run through a divider, so pyro = v_pyro * 27 / 127 ~= v_pyro / 20
+ * v_pyro = pyro * 127 / 27
+ *
+ * v_pyro \
+ * 100k igniter
+ * output /
+ * 100k \
+ * sense relay
+ * 27k /
+ * gnd ---
+ *
+ * If the relay is closed, then sense will be 0
+ * If no igniter is present, then sense will be v_pyro * 27k/227k = pyro * 127 / 227 ~= pyro/2
+ * If igniter is present, then sense will be v_pyro * 27k/127k ~= v_pyro / 20 = pyro
+ */
+
+ if (sense <= pyro / 8) {
+ status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED;
+ if ((ao_time() % 100) < 50)
+ cur |= AO_LED_CONTINUITY(c);
+ }
+ else if (pyro / 8 * 3 <= sense && sense <= pyro / 8 * 5)
+ status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
+ else if (pyro / 8 * 7 <= sense) {
+ status = AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN;
+ cur |= AO_LED_CONTINUITY(c);
+ }
+ query.igniter_status[c] = status;
+ }
+ if (cur != prev) {
+ PRINTD("change leds from %02x to %02x mask %02x\n",
+ prev, cur, AO_LED_CONTINUITY_MASK|AO_LED_ARMED);
+ ao_led_set_mask(cur, AO_LED_CONTINUITY_MASK | AO_LED_ARMED);
+ prev = cur;
+ }
+
+ if (ao_pad_armed) {
+ if (sample & 2)
+ ao_beep(AO_BEEP_HIGH);
+ else
+ ao_beep(AO_BEEP_LOW);
+ beeping = 1;
+ } else if (query.arm_status == AO_PAD_ARM_STATUS_ARMED && !beeping) {
+ if (arm_beep_time == 0) {
+ arm_beep_time = AO_PAD_ARM_BEEP_INTERVAL;
+ beeping = 1;
+ ao_beep(AO_BEEP_HIGH);
+ }
+ --arm_beep_time;
+ } else if (beeping) {
+ beeping = 0;
+ ao_beep(0);
+ }
+ }
+}
+
+void
+ao_pad_disable(void)
+{
+ if (!ao_pad_disabled) {
+ ao_pad_disabled = 1;
+ ao_radio_recv_abort();
+ }
+}
+
+void
+ao_pad_enable(void)
+{
+ ao_pad_disabled = 0;
+ ao_wakeup (&ao_pad_disabled);
+}
+
+static void
+ao_pad(void)
+{
+ int16_t time_difference;
+ int8_t ret;
+
+ ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
+ ao_pad_box = 0;
+ ao_led_set(0);
+ ao_led_on(AO_LED_POWER);
+ for (;;) {
+ FLUSHD();
+ while (ao_pad_disabled)
+ ao_sleep(&ao_pad_disabled);
+ ret = ao_radio_cmac_recv(&command, sizeof (command), 0);
+ PRINTD ("cmac_recv %d\n", ret);
+ if (ret != AO_RADIO_CMAC_OK)
+ continue;
+
+ PRINTD ("tick %d box %d cmd %d channels %02x\n",
+ command.tick, command.box, command.cmd, command.channels);
+
+ if (ao_pad_armed && (int16_t) (ao_time() - ao_pad_arm_time) > AO_PAD_ARM_TIME)
+ ao_pad_armed = 0;
+
+ switch (command.cmd) {
+ case AO_LAUNCH_ARM:
+ if (command.box != ao_pad_box) {
+ PRINTD ("box number mismatch\n");
+ break;
+ }
+
+ if (command.channels & ~(AO_PAD_ALL_PINS))
+ break;
+
+ time_difference = command.tick - ao_time();
+ PRINTD ("arm tick %d local tick %d\n", command.tick, ao_time());
+ if (time_difference < 0)
+ time_difference = -time_difference;
+ if (time_difference > 10) {
+ PRINTD ("time difference too large %d\n", time_difference);
+ break;
+ }
+ PRINTD ("armed\n");
+ ao_pad_armed = command.channels;
+ ao_pad_arm_time = ao_time();
+
+ /* fall through ... */
+
+ case AO_LAUNCH_QUERY:
+ if (command.box != ao_pad_box) {
+ PRINTD ("box number mismatch\n");
+ break;
+ }
+
+ query.tick = ao_time();
+ query.box = ao_pad_box;
+ query.channels = AO_PAD_ALL_PINS;
+ query.armed = ao_pad_armed;
+ PRINTD ("query tick %d box %d channels %02x arm %d arm_status %d igniter %d,%d,%d,%d\n",
+ query.tick, query.box, query.channels, query.armed,
+ query.arm_status,
+ query.igniter_status[0],
+ query.igniter_status[1],
+ query.igniter_status[2],
+ query.igniter_status[3]);
+ ao_radio_cmac_send(&query, sizeof (query));
+ break;
+ case AO_LAUNCH_FIRE:
+ if (!ao_pad_armed) {
+ PRINTD ("not armed\n");
+ break;
+ }
+ if ((uint16_t) (ao_time() - ao_pad_arm_time) > AO_SEC_TO_TICKS(20)) {
+ PRINTD ("late pad arm_time %d time %d\n",
+ ao_pad_arm_time, ao_time());
+ break;
+ }
+ time_difference = command.tick - ao_time();
+ if (time_difference < 0)
+ time_difference = -time_difference;
+ if (time_difference > 10) {
+ PRINTD ("time different too large %d\n", time_difference);
+ break;
+ }
+ PRINTD ("ignite\n");
+ ao_pad_ignite = ao_pad_armed;
+ ao_pad_arm_time = ao_time();
+ ao_wakeup(&ao_pad_ignite);
+ break;
+ }
+ }
+}
+
+void
+ao_pad_test(void)
+{
+ uint8_t c;
+
+ printf ("Arm switch: ");
+ switch (query.arm_status) {
+ case AO_PAD_ARM_STATUS_ARMED:
+ printf ("Armed\n");
+ break;
+ case AO_PAD_ARM_STATUS_DISARMED:
+ printf ("Disarmed\n");
+ break;
+ case AO_PAD_ARM_STATUS_UNKNOWN:
+ printf ("Unknown\n");
+ break;
+ }
+
+ for (c = 0; c < AO_PAD_NUM; c++) {
+ printf ("Pad %d: ");
+ switch (query.igniter_status[c]) {
+ case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED: printf ("No igniter. Relay closed\n"); break;
+ case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN: printf ("No igniter. Relay open\n"); break;
+ case AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN: printf ("Good igniter. Relay open\n"); break;
+ case AO_PAD_IGNITER_STATUS_UNKNOWN: printf ("Unknown\n"); break;
+ }
+ }
+}
+
+void
+ao_pad_manual(void)
+{
+ ao_cmd_white();
+ if (!ao_match_word("DoIt"))
+ return;
+ ao_cmd_decimal();
+ if (ao_cmd_status != ao_cmd_success)
+ return;
+ ao_pad_ignite = 1 << ao_cmd_lex_i;
+ ao_wakeup(&ao_pad_ignite);
+}
+
+static __xdata struct ao_task ao_pad_task;
+static __xdata struct ao_task ao_pad_ignite_task;
+static __xdata struct ao_task ao_pad_monitor_task;
+
+#if DEBUG
+void
+ao_pad_set_debug(void)
+{
+ ao_cmd_decimal();
+ if (ao_cmd_status == ao_cmd_success)
+ ao_pad_debug = ao_cmd_lex_i != 0;
+}
+#endif
+
+__code struct ao_cmds ao_pad_cmds[] = {
+ { ao_pad_test, "t\0Test pad continuity" },
+ { ao_pad_manual, "i <key> <n>\0Fire igniter. <key> is doit with D&I" },
+#if DEBUG
+ { ao_pad_set_debug, "D <0 off, 1 on>\0Debug" },
+#endif
+ { 0, NULL }
+};
+
+void
+ao_pad_init(void)
+{
+#if AO_PAD_NUM > 0
+ ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_0, AO_PAD_0, 0);
+#endif
+#if AO_PAD_NUM > 1
+ ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_1, AO_PAD_1, 0);
+#endif
+#if AO_PAD_NUM > 2
+ ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_2, AO_PAD_2, 0);
+#endif
+#if AO_PAD_NUM > 3
+ ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_3, AO_PAD_3, 0);
+#endif
+ ao_cmd_register(&ao_pad_cmds[0]);
+ ao_add_task(&ao_pad_task, ao_pad, "pad listener");
+ ao_add_task(&ao_pad_ignite_task, ao_pad_run, "pad igniter");
+ ao_add_task(&ao_pad_monitor_task, ao_pad_monitor, "pad monitor");
+}
diff --git a/src/drivers/ao_pad.h b/src/drivers/ao_pad.h
new file mode 100644
index 00000000..23062899
--- /dev/null
+++ b/src/drivers/ao_pad.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_PAD_H_
+#define _AO_PAD_H_
+
+#define AO_PAD_MAX_CHANNELS 8
+#define AO_PAD_MAX_BOXES 100
+
+struct ao_pad_command {
+ uint16_t tick;
+ uint16_t box;
+ uint8_t cmd;
+ uint8_t channels;
+};
+
+/* Report current telefire status.
+ */
+
+#define AO_PAD_QUERY 1
+
+struct ao_pad_query {
+ uint16_t tick; /* telefire tick */
+ uint16_t box; /* telefire box number */
+ uint8_t channels; /* which chanels are present */
+ uint8_t armed; /* which channels are armed */
+ uint8_t arm_status; /* status of arming switch */
+ uint8_t igniter_status[AO_PAD_MAX_CHANNELS]; /* status for each igniter */
+};
+
+/* Arm pads for 3 seconds, no report
+ */
+#define AO_PAD_ARM 2
+
+#define AO_PAD_ARM_TIME AO_SEC_TO_TICKS(3)
+
+/* Fire current armed pads for 200ms, no report
+ */
+#define AO_PAD_FIRE 3
+
+#define AO_PAD_FIRE_TIME AO_MS_TO_TICKS(200)
+
+#define AO_PAD_ARM_STATUS_DISARMED 0
+#define AO_PAD_ARM_STATUS_ARMED 1
+#define AO_PAD_ARM_STATUS_UNKNOWN 2
+
+#define AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN 0
+#define AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN 1
+#define AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED 2
+#define AO_PAD_IGNITER_STATUS_UNKNOWN 3
+
+void
+ao_pad_init(void);
+
+void
+ao_pad_disable(void);
+
+void
+ao_pad_enable(void);
+
+#endif /* _AO_PAD_H_ */
diff --git a/src/drivers/ao_pca9922.c b/src/drivers/ao_pca9922.c
new file mode 100644
index 00000000..6d8d18d8
--- /dev/null
+++ b/src/drivers/ao_pca9922.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+/*
+ * PCA9922 LED driver. This uses SPI to send a single byte to the device to
+ * set the current state of the LEDs using the existing LED interface
+ */
+
+#include <ao.h>
+
+static __xdata uint8_t ao_led_state;
+
+static void
+ao_led_apply(void)
+{
+ /* Don't try the SPI bus during initialization */
+ if (!ao_cur_task)
+ return;
+ ao_spi_get_bit(AO_PCA9922_CS_PORT, AO_PCA9922_CS_PIN, AO_PCA9922_CS, AO_PCA9922_SPI_BUS, AO_SPI_SPEED_FAST);
+ ao_spi_send(&ao_led_state, 1, AO_PCA9922_SPI_BUS);
+ ao_spi_put_bit(AO_PCA9922_CS_PORT, AO_PCA9922_CS_PIN, AO_PCA9922_CS, AO_PCA9922_SPI_BUS);
+}
+
+void
+ao_led_on(uint8_t colors)
+{
+ ao_led_state |= colors;
+ ao_led_apply();
+}
+
+void
+ao_led_off(uint8_t colors)
+{
+ ao_led_state &= ~colors;
+ ao_led_apply();
+}
+
+void
+ao_led_set(uint8_t colors)
+{
+ ao_led_state = colors;
+ ao_led_apply();
+}
+
+void
+ao_led_set_mask(uint8_t colors, uint8_t mask)
+{
+ ao_led_state = (ao_led_state & ~mask) | (colors & mask);
+ ao_led_apply();
+}
+
+void
+ao_led_toggle(uint8_t colors)
+{
+ ao_led_state ^= colors;
+ ao_led_apply();
+}
+
+void
+ao_led_for(uint8_t colors, uint16_t ticks) __reentrant
+{
+ ao_led_on(colors);
+ ao_delay(ticks);
+ ao_led_off(colors);
+}
+
+void
+ao_led_init(uint8_t enable)
+{
+ (void) enable;
+ ao_enable_output(AO_PCA9922_CS_PORT, AO_PCA9922_CS_PIN, AO_PCA9922_CS, 1);
+}
diff --git a/src/drivers/ao_pyro_slave.c b/src/drivers/ao_pyro_slave.c
new file mode 100644
index 00000000..f07c2cba
--- /dev/null
+++ b/src/drivers/ao_pyro_slave.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2011 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_product.h>
+#include <ao_companion.h>
+#include <ao_flight.h>
+#include <ao_pyro.h>
+
+struct ao_companion_command ao_companion_command;
+
+static const struct ao_companion_setup ao_telepyro_setup = {
+ .board_id = AO_idProduct_NUMBER,
+ .board_id_inverse = ~AO_idProduct_NUMBER,
+ .update_period = 50,
+ .channels = AO_TELEPYRO_NUM_ADC,
+};
+
+struct ao_config ao_config;
+
+extern volatile __data uint16_t ao_tick_count;
+uint16_t ao_boost_tick;
+
+void ao_spi_slave(void)
+{
+ if (!ao_spi_slave_recv((uint8_t *) &ao_companion_command,
+ sizeof (ao_companion_command)))
+ return;
+
+ /* Figure out the outbound data */
+ switch (ao_companion_command.command) {
+ case AO_COMPANION_SETUP:
+ ao_spi_slave_send((uint8_t *) &ao_telepyro_setup,
+ sizeof (ao_telepyro_setup));
+ break;
+ case AO_COMPANION_FETCH:
+ ao_spi_slave_send((uint8_t *) &ao_data_ring[ao_data_ring_prev(ao_data_head)].adc.adc,
+ AO_TELEPYRO_NUM_ADC * sizeof (uint16_t));
+ break;
+ case AO_COMPANION_NOTIFY:
+ /* Can't use ao_time because we have interrupts suspended */
+ if (ao_companion_command.flight_state < ao_flight_boost && ao_companion_command.flight_state >= ao_flight_boost)
+ ao_boost_tick = ao_tick_count;
+ ao_wakeup(&ao_pyro_wakeup);
+ break;
+ default:
+ return;
+ }
+}
diff --git a/src/drivers/ao_quadrature.c b/src/drivers/ao_quadrature.c
new file mode 100644
index 00000000..6cc2467a
--- /dev/null
+++ b/src/drivers/ao_quadrature.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_quadrature.h>
+#include <ao_exti.h>
+#if AO_EVENT
+#include <ao_event.h>
+#define ao_quadrature_queue(q) ao_event_put_isr(AO_EVENT_QUADRATURE, q, ao_quadrature_count[q])
+#else
+#define ao_quadrature_queue(q)
+#endif
+
+__xdata int32_t ao_quadrature_count[AO_QUADRATURE_COUNT];
+
+static uint8_t ao_quadrature_state[AO_QUADRATURE_COUNT];
+
+#define BIT(a,b) ((a) | ((b) << 1))
+#define STATE(old_a, old_b, new_a, new_b) (((BIT(old_a, old_b) << 2) | BIT(new_a, new_b)))
+
+#define port(q) AO_QUADRATURE_ ## q ## _PORT
+#define bita(q) AO_QUADRATURE_ ## q ## _A
+#define bitb(q) AO_QUADRATURE_ ## q ## _B
+
+#define ao_quadrature_update(q) do { \
+ ao_quadrature_state[q] = ((ao_quadrature_state[q] & 3) << 2); \
+ ao_quadrature_state[q] |= ao_gpio_get(port(q), bita(q), 0); \
+ ao_quadrature_state[q] |= ao_gpio_get(port(q), bitb(q), 0) << 1; \
+ } while (0)
+
+
+static void
+ao_quadrature_isr(void)
+{
+ uint8_t q;
+#if AO_QUADRATURE_COUNT > 0
+ ao_quadrature_update(0);
+#endif
+#if AO_QUADRATURE_COUNT > 1
+ ao_quadrature_update(1);
+#endif
+
+ for (q = 0; q < AO_QUADRATURE_COUNT; q++) {
+ switch (ao_quadrature_state[q]) {
+ case STATE(0, 1, 0, 0):
+ ao_quadrature_count[q]++;
+ break;
+ case STATE(1, 0, 0, 0):
+ ao_quadrature_count[q]--;
+ break;
+ default:
+ continue;
+ }
+ ao_quadrature_queue(q);
+ ao_wakeup(&ao_quadrature_count[q]);
+ }
+}
+
+int32_t
+ao_quadrature_poll(uint8_t q)
+{
+ int32_t ret;
+ ao_arch_critical(ret = ao_quadrature_count[q];);
+ return ret;
+}
+
+int32_t
+ao_quadrature_wait(uint8_t q)
+{
+ ao_sleep(&ao_quadrature_count[q]);
+ return ao_quadrature_poll(q);
+}
+
+static void
+ao_quadrature_test(void)
+{
+ uint8_t q;
+
+ ao_cmd_decimal();
+ q = ao_cmd_lex_i;
+ for (;;) {
+ int32_t c;
+ flush();
+ c = ao_quadrature_wait(q);
+ printf ("new count %6d\n", c);
+ if (c == 100)
+ break;
+ }
+}
+
+static const struct ao_cmds ao_quadrature_cmds[] = {
+ { ao_quadrature_test, "q <unit>\0Test quadrature" },
+ { 0, NULL }
+};
+
+#define init(q) do { \
+ ao_enable_port(port(q)); \
+ \
+ ao_exti_setup(port(q), bita(q), \
+ AO_QUADRATURE_MODE|AO_EXTI_MODE_FALLING|AO_EXTI_MODE_RISING|AO_EXTI_PRIORITY_MED, \
+ ao_quadrature_isr); \
+ ao_exti_enable(port(q), bita(q)); \
+ \
+ ao_exti_setup(port(q), bitb(q), \
+ AO_QUADRATURE_MODE|AO_EXTI_MODE_FALLING|AO_EXTI_MODE_RISING|AO_EXTI_PRIORITY_MED, \
+ ao_quadrature_isr); \
+ ao_exti_enable(port(q), bitb(q)); \
+ } while (0)
+
+void
+ao_quadrature_init(void)
+{
+#if AO_QUADRATURE_COUNT > 0
+ init(0);
+#endif
+#if AO_QUADRATURE_COUNT > 1
+ init(1);
+#endif
+ ao_cmd_register(&ao_quadrature_cmds[0]);
+}
diff --git a/src/drivers/ao_quadrature.h b/src/drivers/ao_quadrature.h
new file mode 100644
index 00000000..d7dda682
--- /dev/null
+++ b/src/drivers/ao_quadrature.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_QUADRATURE_H_
+#define _AO_QUADRATURE_H_
+
+extern __xdata int32_t ao_quadrature_count[AO_QUADRATURE_COUNT];
+
+int32_t
+ao_quadrature_wait(uint8_t q);
+
+int32_t
+ao_quadrature_poll(uint8_t q);
+
+void
+ao_quadrature_init(void);
+
+#endif /* _AO_QUADRATURE_H_ */
diff --git a/src/drivers/ao_radio_master.c b/src/drivers/ao_radio_master.c
new file mode 100644
index 00000000..73ac3c03
--- /dev/null
+++ b/src/drivers/ao_radio_master.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_radio_spi.h>
+#include <ao_exti.h>
+#include <ao_radio_cmac.h>
+
+static __xdata struct ao_radio_spi_reply ao_radio_spi_reply;
+static __xdata struct ao_radio_spi_request ao_radio_spi_request;
+static volatile __xdata uint8_t ao_radio_wait_mode;
+static volatile __xdata uint8_t ao_radio_done = 0;
+static volatile __xdata uint8_t ao_radio_ready = 1;
+static __xdata uint8_t ao_radio_mutex;
+static __xdata uint8_t ao_radio_aes_seq;
+
+__xdata int8_t ao_radio_cmac_rssi;
+
+#if 0
+#define PRINTD(...) do { printf ("\r%5u %s: ", ao_tick_count, __func__); printf(__VA_ARGS__); flush(); } while(0)
+#else
+#define PRINTD(...)
+#endif
+
+static void
+ao_radio_isr(void)
+{
+ if (ao_gpio_get(AO_RADIO_INT_PORT, AO_RADIO_INT_PIN, AO_RADIO_INT)) {
+ ao_radio_ready = 1;
+ ao_wakeup((void *) &ao_radio_ready);
+ } else {
+ ao_radio_done = 1;
+ ao_wakeup((void *) &ao_radio_done);
+ }
+}
+
+static void
+ao_radio_master_start(void)
+{
+ ao_spi_get_bit(AO_RADIO_CS_PORT, AO_RADIO_CS_PIN, AO_RADIO_CS,
+ AO_RADIO_SPI_BUS,
+ AO_SPI_SPEED_200kHz);
+}
+
+static void
+ao_radio_master_stop(void)
+{
+ ao_spi_put_bit(AO_RADIO_CS_PORT, AO_RADIO_CS_PIN, AO_RADIO_CS,
+ AO_RADIO_SPI_BUS);
+}
+
+static uint8_t
+ao_radio_master_send(void)
+{
+ uint8_t ret;
+
+ PRINTD("send %d\n", ao_radio_spi_request.len);
+ ao_radio_done = 0;
+
+ /* Wait for radio chip to be ready for a command
+ */
+
+ PRINTD("Waiting radio ready\n");
+ cli();
+ ao_radio_ready = ao_gpio_get(AO_RADIO_INT_PORT,
+ AO_RADIO_INT_PIN, AO_RADIO_INT);
+ ret = 0;
+ while (!ao_radio_ready) {
+ ret = ao_sleep((void *) &ao_radio_ready);
+ if (ret)
+ break;
+ }
+ sei();
+ if (ret)
+ return 0;
+
+ PRINTD("radio_ready %d radio_done %d\n", ao_radio_ready, ao_radio_done);
+
+ /* Send the command
+ */
+ ao_radio_wait_mode = 0;
+ ao_radio_master_start();
+ ao_spi_send(&ao_radio_spi_request,
+ ao_radio_spi_request.len,
+ AO_RADIO_SPI_BUS);
+ ao_radio_master_stop();
+ PRINTD("waiting for send done %d\n", ao_radio_done);
+ cli();
+ while (!ao_radio_done)
+ if (ao_sleep((void *) &ao_radio_done))
+ break;
+ sei();
+ PRINTD ("sent, radio done %d isr_0 %d isr_1 %d\n", ao_radio_done, isr_0_count, isr_1_count);
+ return ao_radio_done;
+}
+
+static void
+ao_radio_get(uint8_t req, uint8_t len)
+{
+ ao_config_get();
+ ao_mutex_get(&ao_radio_mutex);
+ ao_radio_spi_request.len = AO_RADIO_SPI_REQUEST_HEADER_LEN + len;
+ ao_radio_spi_request.request = req;
+ ao_radio_spi_request.setting = ao_config.radio_setting;
+}
+
+static void
+ao_radio_put(void)
+{
+ ao_mutex_put(&ao_radio_mutex);
+}
+
+static void
+ao_radio_get_data(__xdata void *d, uint8_t size)
+{
+ PRINTD ("fetch\n");
+ ao_radio_master_start();
+ ao_spi_recv(&ao_radio_spi_reply,
+ AO_RADIO_SPI_REPLY_HEADER_LEN + size,
+ AO_RADIO_SPI_BUS);
+ ao_radio_master_stop();
+ ao_xmemcpy(d, ao_radio_spi_reply.payload, size);
+ PRINTD ("fetched %d\n", size);
+}
+
+void
+ao_radio_recv_abort(void)
+{
+ ao_radio_get(AO_RADIO_SPI_RECV_ABORT, 0);
+ ao_radio_master_send();
+ ao_radio_put();
+}
+
+void
+ao_radio_send(const void *d, uint8_t size)
+{
+ ao_radio_get(AO_RADIO_SPI_SEND, size);
+ ao_xmemcpy(&ao_radio_spi_request.payload, d, size);
+ ao_radio_master_send();
+ ao_radio_put();
+}
+
+
+uint8_t
+ao_radio_recv(__xdata void *d, uint8_t size)
+{
+ int8_t ret;
+ uint8_t recv;
+
+ /* Recv the data
+ */
+
+ ao_radio_get(AO_RADIO_SPI_RECV, 0);
+ ao_radio_spi_request.recv_len = size;
+ recv = ao_radio_master_send();
+ if (!recv) {
+ ao_radio_put();
+ ao_radio_recv_abort();
+ return 0;
+ }
+ ao_radio_get_data(d, size);
+ recv = ao_radio_spi_reply.status;
+ ao_radio_put();
+
+ return recv;
+}
+
+static void
+ao_radio_cmac_set_key(void)
+{
+ if (ao_radio_aes_seq == ao_config_aes_seq)
+ return;
+ /* Set the key.
+ */
+ PRINTD ("set key\n");
+ ao_radio_get(AO_RADIO_SPI_CMAC_KEY, AO_AES_LEN);
+ ao_xmemcpy(&ao_radio_spi_request.payload, &ao_config.aes_key, AO_AES_LEN);
+ ao_radio_master_send();
+ ao_radio_put();
+ PRINTD ("key set\n");
+ ao_radio_aes_seq = ao_config_aes_seq;
+}
+
+int8_t
+ao_radio_cmac_send(__xdata void *packet, uint8_t len) __reentrant
+{
+ if (len > AO_CMAC_MAX_LEN)
+ return AO_RADIO_CMAC_LEN_ERROR;
+
+ ao_radio_cmac_set_key();
+
+ PRINTD ("cmac_send: send %d\n", len);
+
+ /* Send the data
+ */
+
+ PRINTD ("sending packet\n");
+ ao_radio_get(AO_RADIO_SPI_CMAC_SEND, len);
+ ao_xmemcpy(&ao_radio_spi_request.payload, packet, len);
+ ao_radio_master_send();
+ ao_radio_put();
+ PRINTD ("packet sent\n");
+ return AO_RADIO_CMAC_OK;
+}
+
+int8_t
+ao_radio_cmac_recv(__xdata void *packet, uint8_t len, uint16_t timeout) __reentrant
+{
+ int8_t ret;
+ int8_t recv;
+
+ if (len > AO_CMAC_MAX_LEN)
+ return AO_RADIO_CMAC_LEN_ERROR;
+
+ ao_radio_cmac_set_key();
+
+ /* Recv the data
+ */
+ PRINTD ("queuing recv\n");
+ ao_radio_get(AO_RADIO_SPI_CMAC_RECV, 0);
+ ao_radio_spi_request.recv_len = len;
+ ao_radio_spi_request.timeout = timeout;
+ recv = ao_radio_master_send();
+ PRINTD ("recv queued: %d\n", recv);
+ if (!recv) {
+ ao_radio_put();
+ ao_radio_recv_abort();
+ return AO_RADIO_CMAC_TIMEOUT;
+ }
+
+ PRINTD ("fetching data\n");
+ ao_radio_get_data(packet, len);
+ recv = ao_radio_spi_reply.status;
+ ao_radio_cmac_rssi = ao_radio_spi_reply.rssi;
+ ao_radio_put();
+ PRINTD ("data fetched: %d %d\n", recv, ao_radio_cmac_rssi);
+ return recv;
+}
+
+static uint8_t ao_radio_test_on;
+
+void
+ao_radio_test(uint8_t on)
+{
+ if (on) {
+ if (!ao_radio_test_on) {
+ ao_radio_get(AO_RADIO_SPI_TEST_ON, 0);
+ ao_radio_test_on = 1;
+ ao_radio_master_send();
+ }
+ } else {
+ if (ao_radio_test_on) {
+ ao_radio_spi_request.len = AO_RADIO_SPI_REQUEST_HEADER_LEN;
+ ao_radio_spi_request.request = AO_RADIO_SPI_TEST_OFF;
+ ao_radio_master_send();
+ ao_radio_test_on = 0;
+ ao_radio_put();
+ }
+ }
+}
+
+static void
+ao_radio_test_cmd(void)
+{
+ uint8_t mode = 2;
+ ao_cmd_white();
+ if (ao_cmd_lex_c != '\n') {
+ ao_cmd_decimal();
+ mode = (uint8_t) ao_cmd_lex_u32;
+ }
+ mode++;
+ if ((mode & 2))
+ ao_radio_test(1);
+ if (mode == 3) {
+ printf ("Hit a character to stop..."); flush();
+ getchar();
+ putchar('\n');
+ }
+ if ((mode & 1))
+ ao_radio_test(0);
+}
+
+__code struct ao_cmds ao_radio_cmds[] = {
+ { ao_radio_test_cmd, "C <1 start, 0 stop, none both>\0Radio carrier test" },
+ { 0, NULL },
+};
+
+void
+ao_radio_init(void)
+{
+ ao_spi_init_cs(AO_RADIO_CS_PORT, (1 << AO_RADIO_CS_PIN));
+
+ ao_enable_port(AO_RADIO_INT_PORT);
+ ao_exti_setup(AO_RADIO_INT_PORT,
+ AO_RADIO_INT_PIN,
+ AO_EXTI_MODE_RISING|AO_EXTI_MODE_FALLING,
+ ao_radio_isr);
+ ao_exti_enable(AO_RADIO_INT_PORT, AO_RADIO_INT_PIN);
+ ao_cmd_register(&ao_radio_cmds[0]);
+}
diff --git a/src/drivers/ao_radio_slave.c b/src/drivers/ao_radio_slave.c
new file mode 100644
index 00000000..1d1f16fe
--- /dev/null
+++ b/src/drivers/ao_radio_slave.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_radio_spi.h>
+#include <ao_radio_cmac.h>
+
+static __xdata struct ao_radio_spi_reply ao_radio_spi_reply;
+
+static __xdata struct ao_radio_spi_request ao_radio_spi_request;
+
+static __xdata uint8_t slave_state;
+
+static void
+ao_radio_slave_low(void)
+{
+ uint16_t i;
+
+ if (slave_state != 1)
+ ao_panic(1);
+ ao_gpio_set(AO_RADIO_SLAVE_INT_PORT, AO_RADIO_SLAVE_INT_BIT, AO_RADIO_SLAVE_INT_PIN, 0);
+ for (i = 0; i < 1000; i++)
+ ao_arch_nop();
+ slave_state = 0;
+}
+
+static void
+ao_radio_slave_high(void)
+{
+ if (slave_state != 0)
+ ao_panic(2);
+ ao_gpio_set(AO_RADIO_SLAVE_INT_PORT, AO_RADIO_SLAVE_INT_BIT, AO_RADIO_SLAVE_INT_PIN, 1);
+ slave_state = 1;
+}
+
+static void
+ao_radio_slave_spi(void)
+{
+ ao_spi_get_slave(AO_RADIO_SLAVE_BUS);
+ for (;;) {
+ ao_spi_recv(&ao_radio_spi_request,
+ (2 << 13) | sizeof (ao_radio_spi_request),
+ AO_RADIO_SLAVE_BUS);
+ ao_radio_slave_high();
+ ao_spi_recv_wait();
+ switch (ao_radio_spi_request.request) {
+ case AO_RADIO_SPI_RECV:
+
+ /* XXX monitor CS to interrupt the receive */
+
+ ao_config.radio_setting = ao_radio_spi_request.setting;
+ ao_led_on(AO_LED_RX);
+ ao_radio_spi_reply.status = ao_radio_recv(&ao_radio_spi_reply.payload,
+ ao_radio_spi_request.recv_len);
+ ao_led_off(AO_LED_RX);
+ ao_radio_spi_reply.rssi = 0;
+ ao_spi_send(&ao_radio_spi_reply,
+ AO_RADIO_SPI_REPLY_HEADER_LEN + ao_radio_spi_request.recv_len,
+ AO_RADIO_SLAVE_BUS);
+ ao_radio_slave_low();
+ ao_spi_send_wait();
+ continue;
+ case AO_RADIO_SPI_CMAC_RECV:
+ ao_config.radio_setting = ao_radio_spi_request.setting;
+ ao_led_on(AO_LED_RX);
+ ao_radio_spi_reply.status = ao_radio_cmac_recv(&ao_radio_spi_reply.payload,
+ ao_radio_spi_request.recv_len,
+ ao_radio_spi_request.timeout);
+ ao_led_off(AO_LED_RX);
+ ao_radio_spi_reply.rssi = ao_radio_cmac_rssi;
+ ao_spi_send(&ao_radio_spi_reply,
+ AO_RADIO_SPI_REPLY_HEADER_LEN + ao_radio_spi_request.recv_len,
+ AO_RADIO_SLAVE_BUS);
+ ao_radio_slave_low();
+ ao_spi_send_wait();
+ continue;
+ case AO_RADIO_SPI_SEND:
+ ao_config.radio_setting = ao_radio_spi_request.setting;
+ ao_led_on(AO_LED_TX);
+ ao_radio_send(&ao_radio_spi_request.payload,
+ ao_radio_spi_request.len - AO_RADIO_SPI_REQUEST_HEADER_LEN);
+ ao_led_off(AO_LED_TX);
+ break;
+
+ case AO_RADIO_SPI_CMAC_SEND:
+ ao_config.radio_setting = ao_radio_spi_request.setting;
+ ao_led_on(AO_LED_TX);
+ ao_radio_cmac_send(&ao_radio_spi_request.payload,
+ ao_radio_spi_request.len - AO_RADIO_SPI_REQUEST_HEADER_LEN);
+ ao_led_off(AO_LED_TX);
+ break;
+
+ case AO_RADIO_SPI_CMAC_KEY:
+ ao_xmemcpy(&ao_config.aes_key, ao_radio_spi_request.payload, AO_AES_LEN);
+ break;
+
+ case AO_RADIO_SPI_TEST_ON:
+ ao_config.radio_setting = ao_radio_spi_request.setting;
+ ao_radio_test(1);
+ break;
+
+ case AO_RADIO_SPI_TEST_OFF:
+ ao_radio_test(0);
+ break;
+ }
+ ao_radio_slave_low();
+ }
+}
+
+static __xdata struct ao_task ao_radio_slave_spi_task;
+
+void
+ao_radio_slave_init(void)
+{
+ ao_add_task(&ao_radio_slave_spi_task, ao_radio_slave_spi, "radio_spi");
+ ao_enable_output(AO_RADIO_SLAVE_INT_PORT, AO_RADIO_SLAVE_INT_BIT, AO_RADIO_SLAVE_INT_PIN, 0);
+}
diff --git a/src/drivers/ao_radio_spi.h b/src/drivers/ao_radio_spi.h
new file mode 100644
index 00000000..2957f70d
--- /dev/null
+++ b/src/drivers/ao_radio_spi.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_RADIO_SPI_H_
+#define _AO_RADIO_SPI_H_
+
+#define AO_RADIO_SPI_RECV 0
+#define AO_RADIO_SPI_RECV_ABORT 1
+#define AO_RADIO_SPI_RECV_FETCH 2
+#define AO_RADIO_SPI_SEND 3
+
+#define AO_RADIO_SPI_CMAC_KEY 4
+#define AO_RADIO_SPI_CMAC_RECV 5
+#define AO_RADIO_SPI_CMAC_SEND 6
+
+#define AO_RADIO_SPI_TEST_ON 7
+#define AO_RADIO_SPI_TEST_OFF 8
+
+#define AO_RADIO_SPI_MAX_PAYLOAD 128
+
+struct ao_radio_spi_request {
+ uint8_t len; /* required to be first by cc1111 DMA engine */
+ uint8_t request;
+ uint8_t recv_len;
+ uint8_t pad;
+ uint32_t setting;
+ uint16_t timeout;
+ uint8_t payload[AO_RADIO_SPI_MAX_PAYLOAD];
+};
+
+#define AO_RADIO_SPI_REQUEST_HEADER_LEN (sizeof (struct ao_radio_spi_request) - AO_RADIO_SPI_MAX_PAYLOAD)
+
+struct ao_radio_spi_reply {
+ uint8_t status;
+ int8_t rssi;
+ uint8_t payload[AO_RADIO_SPI_MAX_PAYLOAD];
+};
+
+#define AO_RADIO_SPI_REPLY_HEADER_LEN (sizeof (struct ao_radio_spi_reply) - AO_RADIO_SPI_MAX_PAYLOAD)
+
+void
+ao_radio_slave_init(void);
+
+#endif /* _AO_RADIO_SPI_H_ */
diff --git a/src/drivers/ao_science_slave.c b/src/drivers/ao_science_slave.c
new file mode 100644
index 00000000..996e98d9
--- /dev/null
+++ b/src/drivers/ao_science_slave.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2011 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+#include "ao_product.h"
+#include "ao_flight.h"
+#include "ao_companion.h"
+
+struct ao_companion_command ao_companion_command;
+
+static const struct ao_companion_setup ao_telescience_setup = {
+ .board_id = AO_idProduct_NUMBER,
+ .board_id_inverse = ~AO_idProduct_NUMBER,
+ .update_period = 50,
+ .channels = AO_LOG_TELESCIENCE_NUM_ADC,
+};
+
+void ao_spi_slave(void)
+{
+ if (!ao_spi_slave_recv((uint8_t *) &ao_companion_command,
+ sizeof (ao_companion_command)))
+ return;
+
+ /* Figure out the outbound data */
+ switch (ao_companion_command.command) {
+ case AO_COMPANION_SETUP:
+ ao_spi_slave_send((uint8_t *) &ao_telescience_setup,
+ sizeof (ao_telescience_setup));
+ break;
+ case AO_COMPANION_FETCH:
+ ao_spi_slave_send((uint8_t *) &ao_data_ring[ao_data_ring_prev(ao_data_head)].adc,
+ AO_LOG_TELESCIENCE_NUM_ADC * sizeof (uint16_t));
+ break;
+ case AO_COMPANION_NOTIFY:
+ break;
+ default:
+ return;
+ }
+
+#if HAS_LOG
+ ao_log_single_write_data.telescience.tm_tick = ao_companion_command.tick;
+ if (ao_log_single_write_data.telescience.tm_state != ao_companion_command.flight_state) {
+ ao_log_single_write_data.telescience.tm_state = ao_companion_command.flight_state;
+ if (ao_flight_boost <= ao_log_single_write_data.telescience.tm_state) {
+ if (ao_log_single_write_data.telescience.tm_state < ao_flight_landed)
+ ao_log_single_start();
+ else
+ ao_log_single_stop();
+ }
+ }
+#endif
+}
diff --git a/src/drivers/ao_seven_segment.c b/src/drivers/ao_seven_segment.c
new file mode 100644
index 00000000..b3b5f878
--- /dev/null
+++ b/src/drivers/ao_seven_segment.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+#include <ao_seven_segment.h>
+#include <ao_lcd_stm.h>
+
+/*
+ * 0
+ * -------
+ * | |
+ * 1 | | 2
+ * | 3 |
+ * -------
+ * | |
+ * 4 | | 5
+ * | 6 |
+ * -------
+ * [] 7
+ *
+ */
+
+static const uint8_t ao_segments[] = {
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (0 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* 0 */
+
+ (0 << AO_SEGMENT_0) |
+ (0 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (0 << AO_SEGMENT_3) |
+ (0 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (0 << AO_SEGMENT_6), /* 1 */
+
+ (1 << AO_SEGMENT_0) |
+ (0 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (0 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* 2 */
+
+ (1 << AO_SEGMENT_0) |
+ (0 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (0 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* 3 */
+
+ (0 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (0 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (0 << AO_SEGMENT_6), /* 4 */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (0 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (0 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* 5 */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (0 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* 6 */
+
+ (1 << AO_SEGMENT_0) |
+ (0 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (0 << AO_SEGMENT_3) |
+ (0 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (0 << AO_SEGMENT_6), /* 7 */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* 8 */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (0 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* 9 */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (0 << AO_SEGMENT_6), /* A */
+
+ (0 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (0 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* b */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (0 << AO_SEGMENT_2) |
+ (0 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (0 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* c */
+
+ (0 << AO_SEGMENT_0) |
+ (0 << AO_SEGMENT_1) |
+ (1 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (1 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* d */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (0 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (0 << AO_SEGMENT_5) |
+ (1 << AO_SEGMENT_6), /* E */
+
+ (1 << AO_SEGMENT_0) |
+ (1 << AO_SEGMENT_1) |
+ (0 << AO_SEGMENT_2) |
+ (1 << AO_SEGMENT_3) |
+ (1 << AO_SEGMENT_4) |
+ (0 << AO_SEGMENT_5) |
+ (0 << AO_SEGMENT_6), /* F */
+};
+
+void
+ao_seven_segment_set(uint8_t digit, uint8_t value)
+{
+ uint8_t s;
+ uint8_t segments;
+
+ if (value == AO_SEVEN_SEGMENT_CLEAR)
+ segments = 0;
+ else {
+ segments = ao_segments[value & 0xf];
+
+ /* Check for decimal point */
+ if (value & 0x10)
+ segments |= (1 << AO_SEGMENT_7);
+ }
+
+ for (s = 0; s <= 7; s++)
+ ao_lcd_set(digit, s, !!(segments & (1 << s)));
+ ao_lcd_flush();
+}
+
+void
+ao_seven_segment_clear(void)
+{
+ ao_lcd_clear();
+}
+
+
+#if 0
+static void
+ao_seven_segment_show(void)
+{
+ uint8_t digit, value;
+ ao_cmd_decimal();
+ digit = ao_cmd_lex_i;
+ ao_cmd_decimal();
+ value = ao_cmd_lex_i;
+ ao_seven_segment_set(digit, value);
+}
+
+
+static const struct ao_cmds ao_seven_segment_cmds[] = {
+ { ao_seven_segment_show, "S <digit> <value>\0Set LCD digit" },
+ { 0, NULL },
+};
+#endif
+
+void
+ao_seven_segment_init(void)
+{
+#if 0
+ ao_cmd_register(ao_seven_segment_cmds);
+#endif
+}
diff --git a/src/drivers/ao_seven_segment.h b/src/drivers/ao_seven_segment.h
new file mode 100644
index 00000000..5b29deaf
--- /dev/null
+++ b/src/drivers/ao_seven_segment.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2012 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_SEVEN_SEGMENT_H_
+#define _AO_SEVEN_SEGMENT_H_
+
+#define AO_SEVEN_SEGMENT_DECIMAL 0x10
+
+#define AO_SEVEN_SEGMENT_CLEAR 0xff
+
+void
+ao_seven_segment_set(uint8_t digit, uint8_t value);
+
+void
+ao_seven_segment_clear(void);
+
+void
+ao_seven_segment_init(void);
+
+#endif /* _AO_SEVEN_SEGMENT_H_ */