diff options
Diffstat (limited to 'src/drivers')
40 files changed, 7168 insertions, 372 deletions
diff --git a/src/drivers/ao_aprs.c b/src/drivers/ao_aprs.c new file mode 100644 index 00000000..6ab61e6a --- /dev/null +++ b/src/drivers/ao_aprs.c @@ -0,0 +1,599 @@ +/** + * http://ad7zj.net/kd7lmo/aprsbeacon_code.html + * + * @mainpage Pico Beacon + * + * @section overview_sec Overview + * + * The Pico Beacon is an APRS based tracking beacon that operates in the UHF 420-450MHz band. The device utilizes a + * Microchip PIC 18F2525 embedded controller, Motorola M12+ GPS engine, and Analog Devices AD9954 DDS. The device is capable + * of generating a 1200bps A-FSK and 9600 bps FSK AX.25 compliant APRS (Automatic Position Reporting System) message. + + + * + * @section history_sec Revision History + * + * @subsection v305 V3.05 + * 23 Dec 2006, Change include; (1) change printf format width to conform to ANSI standard when new CCS 4.xx compiler released. + * + * + * @subsection v304 V3.04 + * 10 Jan 2006, Change include; (1) added amplitude control to engineering mode, + * (2) corrected number of bytes reported in log, + * (3) add engineering command to set high rate position reports (5 seconds), and + * (4) corrected size of LOG_COORD block when searching for end of log. + * + * @subsection v303 V3.03 + * 15 Sep 2005, Change include; (1) removed AD9954 setting SDIO as input pin, + * (2) additional comments and Doxygen tags, + * (3) integration and test code calculates DDS FTW, + * (4) swapped bus and reference analog input ports (hardware change), + * (5) added message that indicates we are reading flash log and reports length, + * (6) report bus voltage in 10mV steps, and + * (7) change log type enumerated values to XORed nibbles for error detection. + * + * + * @subsection v302 V3.02 + * 6 Apr 2005, Change include; (1) corrected tracked satellite count in NMEA-0183 $GPGGA message, + * (2) Doxygen documentation clean up and additions, and + * (3) added integration and test code to baseline. + * + * + * @subsection v301 V3.01 + * 13 Jan 2005, Renamed project and files to Pico Beacon. + * + * + * @subsection v300 V3.00 + * 15 Nov 2004, Change include; (1) Micro Beacon extreme hardware changes including integral transmitter, + * (2) PIC18F2525 processor, + * (3) AD9954 DDS support functions, + * (4) added comments and formatting for doxygen, + * (5) process GPS data with native Motorola protocol, + * (6) generate plain text $GPGGA and $GPRMC messages, + * (7) power down GPS 5 hours after lock, + * (8) added flight data recorder, and + * (9) added diagnostics terminal mode. + * + * + * @subsection v201 V2.01 + * 30 Jan 2004, Change include; (1) General clean up of in-line documentation, and + * (2) changed temperature resolution to 0.1 degrees F. + * + * + * @subsection v200 V2.00 + * 26 Oct 2002, Change include; (1) Micro Beacon II hardware changes including PIC18F252 processor, + * (2) serial EEPROM, + * (3) GPS power control, + * (4) additional ADC input, and + * (5) LM60 temperature sensor. + * + * + * @subsection v101 V1.01 + * 5 Dec 2001, Change include; (1) Changed startup message, and + * (2) applied SEPARATE pragma to several methods for memory usage. + * + * + * @subsection v100 V1.00 + * 25 Sep 2001, Initial release. Flew ANSR-3 and ANSR-4. + * + + + * + * + * @section copyright_sec Copyright + * + * Copyright (c) 2001-2009 Michael Gray, KD7LMO + + + * + * + * @section gpl_sec GNU General Public License + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 + * + + + * + * + * @section design Design Details + * + * Provides design details on a variety of the components that make up the Pico Beacon. + * + * @subpage power + */ + +/** + * @page power Power Consumption + * + * Measured DC power consumption. + * + * 3VDC prime power current + + * + * 7mA Held in reset + + * 18mA Processor running, all I/O off + + * 110mA GPS running + + * 120mA GPS running w/antenna + + * 250mA DDS running and GPS w/antenna + + * 420mA DDS running, GPS w/antenna, and PA chain on with no RF + + * 900mA Transmit + + * + */ + +#ifndef AO_APRS_TEST +#include <ao.h> +#endif + +#include <ao_aprs.h> + +// Public methods, constants, and data structures for each class. + +static void timeInit(void); + +static void tncInit(void); +static void tnc1200TimerTick(void); + +/** @} */ + +/** + * @defgroup sys System Library Functions + * + * Generic system functions similiar to the run-time C library. + * + * @{ + */ + +/** + * Calculate the CRC-16 CCITT of buffer that is length bytes long. + * The crc parameter allow the calculation on the CRC on multiple buffers. + * + * @param buffer Pointer to data buffer. + * @param length number of bytes in data buffer + * @param crc starting value + * + * @return CRC-16 of buffer[0 .. length] + */ +static uint16_t sysCRC16(const uint8_t *buffer, uint8_t length, uint16_t crc) +{ + uint8_t i, bit, value; + + for (i = 0; i < length; ++i) + { + value = buffer[i]; + + for (bit = 0; bit < 8; ++bit) + { + crc ^= (value & 0x01); + crc = ( crc & 0x01 ) ? ( crc >> 1 ) ^ 0x8408 : ( crc >> 1 ); + value = value >> 1; + } // END for + } // END for + + return crc ^ 0xffff; +} + +/** @} */ + +/** + * @defgroup rtc Real Time Interrupt tick + * + * Manage the built-in real time interrupt. The interrupt clock PRI is 104uS (9600 bps). + * + * @{ + */ + +/// 16-bit NCO where the upper 8-bits are used to index into the frequency generation table. +static uint16_t timeNCO; + +/// Audio tone NCO update step (phase). +static uint16_t timeNCOFreq; + +/** + * Initialize the real-time clock. + */ +static void timeInit() +{ + timeNCO = 0x00; + timeNCOFreq = 0x2000; +} + +/** @} */ + +/** + * @defgroup tnc TNC (Terminal Node Controller) + * + * Functions that provide a subset of the TNC functions. + * + * @{ + */ + +/// The number of start flag bytes to send before the packet message. (360bits * 1200bps = 300mS) +#define TNC_TX_DELAY 45 + +/// The size of the TNC output buffer. +#define TNC_BUFFER_SIZE 40 + +/// States that define the current mode of the 1200 bps (A-FSK) state machine. +typedef enum +{ + /// Stand by state ready to accept new message. + TNC_TX_READY, + + /// 0x7E bit stream pattern used to define start of APRS message. + TNC_TX_SYNC, + + /// Transmit the AX.25 header that contains the source/destination call signs, APRS path, and flags. + TNC_TX_HEADER, + + /// Transmit the message data. + TNC_TX_DATA, + + /// Transmit the end flag sequence. + TNC_TX_END +} TNC_TX_1200BPS_STATE; + +/// AX.25 compliant packet header that contains destination, station call sign, and path. +/// 0x76 for SSID-11, 0x78 for SSID-12 +static uint8_t TNC_AX25_HEADER[] = { + 'A' << 1, 'P' << 1, 'A' << 1, 'M' << 1, ' ' << 1, ' ' << 1, 0x60, \ + 'N' << 1, '0' << 1, 'C' << 1, 'A' << 1, 'L' << 1, 'L' << 1, 0x78, \ + 'W' << 1, 'I' << 1, 'D' << 1, 'E' << 1, '2' << 1, ' ' << 1, 0x65, \ + 0x03, 0xf0 }; + +#define TNC_CALLSIGN_OFF 7 +#define TNC_CALLSIGN_LEN 6 + +static void +tncSetCallsign(void) +{ +#ifndef AO_APRS_TEST + uint8_t i; + + for (i = 0; i < TNC_CALLSIGN_LEN; i++) { + if (!ao_config.callsign[i]) + break; + TNC_AX25_HEADER[TNC_CALLSIGN_OFF + i] = ao_config.callsign[i] << 1; + } + for (; i < TNC_CALLSIGN_LEN; i++) + TNC_AX25_HEADER[TNC_CALLSIGN_OFF + i] = ' ' << 1; +#endif +} + +/// The next bit to transmit. +static uint8_t tncTxBit; + +/// Current mode of the 1200 bps state machine. +static TNC_TX_1200BPS_STATE tncMode; + +/// Counter for each bit (0 - 7) that we are going to transmit. +static uint8_t tncBitCount; + +/// A shift register that holds the data byte as we bit shift it for transmit. +static uint8_t tncShift; + +/// Index into the APRS header and data array for each byte as we transmit it. +static uint8_t tncIndex; + +/// The number of bytes in the message portion of the AX.25 message. +static uint8_t tncLength; + +/// A copy of the last 5 bits we've transmitted to determine if we need to bit stuff on the next bit. +static uint8_t tncBitStuff; + +/// Buffer to hold the message portion of the AX.25 packet as we prepare it. +static uint8_t tncBuffer[TNC_BUFFER_SIZE]; + +/** + * Initialize the TNC internal variables. + */ +static void tncInit() +{ + tncTxBit = 0; + tncMode = TNC_TX_READY; +} + +/** + * Method that is called every 833uS to transmit the 1200bps A-FSK data stream. + * The provides the pre and postamble as well as the bit stuffed data stream. + */ +static void tnc1200TimerTick() +{ + // Set the A-FSK frequency. + if (tncTxBit == 0x00) + timeNCOFreq = 0x2000; + else + timeNCOFreq = 0x3aab; + + switch (tncMode) + { + case TNC_TX_READY: + // Generate a test signal alteranting between high and low tones. + tncTxBit = (tncTxBit == 0 ? 1 : 0); + break; + + case TNC_TX_SYNC: + // The variable tncShift contains the lastest data byte. + // NRZI enocde the data stream. + if ((tncShift & 0x01) == 0x00) { + if (tncTxBit == 0) + tncTxBit = 1; + else + tncTxBit = 0; + } + + // When the flag is done, determine if we need to send more or data. + if (++tncBitCount == 8) + { + tncBitCount = 0; + tncShift = 0x7e; + + // Once we transmit x mS of flags, send the data. + // txDelay bytes * 8 bits/byte * 833uS/bit = x mS + if (++tncIndex == TNC_TX_DELAY) + { + tncIndex = 0; + tncShift = TNC_AX25_HEADER[0]; + tncBitStuff = 0; + tncMode = TNC_TX_HEADER; + } // END if + } else + tncShift = tncShift >> 1; + break; + + case TNC_TX_HEADER: + // Determine if we have sent 5 ones in a row, if we have send a zero. + if (tncBitStuff == 0x1f) + { + if (tncTxBit == 0) + tncTxBit = 1; + else + tncTxBit = 0; + + tncBitStuff = 0x00; + return; + } // END if + + // The variable tncShift contains the lastest data byte. + // NRZI enocde the data stream. + if ((tncShift & 0x01) == 0x00) { + if (tncTxBit == 0) + tncTxBit = 1; + else + tncTxBit = 0; + } + + // Save the data stream so we can determine if bit stuffing is + // required on the next bit time. + tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f; + + // If all the bits were shifted, get the next byte. + if (++tncBitCount == 8) + { + tncBitCount = 0; + + // After the header is sent, then send the data. + if (++tncIndex == sizeof(TNC_AX25_HEADER)) + { + tncIndex = 0; + tncShift = tncBuffer[0]; + tncMode = TNC_TX_DATA; + } else + tncShift = TNC_AX25_HEADER[tncIndex]; + + } else + tncShift = tncShift >> 1; + + break; + + case TNC_TX_DATA: + // Determine if we have sent 5 ones in a row, if we have send a zero. + if (tncBitStuff == 0x1f) + { + if (tncTxBit == 0) + tncTxBit = 1; + else + tncTxBit = 0; + + tncBitStuff = 0x00; + return; + } // END if + + // The variable tncShift contains the lastest data byte. + // NRZI enocde the data stream. + if ((tncShift & 0x01) == 0x00) { + if (tncTxBit == 0) + tncTxBit = 1; + else + tncTxBit = 0; + } + + // Save the data stream so we can determine if bit stuffing is + // required on the next bit time. + tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f; + + // If all the bits were shifted, get the next byte. + if (++tncBitCount == 8) + { + tncBitCount = 0; + + // If everything was sent, transmit closing flags. + if (++tncIndex == tncLength) + { + tncIndex = 0; + tncShift = 0x7e; + tncMode = TNC_TX_END; + } else + tncShift = tncBuffer[tncIndex]; + + } else + tncShift = tncShift >> 1; + + break; + + case TNC_TX_END: + // The variable tncShift contains the lastest data byte. + // NRZI enocde the data stream. + if ((tncShift & 0x01) == 0x00) { + if (tncTxBit == 0) + tncTxBit = 1; + else + tncTxBit = 0; + } + + // If all the bits were shifted, get the next one. + if (++tncBitCount == 8) + { + tncBitCount = 0; + tncShift = 0x7e; + + // Transmit two closing flags. + if (++tncIndex == 2) + { + tncMode = TNC_TX_READY; + + return; + } // END if + } else + tncShift = tncShift >> 1; + + break; + } // END switch +} + +/** + * Generate the plain text position packet. + */ +static int tncPositionPacket(void) +{ + int32_t latitude = ao_gps_data.latitude; + int32_t longitude = ao_gps_data.longitude; + int32_t altitude = ao_gps_data.altitude; + + uint16_t lat_deg; + uint16_t lon_deg; + uint16_t lat_min; + uint16_t lat_frac; + uint16_t lon_min; + uint16_t lon_frac; + + char lat_sign = 'N', lon_sign = 'E'; + + if (latitude < 0) { + lat_sign = 'S'; + latitude = -latitude; + } + + if (longitude < 0) { + lon_sign = 'W'; + longitude = -longitude; + } + + /* Round latitude and longitude by 0.005 minutes */ + latitude = latitude + 833; + if (latitude > 900000000) + latitude = 900000000; + longitude = longitude + 833; + if (longitude > 1800000000) + longitude = 1800000000; + + lat_deg = latitude / 10000000; + latitude -= lat_deg * 10000000; + latitude *= 60; + lat_min = latitude / 10000000; + latitude -= lat_min * 10000000; + lat_frac = latitude / 100000; + + lon_deg = longitude / 10000000; + longitude -= lon_deg * 10000000; + longitude *= 60; + lon_min = longitude / 10000000; + longitude -= lon_min * 10000000; + lon_frac = longitude / 100000; + + if (altitude < 0) + altitude = 0; + + altitude = (altitude * (int32_t) 10000 + (3048/2)) / (int32_t) 3048; + + return sprintf ((char *) tncBuffer, "=%02u%02u.%02u%c\\%03u%02u.%02u%cO /A=%06u\015", + lat_deg, lat_min, lat_frac, lat_sign, + lon_deg, lon_min, lon_frac, lon_sign, + altitude); +} + +static int16_t +tncFill(uint8_t *buf, int16_t len) +{ + int16_t l = 0; + uint8_t b; + uint8_t bit; + + while (tncMode != TNC_TX_READY && l < len) { + b = 0; + for (bit = 0; bit < 8; bit++) { + b = b << 1 | (timeNCO >> 15); + timeNCO += timeNCOFreq; + } + *buf++ = b; + l++; + tnc1200TimerTick(); + } + if (tncMode == TNC_TX_READY) + l = -l; + return l; +} + +/** + * Prepare an AX.25 data packet. Each time this method is called, it automatically + * rotates through 1 of 3 messages. + * + * @param dataMode enumerated type that specifies 1200bps A-FSK or 9600bps FSK + */ +void ao_aprs_send(void) +{ + uint16_t crc; + + timeInit(); + tncInit(); + tncSetCallsign(); + + tncLength = tncPositionPacket(); + + // Calculate the CRC for the header and message. + crc = sysCRC16(TNC_AX25_HEADER, sizeof(TNC_AX25_HEADER), 0xffff); + crc = sysCRC16(tncBuffer, tncLength, crc ^ 0xffff); + + // Save the CRC in the message. + tncBuffer[tncLength++] = crc & 0xff; + tncBuffer[tncLength++] = (crc >> 8) & 0xff; + + // Prepare the variables that are used in the real-time clock interrupt. + tncBitCount = 0; + tncShift = 0x7e; + tncTxBit = 0; + tncIndex = 0; + tncMode = TNC_TX_SYNC; + + ao_radio_send_aprs(tncFill); +} + +/** @} */ diff --git a/src/drivers/ao_aprs.h b/src/drivers/ao_aprs.h new file mode 100644 index 00000000..a033fa0b --- /dev/null +++ b/src/drivers/ao_aprs.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_APRS_H_ +#define _AO_APRS_H_ + +void +ao_aprs_send(void); + +#endif /* _AO_APRS_H_ */ diff --git a/src/drivers/ao_at24c.c b/src/drivers/ao_at24c.c new file mode 100644 index 00000000..2a23be3a --- /dev/null +++ b/src/drivers/ao_at24c.c @@ -0,0 +1,104 @@ +/* + * 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> + +#if HAS_EEPROM +#define AO_AT24C_ADDR 0xa0 +#define AO_AT24C_ADDR_WRITE (AO_AT24C_ADDR|0) +#define AO_AT24C_ADDR_READ (AO_AT24C_ADDR|1) +#define AO_AT24C_PAGE_LEN 128 + +/* Total bytes of available storage */ +__pdata ao_pos_t ao_storage_total = 64l * 1024l; + +/* Storage unit size - device reads and writes must be within blocks of this size. */ +__pdata uint16_t ao_storage_unit = 128; + +static void +ao_at24c_set_address(uint8_t addr, ao_pos_t pos) +{ + uint8_t a[2]; + + a[0] = pos >> 8; + a[1] = pos; + ao_i2c_start_bus(addr); + ao_i2c_send_bus(a, 2, 0); +} + +/* + * Erase the specified sector + */ +uint8_t +ao_storage_erase(ao_pos_t pos) __reentrant +{ + if (pos >= ao_storage_total || pos + AO_AT24C_PAGE_LEN > ao_storage_total) + return 0; + + ao_mutex_get(&ao_at24c_mutex); + ao_at24c_set_address(AO_AT24C_ADDR_WRITE, pos); + ao_i2c_send_fixed_bus(0xff, AO_AT24C_PAGE_LEN, 1); + ao_mutex_put(&ao_at24c_mutex); + return 1; +} + +/* + * Write to flash + */ +uint8_t +ao_storage_device_write(ao_pos_t pos, __xdata void *d, uint16_t len) __reentrant +{ + if (pos >= ao_storage_total || pos + len > ao_storage_total) + return 0; + + ao_mutex_get(&ao_m25_mutex); + ao_at24c_set_address(AO_AT24C_ADDR_WRITE, pos); + ao_i2c_send_bus(d, len, 1); + ao_mutex_put(&ao_m25_mutex); + return 1; +} + +/* + * Read from flash + */ +uint8_t +ao_storage_device_read(ao_pos_t pos, __xdata void *d, uint16_t len) __reentrant +{ + if (pos >= ao_storage_total || pos + len > ao_storage_total) + return 0; + ao_mutex_get(&ao_m25_mutex); + ao_at24c_set_address(AO_AT24C_ADDR_READ, pos); + ao_i2c_recv_bus(d, len, 1); + ao_mutex_put(&ao_m25_mutex); + return 1; +} + +void +ao_storage_flush(void) __reentrant +{ +} + +void +ao_storage_setup(void) +{ +} + +void +ao_storage_device_init(void) +{ +} +#endif diff --git a/src/drivers/ao_btm.c b/src/drivers/ao_btm.c index f193ac8e..3b6028a0 100644 --- a/src/drivers/ao_btm.c +++ b/src/drivers/ao_btm.c @@ -19,9 +19,10 @@ #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_pollchar _ao_serial1_pollchar #define ao_serial_btm_set_speed ao_serial1_set_speed #define ao_serial_btm_drain ao_serial1_drain +#define ao_serial_btm_rx_fifo ao_serial1_rx_fifo #endif int8_t ao_btm_stdio; @@ -112,6 +113,30 @@ __code struct ao_cmds ao_btm_cmds[] = { __xdata char ao_btm_reply[AO_BTM_MAX_REPLY]; /* + * Read one bluetooth character. + * Returns AO_READ_AGAIN if no character arrives within 10ms + */ + +static int +ao_btm_getchar(void) +{ + int c; + + ao_arch_block_interrupts(); + while ((c = _ao_serial_btm_pollchar()) == AO_READ_AGAIN) { + ao_alarm(AO_MS_TO_TICKS(10)); + c = ao_sleep(&ao_serial_btm_rx_fifo); + ao_clear_alarm(); + if (c) { + c = AO_READ_AGAIN; + break; + } + } + ao_arch_release_interrupts(); + return c; +} + +/* * Read a line of data from the serial port, truncating * it after a few characters. */ @@ -120,26 +145,15 @@ 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; + int c; + + while ((c = ao_btm_getchar()) != 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') + break; } -done: for (c = ao_btm_reply_len; c < sizeof (ao_btm_reply);) ao_btm_reply[c++] = '\0'; return ao_btm_reply_len; @@ -279,7 +293,7 @@ ao_btm(void) /* Turn off status reporting */ ao_btm_cmd("ATQ1\r"); - ao_btm_stdio = ao_add_stdio(ao_serial_btm_pollchar, + ao_btm_stdio = ao_add_stdio(_ao_serial_btm_pollchar, ao_serial_btm_putchar, NULL); ao_btm_echo(0); @@ -288,7 +302,7 @@ ao_btm(void) 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_led_for(AO_BT_LED, AO_MS_TO_TICKS(20)); ao_delay(AO_SEC_TO_TICKS(3)); } } @@ -312,18 +326,20 @@ __xdata struct ao_task ao_btm_task; #endif void -ao_btm_check_link() __critical +ao_btm_check_link() { - /* 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; - } + ao_arch_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 diff --git a/src/drivers/ao_bufio.c b/src/drivers/ao_bufio.c new file mode 100644 index 00000000..c0fe604a --- /dev/null +++ b/src/drivers/ao_bufio.c @@ -0,0 +1,321 @@ +/* + * Copyright © 2013 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_FAT_TEST +#include "ao.h" +#endif + +/* Include bufio commands */ +#ifndef AO_FAT_TEST +#define BUFIO_COMMANDS 0 +#endif + +#include "ao_sdcard.h" +#include "ao_bufio.h" + +#define AO_NUM_BUF 16 +#define AO_BUFSIZ 512 + +struct ao_bufio { + uint32_t block; + int16_t seqno; + uint8_t busy; + uint8_t dirty; +}; + +static struct ao_bufio ao_bufio[AO_NUM_BUF]; +static uint8_t ao_buffer[AO_NUM_BUF][AO_BUFSIZ]; +static int16_t ao_seqno; +static uint8_t ao_bufio_mutex; + +#if 0 +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +static inline void +ao_bufio_lock(void) +{ + ao_mutex_get(&ao_bufio_mutex); +} + +static inline void +ao_bufio_unlock(void) +{ + ao_mutex_put(&ao_bufio_mutex); +} + +static inline int16_t +ao_seqno_age(int16_t s) +{ + return ao_seqno - s; +} + +static inline int16_t +ao_seqno_next(void) +{ + return ++ao_seqno; +} + +static inline int +ao_seqno_older(int16_t a, int16_t b) +{ + return ao_seqno_age(a) > ao_seqno_age(b); +} + +static inline void +ao_validate_bufno(int b) +{ + if (b < 0 || AO_NUM_BUF <= b) + ao_panic(AO_PANIC_BUFIO); +} + +static inline int +ao_buf_to_num(uint8_t *buf) +{ + int b = (buf - &ao_buffer[0][0]) / AO_BUFSIZ; + + ao_validate_bufno(b); + return b; +} + +static inline int +ao_bufio_to_num(struct ao_bufio *bufio) +{ + int b = (bufio - ao_bufio); + + ao_validate_bufno(b); + return b; +} + +static inline struct ao_bufio * +ao_buf_to_bufio(uint8_t *buf) +{ + int b = ao_buf_to_num(buf); + struct ao_bufio *bufio; + + bufio = &ao_bufio[b]; + DBG ("buf %08x is %d bufio %08x\n", buf, b, bufio); + return bufio; +} + +static inline uint8_t * +ao_bufio_to_buf(struct ao_bufio *bufio) +{ + int b = ao_bufio_to_num(bufio); + uint8_t *buf; + + buf = &ao_buffer[b][0]; + DBG ("bufio %08x is %d buf %08x\n", bufio, b, buf); + return buf; +} + +/* + * Write a buffer to storage if it is dirty + */ +static void +ao_bufio_write(struct ao_bufio *bufio) +{ + if (bufio->dirty) { + ao_sdcard_write_block(bufio->block, ao_bufio_to_buf(bufio)); + bufio->dirty = 0; + } +} + +/* + * Read a buffer from storage + */ +static uint8_t +ao_bufio_read(struct ao_bufio *bufio) +{ + uint8_t *buf = ao_bufio_to_buf(bufio); + + return ao_sdcard_read_block(bufio->block, buf); +} + +/* + * Find a buffer containing the specified block + */ +static struct ao_bufio * +ao_bufio_find_block(uint32_t block) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + struct ao_bufio *bufio = &ao_bufio[b]; + if (bufio->block == block) { + DBG ("Found existing buffer %d (seqno %d)\n", + ao_bufio_to_num(bufio), bufio->seqno); + return bufio; + } + } + return NULL; +} + +/* + * Find the least recently used idle buffer + */ +static struct ao_bufio * +ao_bufio_find_idle(void) +{ + int b; + struct ao_bufio *oldest = NULL; + + for (b = 0; b < AO_NUM_BUF; b++) { + struct ao_bufio *bufio = &ao_bufio[b]; + if (!bufio->busy) + if (!oldest || ao_seqno_older(bufio->seqno, oldest->seqno)) + oldest = bufio; + } + if (oldest) + DBG ("Using idle buffer %d (seqno %d)\n", + ao_bufio_to_num(oldest), oldest->seqno); + return oldest; +} + +/* + * Return a pointer to a buffer containing + * the contents of the specified block + */ +uint8_t * +ao_bufio_get(uint32_t block) +{ + struct ao_bufio *bufio; + uint8_t *buf = NULL; + + ao_bufio_lock(); + bufio = ao_bufio_find_block(block); + if (!bufio) { + bufio = ao_bufio_find_idle(); + if (bufio) { + ao_bufio_write(bufio); + bufio->block = block; + DBG ("read buffer\n"); + if (!ao_bufio_read(bufio)) { + bufio->block = 0xffffffff; + bufio = NULL; + } + } else + ao_panic(AO_PANIC_BUFIO); + } + if (bufio) { + bufio->busy++; + if (!bufio->busy) + ao_panic(AO_PANIC_BUFIO); + buf = ao_bufio_to_buf(bufio); + } + ao_bufio_unlock(); + return buf; +} + +/* + * Release a buffer, marking it dirty + * if it has been written to + */ +void +ao_bufio_put(uint8_t *buf, uint8_t write) +{ + struct ao_bufio *bufio; + + ao_bufio_lock(); + bufio = ao_buf_to_bufio(buf); + + if (!bufio->busy) + ao_panic(AO_PANIC_BUFIO); + + DBG ("idle buffer %d write %d\n", ao_bufio_to_num(bufio), write); + bufio->dirty |= write; + if (!--bufio->busy) { + bufio->seqno = ao_seqno_next(); + DBG ("not busy, seqno %d\n", bufio->seqno); + } + ao_bufio_unlock(); +} + +/* + * Flush a single buffer immediately. Useful + * if write order is important + */ +void +ao_bufio_flush_one(uint8_t *buf) +{ + ao_bufio_lock(); + ao_bufio_write(ao_buf_to_bufio(buf)); + ao_bufio_unlock(); +} + +/* + * Flush all buffers to storage + */ +void +ao_bufio_flush(void) +{ + int b; + + ao_bufio_lock(); + for (b = 0; b < AO_NUM_BUF; b++) + ao_bufio_write(&ao_bufio[b]); + ao_bufio_unlock(); +} + +#if BUFIO_COMMANDS +static void +ao_bufio_test_read(void) +{ + uint8_t *buf; + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + if ((buf = ao_bufio_get(ao_cmd_lex_u32))) { + int i; + for (i = 0; i < 512; i++) { + printf (" %02x", buf[i]); + if ((i & 0xf) == 0xf) + printf("\n"); + } + ao_bufio_put(buf, 0); + } +} + +static const struct ao_cmds ao_bufio_cmds[] = { + { ao_bufio_test_read, "q\0Test bufio read" }, + { 0, NULL }, +}; +#endif + +void +ao_bufio_setup(void) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + ao_bufio[b].dirty = 0; + ao_bufio[b].busy = 0; + ao_bufio[b].block = 0xffffffff; + } +} + +void +ao_bufio_init(void) +{ + ao_bufio_setup(); + ao_sdcard_init(); +#if BUFIO_COMMANDS + ao_cmd_register(&ao_bufio_cmds[0]); +#endif +} diff --git a/src/drivers/ao_bufio.h b/src/drivers/ao_bufio.h new file mode 100644 index 00000000..6629f143 --- /dev/null +++ b/src/drivers/ao_bufio.h @@ -0,0 +1,39 @@ +/* + * Copyright © 2013 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_BUFIO_H_ +#define _AO_BUFIO_H_ + +uint8_t * +ao_bufio_get(uint32_t block); + +void +ao_bufio_put(uint8_t *buf, uint8_t write); + +void +ao_bufio_flush_one(uint8_t *buf); + +void +ao_bufio_flush(void); + +void +ao_bufio_setup(void); + +void +ao_bufio_init(void); + +#endif /* _AO_BUFIO_H_ */ diff --git a/src/drivers/ao_cc1120.c b/src/drivers/ao_cc1120.c index 2f9c296f..772014ee 100644 --- a/src/drivers/ao_cc1120.c +++ b/src/drivers/ao_cc1120.c @@ -21,10 +21,18 @@ #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 AO_RADIO_MAX_RECV sizeof(struct ao_packet) +#define AO_RADIO_MAX_SEND sizeof(struct ao_packet) + +static uint8_t ao_radio_mutex; + +static uint8_t ao_radio_wake; /* radio ready. Also used as sleep address */ +static uint8_t ao_radio_abort; /* radio operation should abort */ +static uint8_t ao_radio_mcu_wake; /* MARC status change */ +static uint8_t ao_radio_marc_status; /* Last read MARC status value */ +static uint8_t ao_radio_tx_finished; /* MARC status indicates TX finished */ + +int8_t ao_radio_rssi; /* Last received RSSI value */ #define CC1120_DEBUG AO_FEC_DEBUG #define CC1120_TRACE 0 @@ -33,7 +41,7 @@ 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_select() ao_spi_get_mask(AO_CC1120_SPI_CS_PORT,(1 << AO_CC1120_SPI_CS_PIN),AO_CC1120_SPI_BUS,AO_SPI_SPEED_4MHz) #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) @@ -215,13 +223,34 @@ ao_radio_recv_abort(void) #define ao_radio_rdf_value 0x55 static uint8_t -ao_radio_marc_status(void) +ao_radio_get_marc_status(void) { return ao_radio_reg_read(CC1120_MARC_STATUS1); } static void -ao_radio_tx_isr(void) +ao_radio_mcu_wakeup_isr(void) +{ + ao_radio_mcu_wake = 1; + ao_wakeup(&ao_radio_wake); +} + + +static void +ao_radio_check_marc_status(void) +{ + ao_radio_mcu_wake = 0; + ao_radio_marc_status = ao_radio_get_marc_status(); + + /* Anyt other than 'tx/rx finished' means an error occurred */ + if (ao_radio_marc_status & ~(CC1120_MARC_STATUS1_TX_FINISHED|CC1120_MARC_STATUS1_RX_FINISHED)) + ao_radio_abort = 1; + if (ao_radio_marc_status & (CC1120_MARC_STATUS1_TX_FINISHED)) + ao_radio_tx_finished = 1; +} + +static void +ao_radio_isr(void) { ao_exti_disable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN); ao_radio_wake = 1; @@ -231,8 +260,10 @@ ao_radio_tx_isr(void) 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_set_callback(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN, ao_radio_isr); ao_exti_enable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN); + ao_exti_enable(AO_CC1120_MCU_WAKEUP_PORT, AO_CC1120_MCU_WAKEUP_PIN); + ao_radio_tx_finished = 0; ao_radio_strobe(CC1120_STX); } @@ -243,7 +274,11 @@ ao_radio_idle(void) uint8_t state = ao_radio_strobe(CC1120_SIDLE); if ((state >> CC1120_STATUS_STATE) == CC1120_STATUS_STATE_IDLE) break; + if ((state >> CC1120_STATUS_STATE) == CC1120_STATUS_STATE_TX_FIFO_ERROR) + ao_radio_strobe(CC1120_SFTX); } + /* Flush any pending TX bytes */ + ao_radio_strobe(CC1120_SFTX); } /* @@ -258,7 +293,7 @@ ao_radio_idle(void) #define PACKET_DEV_M 80 /* - * For our packet data, set the symbol rate to 38360 Baud + * For our packet data, set the symbol rate to 38400 Baud * * (2**20 + DATARATE_M) * 2 ** DATARATE_E * Rdata = -------------------------------------- * fosc @@ -291,18 +326,19 @@ static const uint16_t packet_setup[] = { (0 << CC1120_PKT_CFG0_PKG_BIT_LEN) | (0 << CC1120_PKT_CFG0_UART_MODE_EN) | (0 << CC1120_PKT_CFG0_UART_SWAP_EN)), + AO_CC1120_MARC_GPIO_IOCFG, CC1120_IOCFG_GPIO_CFG_MARC_MCU_WAKEUP, }; 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, + AO_CC1120_INT_GPIO_IOCFG, 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, + AO_CC1120_INT_GPIO_IOCFG, CC1120_IOCFG_GPIO_CFG_CLKEN_SOFT, }; /* @@ -320,12 +356,12 @@ static const uint16_t packet_rx_setup[] = { /* * For our RDF beacon, set the symbol rate to 2kBaud (for a 1kHz tone) * - * (2**20 - DATARATE_M) * 2 ** DATARATE_E + * (2**20 + DATARATE_M) * 2 ** DATARATE_E * Rdata = -------------------------------------- * fosc * 2 ** 39 * - * DATARATE_M = 511705 - * DATARATE_E = 6 + * DATARATE_M = 25166 + * DATARATE_E = 5 * * To make the tone last for 200ms, we need 2000 * .2 = 400 bits or 50 bytes */ @@ -355,7 +391,63 @@ static const uint16_t rdf_setup[] = { (0 << CC1120_PKT_CFG0_UART_SWAP_EN)), }; -static uint8_t ao_radio_mode; +/* + * APRS deviation is 3kHz + * + * fdev = fosc >> 24 * (256 + dev_m) << dev_e + * + * 32e6Hz / (2 ** 24) * (256 + 137) * (2 ** 2) = 2998Hz + */ + +#define APRS_DEV_E 2 +#define APRS_DEV_M 137 + +/* + * For our APRS beacon, set the symbol rate to 9.6kBaud (8x oversampling for 1200 baud data rate) + * + * (2**20 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 39 + * + * DATARATE_M = 239914 + * DATARATE_E = 7 + * + * Rdata = 9599.998593330383301 + * + */ +#define APRS_DRATE_E 7 +#define APRS_DRATE_M 239914 + +static const uint16_t aprs_setup[] = { + CC1120_DEVIATION_M, APRS_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) | + (APRS_DEV_E << CC1120_MODCFG_DEV_E_DEV_E)), + CC1120_DRATE2, ((APRS_DRATE_E << CC1120_DRATE2_DATARATE_E) | + (((APRS_DRATE_M >> 16) & CC1120_DRATE2_DATARATE_M_19_16_MASK) << CC1120_DRATE2_DATARATE_M_19_16)), + CC1120_DRATE1, ((APRS_DRATE_M >> 8) & 0xff), + CC1120_DRATE0, ((APRS_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)), +}; + +#define AO_PKT_CFG0_INFINITE ((0 << CC1120_PKT_CFG0_RESERVED7) | \ + (CC1120_PKT_CFG0_LENGTH_CONFIG_INFINITE << 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)) + +#define AO_PKT_CFG0_FIXED ((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 uint16_t ao_radio_mode; #define AO_RADIO_MODE_BITS_PACKET 1 #define AO_RADIO_MODE_BITS_PACKET_TX 2 @@ -363,17 +455,23 @@ static uint8_t ao_radio_mode; #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_BITS_APRS 64 +#define AO_RADIO_MODE_BITS_INFINITE 128 +#define AO_RADIO_MODE_BITS_FIXED 256 #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) +#define AO_RADIO_MODE_APRS_BUF (AO_RADIO_MODE_BITS_APRS | AO_RADIO_MODE_BITS_INFINITE | AO_RADIO_MODE_BITS_TX_BUF) +#define AO_RADIO_MODE_APRS_LAST_BUF (AO_RADIO_MODE_BITS_APRS | AO_RADIO_MODE_BITS_FIXED | AO_RADIO_MODE_BITS_TX_BUF) +#define AO_RADIO_MODE_APRS_FINISH (AO_RADIO_MODE_BITS_APRS | AO_RADIO_MODE_BITS_FIXED | AO_RADIO_MODE_BITS_TX_FINISH) static void -ao_radio_set_mode(uint8_t new_mode) +ao_radio_set_mode(uint16_t new_mode) { - uint8_t changes; + uint16_t changes; int i; if (new_mode == ao_radio_mode) @@ -389,10 +487,10 @@ ao_radio_set_mode(uint8_t new_mode) 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); + ao_radio_reg_write(AO_CC1120_INT_GPIO_IOCFG, 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); + ao_radio_reg_write(AO_CC1120_INT_GPIO_IOCFG, 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) @@ -401,6 +499,17 @@ ao_radio_set_mode(uint8_t new_mode) 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]); + + if (changes & AO_RADIO_MODE_BITS_APRS) + for (i = 0; i < sizeof (aprs_setup) / sizeof (aprs_setup[0]); i += 2) + ao_radio_reg_write(aprs_setup[i], aprs_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_INFINITE) + ao_radio_reg_write(CC1120_PKT_CFG0, AO_PKT_CFG0_INFINITE); + + if (changes & AO_RADIO_MODE_BITS_FIXED) + ao_radio_reg_write(CC1120_PKT_CFG0, AO_PKT_CFG0_FIXED); + ao_radio_mode = new_mode; } @@ -428,12 +537,23 @@ ao_radio_setup(void) } static void +ao_radio_set_len(uint8_t len) +{ + static uint8_t last_len; + + if (len != last_len) { + ao_radio_reg_write(CC1120_PKT_LEN, len); + last_len = len; + } +} + +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) { @@ -442,10 +562,7 @@ ao_radio_get(uint8_t len) 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; - } + ao_radio_set_len(len); } #define ao_radio_put() ao_mutex_put(&ao_radio_mutex) @@ -466,10 +583,12 @@ ao_rdf_run(void) { ao_radio_start_tx(); - cli(); - while (!ao_radio_wake && !ao_radio_abort) + ao_arch_block_interrupts(); + while (!ao_radio_wake && !ao_radio_abort && !ao_radio_mcu_wake) ao_sleep(&ao_radio_wake); - sei(); + ao_arch_release_interrupts(); + if (ao_radio_mcu_wake) + ao_radio_check_marc_status(); if (!ao_radio_wake) ao_radio_idle(); ao_radio_put(); @@ -518,7 +637,7 @@ static void ao_radio_test_cmd(void) { uint8_t mode = 2; - uint8_t radio_on; + static uint8_t radio_on; ao_cmd_white(); if (ao_cmd_lex_c != '\n') { ao_cmd_decimal(); @@ -559,26 +678,64 @@ ao_radio_test_cmd(void) } } +static void +ao_radio_wait_isr(uint16_t timeout) +{ + if (timeout) + ao_alarm(timeout); + ao_arch_block_interrupts(); + while (!ao_radio_wake && !ao_radio_mcu_wake && !ao_radio_abort) + if (ao_sleep(&ao_radio_wake)) + ao_radio_abort = 1; + ao_arch_release_interrupts(); + if (timeout) + ao_clear_alarm(); + if (ao_radio_mcu_wake) + ao_radio_check_marc_status(); +} + +static uint8_t +ao_radio_wait_tx(uint8_t wait_fifo) +{ + uint8_t fifo_space = 0; + + do { + ao_radio_wait_isr(0); + if (!wait_fifo) + return 0; + fifo_space = ao_radio_tx_fifo_space(); + } while (!fifo_space && !ao_radio_abort); + return fifo_space; +} + +static uint8_t tx_data[(AO_RADIO_MAX_SEND + 4) * 2]; + 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 *e = tx_data; uint8_t encode_len; uint8_t this_len; uint8_t started = 0; uint8_t fifo_space; + uint8_t q; - encode_len = ao_fec_encode(d, size, encode); + encode_len = ao_fec_encode(d, size, tx_data); ao_radio_get(encode_len); + ao_radio_abort = 0; + + /* Flush any pending TX bytes */ + ao_radio_strobe(CC1120_SFTX); + started = 0; fifo_space = CC1120_FIFO_SIZE; while (encode_len) { this_len = encode_len; + ao_radio_wake = 0; if (this_len > fifo_space) { this_len = fifo_space; ao_radio_set_mode(AO_RADIO_MODE_PACKET_TX_BUF); @@ -597,21 +754,86 @@ ao_radio_send(const void *d, uint8_t size) 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); + fifo_space = ao_radio_wait_tx(encode_len != 0); + if (ao_radio_abort) { + ao_radio_idle(); + break; + } } + while (started && !ao_radio_abort && !ao_radio_tx_finished) + ao_radio_wait_isr(0); ao_radio_put(); } -#define AO_RADIO_MAX_RECV 90 +#define AO_RADIO_LOTS 64 + +void +ao_radio_send_aprs(ao_radio_fill_func fill) +{ + uint8_t buf[AO_RADIO_LOTS], *b; + int cnt; + int total = 0; + uint8_t done = 0; + uint8_t started = 0; + uint8_t fifo_space; + + ao_radio_get(0xff); + fifo_space = CC1120_FIFO_SIZE; + while (!done) { + cnt = (*fill)(buf, sizeof(buf)); + if (cnt < 0) { + done = 1; + cnt = -cnt; + } + total += cnt; + + /* At the last buffer, set the total length */ + if (done) + ao_radio_set_len(total & 0xff); + + b = buf; + while (cnt) { + uint8_t this_len = cnt; + + /* Wait for some space in the fifo */ + while (!ao_radio_abort && (fifo_space = ao_radio_tx_fifo_space()) == 0) { + ao_radio_wake = 0; + ao_radio_wait_isr(0); + } + if (ao_radio_abort) + break; + if (this_len > fifo_space) + this_len = fifo_space; + + cnt -= this_len; + + if (done) { + if (cnt) + ao_radio_set_mode(AO_RADIO_MODE_APRS_LAST_BUF); + else + ao_radio_set_mode(AO_RADIO_MODE_APRS_FINISH); + } else + ao_radio_set_mode(AO_RADIO_MODE_APRS_BUF); + + ao_radio_fifo_write(b, this_len); + b += this_len; + + if (!started) { + ao_radio_start_tx(); + started = 1; + } else + ao_exti_enable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN); + } + if (ao_radio_abort) { + ao_radio_idle(); + break; + } + /* Wait for the transmitter to go idle */ + ao_radio_wake = 0; + ao_radio_wait_isr(0); + } + ao_radio_put(); +} static uint8_t rx_data[(AO_RADIO_MAX_RECV + 4) * 2 * 8]; static uint16_t rx_data_count; @@ -633,8 +855,8 @@ ao_radio_rx_isr(void) { uint8_t d; - d = stm_spi2.dr; - stm_spi2.dr = 0; + d = AO_CC1120_SPI.dr; + AO_CC1120_SPI.dr = 0; if (rx_ignore == 0) { if (rx_data_cur >= rx_data_count) ao_exti_disable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN); @@ -658,14 +880,23 @@ ao_radio_rx_isr(void) 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(); + do { + if (ao_radio_mcu_wake) + ao_radio_check_marc_status(); + ao_alarm(AO_MS_TO_TICKS(100)); + ao_arch_block_interrupts(); + rx_waiting = 1; + while (rx_data_cur - rx_data_consumed < AO_FEC_DECODE_BLOCK && + !ao_radio_abort && + !ao_radio_mcu_wake) + { + if (ao_sleep(&ao_radio_wake)) + ao_radio_abort = 1; + } + rx_waiting = 0; + ao_arch_release_interrupts(); + ao_clear_alarm(); + } while (ao_radio_mcu_wake); if (ao_radio_abort) return 0; rx_data_consumed += AO_FEC_DECODE_BLOCK; @@ -676,11 +907,12 @@ ao_radio_rx_wait(void) } uint8_t -ao_radio_recv(__xdata void *d, uint8_t size) +ao_radio_recv(__xdata void *d, uint8_t size, uint8_t timeout) { uint8_t len; uint16_t i; - uint8_t rssi; + uint8_t radio_rssi = 0; + uint8_t rssi0; uint8_t ret; static int been_here = 0; @@ -701,46 +933,75 @@ ao_radio_recv(__xdata void *d, uint8_t size) rx_data_consumed = 0; rx_ignore = 2; + /* Must be set before changing the frequency; any abort + * after the frequency is set needs to terminate the read + * so that the registers can be reprogrammed + */ 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; + ao_radio_mcu_wake = 0; - stm_spi2.cr2 = 0; + AO_CC1120_SPI.cr2 = 0; /* clear any RXNE */ - (void) stm_spi2.dr; + (void) AO_CC1120_SPI.dr; - ao_exti_set_callback(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN, ao_radio_rx_isr); + /* Have the radio signal when the preamble quality goes high */ + ao_radio_reg_write(AO_CC1120_INT_GPIO_IOCFG, CC1120_IOCFG_GPIO_CFG_PQT_REACHED); + ao_exti_set_mode(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN, + AO_EXTI_MODE_RISING|AO_EXTI_PRIORITY_HIGH); + ao_exti_set_callback(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN, ao_radio_isr); ao_exti_enable(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN); + ao_exti_enable(AO_CC1120_MCU_WAKEUP_PORT, AO_CC1120_MCU_WAKEUP_PIN); ao_radio_strobe(CC1120_SRX); + /* Wait for the preamble to appear */ + ao_radio_wait_isr(timeout); + if (ao_radio_abort) { + ret = 0; + goto abort; + } + + ao_radio_reg_write(AO_CC1120_INT_GPIO_IOCFG, CC1120_IOCFG_GPIO_CFG_CLKEN_SOFT); + ao_exti_set_mode(AO_CC1120_INT_PORT, AO_CC1120_INT_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_HIGH); + + 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_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); - +abort: /* Convert from 'real' rssi to cc1111-style values */ - rssi = AO_RADIO_FROM_RSSI(ao_radio_reg_read(CC1120_RSSI1)); + rssi0 = ao_radio_reg_read(CC1120_RSSI0); + if (rssi0 & 1) { + int8_t rssi = ao_radio_reg_read(CC1120_RSSI1); + ao_radio_rssi = rssi; - ao_radio_put(); + /* Bound it to the representable range */ + if (rssi > -11) + rssi = -11; + radio_rssi = AO_RADIO_FROM_RSSI (rssi); + } - /* Store the received RSSI value; the crc-OK byte is already done */ + ao_radio_strobe(CC1120_SIDLE); - ((uint8_t *) d)[size] = (uint8_t) rssi; + ao_radio_put(); - ao_radio_in_recv = 0; + /* Store the received RSSI value; the crc-OK byte is already done */ - if (ao_radio_abort) - ao_delay(1); + ((uint8_t *) d)[size] = radio_rssi; #if AO_PROFILE rx_last_done_tick = rx_done_tick; @@ -961,7 +1222,7 @@ static void ao_radio_show(void) { 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()); + printf ("MARC: %02x\n", ao_radio_get_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); @@ -969,7 +1230,7 @@ static void ao_radio_show(void) { } static void ao_radio_beep(void) { - ao_radio_rdf(RDF_PACKET_LEN); + ao_radio_rdf(); } static void ao_radio_packet(void) { @@ -1005,11 +1266,25 @@ ao_radio_test_recv() } } +#if HAS_APRS +#include <ao_aprs.h> + +static void +ao_radio_aprs() +{ + ao_packet_slave_stop(); + ao_aprs_send(); +} +#endif + #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 +#if HAS_APRS + { ao_radio_aprs, "G\0Send APRS packet" }, +#endif { ao_radio_show, "R\0Show CC1120 status" }, { ao_radio_beep, "b\0Emit an RDF beacon" }, { ao_radio_packet, "p\0Send a test packet" }, @@ -1026,6 +1301,7 @@ ao_radio_init(void) ao_radio_configured = 0; ao_spi_init_cs (AO_CC1120_SPI_CS_PORT, (1 << AO_CC1120_SPI_CS_PIN)); +#if 0 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) @@ -1034,12 +1310,19 @@ ao_radio_init(void) AO_CC1120_SPI_CS_PORT->bsrr = (1 << AO_CC1120_SPI_CS_PIN); if (i == 10000) ao_panic(AO_PANIC_SELF_TEST_CC1120); +#endif /* 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_radio_isr); + + /* Enable the hacked up GPIO3 pin */ + ao_enable_port(AO_CC1120_MCU_WAKEUP_PORT); + ao_exti_setup(AO_CC1120_MCU_WAKEUP_PORT, AO_CC1120_MCU_WAKEUP_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_MED, + ao_radio_mcu_wakeup_isr); ao_cmd_register(&ao_radio_cmds[0]); } diff --git a/src/drivers/ao_cc1120.h b/src/drivers/ao_cc1120.h index 60b9621e..5d226b64 100644 --- a/src/drivers/ao_cc1120.h +++ b/src/drivers/ao_cc1120.h @@ -215,10 +215,72 @@ #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_CFG3_RSSI_STEP_THR 7 +#define CC1120_AGC_CFG3_AGC_MIN_GAIN 0 +#define CC1120_AGC_CFG3_AGC_MIN_GAIN_MASK 0x1f + #define CC1120_AGC_CFG2 0x1b +#define CC1120_AGC_CFG2_START_PREVIOUS_GAIN_EN 7 +#define CC1120_AGC_CFG2_FE_PERFORMANCE_MODE 5 +#define CC1120_AGC_CFG2_FE_PERFORMANCE_MODE_OPTIMIZE_LINEARITY 0 +#define CC1120_AGC_CFG2_FE_PERFORMANCE_MODE_NORMAL 1 +#define CC1120_AGC_CFG2_FE_PERFORMANCE_MODE_LOW_POWER 2 +#define CC1120_AGC_CFG2_FE_PERFORMANCE_MODE_MASK 3 +#define CC1120_AGC_CFG2_AGC_MAX_GAIN 0 +#define CC1120_AGC_CFG2_AGC_MAX_MASK 0x1f + #define CC1120_AGC_CFG1 0x1c +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR 5 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_UPDATE_AGC_UPDATE_RSSI 0 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_FREEZE_AGC_UPDATE_RSSI 1 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_UPDATE_AGC_UPDATE_RSSI_SLOW 2 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_FREEZE_AGC_FREEZE_RSSI 3 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_UPDATE_AGC_UPDATE_RSSI_4 4 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_FREEZE_AGC_FREEZE_RSSI_5 5 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_UPDATE_AGC_UPDATE_RSSI_SLOW_6 6 +#define CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_FREEZE_AGC_FREEZE_RSSI_7 7 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE 2 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE_8 0 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE_16 1 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE_32 2 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE_64 3 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE_128 4 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE_256 5 +#define CC1120_AGC_CFG1_AGC_WIN_SIZE_MASK 7 +#define CC1120_AGC_CFG1_AGC_SETTLE_WAIT 0 +#define CC1120_AGC_CFG1_AGC_SETTLE_WAIT_24 0 +#define CC1120_AGC_CFG1_AGC_SETTLE_WAIT_32 1 +#define CC1120_AGC_CFG1_AGC_SETTLE_WAIT_40 2 +#define CC1120_AGC_CFG1_AGC_SETTLE_WAIT_48 3 + #define CC1120_AGC_CFG0 0x1d + +#define CC1120_AGC_CFG0_AGC_HYST_LEVEL 6 +#define CC1120_AGC_CFG0_AGC_HYST_LEVEL_2 0 +#define CC1120_AGC_CFG0_AGC_HYST_LEVEL_4 1 +#define CC1120_AGC_CFG0_AGC_HYST_LEVEL_7 2 +#define CC1120_AGC_CFG0_AGC_HYST_LEVEL_10 3 + +#define CC1120_AGC_CFG0_AGC_SLEWRATE_LIMIT 4 +#define CC1120_AGC_CFG0_AGC_SLEWRATE_LIMIT_60 0 +#define CC1120_AGC_CFG0_AGC_SLEWRATE_LIMIT_30 1 +#define CC1120_AGC_CFG0_AGC_SLEWRATE_LIMIT_18 2 +#define CC1120_AGC_CFG0_AGC_SLEWRATE_LIMIT_9 3 + +#define CC1120_AGC_CFG0_RSSI_VALID_CNT 2 +#define CC1120_AGC_CFG0_RSSI_VALID_CNT_2 0 +#define CC1120_AGC_CFG0_RSSI_VALID_CNT_3 1 +#define CC1120_AGC_CFG0_RSSI_VALID_CNT_5 2 +#define CC1120_AGC_CFG0_RSSI_VALID_CNT_9 3 + +#define CC1120_AGC_CFG0_AGC_ASK_DECAY 0 +#define CC1120_AGC_CFG0_AGC_ASK_DECAY_1_16 0 +#define CC1120_AGC_CFG0_AGC_ASK_DECAY_1_32 1 +#define CC1120_AGC_CFG0_AGC_ASK_DECAY_1_64 2 +#define CC1120_AGC_CFG0_AGC_ASK_DECAY_1_128 3 + #define CC1120_FIFO_CFG 0x1e #define CC1120_FIFO_CFG_CRC_AUTOFLUSH 7 #define CC1120_FIFO_CFG_FIFO_THR 0 diff --git a/src/drivers/ao_cc1120_CC1120.h b/src/drivers/ao_cc1120_CC1120.h index 44cca938..399abc4d 100644 --- a/src/drivers/ao_cc1120_CC1120.h +++ b/src/drivers/ao_cc1120_CC1120.h @@ -21,6 +21,10 @@ *
***************************************************************/
+#ifndef AO_CC1120_AGC_GAIN_ADJUST
+#define AO_CC1120_AGC_GAIN_ADJUST -80
+#endif
+
CC1120_SYNC3, 0xD3, /* Sync Word Configuration [31:24] */
CC1120_SYNC2, 0x91, /* Sync Word Configuration [23:16] */
CC1120_SYNC1, 0xD3, /* Sync Word Configuration [15:8] */
@@ -53,24 +57,49 @@ (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 */
+ /* AGC reference = 10 * log10(receive BW) - 4 = 10 * log10(100e3) - 4 = 46 */
+ CC1120_AGC_REF, 46, /* AGC Reference Level Configuration */
+
+ /* Carrier sense threshold - 25dB above the noise */
+ CC1120_AGC_CS_THR, 25, /* Carrier Sense Threshold Configuration */
+ CC1120_AGC_GAIN_ADJUST, /* RSSI Offset Configuration */
+ AO_CC1120_AGC_GAIN_ADJUST,
+
+ CC1120_AGC_CFG3, /* AGC Configuration */
+ (1 << CC1120_AGC_CFG3_RSSI_STEP_THR) |
+ (17 << CC1120_AGC_CFG3_AGC_MIN_GAIN),
+
+ CC1120_AGC_CFG2, /* AGC Configuration */
+ (0 << CC1120_AGC_CFG2_START_PREVIOUS_GAIN_EN) |
+ (CC1120_AGC_CFG2_FE_PERFORMANCE_MODE_NORMAL << CC1120_AGC_CFG2_FE_PERFORMANCE_MODE) |
+ (0 << CC1120_AGC_CFG2_AGC_MAX_GAIN),
+
+ CC1120_AGC_CFG1, /* AGC Configuration */
+ (CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR_UPDATE_AGC_UPDATE_RSSI_SLOW << CC1120_AGC_CFG1_AGC_SYNC_BEHAVIOR) |
+ (CC1120_AGC_CFG1_AGC_WIN_SIZE_32 << CC1120_AGC_CFG1_AGC_WIN_SIZE) |
+ (CC1120_AGC_CFG1_AGC_SETTLE_WAIT_32 << CC1120_AGC_CFG1_AGC_SETTLE_WAIT),
+
+ CC1120_AGC_CFG0, /* AGC Configuration */
+ (CC1120_AGC_CFG0_AGC_HYST_LEVEL_10 << CC1120_AGC_CFG0_AGC_HYST_LEVEL) |
+ (CC1120_AGC_CFG0_AGC_SLEWRATE_LIMIT_60 << CC1120_AGC_CFG0_AGC_SLEWRATE_LIMIT) |
+ (CC1120_AGC_CFG0_RSSI_VALID_CNT_9 << CC1120_AGC_CFG0_RSSI_VALID_CNT) |
+ (CC1120_AGC_CFG0_AGC_ASK_DECAY_1_128 << CC1120_AGC_CFG0_AGC_ASK_DECAY),
+
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 */
diff --git a/src/drivers/ao_cc115l.c b/src/drivers/ao_cc115l.c new file mode 100644 index 00000000..05e6a762 --- /dev/null +++ b/src/drivers/ao_cc115l.c @@ -0,0 +1,960 @@ +/* + * Copyright © 2013 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_cc115l.h> +#include <ao_exti.h> +#include <ao_telemetry.h> +#include <ao_fec.h> + +#define AO_RADIO_MAX_SEND sizeof (struct ao_telemetry_generic) + +uint8_t ao_radio_mutex; + +static uint8_t ao_radio_fifo; /* fifo drained interrupt received */ +static uint8_t ao_radio_done; /* tx done interrupt received */ +static uint8_t ao_radio_wake; /* sleep address for radio interrupts */ +static uint8_t ao_radio_abort; /* radio operation should abort */ +static uint8_t ao_radio_mcu_wake; /* MARC status change */ +static uint8_t ao_radio_marcstate; /* Last read MARC state value */ + +/* Debugging commands */ +#define CC115L_DEBUG 0 + +/* Runtime tracing */ +#define CC115L_TRACE 0 + +#define FOSC 26000000 + +#define ao_radio_select() ao_spi_get_mask(AO_CC115L_SPI_CS_PORT,(1 << AO_CC115L_SPI_CS_PIN),AO_CC115L_SPI_BUS,AO_SPI_SPEED_1MHz) +#define ao_radio_deselect() ao_spi_put_mask(AO_CC115L_SPI_CS_PORT,(1 << AO_CC115L_SPI_CS_PIN),AO_CC115L_SPI_BUS) +#define ao_radio_spi_send(d,l) ao_spi_send((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_spi_send_fixed(d,l) ao_spi_send_fixed((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_spi_recv(d,l) ao_spi_recv((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_duplex(o,i,l) ao_spi_duplex((o), (i), (l), AO_CC115L_SPI_BUS) + +struct ao_cc115l_reg { + uint16_t addr; + char *name; +}; + +#if CC115L_TRACE + +const static struct ao_cc115l_reg ao_cc115l_reg[]; +const static char *cc115l_state_name[]; + +enum ao_cc115l_trace_type { + trace_strobe, + trace_read, + trace_write, + trace_dma, + trace_line, +}; + +struct ao_cc115l_trace { + enum ao_cc115l_trace_type type; + int16_t addr; + int16_t value; + const char *comment; +}; + +#define NUM_TRACE 256 + +static struct ao_cc115l_trace trace[NUM_TRACE]; +static int trace_i; +static int trace_disable; + +static void trace_add(enum ao_cc115l_trace_type type, int16_t addr, int16_t value, const char *comment) +{ + if (trace_disable) + return; + switch (type) { + case trace_read: + case trace_write: + comment = ao_cc115l_reg[addr].name; + break; + case trace_strobe: + comment = cc115l_state_name[(value >> 4) & 0x7]; + break; + } + trace[trace_i].type = type; + trace[trace_i].addr = addr; + trace[trace_i].value = value; + trace[trace_i].comment = comment; + if (++trace_i == NUM_TRACE) + trace_i = 0; +} +#else +#define trace_add(t,a,v,c) +#endif + +static uint8_t +ao_radio_reg_read(uint8_t addr) +{ + uint8_t data[1]; + uint8_t d; + + data[0] = ((1 << CC115L_READ) | + (0 << CC115L_BURST) | + addr); + ao_radio_select(); + ao_radio_spi_send(data, 1); + ao_radio_spi_recv(data, 1); + ao_radio_deselect(); + trace_add(trace_read, addr, data[0], NULL); + return data[0]; +} + +static void +ao_radio_reg_write(uint8_t addr, uint8_t value) +{ + uint8_t data[2]; + uint8_t d; + + trace_add(trace_write, addr, value, NULL); + data[0] = ((0 << CC115L_READ) | + (0 << CC115L_BURST) | + addr); + data[1] = value; + ao_radio_select(); + ao_radio_spi_send(data, 2); + ao_radio_deselect(); +} + +static void +ao_radio_burst_read_start (uint16_t addr) +{ + uint8_t data[1]; + uint8_t d; + + data[0] = ((1 << CC115L_READ) | + (1 << CC115L_BURST) | + addr); + ao_radio_select(); + ao_radio_spi_send(data, 1); +} + +static void +ao_radio_burst_read_stop (void) +{ + ao_radio_deselect(); +} + + +static uint8_t +ao_radio_strobe(uint8_t addr) +{ + uint8_t in; + + ao_radio_select(); + ao_radio_duplex(&addr, &in, 1); + ao_radio_deselect(); + trace_add(trace_strobe, addr, in, NULL); + return in; +} + +static uint8_t +ao_radio_fifo_write_start(void) +{ + uint8_t addr = ((0 << CC115L_READ) | + (1 << CC115L_BURST) | + CC115L_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(); + trace_add(trace_dma, CC115L_FIFO, len, NULL); + ao_radio_spi_send(data, len); + return ao_radio_fifo_write_stop(status); +} + +static uint8_t +ao_radio_tx_fifo_space(void) +{ + return CC115L_FIFO_SIZE - (ao_radio_reg_read(CC115L_TXBYTES) & CC115L_TXBYTES_NUM_TX_BYTES_MASK); +} + +static uint8_t +ao_radio_status(void) +{ + return ao_radio_strobe (CC115L_SNOP); +} + +#define ao_radio_rdf_value 0x55 + +static uint8_t +ao_radio_get_marcstate(void) +{ + return ao_radio_reg_read(CC115L_MARCSTATE) & CC115L_MARCSTATE_MASK; +} + +static void +ao_radio_done_isr(void) +{ + ao_exti_disable(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN); + trace_add(trace_line, __LINE__, 0, "done_isr"); + ao_radio_done = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_fifo_isr(void) +{ + ao_exti_disable(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN); + trace_add(trace_line, __LINE__, 0, "fifo_isr"); + ao_radio_fifo = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_start_tx(void) +{ +} + +static void +ao_radio_idle(void) +{ + ao_radio_pa_off(); + for (;;) { + uint8_t state = ao_radio_strobe(CC115L_SIDLE); + if ((state >> CC115L_STATUS_STATE) == CC115L_STATUS_STATE_IDLE) + break; + } + /* Flush any pending TX bytes */ + ao_radio_strobe(CC115L_SFTX); +} + +/* + * Packet deviation is 20.5kHz + * + * fdev = fosc >> 17 * (8 + dev_m) << dev_e + * + * 26e6 / (2 ** 17) * (8 + 5) * (2 ** 3) = 20630Hz + */ + +#define PACKET_DEV_E 3 +#define PACKET_DEV_M 5 + +/* + * For our packet data, set the symbol rate to 38400 Baud + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 131) * (2 ** 10) / (2**28) * 26e6 = 38383 + * + * DATARATE_M = 131 + * DATARATE_E = 10 + */ +#define PACKET_DRATE_E 10 +#define PACKET_DRATE_M 131 + +static const uint16_t packet_setup[] = { + CC115L_DEVIATN, ((PACKET_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (PACKET_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (PACKET_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (PACKET_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_16BITS << CC115L_MDMCFG2_SYNC_MODE)), +}; + + +/* + * RDF deviation is 5kHz + * + * fdev = fosc >> 17 * (8 + dev_m) << dev_e + * + * 26e6 / (2 ** 17) * (8 + 4) * (2 ** 1) = 4761Hz + */ + +#define RDF_DEV_E 1 +#define RDF_DEV_M 4 + +/* + * For our RDF beacon, set the symbol rate to 2kBaud (for a 1kHz tone) + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 67) * (2 ** 6) / (2**28) * 26e6 = 2002 + * + * DATARATE_M = 67 + * DATARATE_E = 6 + */ +#define RDF_DRATE_E 6 +#define RDF_DRATE_M 67 + +static const uint16_t rdf_setup[] = { + CC115L_DEVIATN, ((RDF_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (RDF_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (RDF_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (RDF_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_NONE << CC115L_MDMCFG2_SYNC_MODE)), +}; + +/* + * APRS deviation is 3kHz + * + * 26e6 / (2 ** 17) * (8 + 7) * (2 ** 0) = 2975 + */ + +#define APRS_DEV_E 0 +#define APRS_DEV_M 7 + +/* + * For our APRS beacon, set the symbol rate to 9.6kBaud (8x oversampling for 1200 baud data rate) + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 131) * (2 ** 8) / (2**28) * 26e6 = 9596 + * + * DATARATE_M = 131 + * DATARATE_E = 8 + * + */ +#define APRS_DRATE_E 8 +#define APRS_DRATE_M 131 + +static const uint16_t aprs_setup[] = { + CC115L_DEVIATN, ((APRS_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (APRS_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (APRS_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (APRS_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_NONE << CC115L_MDMCFG2_SYNC_MODE)), +}; + +#define AO_PKTCTRL0_INFINITE ((CC115L_PKTCTRL0_PKT_FORMAT_NORMAL << CC115L_PKTCTRL0_PKT_FORMAT) | \ + (0 << CC115L_PKTCTRL0_PKT_CRC_EN) | \ + (CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_INFINITE << CC115L_PKTCTRL0_PKT_LENGTH_CONFIG)) +#define AO_PKTCTRL0_FIXED ((CC115L_PKTCTRL0_PKT_FORMAT_NORMAL << CC115L_PKTCTRL0_PKT_FORMAT) | \ + (0 << CC115L_PKTCTRL0_PKT_CRC_EN) | \ + (CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_FIXED << CC115L_PKTCTRL0_PKT_LENGTH_CONFIG)) + +static uint16_t ao_radio_mode; + + +/* + * These set the data rate and modulation parameters + */ +#define AO_RADIO_MODE_BITS_PACKET_TX 1 +#define AO_RADIO_MODE_BITS_RDF 2 +#define AO_RADIO_MODE_BITS_APRS 4 + +/* + * Flips between infinite packet mode and fixed packet mode; + * we use infinite mode until the sender gives us the + * last chunk of data + */ +#define AO_RADIO_MODE_BITS_INFINITE 40 +#define AO_RADIO_MODE_BITS_FIXED 80 + +#define AO_RADIO_MODE_NONE 0 + +#define AO_RADIO_MODE_RDF AO_RADIO_MODE_BITS_RDF +#define AO_RADIO_MODE_PACKET_TX AO_RADIO_MODE_BITS_PACKET_TX +#define AO_RADIO_MODE_APRS AO_RADIO_MODE_BITS_APRS + +static void +ao_radio_set_mode(uint16_t new_mode) +{ + uint16_t changes; + int i; + + if (new_mode == ao_radio_mode) + return; + + changes = new_mode & (~ao_radio_mode); + if (changes & AO_RADIO_MODE_BITS_PACKET_TX) + 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_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]); + + if (changes & AO_RADIO_MODE_BITS_APRS) + for (i = 0; i < sizeof (aprs_setup) / sizeof (aprs_setup[0]); i += 2) + ao_radio_reg_write(aprs_setup[i], aprs_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_INFINITE) + ao_radio_reg_write(CC115L_PKTCTRL0, AO_PKTCTRL0_INFINITE); + + if (changes & AO_RADIO_MODE_BITS_FIXED) + ao_radio_reg_write(CC115L_PKTCTRL0, AO_PKTCTRL0_FIXED); + + ao_radio_mode = new_mode; +} + +/*************************************************************** + * SmartRF Studio(tm) Export + * + * Radio register settings specifed with address, value + * + * RF device: CC115L + * + ***************************************************************/ + +static const uint16_t radio_setup[] = { + + /* High when FIFO is above threshold, low when fifo is below threshold */ + AO_CC115L_FIFO_INT_GPIO_IOCFG, CC115L_IOCFG_GPIO_CFG_TXFIFO_THR, + + /* High when transmitter is running, low when off */ + AO_CC115L_DONE_INT_GPIO_IOCFG, CC115L_IOCFG_GPIO_CFG_PA_PD | (1 << CC115L_IOCFG_GPIO_INV), + + CC115L_FIFOTHR, 0x47, /* TX FIFO Thresholds */ + CC115L_MDMCFG1, (0x00 | + (CC115L_MDMCFG1_NUM_PREAMBLE_4 << CC115L_MDMCFG1_NUM_PREAMBLE) | + (1 << CC115L_MDMCFG1_CHANSPC_E)), + CC115L_MDMCFG0, 248, /* Channel spacing M value (100kHz channels) */ + CC115L_MCSM0, 0x38, /* Main Radio Control State Machine Configuration */ + CC115L_RESERVED_0X20, 0xfb, /* Use setting from SmartRF Studio */ + CC115L_FSCAL3, 0xe9, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL2, 0x2a, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL1, 0x00, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL0, 0x1f, /* Frequency Synthesizer Calibration */ + CC115L_TEST2, 0x81, /* Various Test Settings */ + CC115L_TEST1, 0x35, /* Various Test Settings */ + CC115L_TEST0, 0x09, /* Various Test Settings */ +}; + +static uint8_t ao_radio_configured = 0; + +static void +ao_radio_setup(void) +{ + int i; + + ao_radio_strobe(CC115L_SRES); + ao_delay(AO_MS_TO_TICKS(10)); + + 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_set_len(uint8_t len) +{ + static uint8_t last_len; + + if (len != last_len) { + ao_radio_reg_write(CC115L_PKTLEN, len); + last_len = len; + } +} + +static void +ao_radio_get(void) +{ + static uint32_t last_radio_setting; + static uint8_t last_power_setting; + + 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(CC115L_FREQ2, ao_config.radio_setting >> 16); + ao_radio_reg_write(CC115L_FREQ1, ao_config.radio_setting >> 8); + ao_radio_reg_write(CC115L_FREQ0, ao_config.radio_setting); + last_radio_setting = ao_config.radio_setting; + } + if (ao_config.radio_power != last_power_setting) { + ao_radio_reg_write(CC115L_PA, ao_config.radio_power); + last_power_setting = ao_config.radio_power; + } +} + +static void +_ao_radio_send_lots(ao_radio_fill_func fill, uint8_t mode); + +#define ao_radio_put() ao_mutex_put(&ao_radio_mutex) + +struct ao_radio_tone { + uint8_t value; + uint8_t len; +}; + +struct ao_radio_tone *ao_radio_tone; +uint8_t ao_radio_tone_count; +uint8_t ao_radio_tone_current; +uint8_t ao_radio_tone_offset; + +int16_t +ao_radio_tone_fill(uint8_t *buf, int16_t len) +{ + int16_t ret = 0; + + while (len) { + int16_t this_time; + struct ao_radio_tone *t; + + /* Figure out how many to send of the current value */ + t = &ao_radio_tone[ao_radio_tone_current]; + this_time = t->len - ao_radio_tone_offset; + if (this_time > len) + this_time = len; + + /* queue the data */ + memset(buf, t->value, this_time); + + /* mark as sent */ + len -= this_time; + ao_radio_tone_offset += this_time; + ret += this_time; + + if (ao_radio_tone_offset >= t->len) { + ao_radio_tone_offset = 0; + ao_radio_tone_current++; + if (ao_radio_tone_current >= ao_radio_tone_count) { + trace_add(trace_line, __LINE__, ret, "done with tone"); + return -ret; + } + } + } + trace_add(trace_line, __LINE__, ret, "got some tone"); + return ret; +} + +static void +ao_radio_tone_run(struct ao_radio_tone *tones, int ntones) +{ + ao_radio_get(); + ao_radio_tone = tones; + ao_radio_tone_current = 0; + ao_radio_tone_offset = 0; + _ao_radio_send_lots(ao_radio_tone_fill, AO_RADIO_MODE_RDF); + ao_radio_put(); +} + +void +ao_radio_rdf(void) +{ + struct ao_radio_tone tone; + + tone.value = ao_radio_rdf_value; + tone.len = AO_RADIO_RDF_LEN; + ao_radio_tone_run(&tone, 1); +} + +void +ao_radio_continuity(uint8_t c) +{ + struct ao_radio_tone tones[7]; + uint8_t count = 0; + uint8_t i; + + for (i = 0; i < 3; i++) { + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_PAUSE_LEN; + count++; + if (i < c) + tones[count].value = ao_radio_rdf_value; + else + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_TONE_LEN; + count++; + } + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_PAUSE_LEN; + count++; + ao_radio_tone_run(tones, count); +} + +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; + static 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(); + ao_radio_set_len(0xff); + ao_radio_set_mode(AO_RADIO_MODE_RDF|AO_RADIO_MODE_BITS_FIXED); + ao_radio_strobe(CC115L_SFTX); + ao_radio_pa_on(); + ao_radio_strobe(CC115L_STX); + 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 + } +} + +static inline int16_t +ao_radio_gpio_bits(void) +{ + return AO_CC115L_DONE_INT_PORT->idr & ((1 << AO_CC115L_FIFO_INT_PIN) | + (1 << AO_CC115L_DONE_INT_PIN)); +} + +static void +ao_radio_wait_fifo(void) +{ + ao_arch_block_interrupts(); + while (!ao_radio_fifo && !ao_radio_done && !ao_radio_abort) { + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wait_fifo"); + ao_sleep(&ao_radio_wake); + } + ao_arch_release_interrupts(); + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wake bits"); + trace_add(trace_line, __LINE__, ao_radio_fifo, "wake fifo"); + trace_add(trace_line, __LINE__, ao_radio_done, "wake done"); + trace_add(trace_line, __LINE__, ao_radio_abort, "wake abort"); +} + +static void +ao_radio_wait_done(void) +{ + ao_arch_block_interrupts(); + while (!ao_radio_done && !ao_radio_abort) { + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wait_done"); + ao_sleep(&ao_radio_wake); + } + ao_arch_release_interrupts(); + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wake bits"); + trace_add(trace_line, __LINE__, ao_radio_fifo, "wake fifo"); + trace_add(trace_line, __LINE__, ao_radio_done, "wake done"); + trace_add(trace_line, __LINE__, ao_radio_abort, "wake abort"); +} + +static uint8_t tx_data[(AO_RADIO_MAX_SEND + 4) * 2]; + +static uint8_t *ao_radio_send_buf; +static int16_t ao_radio_send_len; + +static int16_t +ao_radio_send_fill(uint8_t *buf, int16_t len) +{ + int16_t this_time; + + this_time = ao_radio_send_len; + if (this_time > len) + this_time = len; + memcpy(buf, ao_radio_send_buf, this_time); + ao_radio_send_buf += this_time; + ao_radio_send_len -= this_time; + if (ao_radio_send_len == 0) + return -this_time; + return this_time; +} + +void +ao_radio_send(const void *d, uint8_t size) +{ + int i; + + ao_radio_get(); + ao_radio_send_len = ao_fec_encode(d, size, tx_data); + ao_radio_send_buf = tx_data; + _ao_radio_send_lots(ao_radio_send_fill, AO_RADIO_MODE_PACKET_TX); + ao_radio_put(); +} + +#define AO_RADIO_LOTS 64 + +static void +_ao_radio_send_lots(ao_radio_fill_func fill, uint8_t mode) +{ + uint8_t buf[AO_RADIO_LOTS], *b; + int cnt; + int total = 0; + uint8_t done = 0; + uint8_t started = 0; + uint8_t fifo_space; + + fifo_space = CC115L_FIFO_SIZE; + ao_radio_done = 0; + ao_radio_fifo = 0; + while (!done) { + cnt = (*fill)(buf, sizeof(buf)); + trace_add(trace_line, __LINE__, cnt, "send data count"); + if (cnt < 0) { + done = 1; + cnt = -cnt; + } + total += cnt; + + /* At the last buffer, set the total length */ + if (done) { + ao_radio_set_len(total & 0xff); + ao_radio_set_mode(mode | AO_RADIO_MODE_BITS_FIXED); + } else { + ao_radio_set_len(0xff); + ao_radio_set_mode(mode | AO_RADIO_MODE_BITS_INFINITE); + } + + b = buf; + while (cnt) { + uint8_t this_len = cnt; + + /* Wait for some space in the fifo */ + while (!ao_radio_abort && (fifo_space = ao_radio_tx_fifo_space()) == 0) { + trace_add(trace_line, __LINE__, this_len, "wait for space"); + ao_radio_wait_fifo(); + } + if (ao_radio_abort || ao_radio_done) + break; + trace_add(trace_line, __LINE__, fifo_space, "got space"); + if (this_len > fifo_space) + this_len = fifo_space; + + cnt -= this_len; + + ao_radio_done = 0; + ao_radio_fifo = 0; + ao_radio_fifo_write(b, this_len); + b += this_len; + + ao_exti_enable(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN); + ao_exti_enable(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN); + + if (!started) { + ao_radio_pa_on(); + ao_radio_strobe(CC115L_STX); + started = 1; + } + } + if (ao_radio_abort || ao_radio_done) + break; + } + if (ao_radio_abort) + ao_radio_idle(); + ao_radio_wait_done(); + ao_radio_pa_off(); +} + +void +ao_radio_send_aprs(ao_radio_fill_func fill) +{ + ao_radio_get(); + _ao_radio_send_lots(fill, AO_RADIO_MODE_APRS); + ao_radio_put(); +} + +#if CC115L_DEBUG +const static char *cc115l_state_name[] = { + [CC115L_STATUS_STATE_IDLE] = "IDLE", + [CC115L_STATUS_STATE_TX] = "TX", + [CC115L_STATUS_STATE_FSTXON] = "FSTXON", + [CC115L_STATUS_STATE_CALIBRATE] = "CALIBRATE", + [CC115L_STATUS_STATE_SETTLING] = "SETTLING", + [CC115L_STATUS_STATE_TX_FIFO_UNDERFLOW] = "TX_FIFO_UNDERFLOW", +}; + +const static struct ao_cc115l_reg ao_cc115l_reg[] = { + { .addr = CC115L_IOCFG2, .name = "IOCFG2" }, + { .addr = CC115L_IOCFG1, .name = "IOCFG1" }, + { .addr = CC115L_IOCFG0, .name = "IOCFG0" }, + { .addr = CC115L_FIFOTHR, .name = "FIFOTHR" }, + { .addr = CC115L_SYNC1, .name = "SYNC1" }, + { .addr = CC115L_SYNC0, .name = "SYNC0" }, + { .addr = CC115L_PKTLEN, .name = "PKTLEN" }, + { .addr = CC115L_PKTCTRL0, .name = "PKTCTRL0" }, + { .addr = CC115L_CHANNR, .name = "CHANNR" }, + { .addr = CC115L_FSCTRL0, .name = "FSCTRL0" }, + { .addr = CC115L_FREQ2, .name = "FREQ2" }, + { .addr = CC115L_FREQ1, .name = "FREQ1" }, + { .addr = CC115L_FREQ0, .name = "FREQ0" }, + { .addr = CC115L_MDMCFG4, .name = "MDMCFG4" }, + { .addr = CC115L_MDMCFG3, .name = "MDMCFG3" }, + { .addr = CC115L_MDMCFG2, .name = "MDMCFG2" }, + { .addr = CC115L_MDMCFG1, .name = "MDMCFG1" }, + { .addr = CC115L_MDMCFG0, .name = "MDMCFG0" }, + { .addr = CC115L_DEVIATN, .name = "DEVIATN" }, + { .addr = CC115L_MCSM1, .name = "MCSM1" }, + { .addr = CC115L_MCSM0, .name = "MCSM0" }, + { .addr = CC115L_RESERVED_0X20, .name = "RESERVED_0X20" }, + { .addr = CC115L_FREND0, .name = "FREND0" }, + { .addr = CC115L_FSCAL3, .name = "FSCAL3" }, + { .addr = CC115L_FSCAL2, .name = "FSCAL2" }, + { .addr = CC115L_FSCAL1, .name = "FSCAL1" }, + { .addr = CC115L_FSCAL0, .name = "FSCAL0" }, + { .addr = CC115L_RESERVED_0X29, .name = "RESERVED_0X29" }, + { .addr = CC115L_RESERVED_0X2A, .name = "RESERVED_0X2A" }, + { .addr = CC115L_RESERVED_0X2B, .name = "RESERVED_0X2B" }, + { .addr = CC115L_TEST2, .name = "TEST2" }, + { .addr = CC115L_TEST1, .name = "TEST1" }, + { .addr = CC115L_TEST0, .name = "TEST0" }, + { .addr = CC115L_PARTNUM, .name = "PARTNUM" }, + { .addr = CC115L_VERSION, .name = "VERSION" }, + { .addr = CC115L_MARCSTATE, .name = "MARCSTATE" }, + { .addr = CC115L_PKTSTATUS, .name = "PKTSTATUS" }, + { .addr = CC115L_TXBYTES, .name = "TXBYTES" }, + { .addr = CC115L_PA, .name = "PA" }, +}; + +#define AO_NUM_CC115L_REG (sizeof ao_cc115l_reg / sizeof ao_cc115l_reg[0]) + +static void ao_radio_show(void) { + uint8_t status = ao_radio_status(); + int i; + + ao_radio_get(); + status = ao_radio_status(); + printf ("Status: %02x\n", status); + printf ("CHIP_RDY: %d\n", (status >> CC115L_STATUS_CHIP_RDY) & 1); + printf ("STATE: %s\n", cc115l_state_name[(status >> CC115L_STATUS_STATE) & CC115L_STATUS_STATE_MASK]); + printf ("MARC: %02x\n", ao_radio_get_marcstate()); + + for (i = 0; i < AO_NUM_CC115L_REG; i++) + printf ("\t%02x %-20.20s\n", ao_radio_reg_read(ao_cc115l_reg[i].addr), ao_cc115l_reg[i].name); + ao_radio_put(); +} + +static void ao_radio_beep(void) { + ao_radio_rdf(); +} + +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)); +} + +#endif /* CC115L_DEBUG */ + +#if HAS_APRS +#include <ao_aprs.h> + +static void +ao_radio_aprs() +{ +#if PACKET_HAS_SLAVE + ao_packet_slave_stop(); +#endif + ao_aprs_send(); +} +#endif + +static const struct ao_cmds ao_radio_cmds[] = { + { ao_radio_test_cmd, "C <1 start, 0 stop, none both>\0Radio carrier test" }, +#if CC115L_DEBUG +#if HAS_APRS + { ao_radio_aprs, "G\0Send APRS packet" }, +#endif + { ao_radio_show, "R\0Show CC115L status" }, + { ao_radio_beep, "b\0Emit an RDF beacon" }, + { ao_radio_packet, "p\0Send a test packet" }, +#endif + { 0, NULL } +}; + +void +ao_radio_init(void) +{ + int i; + + ao_radio_configured = 0; + ao_spi_init_cs (AO_CC115L_SPI_CS_PORT, (1 << AO_CC115L_SPI_CS_PIN)); + +#if 0 + AO_CC115L_SPI_CS_PORT->bsrr = ((uint32_t) (1 << AO_CC115L_SPI_CS_PIN)); + for (i = 0; i < 10000; i++) { + if ((SPI_2_PORT->idr & (1 << SPI_2_MISO_PIN)) == 0) + break; + } + AO_CC115L_SPI_CS_PORT->bsrr = (1 << AO_CC115L_SPI_CS_PIN); + if (i == 10000) + ao_panic(AO_PANIC_SELF_TEST_CC115L); +#endif + + /* Enable the fifo threhold interrupt pin */ + ao_enable_port(AO_CC115L_FIFO_INT_PORT); + ao_exti_setup(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_HIGH, + ao_radio_fifo_isr); + + /* Enable the tx done interrupt pin */ + ao_enable_port(AO_CC115L_DONE_INT_PORT); + ao_exti_setup(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_MED, + ao_radio_done_isr); + + ao_radio_pa_init(); + + ao_cmd_register(&ao_radio_cmds[0]); +} diff --git a/src/drivers/ao_cc115l.h b/src/drivers/ao_cc115l.h new file mode 100644 index 00000000..811c14aa --- /dev/null +++ b/src/drivers/ao_cc115l.h @@ -0,0 +1,226 @@ +/* + * Copyright © 2013 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_CC115L_H_ +#define _AO_CC115L_H_ + +#define CC115L_BURST 6 +#define CC115L_READ 7 + +/* Register space */ +#define CC115L_IOCFG2 0x00 /* GDO2 Output Pin Configuration */ +#define CC115L_IOCFG1 0x01 /* GDO1 Output Pin Configuration */ +#define CC115L_IOCFG0 0x02 /* GDO0 Output Pin Configuration */ + +#define CC115L_IOCFG_GPIO1_DS 7 +#define CC115L_IOCFG_GPIO_INV 6 + +#define CC115L_IOCFG_GPIO_CFG 0 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_THR 2 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_THR_PKT 3 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_UNDERFLOW 5 +#define CC115L_IOCFG_GPIO_CFG_PKT_SYNC_TX 6 +#define CC115L_IOCFG_GPIO_CFG_PLL_LOCKED 10 +#define CC115L_IOCFG_GPIO_CFG_SERIAL_CLK 11 +#define CC115L_IOCFG_GPIO_CFG_SYNC_DATA 12 +#define CC115L_IOCFG_GPIO_CFG_ASYNC_DATA 13 +#define CC115L_IOCFG_GPIO_CFG_PA_PD 27 +#define CC115L_IOCFG_GPIO_CFG_CHIP_RDYn 41 +#define CC115L_IOCFG_GPIO_CFG_XOSC_STABLE 43 +#define CC115L_IOCFG_GPIO_CFG_HIGHZ 46 +#define CC115L_IOCFG_GPIO_CFG_HW_0 47 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_1 48 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_1_5 49 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_2 50 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_3 51 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_4 52 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_6 53 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_8 54 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_12 55 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_16 56 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_24 57 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_32 58 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_48 59 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_64 60 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_96 61 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_128 62 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_192 63 +#define CC115L_IOCFG_GPIO_CFG_MASK 0x3f + +#define CC115L_FIFOTHR 0x03 /* TX FIFO Thresholds */ +#define CC115L_FIFOTHR_THR_MASK 0x0f +#define CC115L_FIFOTHR_THR_61 0 +#define CC115L_FIFOTHR_THR_57 1 +#define CC115L_FIFOTHR_THR_53 2 +#define CC115L_FIFOTHR_THR_49 3 +#define CC115L_FIFOTHR_THR_45 4 +#define CC115L_FIFOTHR_THR_41 5 +#define CC115L_FIFOTHR_THR_37 6 +#define CC115L_FIFOTHR_THR_33 7 +#define CC115L_FIFOTHR_THR_29 8 +#define CC115L_FIFOTHR_THR_25 9 +#define CC115L_FIFOTHR_THR_21 10 +#define CC115L_FIFOTHR_THR_17 11 +#define CC115L_FIFOTHR_THR_13 12 +#define CC115L_FIFOTHR_THR_9 13 +#define CC115L_FIFOTHR_THR_5 14 +#define CC115L_FIFOTHR_THR_1 15 + +#define CC115L_SYNC1 0x04 /* Sync Word, High Byte */ +#define CC115L_SYNC0 0x05 /* Sync Word, Low Byte */ +#define CC115L_PKTLEN 0x06 /* Packet Length */ +#define CC115L_PKTCTRL0 0x08 /* Packet Automation Control */ +#define CC115L_PKTCTRL0_PKT_FORMAT 4 +#define CC115L_PKTCTRL0_PKT_FORMAT_NORMAL 0 +#define CC115L_PKTCTRL0_PKT_FORMAT_SYNC_SERIAL 1 +#define CC115L_PKTCTRL0_PKT_FORMAT_RANDOM 2 +#define CC115L_PKTCTRL0_PKT_FORMAT_ASYNC_SERIAL 3 +#define CC115L_PKTCTRL0_PKT_FORMAT_MASK 3 +#define CC115L_PKTCTRL0_PKT_CRC_EN 2 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG 0 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_FIXED 0 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_VARIABLE 1 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_INFINITE 2 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_MASK 3 +#define CC115L_CHANNR 0x0a /* Channel Number */ +#define CC115L_FSCTRL0 0x0c /* Frequency Synthesizer Control */ +#define CC115L_FREQ2 0x0d /* Frequency Control Word, High Byte */ +#define CC115L_FREQ1 0x0e /* Frequency Control Word, Middle Byte */ +#define CC115L_FREQ0 0x0f /* Frequency Control Word, Low Byte */ +#define CC115L_MDMCFG4 0x10 /* Modem Configuration */ +#define CC115L_MDMCFG4_DRATE_E 0 +#define CC115L_MDMCFG3 0x11 /* Modem Configuration */ +#define CC115L_MDMCFG2 0x12 /* Modem Configuration */ +#define CC115L_MDMCFG2_MOD_FORMAT 4 +#define CC115L_MDMCFG2_MOD_FORMAT_2FSK 0 +#define CC115L_MDMCFG2_MOD_FORMAT_GFSK 1 +#define CC115L_MDMCFG2_MOD_FORMAT_OOK 3 +#define CC115L_MDMCFG2_MOD_FORMAT_4FSK 4 +#define CC115L_MDMCFG2_MOD_FORMAT_MASK 7 +#define CC115L_MDMCFG2_MANCHESTER_EN 3 +#define CC115L_MDMCFG2_SYNC_MODE 0 +#define CC115L_MDMCFG2_SYNC_MODE_NONE 0 +#define CC115L_MDMCFG2_SYNC_MODE_16BITS 1 +#define CC115L_MDMCFG2_SYNC_MODE_32BITS 3 +#define CC115L_MDMCFG2_SYNC_MODE_MASK 3 +#define CC115L_MDMCFG1 0x13 /* Modem Configuration */ +#define CC115L_MDMCFG1_NUM_PREAMBLE 4 +#define CC115L_MDMCFG1_NUM_PREAMBLE_2 0 +#define CC115L_MDMCFG1_NUM_PREAMBLE_3 1 +#define CC115L_MDMCFG1_NUM_PREAMBLE_4 2 +#define CC115L_MDMCFG1_NUM_PREAMBLE_6 3 +#define CC115L_MDMCFG1_NUM_PREAMBLE_8 4 +#define CC115L_MDMCFG1_NUM_PREAMBLE_12 5 +#define CC115L_MDMCFG1_NUM_PREAMBLE_16 6 +#define CC115L_MDMCFG1_NUM_PREAMBLE_24 7 +#define CC115L_MDMCFG1_NUM_PREAMBLE_MASK 7 +#define CC115L_MDMCFG1_CHANSPC_E 0 +#define CC115L_MDMCFG0 0x14 /* Modem Configuration */ +#define CC115L_DEVIATN 0x15 /* Modem Deviation Setting */ +#define CC115L_DEVIATN_DEVIATION_E 4 +#define CC115L_DEVIATN_DEVIATION_E_MASK 7 +#define CC115L_DEVIATN_DEVIATION_M 0 +#define CC115L_DEVIATN_DEVIATION_M_MASK 7 +#define CC115L_MCSM1 0x17 /* Main Radio Control State Machine Configuration */ +#define CC115L_MCSM1_TXOFF_MODE 0 +#define CC115L_MCSM1_TXOFF_MODE_IDLE 0 +#define CC115L_MCSM1_TXOFF_MODE_FSTXON 1 +#define CC115L_MCSM1_TXOFF_MODE_TX 2 +#define CC115L_MCSM1_TXOFF_MODE_MASK 3 +#define CC115L_MCSM0 0x18 /* Main Radio Control State Machine Configuration */ +#define CC115L_MCSM0_FS_AUTOCAL 4 +#define CC115L_MCSM0_FS_AUTOCAL_NEVER 0 +#define CC115L_MCSM0_FS_AUTOCAL_IDLE_TO_TX 1 +#define CC115L_MCSM0_FS_AUTOCAL_TX_TO_IDLE 2 +#define CC115L_MCSM0_FS_AUTOCAL_4TH_TX_TO_IDLE 3 +#define CC115L_MCSM0_FS_AUTOCAL_MASK 3 +#define CC115L_MCSM0_PO_TIMEOUT 2 +#define CC115L_MCSM0_PO_TIMEOUT_1 0 +#define CC115L_MCSM0_PO_TIMEOUT_16 1 +#define CC115L_MCSM0_PO_TIMEOUT_64 2 +#define CC115L_MCSM0_PO_TIMEOUT_256 3 +#define CC115L_MCSM0_PO_TIMEOUT_MASK 3 +#define CC115L_MCSM0_XOSC_FORCE_ON 0 +#define CC115L_RESERVED_0X20 0x20 /* Use setting from SmartRF Studio */ +#define CC115L_FREND0 0x22 /* Front End TX Configuration */ +#define CC115L_FSCAL3 0x23 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL2 0x24 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL1 0x25 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL0 0x26 /* Frequency Synthesizer Calibration */ +#define CC115L_RESERVED_0X29 0x29 /* Use setting from SmartRF Studio */ +#define CC115L_RESERVED_0X2A 0x2a /* Use setting from SmartRF Studio */ +#define CC115L_RESERVED_0X2B 0x2b /* Use setting from SmartRF Studio */ +#define CC115L_TEST2 0x2c /* Various Test Settings */ +#define CC115L_TEST1 0x2d /* Various Test Settings */ +#define CC115L_TEST0 0x2e /* Various Test Settings */ + +/* Status registers (use BURST bit to select these) */ +#define CC115L_PARTNUM (0x30|(1<<CC115L_BURST)) /* Part number for CC115L */ +#define CC115L_VERSION (0x31|(1<<CC115L_BURST)) /* Current version number */ +#define CC115L_MARCSTATE (0x35|(1<<CC115L_BURST)) /* Control state machine state */ +#define CC115L_MARCSTATE_MASK 0x1f +#define CC115L_MARCSTATE_SLEEP 0x00 +#define CC115L_MARCSTATE_IDLE 0x01 +#define CC115L_MARCSTATE_XOFF 0x02 +#define CC115L_MARCSTATE_VCOON_MC 0x03 +#define CC115L_MARCSTATE_REGON_MC 0x04 +#define CC115L_MARCSTATE_MANCAL 0x05 +#define CC115L_MARCSTATE_VCOON 0x06 +#define CC115L_MARCSTATE_REGON 0x07 +#define CC115L_MARCSTATE_STARTCAL 0x08 +#define CC115L_MARCSTATE_BWBOOST 0x09 +#define CC115L_MARCSTATE_FS_LOCK 0x0a +#define CC115L_MARCSTATE_ENDCAL 0x0c +#define CC115L_MARCSTATE_FSTXON 0x12 +#define CC115L_MARCSTATE_TX 0x13 +#define CC115L_MARCSTATE_TX_END 0x14 +#define CC115L_MARCSTATE_TX_UNDERFLOW 0x16 +#define CC115L_PKTSTATUS (0x38|(1<<CC115L_BURST)) /* Current GDOx status and packet status */ +#define CC115L_TXBYTES (0x3a|(1<<CC115L_BURST)) /* Underflow and number of bytes in the TX FIFO */ +#define CC115L_TXBYTES_TXFIFO_UNDERFLOW 7 +#define CC115L_TXBYTES_NUM_TX_BYTES 0 +#define CC115L_TXBYTES_NUM_TX_BYTES_MASK 0x7f + +/* Command strobes (no BURST bit for these) */ +#define CC115L_SRES 0x30 +#define CC115L_SFSTXON 0x31 +#define CC115L_SXOFF 0x32 +#define CC115L_SCAL 0x33 +#define CC115L_STX 0x35 +#define CC115L_SIDLE 0x36 +#define CC115L_SPWD 0x39 +#define CC115L_SFTX 0x3b +#define CC115L_SNOP 0x3d + +#define CC115L_PA 0x3e +#define CC115L_FIFO 0x3f + +#define CC115L_FIFO_SIZE 64 + +/* Status byte */ +#define CC115L_STATUS_CHIP_RDY 7 +#define CC115L_STATUS_STATE 4 +#define CC115L_STATUS_STATE_IDLE 0 +#define CC115L_STATUS_STATE_TX 2 +#define CC115L_STATUS_STATE_FSTXON 3 +#define CC115L_STATUS_STATE_CALIBRATE 4 +#define CC115L_STATUS_STATE_SETTLING 5 +#define CC115L_STATUS_STATE_TX_FIFO_UNDERFLOW 7 +#define CC115L_STATUS_STATE_MASK 7 + +#define CC115L_STATUS_FIFO_BYTES_AVAILABLE 0 +#endif /* _AO_CC115L_H_ */ diff --git a/src/drivers/ao_companion.c b/src/drivers/ao_companion.c index c749adea..0f405253 100644 --- a/src/drivers/ao_companion.c +++ b/src/drivers/ao_companion.c @@ -18,7 +18,7 @@ #include <ao.h> #include <ao_companion.h> -#ifdef MEGAMETRUM +#ifdef TELEMEGA #define ao_spi_slow(b) #define ao_spi_fast(b) #endif @@ -118,10 +118,13 @@ ao_companion_status(void) __reentrant 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:"); + printf("device: %d\n" + "update period: %d\n" + "channels: %d\n" + "data:", + ao_companion_setup.board_id, + ao_companion_setup.update_period, + ao_companion_setup.channels); for(i = 0; i < ao_companion_setup.channels; i++) printf(" %5u", ao_companion_data[i]); printf("\n"); diff --git a/src/drivers/ao_fat.c b/src/drivers/ao_fat.c new file mode 100644 index 00000000..1a1b8eb0 --- /dev/null +++ b/src/drivers/ao_fat.c @@ -0,0 +1,1651 @@ +/* + * Copyright © 2013 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_FAT_TEST +#include "ao.h" +#endif + +#include "ao_fat.h" +#include "ao_bufio.h" + +/* Include FAT commands */ +#ifndef AO_FAT_TEST +#define FAT_COMMANDS 1 +#endif + +/* Spew FAT tracing */ +#define FAT_TRACE 0 + +#ifdef DBG +#undef DBG +#endif + +#if FAT_TRACE +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +/* + * Basic file system types + */ + +typedef ao_fat_offset_t offset_t; +typedef ao_fat_sector_t sector_t; +typedef ao_fat_cluster_t cluster_t; +typedef ao_fat_dirent_t dirent_t; +typedef ao_fat_cluster_offset_t cluster_offset_t; + +/* Global FAT lock */ +static uint8_t ao_fat_mutex; + +/* Partition information, sector numbers */ + +static uint8_t partition_type; +static sector_t partition_start, partition_end; + +#define AO_FAT_BAD_CLUSTER 0xffffff7 +#define AO_FAT_LAST_CLUSTER 0xfffffff +#define AO_FAT_IS_LAST_CLUSTER(c) (((c) & 0xffffff8) == 0xffffff8) +#define AO_FAT_IS_LAST_CLUSTER16(c) (((c) & 0xfff8) == 0xfff8) + + +#define SECTOR_SIZE 512 +#define SECTOR_MASK (SECTOR_SIZE - 1) +#define SECTOR_SHIFT 9 + +#define DIRENT_SIZE 32 + +/* File system parameters */ +static uint8_t sectors_per_cluster; +static uint32_t bytes_per_cluster; +static sector_t reserved_sector_count; +static uint8_t number_fat; +static dirent_t root_entries; +static sector_t sectors_per_fat; +static cluster_t number_cluster; +static sector_t fat_start; +static sector_t root_start; +static sector_t data_start; +static cluster_t next_free; +static uint8_t filesystem_full; + +/* FAT32 extra data */ +static uint8_t fat32; +static uint8_t fsinfo_dirty; +static cluster_t root_cluster; +static sector_t fsinfo_sector; +static cluster_t free_count; + +/* + * Deal with LSB FAT data structures + */ +static uint32_t +get_u32(uint8_t *base) +{ + return ((uint32_t) base[0] | + ((uint32_t) base[1] << 8) | + ((uint32_t) base[2] << 16) | + ((uint32_t) base[3] << 24)); +} + +static void +put_u32(uint8_t *base, uint32_t value) +{ + base[0] = value; + base[1] = value >> 8; + base[2] = value >> 16; + base[3] = value >> 24; +} + +static uint16_t +get_u16(uint8_t *base) +{ + return ((uint16_t) base[0] | ((uint16_t) base[1] << 8)); +} + +static void +put_u16(uint8_t *base, uint16_t value) +{ + base[0] = value; + base[1] = value >> 8; +} + +static uint8_t +_ao_fat_cluster_valid(cluster_t cluster) +{ + return (2 <= cluster && cluster < number_cluster); +} + +/* Start using a sector */ +static uint8_t * +_ao_fat_sector_get(sector_t sector) +{ + sector += partition_start; + if (sector >= partition_end) + return NULL; + return ao_bufio_get(sector); +} + +/* Finish using a sector, 'w' is 1 if modified */ +#define _ao_fat_sector_put(b,w) ao_bufio_put(b,w) + +/* Get the next cluster entry in the chain */ +static cluster_t +_ao_fat_entry_read(cluster_t cluster) +{ + sector_t sector; + cluster_t offset; + uint8_t *buf; + cluster_t ret; + + if (!_ao_fat_cluster_valid(cluster)) + return 0xfffffff7; + + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> (SECTOR_SHIFT); + offset = cluster & SECTOR_MASK; + buf = _ao_fat_sector_get(fat_start + sector); + if (!buf) + return 0; + + if (fat32) { + ret = get_u32(buf + offset); + ret &= 0xfffffff; + } else { + ret = get_u16(buf + offset); + if (AO_FAT_IS_LAST_CLUSTER16(ret)) + ret |= 0xfff0000; + } + _ao_fat_sector_put(buf, 0); + return ret; +} + +/* Replace the referenced cluster entry in the chain with + * 'new_value'. Return the previous value. + */ +static cluster_t +_ao_fat_entry_replace(cluster_t cluster, cluster_t new_value) +{ + sector_t sector; + cluster_offset_t offset; + uint8_t *buf; + cluster_t ret; + cluster_t old_value; + uint8_t fat; + + if (!_ao_fat_cluster_valid(cluster)) + return 0xfffffff7; + + /* Convert from cluster index to byte index */ + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> SECTOR_SHIFT; + offset = cluster & SECTOR_MASK; + + new_value &= 0xfffffff; + for (fat = 0; fat < number_fat; fat++) { + buf = _ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector); + if (!buf) + return 0; + if (fat32) { + old_value = get_u32(buf + offset); + put_u32(buf + offset, new_value | (old_value & 0xf0000000)); + if (fat == 0) { + ret = old_value & 0xfffffff; + + /* Track the free count if it wasn't marked + * invalid when we mounted the file system + */ + if (free_count != 0xffffffff) { + if (new_value && !ret) { + --free_count; + fsinfo_dirty = 1; + } else if (!new_value && ret) { + ++free_count; + fsinfo_dirty = 1; + } + } + } + } else { + if (fat == 0) { + ret = get_u16(buf + offset); + if (AO_FAT_IS_LAST_CLUSTER16(ret)) + ret |= 0xfff0000; + } + put_u16(buf + offset, new_value); + } + _ao_fat_sector_put(buf, 1); + } + return ret; + +} + +/* + * Walk a cluster chain and mark + * all of them as free + */ +static void +_ao_fat_free_cluster_chain(cluster_t cluster) +{ + while (_ao_fat_cluster_valid(cluster)) { + if (cluster < next_free) { + next_free = cluster; + fsinfo_dirty = 1; + } + cluster = _ao_fat_entry_replace(cluster, 0x00000000); + } +} + +/* + * _ao_fat_cluster_seek + * + * The basic file system operation -- map a file cluster index to a + * partition cluster number. Done by computing the cluster number and + * then walking that many clusters from the first cluster. Returns + * 0xffff if we walk off the end of the file or the cluster chain + * is damaged somehow + */ +static cluster_t +_ao_fat_cluster_seek(cluster_t cluster, cluster_t distance) +{ + while (distance) { + cluster = _ao_fat_entry_read(cluster); + if (!_ao_fat_cluster_valid(cluster)) + break; + distance--; + } + return cluster; +} + +/* + * _ao_fat_cluster_set_size + * + * Set the number of clusters in the specified chain, + * freeing extra ones or alocating new ones as needed + * + * Returns AO_FAT_BAD_CLUSTER on allocation failure + */ + +static cluster_t +_ao_fat_cluster_set_size(cluster_t first_cluster, cluster_t size) +{ + cluster_t have; + cluster_t last_cluster; + cluster_t next_cluster; + + /* Walk the cluster chain to the + * spot where it needs to change. That + * will either be the end of the chain (in case it needs to grow), + * or after the desired number of clusters, in which case it needs to shrink + */ + next_cluster = first_cluster; + last_cluster = 0; + DBG("\tclusters:"); + for (have = 0; have < size; have++) { + DBG(" %08x", next_cluster); + if (!_ao_fat_cluster_valid(next_cluster)) + break; + last_cluster = next_cluster; + next_cluster = _ao_fat_entry_read(next_cluster); + } + DBG("\n"); + + /* At this point, last_cluster points to the last valid + * cluster in the file, if any. That's the spot in the FAT + * that needs to be rewritten, either to truncate the file by + * writing an END marker, or to extend the file by writing + * more clusters. next_cluster will contain the value of the + * FAT at last_cluster. + * + * If this is at the head of the cluster chain, then + * last_cluster will be zero and next_cluster will + * be the first cluster in the chain. + */ + if (have == size) { + /* The file is large enough, truncate as needed */ + if (_ao_fat_cluster_valid(next_cluster)) { + DBG("truncate between %08x and %08x\n", last_cluster, next_cluster); + if (last_cluster) + /* + * Otherwise, rewrite the last cluster + * in the chain with a LAST marker + */ + (void) _ao_fat_entry_replace(last_cluster, + AO_FAT_LAST_CLUSTER); + else + /* + * If the file is getting erased, then + * rewrite the directory entry cluster + * value + */ + first_cluster = 0; + + /* Clear the remaining clusters in the chain */ + _ao_fat_free_cluster_chain(next_cluster); + + /* The file system is no longer full (if it was) */ + filesystem_full = 0; + } else { + DBG("unchanged FAT chain\n"); + /* The chain is already the right length, don't mess with it */ + ; + } + } else { + cluster_t need; + cluster_t free; + + if (filesystem_full) + return AO_FAT_BAD_CLUSTER; + + /* Set next_free if it has wrapped or wasn't set before */ + if (next_free < 2 || number_cluster <= next_free) { + next_free = 2; + fsinfo_dirty = 1; + } + + /* See if there are enough free clusters in the file system */ + need = size - have; + +#define loop_cluster for (free = next_free; need > 0;) +#define next_cluster \ + if (++free == number_cluster) \ + free = 2; \ + if (free == next_free) \ + break; \ + + loop_cluster { + if (!_ao_fat_entry_read(free)) + need--; + next_cluster; + } + + /* Still need some, tell the user that we've failed */ + if (need) { + filesystem_full = 1; + return AO_FAT_BAD_CLUSTER; + } + + /* Now go allocate those clusters and + * thread them onto the chain + */ + need = size - have; + loop_cluster { + if (_ao_fat_entry_read(free) == 0) { + next_free = free + 1; + if (next_free >= number_cluster) + next_free = 2; + fsinfo_dirty = 1; + DBG("\tadd cluster. old %08x new %08x\n", last_cluster, free); + if (last_cluster) + _ao_fat_entry_replace(last_cluster, free); + else + first_cluster = free; + last_cluster = free; + need--; + } + next_cluster; + } +#undef loop_cluster +#undef next_cluster + DBG("\tlast cluster %08x\n", last_cluster); + /* Mark the new end of the chain */ + _ao_fat_entry_replace(last_cluster, AO_FAT_LAST_CLUSTER); + } + + DBG("\tfirst cluster %08x\n", first_cluster); + return first_cluster; +} + +/* Start using a root directory entry */ +static uint8_t * +_ao_fat_root_get(dirent_t e) +{ + offset_t byte = e * DIRENT_SIZE; + sector_t sector = byte >> SECTOR_SHIFT; + cluster_offset_t offset = byte & SECTOR_MASK; + uint8_t *buf; + + if (fat32) { + cluster_t cluster_distance = sector / sectors_per_cluster; + sector_t sector_index = sector % sectors_per_cluster; + cluster_t cluster = _ao_fat_cluster_seek(root_cluster, cluster_distance); + + if (_ao_fat_cluster_valid(cluster)) + sector = data_start + (cluster-2) * sectors_per_cluster + sector_index; + else + return NULL; + } else { + if (e >= root_entries) + return NULL; + sector = root_start + sector; + } + + buf = _ao_fat_sector_get(sector); + if (!buf) + return NULL; + return buf + offset; +} + +/* Finish using a root directory entry, 'w' is 1 if modified */ +static void +_ao_fat_root_put(uint8_t *root, dirent_t e, uint8_t write) +{ + cluster_offset_t offset = ((e * DIRENT_SIZE) & SECTOR_MASK); + uint8_t *buf = root - offset; + + _ao_fat_sector_put(buf, write); +} + +/* + * _ao_fat_root_extend + * + * On FAT32, make the root directory at least 'ents' entries long + */ +static int8_t +_ao_fat_root_extend(dirent_t ents) +{ + offset_t byte_size; + cluster_t cluster_size; + if (!fat32) + return 0; + + byte_size = (ents + 1) * 0x20; + cluster_size = (byte_size + bytes_per_cluster - 1) / bytes_per_cluster; + if (_ao_fat_cluster_set_size(root_cluster, cluster_size) != AO_FAT_BAD_CLUSTER) + return 1; + return 0; +} + +/* + * _ao_fat_setup_partition + * + * Load the boot block and find the first partition + */ +static uint8_t +_ao_fat_setup_partition(void) +{ + uint8_t *mbr; + uint8_t *partition; + uint32_t partition_size; + + mbr = ao_bufio_get(0); + if (!mbr) + return AO_FAT_FILESYSTEM_MBR_READ_FAILURE; + + /* Check the signature */ + if (mbr[0x1fe] != 0x55 || mbr[0x1ff] != 0xaa) { + DBG ("Invalid MBR signature %02x %02x\n", + mbr[0x1fe], mbr[0x1ff]); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE; + } + + /* Check to see if it's actually a boot block, in which + * case it's presumably not a paritioned device + */ + + if (mbr[0] == 0xeb) { + partition_start = 0; + partition_size = get_u16(mbr + 0x13); + if (partition_size == 0) + partition_size = get_u32(mbr + 0x20); + } else { + /* Just use the first partition */ + partition = &mbr[0x1be]; + + partition_type = partition[4]; + switch (partition_type) { + case 4: /* FAT16 up to 32M */ + case 6: /* FAT16 over 32M */ + break; + case 0x0b: /* FAT32 up to 2047GB */ + case 0x0c: /* FAT32 LBA */ + break; + default: + DBG ("Invalid partition type %02x\n", partition_type); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE; + } + + partition_start = get_u32(partition+8); + partition_size = get_u32(partition+12); + if (partition_size == 0) { + DBG ("Zero-sized partition\n"); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION; + } + } + partition_end = partition_start + partition_size; + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_SUCCESS; +} + +static uint8_t +_ao_fat_setup_fs(void) +{ + uint8_t *boot = _ao_fat_sector_get(0); + uint32_t data_sectors; + + if (!boot) + return AO_FAT_FILESYSTEM_BOOT_READ_FAILURE; + + /* Check the signature */ + if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) { + DBG ("Invalid BOOT signature %02x %02x\n", + boot[0x1fe], boot[0x1ff]); + _ao_fat_sector_put(boot, 0); + return AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE; + } + + /* Check the sector size */ + if (get_u16(boot + 0xb) != SECTOR_SIZE) { + DBG ("Invalid sector size %d\n", + get_u16(boot + 0xb)); + _ao_fat_sector_put(boot, 0); + return AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE; + } + + sectors_per_cluster = boot[0xd]; + bytes_per_cluster = sectors_per_cluster << SECTOR_SHIFT; + reserved_sector_count = get_u16(boot+0xe); + number_fat = boot[0x10]; + root_entries = get_u16(boot + 0x11); + sectors_per_fat = get_u16(boot+0x16); + fat32 = 0; + if (sectors_per_fat == 0) { + fat32 = 1; + sectors_per_fat = get_u32(boot+0x24); + root_cluster = get_u32(boot+0x2c); + fsinfo_sector = get_u16(boot + 0x30); + } + _ao_fat_sector_put(boot, 0); + + free_count = 0xffffffff; + next_free = 0; + if (fat32 && fsinfo_sector) { + uint8_t *fsinfo = _ao_fat_sector_get(fsinfo_sector); + + if (fsinfo) { + free_count = get_u32(fsinfo + 0x1e8); + next_free = get_u32(fsinfo + 0x1ec); + _ao_fat_sector_put(fsinfo, 0); + } + } + + fat_start = reserved_sector_count;; + root_start = fat_start + number_fat * sectors_per_fat; + data_start = root_start + ((root_entries * DIRENT_SIZE + SECTOR_MASK) >> SECTOR_SHIFT); + + data_sectors = (partition_end - partition_start) - data_start; + + number_cluster = data_sectors / sectors_per_cluster; + + return AO_FAT_FILESYSTEM_SUCCESS; +} + +/* + * State for an open file + */ + +struct ao_file { + struct ao_fat_dirent *dirent; + offset_t offset; + offset_t cluster_offset; + cluster_t cluster; + uint8_t busy; +}; + +#define AO_FAT_NFILE 8 + +static struct ao_fat_dirent ao_file_dirent[AO_FAT_NFILE]; + +static struct ao_fat_dirent * +_ao_fat_file_dirent_alloc(struct ao_fat_dirent *want) +{ + int8_t d; + struct ao_fat_dirent *free = NULL, *dirent; + + for (d = 0; d < AO_FAT_NFILE; d++) { + + dirent = &ao_file_dirent[d]; + /* See if there's another user of this file already */ + if (want && dirent->name[0] != 0) { + if (dirent->entry == want->entry) + return dirent; + } else { + if (!free) { + free = dirent; + if (!want) + break; + } + } + } + if (free && want) + *free = *want; + return free; +} + +static struct ao_file ao_file_table[AO_FAT_NFILE]; + +static int8_t +_ao_fat_fd_alloc(struct ao_fat_dirent *dirent) +{ + int8_t fd; + + for (fd = 0; fd < AO_FAT_NFILE; fd++) + if (!ao_file_table[fd].busy) { + ao_file_table[fd].dirent = _ao_fat_file_dirent_alloc(dirent); + ao_file_table[fd].busy = 1; + ao_file_table[fd].offset = 0; + ao_file_table[fd].cluster_offset = 0; + ao_file_table[fd].cluster = ao_file_table[fd].dirent->cluster; + + return fd; + } + return -AO_FAT_EMFILE; +} + +static void +_ao_fat_fd_free(int8_t fd) +{ + struct ao_file *file = &ao_file_table[fd]; + struct ao_fat_dirent *dirent = file->dirent; + memset(&ao_file_table[fd], '\0', sizeof (struct ao_file)); + + /* Check and see if another ao_file references the same dirent */ + for (fd = 0; fd < AO_FAT_NFILE; fd++) + if (ao_file_table[fd].dirent == dirent) + return; + memset(dirent, '\0', sizeof (struct ao_fat_dirent)); +} + +static struct ao_file * +_ao_fat_fd_to_file(int8_t fd) +{ + struct ao_file *file; + if (fd < 0 || AO_FAT_NFILE <= fd) + return NULL; + + file = &ao_file_table[fd]; + if (!file->busy) + return NULL; + return file; +} + +static uint8_t ao_filesystem_setup; +static uint8_t ao_filesystem_status; + +static uint8_t +_ao_fat_setup(void) +{ + if (!ao_filesystem_setup) { + + ao_filesystem_setup = 1; + ao_bufio_setup(); + + /* Re-initialize all global state; this will help to allow the + * file system to get swapped someday + */ + partition_type = partition_start = partition_end = 0; + sectors_per_cluster = bytes_per_cluster = reserved_sector_count = 0; + number_fat = root_entries = sectors_per_fat = 0; + number_cluster = fat_start = root_start = data_start = 0; + next_free = filesystem_full = 0; + fat32 = fsinfo_dirty = root_cluster = fsinfo_sector = free_count = 0; + + /* Reset open file table */ + memset(&ao_file_table, '\0', sizeof (ao_file_table)); + + ao_filesystem_status = _ao_fat_setup_partition(); + if (ao_filesystem_status != AO_FAT_FILESYSTEM_SUCCESS) + return ao_filesystem_status; + ao_filesystem_status = _ao_fat_setup_fs(); + if (ao_filesystem_status != AO_FAT_FILESYSTEM_SUCCESS) + return ao_filesystem_status; + } + return ao_filesystem_status; +} + +void +ao_fat_unmount(void) +{ + ao_filesystem_setup = 0; +} + +/* + * Basic file operations + */ + +static uint32_t +_ao_fat_current_sector(struct ao_file *file) +{ + cluster_t cluster_offset; + uint32_t sector_offset; + uint16_t sector_index; + cluster_t cluster; + + DBG("current sector offset %d size %d\n", + file->offset, file->dirent->size); + + if (file->offset > file->dirent->size) { + printf ("file offset %d larger than size %d\n", + file->offset, file->dirent->size); + return 0xffffffff; + } + + sector_offset = file->offset >> SECTOR_SHIFT; + + if (!file->cluster || file->offset < file->cluster_offset) { + file->cluster = file->dirent->cluster; + file->cluster_offset = 0; + DBG("\treset to start of file %08x\n", file->cluster); + } + + if (file->cluster_offset + bytes_per_cluster <= file->offset) { + cluster_t cluster_distance; + + cluster_offset = sector_offset / sectors_per_cluster; + + cluster_distance = cluster_offset - file->cluster_offset / bytes_per_cluster; + + DBG("\tseek forward %d clusters\n", cluster_distance); + cluster = _ao_fat_cluster_seek(file->cluster, cluster_distance); + + if (!_ao_fat_cluster_valid(cluster)) { + printf ("invalid cluster %08x\n", cluster); + return 0xffffffff; + } + file->cluster = cluster; + file->cluster_offset = cluster_offset * bytes_per_cluster; + } + + sector_index = sector_offset % sectors_per_cluster; + DBG("current cluster %08x sector_index %d sector %d\n", + file->cluster, sector_index, + data_start + (uint32_t) (file->cluster-2) * sectors_per_cluster + sector_index); + return data_start + (uint32_t) (file->cluster-2) * sectors_per_cluster + sector_index; +} + +/* + * _ao_fat_invaldate_cluster_offset + * + * When the file size gets shrunk, invalidate + * any file structures referencing clusters beyond that point + */ + +static void +_ao_fat_invalidate_cluster_offset(struct ao_fat_dirent *dirent) +{ + int8_t fd; + struct ao_file *file; + + for (fd = 0; fd < AO_FAT_NFILE; fd++) { + file = &ao_file_table[fd]; + if (!file->busy) + continue; + if (file->dirent == dirent) { + if (file->cluster_offset >= dirent->size) { + file->cluster_offset = 0; + file->cluster = dirent->cluster; + } + } + } +} + + +/* + * _ao_fat_set_size + * + * Set the size of the current file, truncating or extending + * the cluster chain as needed + */ +static int8_t +_ao_fat_set_size(struct ao_file *file, uint32_t size) +{ + uint8_t *dent; + cluster_t first_cluster; + cluster_t have_clusters, need_clusters; + + DBG ("Set size %d\n", size); + if (size == file->dirent->size) { + DBG("\tsize match\n"); + return AO_FAT_SUCCESS; + } + + first_cluster = file->dirent->cluster; + have_clusters = (file->dirent->size + bytes_per_cluster - 1) / bytes_per_cluster; + need_clusters = (size + bytes_per_cluster - 1) / bytes_per_cluster; + + DBG ("\tfirst cluster %08x have %d need %d\n", first_cluster, have_clusters, need_clusters); + if (have_clusters != need_clusters) { + if (file->cluster && size > file->cluster_offset) { + cluster_t offset_clusters = (file->cluster_offset + bytes_per_cluster) / bytes_per_cluster; + cluster_t extra_clusters = need_clusters - offset_clusters; + cluster_t next_cluster; + + DBG ("\tset size relative offset_clusters %d extra_clusters %d\n", + offset_clusters, extra_clusters); + + /* Need one more to account for file->cluster, which we already have */ + next_cluster = _ao_fat_cluster_set_size(file->cluster, extra_clusters + 1); + if (next_cluster == AO_FAT_BAD_CLUSTER) + return -AO_FAT_ENOSPC; + } else { + DBG ("\tset size absolute need_clusters %d\n", need_clusters); + first_cluster = _ao_fat_cluster_set_size(first_cluster, need_clusters); + + if (first_cluster == AO_FAT_BAD_CLUSTER) + return -AO_FAT_ENOSPC; + } + } + + DBG ("\tupdate directory size\n"); + /* Update the directory entry */ + dent = _ao_fat_root_get(file->dirent->entry); + if (!dent) { + printf ("dent update failed\n"); + return -AO_FAT_EIO; + } + put_u32(dent + 0x1c, size); + put_u16(dent + 0x1a, first_cluster); + if (fat32) + put_u16(dent + 0x14, first_cluster >> 16); + _ao_fat_root_put(dent, file->dirent->entry, 1); + + file->dirent->size = size; + file->dirent->cluster = first_cluster; + if (have_clusters > need_clusters) + _ao_fat_invalidate_cluster_offset(file->dirent); + DBG ("set size done\n"); + return AO_FAT_SUCCESS; +} + +/* + * _ao_fat_root_init + * + * Initialize a root directory entry + */ +static void +_ao_fat_root_init(uint8_t *dent, char name[11], uint8_t attr) +{ + memset(dent, '\0', 0x20); + memmove(dent, name, 11); + + dent[0x0b] = 0x00; + dent[0x0c] = 0x00; + dent[0x0d] = 0x00; + + /* XXX fix time */ + put_u16(dent + 0x0e, 0); + /* XXX fix date */ + put_u16(dent + 0x10, 0); + /* XXX fix date */ + put_u16(dent + 0x12, 0); + + /* XXX fix time */ + put_u16(dent + 0x16, 0); + /* XXX fix date */ + put_u16(dent + 0x18, 0); + + /* cluster number */ + /* Low cluster bytes */ + put_u16(dent + 0x1a, 0); + /* FAT32 high cluster bytes */ + put_u16(dent + 0x14, 0); + + /* size */ + put_u32(dent + 0x1c, 0); +} + + +static void +_ao_fat_dirent_init(struct ao_fat_dirent *dirent, uint8_t *dent, uint16_t entry) +{ + memcpy(dirent->name, dent + 0x00, 11); + dirent->attr = dent[0x0b]; + dirent->size = get_u32(dent+0x1c); + dirent->cluster = get_u16(dent+0x1a); + if (fat32) + dirent->cluster |= (cluster_t) get_u16(dent + 0x14) << 16; + dirent->entry = entry; +} + +/* + * _ao_fat_flush_fsinfo + * + * Write out any fsinfo changes to disk + */ + +static void +_ao_fat_flush_fsinfo(void) +{ + uint8_t *fsinfo; + + if (!fat32) + return; + + if (!fsinfo_dirty) + return; + fsinfo_dirty = 0; + if (!fsinfo_sector) + return; + + fsinfo = _ao_fat_sector_get(fsinfo_sector); + if (fsinfo) { + put_u32(fsinfo + 0x1e8, free_count); + put_u32(fsinfo + 0x1ec, next_free); + _ao_fat_sector_put(fsinfo, 1); + } +} + +/* + * Public API + */ + +/* + * ao_fat_sync + * + * Flush any pending I/O to storage + */ + +static void +_ao_fat_sync(void) +{ + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return; + _ao_fat_flush_fsinfo(); + ao_bufio_flush(); +} + +void +ao_fat_sync(void) +{ + ao_mutex_get(&ao_fat_mutex); + _ao_fat_sync(); + ao_mutex_put(&ao_fat_mutex); +} + +/* + * ao_fat_full + * + * Returns TRUE if the filesystem cannot take + * more data + */ + +int8_t +ao_fat_full(void) +{ + ao_mutex_get(&ao_fat_mutex); + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) { + ao_mutex_put(&ao_fat_mutex); + return 1; + } + ao_mutex_put(&ao_fat_mutex); + return filesystem_full; +} + +static int8_t +_ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent) +{ + uint8_t *dent; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + for (;;) { + dent = _ao_fat_root_get(*entry); + if (!dent) + return -AO_FAT_EDIREOF; + + if (dent[0] == AO_FAT_DENT_END) { + _ao_fat_root_put(dent, *entry, 0); + return -AO_FAT_EDIREOF; + } + if (dent[0] != AO_FAT_DENT_EMPTY && (dent[0xb] & 0xf) != 0xf) { + _ao_fat_dirent_init(dirent, dent, *entry); + _ao_fat_root_put(dent, *entry, 0); + (*entry)++; + return AO_FAT_SUCCESS; + } + _ao_fat_root_put(dent, *entry, 0); + (*entry)++; + } +} + +int8_t +ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_readdir(entry, dirent); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_open + * + * Open an existing file. + */ +static int8_t +_ao_fat_open(char name[11], uint8_t mode) +{ + uint16_t entry = 0; + static struct ao_fat_dirent dirent; + int8_t status; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + for (;;) { + status = _ao_fat_readdir(&entry, &dirent); + if (status < 0) { + if (status == -AO_FAT_EDIREOF) + return -AO_FAT_ENOENT; + return status; + } + if (!memcmp(name, dirent.name, 11)) { + if (AO_FAT_IS_DIR(dirent.attr)) + return -AO_FAT_EISDIR; + if (!AO_FAT_IS_FILE(dirent.attr)) + return -AO_FAT_EPERM; + if (mode > AO_FAT_OPEN_READ && (dirent.attr & AO_FAT_FILE_READ_ONLY)) + return -AO_FAT_EACCESS; + return _ao_fat_fd_alloc(&dirent); + } + } + return -AO_FAT_ENOENT; +} + +int8_t +ao_fat_open(char name[11], uint8_t mode) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_open(name, mode); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_close + * + * Close the currently open file + */ +static int8_t +_ao_fat_close(int8_t fd) +{ + struct ao_file *file; + + file = _ao_fat_fd_to_file(fd); + if (!file) + return -AO_FAT_EBADF; + + _ao_fat_fd_free(fd); + _ao_fat_sync(); + return AO_FAT_SUCCESS; +} + +int8_t +ao_fat_close(int8_t fd) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_close(fd); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_creat + * + * Open and truncate an existing file or + * create a new file + */ + +static int8_t +_ao_fat_creat(char name[11]) +{ + uint16_t entry; + int8_t fd; + int8_t status; + uint8_t *dent; + struct ao_file *file; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + fd = _ao_fat_open(name, AO_FAT_OPEN_WRITE); + if (fd >= 0) { + file = &ao_file_table[fd]; + status = _ao_fat_set_size(file, 0); + if (status < 0) { + _ao_fat_close(fd); + fd = status; + } + } else { + if (fd == -AO_FAT_ENOENT) { + entry = 0; + for (;;) { + dent = _ao_fat_root_get(entry); + if (!dent) { + + if (_ao_fat_root_extend(entry)) + continue; + fd = -AO_FAT_ENOSPC; + break; + } + if (dent[0] == AO_FAT_DENT_EMPTY || dent[0] == AO_FAT_DENT_END) { + fd = _ao_fat_fd_alloc(NULL); + if (fd < 0) { + _ao_fat_root_put(dent, entry, 0); + break; + } + + file = &ao_file_table[fd]; + /* Initialize the dent */ + _ao_fat_root_init(dent, name, AO_FAT_FILE_REGULAR); + + /* Now initialize the dirent from the dent */ + _ao_fat_dirent_init(file->dirent, dent, entry); + + /* And write the dent to storage */ + _ao_fat_root_put(dent, entry, 1); + + status = -AO_FAT_SUCCESS; + break; + } else { + _ao_fat_root_put(dent, entry, 0); + } + entry++; + } + } + } + return fd; +} + +int8_t +ao_fat_creat(char name[11]) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_creat(name); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_map_current + * + * Map the sector pointed at by the current file offset + */ + +static void * +ao_fat_map_current(struct ao_file *file, int len, cluster_offset_t *offsetp, cluster_offset_t *this_time) +{ + cluster_offset_t offset; + sector_t sector; + void *buf; + + offset = file->offset & SECTOR_MASK; + sector = _ao_fat_current_sector(file); + if (sector == 0xffffffff) { + return NULL; + } + buf = _ao_fat_sector_get(sector); + if (!buf) + return NULL; + if (offset + len < SECTOR_SIZE) + *this_time = len; + else + *this_time = SECTOR_SIZE - offset; + *offsetp = offset; + return buf; +} + +/* + * ao_fat_read + * + * Read from the file + */ +int +ao_fat_read(int8_t fd, void *dst, int len) +{ + uint8_t *dst_b = dst; + cluster_offset_t this_time; + cluster_offset_t offset; + uint8_t *buf; + int ret = 0; + struct ao_file *file; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + if (file->offset + len > file->dirent->size) + len = file->dirent->size - file->offset; + + if (len < 0) + len = 0; + + while (len) { + buf = ao_fat_map_current(file, len, &offset, &this_time); + if (!buf) { + ret = -AO_FAT_EIO; + break; + } + memcpy(dst_b, buf + offset, this_time); + _ao_fat_sector_put(buf, 0); + + ret += this_time; + len -= this_time; + dst_b += this_time; + file->offset = file->offset + this_time; + } +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_write + * + * Write to the file, extended as necessary + */ +int +ao_fat_write(int8_t fd, void *src, int len) +{ + uint8_t *src_b = src; + cluster_offset_t this_time; + cluster_offset_t offset; + uint8_t *buf; + int ret = 0; + struct ao_file *file; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + if (file->offset + len > file->dirent->size) { + ret = _ao_fat_set_size(file, file->offset + len); + if (ret < 0) + goto done; + } + + while (len) { + buf = ao_fat_map_current(file, len, &offset, &this_time); + if (!buf) { + ret = -AO_FAT_EIO; + break; + } + memcpy(buf + offset, src_b, this_time); + _ao_fat_sector_put(buf, 1); + + ret += this_time; + len -= this_time; + src_b += this_time; + file->offset = file->offset + this_time; + } +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_seek + * + * Set the position for the next I/O operation + * Note that this doesn't actually change the size + * of the file if the requested position is beyond + * the current file length, that would take a future + * write + */ +int32_t +ao_fat_seek(int8_t fd, int32_t pos, uint8_t whence) +{ + offset_t new_offset; + struct ao_file *file; + int32_t ret; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + new_offset = file->offset; + switch (whence) { + case AO_FAT_SEEK_SET: + new_offset = pos; + break; + case AO_FAT_SEEK_CUR: + new_offset += pos; + break; + case AO_FAT_SEEK_END: + new_offset = file->dirent->size + pos; + break; + } + ret = file->offset = new_offset; +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_unlink + * + * Remove a file from the directory, marking + * all clusters as free + */ +int8_t +ao_fat_unlink(char name[11]) +{ + uint16_t entry = 0; + static struct ao_fat_dirent dirent; + int8_t ret; + + ao_mutex_get(&ao_fat_mutex); + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) { + ret = -AO_FAT_EIO; + goto done; + } + + while (ao_fat_readdir(&entry, &dirent)) { + if (memcmp(name, dirent.name, 11) == 0) { + uint8_t *next; + uint8_t *ent; + uint8_t delete; + + if (AO_FAT_IS_DIR(dirent.attr)) { + ret = -AO_FAT_EISDIR; + goto done; + } + if (!AO_FAT_IS_FILE(dirent.attr)) { + ret = -AO_FAT_EPERM; + goto done; + } + + _ao_fat_free_cluster_chain(dirent.cluster); + next = _ao_fat_root_get(dirent.entry + 1); + if (next && next[0] != AO_FAT_DENT_END) + delete = AO_FAT_DENT_EMPTY; + else + delete = AO_FAT_DENT_END; + if (next) + _ao_fat_root_put(next, dirent.entry + 1, 0); + ent = _ao_fat_root_get(dirent.entry); + if (ent) { + memset(ent, '\0', DIRENT_SIZE); + *ent = delete; + _ao_fat_root_put(ent, dirent.entry, 1); + } + ao_bufio_flush(); + ret = AO_FAT_SUCCESS; + goto done; + } + } + ret = -AO_FAT_ENOENT; +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +int8_t +ao_fat_rename(char old[11], char new[11]) +{ + return -AO_FAT_EIO; +} + +#if FAT_COMMANDS + +static const char *filesystem_errors[] = { + [AO_FAT_FILESYSTEM_SUCCESS] = "FAT file system operating normally", + [AO_FAT_FILESYSTEM_MBR_READ_FAILURE] = "MBR media read error", + [AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE] = "MBR signature invalid", + [AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE] = "Unsupported paritition type", + [AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION] = "Partition has zero sectors", + [AO_FAT_FILESYSTEM_BOOT_READ_FAILURE] = "Boot block media read error", + [AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE] = "Boot block signature invalid", + [AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE] = "Sector size not 512", +}; + +static void +ao_fat_mbr_cmd(void) +{ + uint8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_setup(); + if (status == AO_FAT_FILESYSTEM_SUCCESS) { + printf ("partition type: %02x\n", partition_type); + printf ("partition start: %08x\n", partition_start); + + printf ("partition end: %08x\n", partition_end); + + printf ("fat32: %d\n", fat32); + printf ("sectors per cluster %d\n", sectors_per_cluster); + printf ("reserved sectors %d\n", reserved_sector_count); + printf ("number of FATs %d\n", number_fat); + printf ("root entries %d\n", root_entries); + printf ("sectors per fat %d\n", sectors_per_fat); + + printf ("fat start %d\n", fat_start); + printf ("root start %d\n", root_start); + printf ("data start %d\n", data_start); + } else { + printf ("FAT filesystem not available: %s\n", filesystem_errors[status]); + } + ao_mutex_put(&ao_fat_mutex); +} + +struct ao_fat_attr { + uint8_t bit; + char label; +}; + +static const struct ao_fat_attr ao_fat_attr[] = { + { .bit = AO_FAT_FILE_READ_ONLY, .label = 'R' }, + { .bit = AO_FAT_FILE_HIDDEN, .label = 'H' }, + { .bit = AO_FAT_FILE_SYSTEM, .label = 'S' }, + { .bit = AO_FAT_FILE_VOLUME_LABEL, .label = 'V' }, + { .bit = AO_FAT_FILE_DIRECTORY, .label = 'D' }, + { .bit = AO_FAT_FILE_ARCHIVE, .label = 'A' }, +}; + +#define NUM_FAT_ATTR (sizeof (ao_fat_attr) / sizeof (ao_fat_attr[0])) + +static void +ao_fat_list_cmd(void) +{ + uint16_t entry = 0; + static struct ao_fat_dirent dirent; + int i; + int8_t status; + + while ((status = ao_fat_readdir(&entry, &dirent)) == AO_FAT_SUCCESS) { + for (i = 0; i < 8; i++) + putchar(dirent.name[i]); + putchar('.'); + for (; i < 11; i++) + putchar(dirent.name[i]); + for (i = 0; i < NUM_FAT_ATTR; i++) + putchar (dirent.attr & ao_fat_attr[i].bit ? ao_fat_attr[i].label : ' '); + printf (" @%08x %d\n", dirent.cluster, dirent.size); + } + if (status != -AO_FAT_EDIREOF) + printf ("readdir failed: %d\n", status); +} + +static uint8_t +ao_fat_parse_name(char name[11]) +{ + uint8_t c; + + name[0] = '\0'; + ao_cmd_white(); + c = 0; + while (ao_cmd_lex_c != '\n') { + if (ao_cmd_lex_c == '.') { + for (; c < 8; c++) + name[c] = ' '; + } else { + if (c < 11) + name[c++] = ao_cmd_lex_c; + } + ao_cmd_lex(); + } + while (c < 11) + name[c++] = ' '; +} + +static void +ao_fat_dump_cmd(void) +{ + static char name[11]; + int8_t fd; + int cnt, i; + static char buf[32]; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_open(name, AO_FAT_OPEN_READ); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + while ((cnt = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + for (i = 0; i < cnt; i++) + putchar(buf[i]); + } + ao_fat_close(fd); +} + +static void +ao_fat_write_cmd(void) +{ + static char name[11]; + int8_t fd; + int cnt, i; + static char buf[64]; + char c; + int status; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_creat(name); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + flush(); + while ((c = getchar()) != 4) { + if (c == '\r') c = '\n'; + if (ao_echo()) { + if (c == '\n') putchar ('\r'); + putchar(c); flush(); + } + status = ao_fat_write(fd, &c, 1); + if (status != 1) { + printf ("Write failure %d\n", status); + break; + } + } + ao_fat_close(fd); +} + +static void +put32(uint32_t a) +{ + ao_cmd_put16(a >> 16); + ao_cmd_put16(a); +} + +static void +ao_fat_hexdump_cmd(void) +{ + char name[11]; + int8_t fd; + int cnt, i; + char buf[8]; + uint32_t addr; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_open(name, AO_FAT_OPEN_READ); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + addr = 0; + while ((cnt = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + put32(addr); + for (i = 0; i < cnt; i++) { + putchar(' '); + ao_cmd_put8(buf[i]); + } + putchar('\n'); + addr += cnt; + } + ao_fat_close(fd); +} + +static const struct ao_cmds ao_fat_cmds[] = { + { ao_fat_mbr_cmd, "M\0Show FAT MBR and other info" }, + { ao_fat_list_cmd, "F\0List FAT directory" }, + { ao_fat_dump_cmd, "D <name>\0Dump FAT file" }, + { ao_fat_write_cmd, "W <name>\0Write FAT file (end with ^D)" }, + { ao_fat_hexdump_cmd, "H <name>\0HEX dump FAT file" }, + { 0, NULL }, +}; + +#endif + +void +ao_fat_init(void) +{ + ao_bufio_init(); + ao_cmd_register(&ao_fat_cmds[0]); +} diff --git a/src/drivers/ao_fat.h b/src/drivers/ao_fat.h new file mode 100644 index 00000000..01435363 --- /dev/null +++ b/src/drivers/ao_fat.h @@ -0,0 +1,141 @@ +/* + * Copyright © 2013 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_FAT_H_ +#define _AO_FAT_H_ + +void +ao_fat_init(void); + +#define AO_FAT_FILE_REGULAR 0x00 +#define AO_FAT_FILE_READ_ONLY 0x01 +#define AO_FAT_FILE_HIDDEN 0x02 +#define AO_FAT_FILE_SYSTEM 0x04 +#define AO_FAT_FILE_VOLUME_LABEL 0x08 +#define AO_FAT_FILE_DIRECTORY 0x10 +#define AO_FAT_FILE_ARCHIVE 0x20 + +#define AO_FAT_DENT_EMPTY 0xe5 +#define AO_FAT_DENT_END 0x00 + +#define AO_FAT_IS_FILE(attr) (((attr) & (AO_FAT_FILE_VOLUME_LABEL|AO_FAT_FILE_DIRECTORY)) == 0) +#define AO_FAT_IS_DIR(attr) (((attr) & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == AO_FAT_FILE_DIRECTORY) + +/* API error codes */ +#define AO_FAT_SUCCESS 0 +#define AO_FAT_EPERM 1 +#define AO_FAT_ENOENT 2 +#define AO_FAT_EIO 4 +#define AO_FAT_EBADF 9 +#define AO_FAT_EACCESS 13 +#define AO_FAT_EEXIST 17 +#define AO_FAT_ENOTDIR 20 +#define AO_FAT_EISDIR 21 +#define AO_FAT_EMFILE 24 +#define AO_FAT_EFBIG 27 +#define AO_FAT_ENOSPC 28 +#define AO_FAT_EDIREOF 29 + +/* ao_fat_setup return values */ +#define AO_FAT_FILESYSTEM_SUCCESS 0 +#define AO_FAT_FILESYSTEM_MBR_READ_FAILURE 1 +#define AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE 2 +#define AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE 3 +#define AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION 4 + +#define AO_FAT_FILESYSTEM_BOOT_READ_FAILURE 5 +#define AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE 6 +#define AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE 7 + +void +ao_fat_sync(void); + +void +ao_fat_unmount(void); + +int8_t +ao_fat_full(void); + +int8_t +ao_fat_open(char name[11], uint8_t mode); + +#define AO_FAT_OPEN_READ 0 +#define AO_FAT_OPEN_WRITE 1 +#define AO_FAT_OPEN_RW 2 + +int8_t +ao_fat_creat(char name[11]); + +int8_t +ao_fat_close(int8_t fd); + +int +ao_fat_read(int8_t fd, void *dest, int len); + +int +ao_fat_write(int8_t fd, void *src, int len); + +#define AO_FAT_SEEK_SET 0 +#define AO_FAT_SEEK_CUR 1 +#define AO_FAT_SEEK_END 2 + +int32_t +ao_fat_seek(int8_t fd, int32_t pos, uint8_t whence); + +int8_t +ao_fat_unlink(char name[11]); + +int8_t +ao_fat_rename(char old[11], char new[11]); + +/* + * Byte offset within a file. Supports files up to 2GB in size + */ +typedef int32_t ao_fat_offset_t; + +/* + * Cluster index in partition data space + */ +typedef uint32_t ao_fat_cluster_t; + +/* + * Sector offset within partition + */ +typedef uint32_t ao_fat_sector_t; + +/* + * Index within the root directory + */ +typedef uint16_t ao_fat_dirent_t; + +/* + * Offset within a cluster (or sector) + */ +typedef uint16_t ao_fat_cluster_offset_t; + +struct ao_fat_dirent { + char name[11]; + uint8_t attr; + uint32_t size; + ao_fat_cluster_t cluster; + uint16_t entry; +}; + +int8_t +ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent); + +#endif /* _AO_FAT_H_ */ diff --git a/src/drivers/ao_gps_skytraq.c b/src/drivers/ao_gps_skytraq.c index d80da97c..d2f67e6b 100644 --- a/src/drivers/ao_gps_skytraq.c +++ b/src/drivers/ao_gps_skytraq.c @@ -21,6 +21,7 @@ #ifndef ao_gps_getchar #define ao_gps_getchar ao_serial1_getchar +#define ao_gps_fifo ao_serial1_rx_fifo #endif #ifndef ao_gps_putchar @@ -453,6 +454,8 @@ ao_gps_nmea_parse(void) } } +static uint8_t ao_gps_updating; + void ao_gps(void) __reentrant { @@ -468,6 +471,13 @@ ao_gps(void) __reentrant if (ao_gps_getchar() == '$') { ao_gps_nmea_parse(); } +#ifndef AO_GPS_TEST + while (ao_gps_updating) { + ao_usb_putchar(ao_gps_getchar()); + if (ao_fifo_empty(ao_gps_fifo)) + flush(); + } +#endif } } @@ -492,8 +502,38 @@ gps_dump(void) __reentrant ao_mutex_put(&ao_gps_mutex); } +static __code uint8_t ao_gps_115200[] = { + SKYTRAQ_MSG_3(5,0,5,0) /* Set to 115200 baud */ +}; + +static void +ao_gps_set_speed_delay(uint8_t speed) { + ao_delay(AO_MS_TO_TICKS(500)); + ao_gps_set_speed(speed); + ao_delay(AO_MS_TO_TICKS(500)); +} + +static void +gps_update(void) __reentrant +{ + ao_gps_updating = 1; + ao_task_minimize_latency = 1; +#if HAS_ADC + ao_timer_set_adc_interval(0); +#endif + ao_skytraq_sendstruct(ao_gps_115200); + ao_gps_set_speed_delay(AO_SERIAL_SPEED_4800); + ao_skytraq_sendstruct(ao_gps_115200); + ao_gps_set_speed_delay(AO_SERIAL_SPEED_115200); + + /* It's a binary protocol; abandon attempts to escape */ + for (;;) + ao_gps_putchar(ao_usb_getchar()); +} + __code struct ao_cmds ao_gps_cmds[] = { { gps_dump, "g\0Display GPS" }, + { gps_update, "U\0Update GPS firmware" }, { 0, NULL }, }; diff --git a/src/drivers/ao_gps_ublox.c b/src/drivers/ao_gps_ublox.c new file mode 100644 index 00000000..22300df3 --- /dev/null +++ b/src/drivers/ao_gps_ublox.c @@ -0,0 +1,704 @@ +/* + * Copyright © 2013 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 + +#include "ao_gps_ublox.h" + +__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$PUBX,41,1,3,1,57600,0*2d\r\n"; + +const char ao_gps_config[] = { + +}; + +struct ao_ublox_cksum { + uint8_t a, b; +}; + +static __pdata struct ao_ublox_cksum ao_ublox_cksum; +static __pdata uint16_t ao_ublox_len; + +#ifndef ao_ublox_getchar +#define ao_ublox_getchar ao_serial1_getchar +#define ao_ublox_putchar ao_serial1_putchar +#define ao_ublox_set_speed ao_serial1_set_speed +#endif + +#define ao_ublox_byte() ((uint8_t) ao_ublox_getchar()) + +static inline void add_cksum(struct ao_ublox_cksum *cksum, uint8_t c) +{ + cksum->a += c; + cksum->b += cksum->a; +} + +static void ao_ublox_init_cksum(void) +{ + ao_ublox_cksum.a = ao_ublox_cksum.b = 0; +} + +static void ao_ublox_put_u8(uint8_t c) +{ + add_cksum(&ao_ublox_cksum, c); + ao_ublox_putchar(c); +} + +static void ao_ublox_put_i8(int8_t c) +{ + ao_ublox_put_u8((uint8_t) c); +} + +static void ao_ublox_put_u16(uint16_t c) +{ + ao_ublox_put_u8(c); + ao_ublox_put_u8(c>>8); +} + +#if 0 +static void ao_ublox_put_i16(int16_t c) +{ + ao_ublox_put_u16((uint16_t) c); +} +#endif + +static void ao_ublox_put_u32(uint32_t c) +{ + ao_ublox_put_u8(c); + ao_ublox_put_u8(c>>8); + ao_ublox_put_u8(c>>16); + ao_ublox_put_u8(c>>24); +} + +static void ao_ublox_put_i32(int32_t c) +{ + ao_ublox_put_u32((uint32_t) c); +} + +static uint8_t header_byte(void) +{ + uint8_t c = ao_ublox_byte(); + add_cksum(&ao_ublox_cksum, c); + return c; +} + +static uint8_t data_byte(void) +{ + --ao_ublox_len; + return header_byte(); +} + +static char __xdata *ublox_target; + +static void ublox_u16(uint8_t offset) +{ + uint16_t __xdata *ptr = (uint16_t __xdata *) (ublox_target + offset); + uint16_t val; + + val = data_byte(); + val |= data_byte () << 8; + *ptr = val; +} + +static void ublox_u8(uint8_t offset) +{ + uint8_t __xdata *ptr = (uint8_t __xdata *) (ublox_target + offset); + uint8_t val; + + val = data_byte (); + *ptr = val; +} + +static void ublox_u32(uint8_t offset) __reentrant +{ + uint32_t __xdata *ptr = (uint32_t __xdata *) (ublox_target + offset); + uint32_t val; + + val = ((uint32_t) data_byte ()); + val |= ((uint32_t) data_byte ()) << 8; + val |= ((uint32_t) data_byte ()) << 16; + val |= ((uint32_t) data_byte ()) << 24; + *ptr = val; +} + +static void ublox_discard(uint8_t len) +{ + while (len--) + data_byte(); +} + +#define UBLOX_END 0 +#define UBLOX_DISCARD 1 +#define UBLOX_U8 2 +#define UBLOX_U16 3 +#define UBLOX_U32 4 + +struct ublox_packet_parse { + uint8_t type; + uint8_t offset; +}; + +static void +ao_ublox_parse(void __xdata *target, const struct ublox_packet_parse *parse) __reentrant +{ + uint8_t i, offset; + + ublox_target = target; + for (i = 0; ; i++) { + offset = parse[i].offset; + switch (parse[i].type) { + case UBLOX_END: + return; + case UBLOX_DISCARD: + ublox_discard(offset); + break; + case UBLOX_U8: + ublox_u8(offset); + break; + case UBLOX_U16: + ublox_u16(offset); + break; + case UBLOX_U32: + ublox_u32(offset); + break; + } + } +} + +/* + * NAV-DOP message parsing + */ + +static struct nav_dop { + uint16_t pdop; + uint16_t hdop; + uint16_t vdop; +} nav_dop; + +static const struct ublox_packet_parse nav_dop_packet[] = { + { UBLOX_DISCARD, 6 }, /* 0 GPS Millisecond Time of Week, gDOP */ + { UBLOX_U16, offsetof(struct nav_dop, pdop) }, /* 6 pDOP */ + { UBLOX_DISCARD, 2 }, /* 8 tDOP */ + { UBLOX_U16, offsetof(struct nav_dop, vdop) }, /* 10 vDOP */ + { UBLOX_U16, offsetof(struct nav_dop, hdop) }, /* 12 hDOP */ + { UBLOX_DISCARD, 4 }, /* 14 nDOP, eDOP */ + { UBLOX_END, 0 } +}; + +static void +ao_ublox_parse_nav_dop(void) +{ + ao_ublox_parse(&nav_dop, nav_dop_packet); +} + +/* + * NAV-POSLLH message parsing + */ +static struct nav_posllh { + int32_t lat; + int32_t lon; + int32_t alt_msl; +} nav_posllh; + +static const struct ublox_packet_parse nav_posllh_packet[] = { + { UBLOX_DISCARD, 4 }, /* 0 GPS Millisecond Time of Week */ + { UBLOX_U32, offsetof(struct nav_posllh, lon) }, /* 4 Longitude */ + { UBLOX_U32, offsetof(struct nav_posllh, lat) }, /* 8 Latitude */ + { UBLOX_DISCARD, 4 }, /* 12 Height above Ellipsoid */ + { UBLOX_U32, offsetof(struct nav_posllh, alt_msl) }, /* 16 Height above mean sea level */ + { UBLOX_DISCARD, 8 }, /* 20 hAcc, vAcc */ + { UBLOX_END, 0 }, /* 28 */ +}; + +static void +ao_ublox_parse_nav_posllh(void) +{ + ao_ublox_parse(&nav_posllh, nav_posllh_packet); +} + +/* + * NAV-SOL message parsing + */ +static struct nav_sol { + uint8_t gps_fix; + uint8_t flags; + uint8_t nsat; +} nav_sol; + +static const struct ublox_packet_parse nav_sol_packet[] = { + { UBLOX_DISCARD, 10 }, /* 0 iTOW, fTOW, week */ + { UBLOX_U8, offsetof(struct nav_sol, gps_fix) }, /* 10 gpsFix */ + { UBLOX_U8, offsetof(struct nav_sol, flags) }, /* 11 flags */ + { UBLOX_DISCARD, 35 }, /* 12 ecefX, ecefY, ecefZ, pAcc, ecefVX, ecefVY, ecefVZ, sAcc, pDOP, reserved1 */ + { UBLOX_U8, offsetof(struct nav_sol, nsat) }, /* 47 numSV */ + { UBLOX_DISCARD, 4 }, /* 48 reserved2 */ + { UBLOX_END, 0 } +}; + +#define NAV_SOL_FLAGS_GPSFIXOK 0 +#define NAV_SOL_FLAGS_DIFFSOLN 1 +#define NAV_SOL_FLAGS_WKNSET 2 +#define NAV_SOL_FLAGS_TOWSET 3 + +static void +ao_ublox_parse_nav_sol(void) +{ + ao_ublox_parse(&nav_sol, nav_sol_packet); +} + +/* + * NAV-SVINFO message parsing + */ + +static struct nav_svinfo { + uint8_t num_ch; + uint8_t flags; +} nav_svinfo; + +static const struct ublox_packet_parse nav_svinfo_packet[] = { + { UBLOX_DISCARD, 4 }, /* 0 iTOW */ + { UBLOX_U8, offsetof(struct nav_svinfo, num_ch) }, /* 4 numCh */ + { UBLOX_U8, offsetof(struct nav_svinfo, flags) }, /* 5 globalFlags */ + { UBLOX_DISCARD, 2 }, /* 6 reserved2 */ + { UBLOX_END, 0 } +}; + +#define NAV_SVINFO_MAX_SAT 16 + +static struct nav_svinfo_sat { + uint8_t chn; + uint8_t svid; + uint8_t flags; + uint8_t quality; + uint8_t cno; +} nav_svinfo_sat[NAV_SVINFO_MAX_SAT]; + +static uint8_t nav_svinfo_nsat; + +static const struct ublox_packet_parse nav_svinfo_sat_packet[] = { + { UBLOX_U8, offsetof(struct nav_svinfo_sat, chn) }, /* 8 + 12*N chn */ + { UBLOX_U8, offsetof(struct nav_svinfo_sat, svid) }, /* 9 + 12*N svid */ + { UBLOX_U8, offsetof(struct nav_svinfo_sat, flags) }, /* 10 + 12*N flags */ + { UBLOX_U8, offsetof(struct nav_svinfo_sat, quality) }, /* 11 + 12*N quality */ + { UBLOX_U8, offsetof(struct nav_svinfo_sat, cno) }, /* 12 + 12*N cno */ + { UBLOX_DISCARD, 7 }, /* 13 + 12*N elev, azim, prRes */ + { UBLOX_END, 0 } +}; + +#define NAV_SVINFO_SAT_FLAGS_SVUSED 0 +#define NAV_SVINFO_SAT_FLAGS_DIFFCORR 1 +#define NAV_SVINFO_SAT_FLAGS_ORBITAVAIL 2 +#define NAV_SVINFO_SAT_FLAGS_ORBITEPH 3 +#define NAV_SVINFO_SAT_FLAGS_UNHEALTHY 4 +#define NAV_SVINFO_SAT_FLAGS_ORBITALM 5 +#define NAV_SVINFO_SAT_FLAGS_ORBITAOP 6 +#define NAV_SVINFO_SAT_FLAGS_SMOOTHED 7 + +#define NAV_SVINFO_SAT_QUALITY_IDLE 0 +#define NAV_SVINFO_SAT_QUALITY_SEARCHING 1 +#define NAV_SVINFO_SAT_QUALITY_ACQUIRED 2 +#define NAV_SVINFO_SAT_QUALITY_UNUSABLE 3 +#define NAV_SVINFO_SAT_QUALITY_LOCKED 4 +#define NAV_SVINFO_SAT_QUALITY_RUNNING 5 + +static void +ao_ublox_parse_nav_svinfo(void) +{ + uint8_t nsat; + nav_svinfo_nsat = 0; + ao_ublox_parse(&nav_svinfo, nav_svinfo_packet); + for (nsat = 0; nsat < nav_svinfo.num_ch && ao_ublox_len >= 12; nsat++) { + if (nsat < NAV_SVINFO_MAX_SAT) { + ao_ublox_parse(&nav_svinfo_sat[nav_svinfo_nsat++], nav_svinfo_sat_packet); + } else { + ublox_discard(12); + } + } +} + +/* + * NAV-TIMEUTC message parsing + */ +static struct nav_timeutc { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + uint8_t valid; +} nav_timeutc; + +#define NAV_TIMEUTC_VALID_TOW 0 +#define NAV_TIMEUTC_VALID_WKN 1 +#define NAV_TIMEUTC_VALID_UTC 2 + +static const struct ublox_packet_parse nav_timeutc_packet[] = { + { UBLOX_DISCARD, 12 }, /* 0 iTOW, tAcc, nano */ + { UBLOX_U16, offsetof(struct nav_timeutc, year) }, /* 12 year */ + { UBLOX_U8, offsetof(struct nav_timeutc, month) }, /* 14 month */ + { UBLOX_U8, offsetof(struct nav_timeutc, day) }, /* 15 day */ + { UBLOX_U8, offsetof(struct nav_timeutc, hour) }, /* 16 hour */ + { UBLOX_U8, offsetof(struct nav_timeutc, min) }, /* 17 min */ + { UBLOX_U8, offsetof(struct nav_timeutc, sec) }, /* 18 sec */ + { UBLOX_U8, offsetof(struct nav_timeutc, valid) }, /* 19 valid */ + { UBLOX_END, 0 } +}; + +static void +ao_ublox_parse_nav_timeutc(void) +{ + ao_ublox_parse(&nav_timeutc, nav_timeutc_packet); +} + +/* + * NAV-VELNED message parsing + */ + +static struct nav_velned { + int32_t vel_d; + uint32_t g_speed; + int32_t heading; +} nav_velned; + +static const struct ublox_packet_parse nav_velned_packet[] = { + { UBLOX_DISCARD, 12 }, /* 0 iTOW, velN, velE */ + { UBLOX_U32, offsetof(struct nav_velned, vel_d) }, /* 12 velD */ + { UBLOX_DISCARD, 4 }, /* 16 speed */ + { UBLOX_U32, offsetof(struct nav_velned, g_speed) }, /* 20 gSpeed */ + { UBLOX_U32, offsetof(struct nav_velned, heading) }, /* 24 heading */ + { UBLOX_DISCARD, 8 }, /* 28 sAcc, cAcc */ + { UBLOX_END, 0 } +}; + +static void +ao_ublox_parse_nav_velned(void) +{ + ao_ublox_parse(&nav_velned, nav_velned_packet); +} + +/* + * Set the protocol mode and baud rate + */ + +static void +ao_gps_setup(void) +{ + uint8_t i, k; + ao_ublox_set_speed(AO_SERIAL_SPEED_9600); + + /* + * A bunch of nulls so the start bit + * is clear + */ + for (i = 0; i < 64; i++) + ao_ublox_putchar(0x00); + + /* + * Send the baud-rate setting and protocol-setting + * command three times + */ + for (k = 0; k < 3; k++) + for (i = 0; i < sizeof (ao_gps_set_nmea); i++) + ao_ublox_putchar(ao_gps_set_nmea[i]); + + /* + * Increase the baud rate + */ + ao_ublox_set_speed(AO_SERIAL_SPEED_57600); + + /* + * Pad with nulls to give the chip + * time to see the baud rate switch + */ + for (i = 0; i < 64; i++) + ao_ublox_putchar(0x00); +} + +static void +ao_ublox_putstart(uint8_t class, uint8_t id, uint16_t len) +{ + ao_ublox_init_cksum(); + ao_ublox_putchar(0xb5); + ao_ublox_putchar(0x62); + ao_ublox_put_u8(class); + ao_ublox_put_u8(id); + ao_ublox_put_u8(len); + ao_ublox_put_u8(len >> 8); +} + +static void +ao_ublox_putend(void) +{ + ao_ublox_putchar(ao_ublox_cksum.a); + ao_ublox_putchar(ao_ublox_cksum.b); +} + +static void +ao_ublox_set_message_rate(uint8_t class, uint8_t msgid, uint8_t rate) +{ + ao_ublox_putstart(0x06, 0x01, 3); + ao_ublox_put_u8(class); + ao_ublox_put_u8(msgid); + ao_ublox_put_u8(rate); + ao_ublox_putend(); +} + +static void +ao_ublox_set_navigation_settings(uint16_t mask, + uint8_t dyn_model, + uint8_t fix_mode, + int32_t fixed_alt, + uint32_t fixed_alt_var, + int8_t min_elev, + uint8_t dr_limit, + uint16_t pdop, + uint16_t tdop, + uint16_t pacc, + uint16_t tacc, + uint8_t static_hold_thresh, + uint8_t dgps_time_out) +{ + ao_ublox_putstart(UBLOX_CFG, UBLOX_CFG_NAV5, 36); + ao_ublox_put_u16(mask); + ao_ublox_put_u8(dyn_model); + ao_ublox_put_u8(fix_mode); + ao_ublox_put_i32(fixed_alt); + ao_ublox_put_u32(fixed_alt_var); + ao_ublox_put_i8(min_elev); + ao_ublox_put_u8(dr_limit); + ao_ublox_put_u16(pdop); + ao_ublox_put_u16(tdop); + ao_ublox_put_u16(pacc); + ao_ublox_put_u16(tacc); + ao_ublox_put_u8(static_hold_thresh); + ao_ublox_put_u8(dgps_time_out); + ao_ublox_put_u32(0); + ao_ublox_put_u32(0); + ao_ublox_put_u32(0); + ao_ublox_putend(); +} + + +/* + * Disable all MON message + */ +static const uint8_t ublox_disable_mon[] = { + 0x0b, 0x09, 0x02, 0x06, 0x07, 0x21, 0x08, 0x04 +}; + +/* + * Disable all NAV messages. The desired + * ones will be explicitly re-enabled + */ + +static const uint8_t ublox_disable_nav[] = { + 0x60, 0x22, 0x31, 0x04, 0x40, 0x01, 0x02, 0x32, + 0x06, 0x03, 0x30, 0x20, 0x21, 0x11, 0x12 +}; + +/* + * Enable enough messages to get all of the data we want + */ +static const uint8_t ublox_enable_nav[] = { + UBLOX_NAV_DOP, + UBLOX_NAV_POSLLH, + UBLOX_NAV_SOL, + UBLOX_NAV_SVINFO, + UBLOX_NAV_VELNED, + UBLOX_NAV_TIMEUTC +}; + +void +ao_gps(void) __reentrant +{ + uint8_t class, id; + struct ao_ublox_cksum cksum; + uint8_t i; + + ao_gps_setup(); + + /* Disable all messages */ + for (i = 0; i < sizeof (ublox_disable_mon); i++) + ao_ublox_set_message_rate(0x0a, ublox_disable_mon[i], 0); + for (i = 0; i < sizeof (ublox_disable_nav); i++) + ao_ublox_set_message_rate(UBLOX_NAV, ublox_disable_nav[i], 0); + + /* Enable all of the messages we want */ + for (i = 0; i < sizeof (ublox_enable_nav); i++) + ao_ublox_set_message_rate(UBLOX_NAV, ublox_enable_nav[i], 1); + + ao_ublox_set_navigation_settings((1 << UBLOX_CFG_NAV5_MASK_DYN) | (1 << UBLOX_CFG_NAV5_MASK_FIXMODE), + UBLOX_CFG_NAV5_DYNMODEL_AIRBORNE_4G, + UBLOX_CFG_NAV5_FIXMODE_3D, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0); + + for (;;) { + /* Locate the begining of the next record */ + while (ao_ublox_byte() != (uint8_t) 0xb5) + ; + if (ao_ublox_byte() != (uint8_t) 0x62) + continue; + + ao_ublox_init_cksum(); + + class = header_byte(); + id = header_byte(); + + /* Length */ + ao_ublox_len = header_byte(); + ao_ublox_len |= header_byte() << 8; + + if (ao_ublox_len > 1023) + continue; + + switch (class) { + case UBLOX_NAV: + switch (id) { + case UBLOX_NAV_DOP: + if (ao_ublox_len != 18) + break; + ao_ublox_parse_nav_dop(); + break; + case UBLOX_NAV_POSLLH: + if (ao_ublox_len != 28) + break; + ao_ublox_parse_nav_posllh(); + break; + case UBLOX_NAV_SOL: + if (ao_ublox_len != 52) + break; + ao_ublox_parse_nav_sol(); + break; + case UBLOX_NAV_SVINFO: + if (ao_ublox_len < 8) + break; + ao_ublox_parse_nav_svinfo(); + break; + case UBLOX_NAV_VELNED: + if (ao_ublox_len != 36) + break; + ao_ublox_parse_nav_velned(); + break; + case UBLOX_NAV_TIMEUTC: + if (ao_ublox_len != 20) + break; + ao_ublox_parse_nav_timeutc(); + break; + } + break; + } + + if (ao_ublox_len != 0) + continue; + + /* verify checksum and end sequence */ + cksum.a = ao_ublox_byte(); + cksum.b = ao_ublox_byte(); + if (ao_ublox_cksum.a != cksum.a || ao_ublox_cksum.b != cksum.b) + continue; + + switch (class) { + case 0x01: + switch (id) { + case 0x21: + ao_mutex_get(&ao_gps_mutex); + ao_gps_tick = ao_time(); + + ao_gps_data.flags = 0; + ao_gps_data.flags |= AO_GPS_RUNNING; + if (nav_sol.gps_fix & (1 << NAV_SOL_FLAGS_GPSFIXOK)) { + uint8_t nsat = nav_sol.nsat; + ao_gps_data.flags |= AO_GPS_VALID; + if (nsat > 15) + nsat = 15; + ao_gps_data.flags |= nsat; + } + if (nav_timeutc.valid & (1 << NAV_TIMEUTC_VALID_UTC)) + ao_gps_data.flags |= AO_GPS_DATE_VALID; + + ao_gps_data.altitude = nav_posllh.alt_msl / 1000; + ao_gps_data.latitude = nav_posllh.lat; + ao_gps_data.longitude = nav_posllh.lon; + + ao_gps_data.year = nav_timeutc.year - 2000; + ao_gps_data.month = nav_timeutc.month; + ao_gps_data.day = nav_timeutc.day; + + ao_gps_data.hour = nav_timeutc.hour; + ao_gps_data.minute = nav_timeutc.min; + ao_gps_data.second = nav_timeutc.sec; + + ao_gps_data.pdop = nav_dop.pdop; + ao_gps_data.hdop = nav_dop.hdop; + ao_gps_data.vdop = nav_dop.vdop; + + /* mode is not set */ + + ao_gps_data.ground_speed = nav_velned.g_speed; + ao_gps_data.climb_rate = -nav_velned.vel_d; + ao_gps_data.course = nav_velned.heading / 200000; + + ao_gps_tracking_data.channels = 0; + + struct ao_telemetry_satellite_info *dst = &ao_gps_tracking_data.sats[0]; + + for (i = 0; i < nav_svinfo_nsat; i++) { + struct nav_svinfo_sat *src = &nav_svinfo_sat[i]; + + if (!(src->flags & (1 << NAV_SVINFO_SAT_FLAGS_UNHEALTHY)) && + src->quality >= NAV_SVINFO_SAT_QUALITY_ACQUIRED) + { + dst->svid = src->svid; + dst->c_n_1 = src->cno; + dst++; + ao_gps_tracking_data.channels++; + } + } + + ao_mutex_put(&ao_gps_mutex); + ao_wakeup(&ao_gps_data); + ao_wakeup(&ao_gps_tracking_data); + break; + } + 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_ublox.h b/src/drivers/ao_gps_ublox.h new file mode 100644 index 00000000..e4a358a8 --- /dev/null +++ b/src/drivers/ao_gps_ublox.h @@ -0,0 +1,267 @@ +/* + * Copyright © 2013 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_UBLOX_H_ +#define _AO_GPS_UBLOX_H_ + +struct ublox_hdr { + uint8_t class, message; + uint16_t length; +}; + +#define UBLOX_NAV 0x01 + +#define UBLOX_NAV_DOP 0x04 + +struct ublox_nav_dop { + uint8_t class; /* 0x01 */ + uint8_t message; /* 0x04 */ + uint16_t length; /* 18 */ + + uint32_t itow; /* ms */ + uint16_t gdop; + uint16_t ddop; + uint16_t tdop; + uint16_t vdop; + uint16_t hdop; + uint16_t ndop; + uint16_t edop; +}; + +#define UBLOX_NAV_POSLLH 0x02 + +struct ublox_nav_posllh { + uint8_t class; /* 0x01 */ + uint8_t message; /* 0x02 */ + uint16_t length; /* 28 */ + + uint32_t itow; /* ms */ + int32_t lon; /* deg * 1e7 */ + int32_t lat; /* deg * 1e7 */ + int32_t height; /* mm */ + int32_t hmsl; /* mm */ + uint32_t hacc; /* mm */ + uint32_t vacc; /* mm */ +}; + +#define UBLOX_NAV_SOL 0x06 + +struct ublox_nav_sol { + uint8_t class; /* 0x01 */ + uint8_t message; /* 0x06 */ + uint16_t length; /* 52 */ + + uint32_t itow; /* ms */ + int32_t ftow; /* ns */ + int16_t week; + int8_t gpsfix; + uint8_t flags; + int32_t exefx; /* cm */ + int32_t exefy; /* cm */ + int32_t exefz; /* cm */ + uint32_t pacc; /* cm */ + int32_t exefvx; /* cm/s */ + int32_t exefvy; /* cm/s */ + int32_t exefvz; /* cm/s */ + uint32_t sacc; /* cm/s */ + uint16_t pdop; /* * 100 */ + uint8_t reserved1; + uint8_t numsv; + uint32_t reserved2; +}; + +#define UBLOX_NAV_SOL_GPSFIX_NO_FIX 0 +#define UBLOX_NAV_SOL_GPSFIX_DEAD_RECKONING 1 +#define UBLOX_NAV_SOL_GPSFIX_2D 2 +#define UBLOX_NAV_SOL_GPSFIX_3D 3 +#define UBLOX_NAV_SOL_GPSFIX_GPS_DEAD_RECKONING 4 +#define UBLOX_NAV_SOL_GPSFIX_TIME_ONLY 5 + +#define UBLOX_NAV_SOL_FLAGS_GPSFIXOK 0 +#define UBLOX_NAV_SOL_FLAGS_DIFFSOLN 1 +#define UBLOX_NAV_SOL_FLAGS_WKNSET 2 +#define UBLOX_NAV_SOL_FLAGS_TOWSET 3 + +#define UBLOX_NAV_STATUS 0x03 + +struct ublox_nav_status { + uint8_t class; /* 0x01 */ + uint8_t message; /* 0x03 */ + uint16_t length; /* 16 */ + + uint8_t gpsfix; + uint8_t flags; + uint8_t fixstat; + uint8_t flags2; + + uint32_t ttff; /* ms */ + uint32_t msss; /* ms */ +}; + +#define UBLOX_NAV_STATUS_GPSFIX_NO_FIX 0 +#define UBLOX_NAV_STATUS_GPSFIX_DEAD_RECKONING 1 +#define UBLOX_NAV_STATUS_GPSFIX_2D 2 +#define UBLOX_NAV_STATUS_GPSFIX_3D 3 +#define UBLOX_NAV_STATUS_GPSFIX_GPS_DEAD_RECKONING 4 +#define UBLOX_NAV_STATUS_GPSFIX_TIME_ONLY 5 + +#define UBLOX_NAV_STATUS_FLAGS_GPSFIXOK 0 +#define UBLOX_NAV_STATUS_FLAGS_DIFFSOLN 1 +#define UBLOX_NAV_STATUS_FLAGS_WKNSET 2 +#define UBLOX_NAV_STATUS_FLAGS_TOWSET 3 + +#define UBLOX_NAV_STATUS_FIXSTAT_DGPSISTAT 0 +#define UBLOX_NAV_STATUS_FIXSTAT_MAPMATCHING 6 +#define UBLOX_NAV_STATUS_FIXSTAT_MAPMATCHING_NONE 0 +#define UBLOX_NAV_STATUS_FIXSTAT_MAPMATCHING_VALID 1 +#define UBLOX_NAV_STATUS_FIXSTAT_MAPMATCHING_USED 2 +#define UBLOX_NAV_STATUS_FIXSTAT_MAPMATCHING_DR 3 +#define UBLOX_NAV_STATUS_FIXSTAT_MAPMATCHING_MASK 3 + +#define UBLOX_NAV_STATUS_FLAGS2_PSMSTATE 0 +#define UBLOX_NAV_STATUS_FLAGS2_PSMSTATE_ACQUISITION 0 +#define UBLOX_NAV_STATUS_FLAGS2_PSMSTATE_TRACKING 1 +#define UBLOX_NAV_STATUS_FLAGS2_PSMSTATE_POWER_OPTIMIZED_TRACKING 2 +#define UBLOX_NAV_STATUS_FLAGS2_PSMSTATE_INACTIVE 3 +#define UBLOX_NAV_STATUS_FLAGS2_PSMSTATE_MASK 3 + +#define UBLOX_NAV_SVINFO 0x30 + +struct ublox_nav_svinfo { + uint8_t class; /* 0x01 */ + uint8_t message; /* 0x30 */ + uint16_t length; /* 8 + 12 * numch */ + + uint32_t itow; /* ms */ + + uint8_t numch; + uint8_t globalflags; + uint16_t reserved; +}; + +#define UBLOX_NAV_SVINFO_GLOBAL_FLAGS_CHIPGEN 0 +#define UBLOX_NAV_SVINFO_GLOBAL_FLAGS_CHIPGEN_ANTARIS 0 +#define UBLOX_NAV_SVINFO_GLOBAL_FLAGS_CHIPGEN_U_BLOX_5 1 +#define UBLOX_NAV_SVINFO_GLOBAL_FLAGS_CHIPGEN_U_BLOX_6 2 +#define UBLOX_NAV_SVINFO_GLOBAL_FLAGS_CHIPGEN_MASK 7 + +struct ublox_nav_svinfo_block { + uint8_t chn; + uint8_t svid; + uint8_t flags; + uint8_t quality; + + uint8_t cno; /* dbHz */ + int8_t elev; /* deg */ + int16_t azim; /* deg */ + + int32_t prres; /* cm */ +}; + +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_SVUSED 0 +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_DIFFCORR 1 +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_ORBITAVAIL 2 +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_ORBITEPH 3 +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_UNHEALTHY 4 +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_ORBITALM 5 +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_ORBITAOP 6 +#define UBLOX_NAV_SVINFO_BLOCK_FLAGS_SMOOTHED 7 + +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND 0 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_IDLE 0 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_SEARCHING 1 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_ACQUIRED 2 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_UNUSABLE 3 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_CODE_LOCK 4 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_CARRIER_LOCKED_5 5 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_CARRIER_LOCKED_6 6 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_CARRIER_LOCKED_7 7 +#define UBLOX_NAV_SVINFO_BLOCK_QUALITY_QUALITYIND_MASK 7 + +#define UBLOX_NAV_TIMEUTC 0x21 + +struct ublox_nav_timeutc { + uint8_t class; /* 0x01 */ + uint8_t message; /* 0x21 */ + uint16_t length; /* 20 */ + + uint32_t itow; /* ms */ + uint32_t tacc; /* ns */ + int32_t nano; /* ns */ + + uint16_t year; + uint8_t month; + uint8_t day; + + uint8_t hour; + uint8_t min; + uint8_t sec; + uint8_t valid; +}; + +#define UBLOX_NAV_TIMEUTC_VALID_VALIDTOW 0 +#define UBLOX_NAV_TIMEUTC_VALID_VALIDWKN 1 +#define UBLOX_NAV_TIMEUTC_VALID_VALIDUTC 2 + +#define UBLOX_NAV_VELNED 0x12 + +struct ublox_nav_velned { + uint8_t class; /* 0x01 */ + uint8_t message; /* 0x12 */ + uint16_t length; /* 36 */ + + uint32_t itow; /* ms */ + + int32_t veln; /* cm/s */ + int32_t vele; /* cm/s */ + int32_t veld; /* cm/s */ + + uint32_t speed; /* cm/s */ + uint32_t gspeed; /* cm/s */ + + int32_t heading; /* deg */ + uint32_t sacc; /* cm/s */ + uint32_t cacc; /* deg */ +}; + +#define UBLOX_CFG 0x06 + +#define UBLOX_CFG_NAV5 0x24 + +#define UBLOX_CFG_NAV5_MASK_DYN 0 +#define UBLOX_CFG_NAV5_MASK_MINE1 1 +#define UBLOX_CFG_NAV5_MASK_FIXMODE 2 +#define UBLOX_CFG_NAV5_MASK_DRLIM 3 +#define UBLOX_CFG_NAV5_MASK_POSMASK 4 +#define UBLOX_CFG_NAV5_MASK_TIMEMASK 5 +#define UBLOX_CFG_NAV5_MASK_STATICHOLDMASK 6 +#define UBLOX_CFG_NAV5_MASK_DGPSMASK 7 + +#define UBLOX_CFG_NAV5_DYNMODEL_PORTABLE 0 +#define UBLOX_CFG_NAV5_DYNMODEL_STATIONARY 2 +#define UBLOX_CFG_NAV5_DYNMODEL_PEDESTRIAN 3 +#define UBLOX_CFG_NAV5_DYNMODEL_AUTOMOTIVE 4 +#define UBLOX_CFG_NAV5_DYNMODEL_SEA 5 +#define UBLOX_CFG_NAV5_DYNMODEL_AIRBORNE_1G 6 +#define UBLOX_CFG_NAV5_DYNMODEL_AIRBORNE_2G 7 +#define UBLOX_CFG_NAV5_DYNMODEL_AIRBORNE_4G 8 + +#define UBLOX_CFG_NAV5_FIXMODE_2D 1 +#define UBLOX_CFG_NAV5_FIXMODE_3D 2 +#define UBLOX_CFG_NAV5_FIXMODE_AUTO 3 + +#endif /* _AO_GPS_UBLOX_H_ */ diff --git a/src/drivers/ao_hmc5883.c b/src/drivers/ao_hmc5883.c index ade6c263..782d03f4 100644 --- a/src/drivers/ao_hmc5883.c +++ b/src/drivers/ao_hmc5883.c @@ -77,11 +77,11 @@ ao_hmc5883_sample(struct ao_hmc5883_sample *sample) ao_hmc5883_reg_write(HMC5883_MODE, HMC5883_MODE_SINGLE); ao_alarm(AO_MS_TO_TICKS(10)); - cli(); + ao_arch_block_interrupts(); while (!ao_hmc5883_done) if (ao_sleep(&ao_hmc5883_done)) ++ao_hmc5883_missed_irq; - sei(); + ao_arch_release_interrupts(); ao_clear_alarm(); ao_hmc5883_read(HMC5883_X_MSB, (uint8_t *) sample, sizeof (struct ao_hmc5883_sample)); @@ -109,7 +109,7 @@ ao_hmc5883_setup(void) ao_i2c_put(AO_HMC5883_I2C_INDEX); if (!present) - ao_panic(AO_PANIC_SELF_TEST); + ao_panic(AO_PANIC_SELF_TEST_HMC5883); ao_hmc5883_reg_write(HMC5883_CONFIG_A, (HMC5883_CONFIG_A_MA_8 << HMC5883_CONFIG_A_MA) | @@ -123,12 +123,14 @@ ao_hmc5883_setup(void) return 1; } +struct ao_hmc5883_sample ao_hmc5883_current; + static void ao_hmc5883(void) { ao_hmc5883_setup(); for (;;) { - ao_hmc5883_sample((struct ao_hmc5883_sample *) &ao_data_ring[ao_data_head].hmc5883); + ao_hmc5883_sample(&ao_hmc5883_current); ao_arch_critical( AO_DATA_PRESENT(AO_DATA_HMC5883); AO_DATA_WAIT(); diff --git a/src/drivers/ao_hmc5883.h b/src/drivers/ao_hmc5883.h index 55690978..ff2725eb 100644 --- a/src/drivers/ao_hmc5883.h +++ b/src/drivers/ao_hmc5883.h @@ -79,6 +79,8 @@ struct ao_hmc5883_sample { int16_t x, y, z; }; +extern struct ao_hmc5883_sample ao_hmc5883_current; + void ao_hmc5883_init(void); diff --git a/src/drivers/ao_log_fat.c b/src/drivers/ao_log_fat.c new file mode 100644 index 00000000..45b67012 --- /dev/null +++ b/src/drivers/ao_log_fat.c @@ -0,0 +1,95 @@ +/* + * Copyright © 2013 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_log.h" +#include "ao_fat.h" + +static uint8_t log_year, log_month, log_day; +static uint8_t log_open; +static int8_t log_fd; +static uint8_t log_mutex; + +static void +ao_log_open(void) +{ + static char name[12]; + int8_t status; + + sprintf(name,"%04d%02d%02dLOG", 2000 + log_year, log_month, log_day); + status = ao_fat_open(name, AO_FAT_OPEN_WRITE); + if (status >= 0) { + log_fd = status; + ao_fat_seek(log_fd, 0, AO_FAT_SEEK_END); + log_open = 1; + } else if (status == -AO_FAT_ENOENT) { + status = ao_fat_creat(name); + if (status >= 0) { + log_fd = status; + log_open = 1; + } + } +} + +static void +ao_log_close(void) +{ + if (log_open) { + log_open = 0; + ao_fat_close(log_fd); + log_fd = -1; + } +} + +uint8_t +ao_log_full(void) +{ + return ao_fat_full(); +} + +uint8_t +ao_log_mega(struct ao_log_mega *log) +{ + uint8_t wrote = 0; + ao_mutex_get(&log_mutex); + if (log->type == AO_LOG_GPS_TIME) { + if (log_open && + (log_year != log->u.gps.year || + log_month != log->u.gps.month || + log_day != log->u.gps.day)) { + ao_log_close(); + } + if (!log_open) { + log_year = log->u.gps.year; + log_month = log->u.gps.month; + log_day = log->u.gps.day; + ao_log_open(); + } + } + if (log_open) { + wrote = ao_fat_write(log_fd, log, sizeof (*log)) == AO_FAT_SUCCESS; + ao_fat_sync(); + } + ao_mutex_put(&log_mutex); + return wrote; +} + +void +ao_log_flush(void) +{ + ao_fat_sync(); +} diff --git a/src/drivers/ao_m25.c b/src/drivers/ao_m25.c index 9603c1de..390637d7 100644 --- a/src/drivers/ao_m25.c +++ b/src/drivers/ao_m25.c @@ -29,6 +29,7 @@ __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 M25_DEBUG 0 /* * Each flash chip is arranged in 64kB sectors; the * chip cannot erase in units smaller than that. @@ -99,18 +100,7 @@ static __xdata uint8_t ao_m25_mutex; 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_SELECT(cs) ao_spi_get_mask(AO_M25_SPI_CS_PORT,cs,AO_M25_SPI_BUS, AO_SPI_SPEED_FAST) #define M25_DESELECT(cs) ao_spi_put_mask(AO_M25_SPI_CS_PORT,cs,AO_M25_SPI_BUS) #define M25_BLOCK_SHIFT 16 @@ -341,7 +331,9 @@ ao_storage_setup(void) void ao_storage_device_info(void) __reentrant { +#if M25_DEBUG uint8_t cs; +#endif #if M25_MAX_CHIPS > 1 uint8_t chip; #endif @@ -359,6 +351,7 @@ ao_storage_device_info(void) __reentrant printf ("Detected chips 1 size %d\n", ao_m25_total); #endif +#if M25_DEBUG printf ("Available chips:\n"); for (cs = 1; cs != 0; cs <<= 1) { if ((AO_M25_SPI_CS_MASK & cs) == 0) @@ -379,6 +372,7 @@ ao_storage_device_info(void) __reentrant ao_m25_instruction[M25_UID_OFFSET]); ao_mutex_put(&ao_m25_mutex); } +#endif } void diff --git a/src/drivers/ao_mma655x.c b/src/drivers/ao_mma655x.c index 06422206..28fe1e08 100644 --- a/src/drivers/ao_mma655x.c +++ b/src/drivers/ao_mma655x.c @@ -20,7 +20,7 @@ #if HAS_MMA655X -#if 1 +#if 0 #define PRINTD(...) do { printf ("\r%5u %s: ", ao_tick_count, __func__); printf(__VA_ARGS__); } while(0) #else #define PRINTD(...) @@ -238,11 +238,12 @@ ao_mma655x_setup(void) } +uint16_t ao_mma655x_current; + static void ao_mma655x_dump(void) { - ao_mma655x_setup(); - printf ("MMA655X value %d\n", ao_mma655x_value()); + printf ("MMA655X value %d\n", ao_mma655x_current); } __code struct ao_cmds ao_mma655x_cmds[] = { @@ -255,7 +256,7 @@ ao_mma655x(void) { ao_mma655x_setup(); for (;;) { - ao_data_ring[ao_data_head].mma655x = ao_mma655x_value(); + ao_mma655x_current = ao_mma655x_value(); ao_arch_critical( AO_DATA_PRESENT(AO_DATA_MMA655X); AO_DATA_WAIT(); @@ -273,7 +274,7 @@ ao_mma655x_init(void) 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"); + ao_add_task(&ao_mma655x_task, ao_mma655x, "mma655x"); } #endif diff --git a/src/drivers/ao_mma655x.h b/src/drivers/ao_mma655x.h index 9c0c59dc..2d951e07 100644 --- a/src/drivers/ao_mma655x.h +++ b/src/drivers/ao_mma655x.h @@ -78,6 +78,7 @@ #define AO_MMA655X_COUNT 0x15 #define AO_MMA655X_OFFCORR 0x16 +extern uint16_t ao_mma655x_current; void ao_mma655x_init(void); diff --git a/src/drivers/ao_mpu6000.c b/src/drivers/ao_mpu6000.c index b3e284e0..c65aecbc 100644 --- a/src/drivers/ao_mpu6000.c +++ b/src/drivers/ao_mpu6000.c @@ -22,47 +22,71 @@ 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); -} +#define ao_mpu6000_spi_get() ao_spi_get_bit(AO_MPU6000_SPI_CS_PORT, \ + AO_MPU6000_SPI_CS_PIN, \ + AO_MPU6000_SPI_CS, \ + AO_MPU6000_SPI_BUS, \ + AO_SPI_SPEED_1MHz) + +#define ao_mpu6000_spi_put() ao_spi_put_bit(AO_MPU6000_SPI_CS_PORT, \ + AO_MPU6000_SPI_CS_PIN, \ + AO_MPU6000_SPI_CS, \ + AO_MPU6000_SPI_BUS) + static void ao_mpu6000_reg_write(uint8_t addr, uint8_t value) { uint8_t d[2] = { addr, value }; +#ifdef AO_MPU6000_I2C_INDEX 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); +#else + ao_mpu6000_spi_get(); + ao_spi_send(d, 2, AO_MPU6000_SPI_BUS); + ao_mpu6000_spi_put(); +#endif } static void ao_mpu6000_read(uint8_t addr, void *data, uint8_t len) { +#ifdef AO_MPU6000_I2C_INDEX 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); +#else + addr |= 0x80; + ao_mpu6000_spi_get(); + ao_spi_send(&addr, 1, AO_MPU6000_SPI_BUS); + ao_spi_recv(data, len, AO_MPU6000_SPI_BUS); + ao_mpu6000_spi_put(); +#endif } static uint8_t ao_mpu6000_reg_read(uint8_t addr) { uint8_t value; +#ifdef AO_MPU6000_I2C_INDEX 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); +#else + addr |= 0x80; + ao_mpu6000_spi_get(); + ao_spi_send(&addr, 1, AO_MPU6000_SPI_BUS); + ao_spi_recv(&value, 1, AO_MPU6000_SPI_BUS); + ao_mpu6000_spi_put(); +#endif return value; } @@ -199,6 +223,24 @@ ao_mpu6000_setup(void) ao_delay(AO_MS_TO_TICKS(200)); ao_mpu6000_sample(&test_mode); +#if TRIDGE + // read the product ID rev c has 1/2 the sensitivity of rev d + _mpu6000_product_id = _register_read(MPUREG_PRODUCT_ID); + //Serial.printf("Product_ID= 0x%x\n", (unsigned) _mpu6000_product_id); + + if ((_mpu6000_product_id == MPU6000ES_REV_C4) || (_mpu6000_product_id == MPU6000ES_REV_C5) || + (_mpu6000_product_id == MPU6000_REV_C4) || (_mpu6000_product_id == MPU6000_REV_C5)) { + // Accel scale 8g (4096 LSB/g) + // Rev C has different scaling than rev D + register_write(MPUREG_ACCEL_CONFIG,1<<3); + } else { + // Accel scale 8g (4096 LSB/g) + register_write(MPUREG_ACCEL_CONFIG,2<<3); + } + hal.scheduler->delay(1); + +#endif + /* Configure accelerometer to +/-16G */ ao_mpu6000_reg_write(MPU6000_ACCEL_CONFIG, (0 << MPU600_ACCEL_CONFIG_XA_ST) | @@ -240,13 +282,15 @@ ao_mpu6000_setup(void) ao_mpu6000_configured = 1; } +struct ao_mpu6000_sample ao_mpu6000_current; + static void ao_mpu6000(void) { ao_mpu6000_setup(); for (;;) { - ao_mpu6000_sample((struct ao_mpu6000_sample *) &ao_data_ring[ao_data_head].mpu6000); + ao_mpu6000_sample(&ao_mpu6000_current); ao_arch_critical( AO_DATA_PRESENT(AO_DATA_MPU6000); AO_DATA_WAIT(); diff --git a/src/drivers/ao_mpu6000.h b/src/drivers/ao_mpu6000.h index ca76b081..f01e9e83 100644 --- a/src/drivers/ao_mpu6000.h +++ b/src/drivers/ao_mpu6000.h @@ -21,6 +21,27 @@ #define MPU6000_ADDR_WRITE 0xd0 #define MPU6000_ADDR_READ 0xd1 +/* From Tridge */ +#define MPUREG_XG_OFFS_TC 0x00 +#define MPUREG_YG_OFFS_TC 0x01 +#define MPUREG_ZG_OFFS_TC 0x02 +#define MPUREG_X_FINE_GAIN 0x03 +#define MPUREG_Y_FINE_GAIN 0x04 +#define MPUREG_Z_FINE_GAIN 0x05 +#define MPUREG_XA_OFFS_H 0x06 // X axis accelerometer offset (high byte) +#define MPUREG_XA_OFFS_L 0x07 // X axis accelerometer offset (low byte) +#define MPUREG_YA_OFFS_H 0x08 // Y axis accelerometer offset (high byte) +#define MPUREG_YA_OFFS_L 0x09 // Y axis accelerometer offset (low byte) +#define MPUREG_ZA_OFFS_H 0x0A // Z axis accelerometer offset (high byte) +#define MPUREG_ZA_OFFS_L 0x0B // Z axis accelerometer offset (low byte) +#define MPUREG_PRODUCT_ID 0x0C // Product ID Register +#define MPUREG_XG_OFFS_USRH 0x13 // X axis gyro offset (high byte) +#define MPUREG_XG_OFFS_USRL 0x14 // X axis gyro offset (low byte) +#define MPUREG_YG_OFFS_USRH 0x15 // Y axis gyro offset (high byte) +#define MPUREG_YG_OFFS_USRL 0x16 // Y axis gyro offset (low byte) +#define MPUREG_ZG_OFFS_USRH 0x17 // Z axis gyro offset (high byte) +#define MPUREG_ZG_OFFS_USRL 0x18 // Z axis gyro offset (low byte) + #define MPU6000_SMPRT_DIV 0x19 #define MPU6000_CONFIG 0x1a @@ -145,6 +166,9 @@ /* Self test gyro is approximately 50°/s */ #define MPU6000_ST_GYRO(full_scale) ((int16_t) (((int32_t) 32767 * (int32_t) 50) / (full_scale))) +#define MPU6000_GYRO_FULLSCALE 2000 +#define MPU6000_ACCEL_FULLSCALE 16 + struct ao_mpu6000_sample { int16_t accel_x; int16_t accel_y; @@ -155,7 +179,25 @@ struct ao_mpu6000_sample { int16_t gyro_z; }; +extern struct ao_mpu6000_sample ao_mpu6000_current; + void ao_mpu6000_init(void); +/* Product ID Description for MPU6000 + * high 4 bits low 4 bits + * Product Name Product Revision + */ +#define MPU6000ES_REV_C4 0x14 /* 0001 0100 */ +#define MPU6000ES_REV_C5 0x15 /* 0001 0101 */ +#define MPU6000ES_REV_D6 0x16 /* 0001 0110 */ +#define MPU6000ES_REV_D7 0x17 /* 0001 0111 */ +#define MPU6000ES_REV_D8 0x18 /* 0001 1000 */ +#define MPU6000_REV_C4 0x54 /* 0101 0100 */ +#define MPU6000_REV_C5 0x55 /* 0101 0101 */ +#define MPU6000_REV_D6 0x56 /* 0101 0110 */ +#define MPU6000_REV_D7 0x57 /* 0101 0111 */ +#define MPU6000_REV_D8 0x58 /* 0101 1000 */ +#define MPU6000_REV_D9 0x59 /* 0101 1001 */ + #endif /* _AO_MPU6000_H_ */ diff --git a/src/drivers/ao_mr25.c b/src/drivers/ao_mr25.c new file mode 100644 index 00000000..53cbf9d7 --- /dev/null +++ b/src/drivers/ao_mr25.c @@ -0,0 +1,172 @@ +/* + * 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; + +/* + * MRAM is entirely random access; no erase operations are required, + * nor are reads or writes restricted to a particular alignment. + */ + +#define MR25_WREN 0x06 /* Write Enable */ +#define MR25_WRDI 0x04 /* Write Disable */ +#define MR25_RDSR 0x05 /* Read Status Register */ +#define MR25_WRSR 0x01 /* Write Status Register */ +#define MR25_READ 0x03 /* Read Data Bytes */ +#define MR25_WRITE 0x02 /* Write Data Bytes */ + +/* + * Status register bits + */ + +#define MR25_STATUS_SRWD (1 << 7) /* Status register write disable */ +#define MR25_STATUS_BP_MASK (3 << 2) /* Block protect bits */ +#define MR25_STATUS_BP_SHIFT (2) +#define MR25_STATUS_WEL (1 << 1) /* Write enable latch */ + +static __xdata uint8_t ao_mr25_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_mr25_set_page_address and then the + * first byte is used by ao_mr25_write_enable, neither of which touch + * those last three bytes. + */ + +static __xdata uint8_t ao_mr25_instruction[4]; + +#define MR25_SELECT() ao_spi_get_mask(AO_MR25_SPI_CS_PORT,(1 << AO_MR25_SPI_CS_PIN),AO_MR25_SPI_BUS, AO_SPI_SPEED_FAST) +#define MR25_DESELECT() ao_spi_put_mask(AO_MR25_SPI_CS_PORT,(1 << AO_MR25_SPI_CS_PIN),AO_MR25_SPI_BUS) + +/* + * 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_mr25_write_enable(void) +{ + MR25_SELECT(); + ao_mr25_instruction[0] = MR25_WREN; + ao_spi_send(&ao_mr25_instruction, 1, AO_MR25_SPI_BUS); + MR25_DESELECT(); +} + + +static void +ao_mr25_set_address(uint32_t pos) +{ + ao_mr25_instruction[1] = pos >> 16; + ao_mr25_instruction[2] = pos >> 8; + ao_mr25_instruction[3] = pos; +} + +/* + * Erase the specified sector (no-op for MRAM) + */ +uint8_t +ao_storage_erase(uint32_t pos) __reentrant +{ + if (pos >= ao_storage_total || pos + ao_storage_block > ao_storage_total) + return 0; + return 1; +} + +/* + * Write to flash + */ +uint8_t +ao_storage_device_write(uint32_t pos, __xdata void *d, uint16_t len) __reentrant +{ + if (pos >= ao_storage_total || pos + len > ao_storage_total) + return 0; + + ao_mutex_get(&ao_mr25_mutex); + + ao_mr25_set_address(pos); + ao_mr25_write_enable(); + + ao_mr25_instruction[0] = MR25_WRITE; + MR25_SELECT(); + ao_spi_send(ao_mr25_instruction, 4, AO_MR25_SPI_BUS); + ao_spi_send(d, len, AO_MR25_SPI_BUS); + MR25_DESELECT(); + + ao_mutex_put(&ao_mr25_mutex); + return 1; +} + +/* + * Read from flash + */ +uint8_t +ao_storage_device_read(uint32_t pos, __xdata void *d, uint16_t len) __reentrant +{ + if (pos >= ao_storage_total || pos + len > ao_storage_total) + return 0; + ao_mutex_get(&ao_mr25_mutex); + + ao_mr25_set_address(pos); + + ao_mr25_instruction[0] = MR25_READ; + MR25_SELECT(); + ao_spi_send(ao_mr25_instruction, 4, AO_MR25_SPI_BUS); + ao_spi_recv(d, len, AO_MR25_SPI_BUS); + MR25_DESELECT(); + + ao_mutex_put(&ao_mr25_mutex); + return 1; +} + +void +ao_storage_flush(void) __reentrant +{ +} + +void +ao_storage_setup(void) +{ +} + +void +ao_storage_device_info(void) __reentrant +{ + printf ("Detected chips 1 size %d\n", ao_storage_total >> 8); +} + +void +ao_storage_device_init(void) +{ + ao_storage_total = 512 * 1024; /* 4Mb */ + ao_storage_block = 256; + ao_storage_config = ao_storage_total - ao_storage_block; + ao_storage_unit = 256; + ao_spi_init_cs (AO_MR25_SPI_CS_PORT, (1 << AO_MR25_SPI_CS_PIN)); +} diff --git a/src/drivers/ao_ms5607.c b/src/drivers/ao_ms5607.c index 704b4583..bd57400e 100644 --- a/src/drivers/ao_ms5607.c +++ b/src/drivers/ao_ms5607.c @@ -17,9 +17,9 @@ #include <ao.h> #include <ao_exti.h> -#include "ao_ms5607.h" +#include <ao_ms5607.h> -#if HAS_MS5607 +#if HAS_MS5607 || HAS_MS5611 static struct ao_ms5607_prom ms5607_prom; static uint8_t ms5607_configured; @@ -27,12 +27,12 @@ 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); + ao_gpio_set(AO_MS5607_CS_PORT, AO_MS5607_CS_PIN, AO_MS5607_CS, 0); } static void ao_ms5607_stop(void) { - stm_gpio_set(AO_MS5607_CS_PORT, AO_MS5607_CS_PIN, 1); + ao_gpio_set(AO_MS5607_CS_PORT, AO_MS5607_CS_PIN, AO_MS5607_CS, 1); ao_spi_put(AO_MS5607_SPI_INDEX); } @@ -53,7 +53,6 @@ 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; @@ -89,10 +88,12 @@ ao_ms5607_prom_read(struct ao_ms5607_prom *prom) } crc = ao_ms5607_crc((uint8_t *) prom); if (crc != (((uint8_t *) prom)[15] & 0xf)) { +#if HAS_TASK printf ("MS5607 PROM CRC error (computed %x actual %x)\n", crc, (((uint8_t *) prom)[15] & 0xf)); flush(); -// ao_panic(AO_PANIC_SELF_TEST_MS5607); +#endif + ao_panic(AO_PANIC_SELF_TEST_MS5607); } #if __BYTE_ORDER == __LITTLE_ENDIAN @@ -105,7 +106,7 @@ ao_ms5607_prom_read(struct ao_ms5607_prom *prom) #endif } -static void +void ao_ms5607_setup(void) { if (ms5607_configured) @@ -115,34 +116,35 @@ ao_ms5607_setup(void) ao_ms5607_prom_read(&ms5607_prom); } -static uint8_t ao_ms5607_done; +static volatile 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); + ao_wakeup((void *) &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(); + ao_arch_block_interrupts(); while (!ao_ms5607_done) - ao_sleep(&ao_ms5607_done); - sei(); + ao_sleep((void *) &ao_ms5607_done); + ao_arch_release_interrupts(); #if AO_MS5607_PRIVATE_PINS stm_gpio_set(AO_MS5607_CS_PORT, AO_MS5607_CS_PIN, 1); #else @@ -158,50 +160,31 @@ ao_ms5607_get_sample(uint8_t cmd) { return ((uint32_t) reply[0] << 16) | ((uint32_t) reply[1] << 8) | (uint32_t) reply[2]; } +#ifndef AO_MS5607_BARO_OVERSAMPLE +#define AO_MS5607_BARO_OVERSAMPLE 2048 +#endif + +#ifndef AO_MS5607_TEMP_OVERSAMPLE +#define AO_MS5607_TEMP_OVERSAMPLE AO_MS5607_BARO_OVERSAMPLE +#endif + +#define token_paster(x,y) x ## y +#define token_evaluator(x,y) token_paster(x,y) + +#define AO_CONVERT_D1 token_evaluator(AO_MS5607_CONVERT_D1_, AO_MS5607_BARO_OVERSAMPLE) +#define AO_CONVERT_D2 token_evaluator(AO_MS5607_CONVERT_D2_, AO_MS5607_TEMP_OVERSAMPLE) + 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); + sample->pres = ao_ms5607_get_sample(AO_CONVERT_D1); + sample->temp = ao_ms5607_get_sample(AO_CONVERT_D2); } -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; - } +#include "ao_ms5607_convert.c" - value->pres = ((((int64_t) sample->pres * SENS) >> 21) - OFF) >> 15; - value->temp = TEMP; -} +#if HAS_TASK +struct ao_ms5607_sample ao_ms5607_current; static void ao_ms5607(void) @@ -209,7 +192,7 @@ ao_ms5607(void) ao_ms5607_setup(); for (;;) { - ao_ms5607_sample((struct ao_ms5607_sample *) &ao_data_ring[ao_data_head].ms5607_raw); + ao_ms5607_sample(&ao_ms5607_current); ao_arch_critical( AO_DATA_PRESENT(AO_DATA_MS5607); AO_DATA_WAIT(); @@ -235,14 +218,11 @@ ao_ms5607_info(void) 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); + ao_ms5607_convert(&ao_ms5607_current, &value); + printf ("Pressure: %8u %8d\n", ao_ms5607_current.pres, value.pres); + printf ("Temperature: %8u %8d\n", ao_ms5607_current.temp, value.temp); printf ("Altitude: %ld\n", ao_pa_to_altitude(value.pres)); } @@ -250,15 +230,18 @@ __code struct ao_cmds ao_ms5607_cmds[] = { { ao_ms5607_dump, "B\0Display MS5607 data" }, { 0, NULL }, }; +#endif /* HAS_TASK */ 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"); +#if HAS_TASK + ao_cmd_register(&ao_ms5607_cmds[0]); + ao_add_task(&ao_ms5607_task, ao_ms5607, "ms5607"); +#endif /* Configure the MISO pin as an interrupt; when the * conversion is complete, the MS5607 will raise this @@ -269,12 +252,14 @@ ao_ms5607_init(void) AO_EXTI_MODE_RISING, ao_ms5607_isr); +#ifdef STM_MODER_ALTERNATE /* 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 } #endif diff --git a/src/drivers/ao_ms5607.h b/src/drivers/ao_ms5607.h index e9c364d9..b2f98a59 100644 --- a/src/drivers/ao_ms5607.h +++ b/src/drivers/ao_ms5607.h @@ -56,6 +56,11 @@ struct ao_ms5607_value { int32_t temp; /* in °C * 100 */ }; +extern struct ao_ms5607_sample ao_ms5607_current; + +void +ao_ms5607_setup(void); + void ao_ms5607_init(void); diff --git a/src/drivers/ao_ms5607_convert.c b/src/drivers/ao_ms5607_convert.c new file mode 100644 index 00000000..e61d19ed --- /dev/null +++ b/src/drivers/ao_ms5607_convert.c @@ -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. + */ + +#include <ao_ms5607.h> + +void +ao_ms5607_convert(struct ao_ms5607_sample *sample, struct ao_ms5607_value *value) +{ + int32_t dT; + int32_t TEMP; + int64_t OFF; + int64_t SENS; + + dT = sample->temp - ((int32_t) ms5607_prom.tref << 8); + + TEMP = 2000 + (((int64_t) dT * ms5607_prom.tempsens) >> 23); + +#if HAS_MS5611 + OFF = ((int64_t) ms5607_prom.off << 16) + (((int64_t) ms5607_prom.tco * dT) >> 7); + SENS = ((int64_t) ms5607_prom.sens << 15) + (((int64_t) ms5607_prom.tcs * dT) >> 8); +#else + 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); +#endif + + 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; +} diff --git a/src/drivers/ao_packet.c b/src/drivers/ao_packet.c index d813b25f..8cdf85a9 100644 --- a/src/drivers/ao_packet.c +++ b/src/drivers/ao_packet.c @@ -21,16 +21,16 @@ __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 __xdata uint8_t tx_data[AO_PACKET_MAX]; +static __xdata uint8_t rx_data[AO_PACKET_MAX]; static __pdata uint8_t rx_seq; __xdata struct ao_task ao_packet_task; __xdata uint8_t ao_packet_enable; +__xdata uint8_t ao_packet_restart; #if PACKET_HAS_MASTER __xdata uint8_t ao_packet_master_sleeping; -__xdata uint8_t ao_packet_last_rssi; #endif void @@ -61,7 +61,7 @@ ao_packet_recv(void) #ifdef AO_LED_GREEN ao_led_on(AO_LED_GREEN); #endif - dma_done = ao_radio_recv(&ao_rx_packet, sizeof (struct ao_packet_recv)); + dma_done = ao_radio_recv(&ao_rx_packet, sizeof (struct ao_packet_recv), 0); #ifdef AO_LED_GREEN ao_led_off(AO_LED_GREEN); #endif @@ -84,9 +84,6 @@ ao_packet_recv(void) 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 */ @@ -106,7 +103,8 @@ ao_packet_recv(void) /* 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) && + if ((ao_rx_packet.packet.seq == (uint8_t) (rx_seq + (uint8_t) 1) || + ao_packet_restart) && ao_packet_rx_used == ao_packet_rx_len) { /* Copy data to the receive data buffer and set up the @@ -126,6 +124,7 @@ ao_packet_recv(void) ao_wakeup(&ao_stdin_ready); } } + ao_packet_restart = 0; /* If the other side has seen the latest data we queued, * wake up any task waiting to send data and let them go again @@ -152,6 +151,9 @@ ao_packet_flush(void) void ao_packet_putchar(char c) __reentrant { + /* No need to block interrupts, all variables here + * are only manipulated in task context + */ while (ao_packet_tx_used == AO_PACKET_MAX && ao_packet_enable) { #if PACKET_HAS_MASTER ao_packet_flush(); @@ -163,8 +165,9 @@ ao_packet_putchar(char c) __reentrant tx_data[ao_packet_tx_used++] = c; } -char -ao_packet_pollchar(void) __critical +/* May be called with interrupts blocked */ +int +_ao_packet_pollchar(void) { if (!ao_packet_enable) return AO_READ_AGAIN; diff --git a/src/drivers/ao_packet_master.c b/src/drivers/ao_packet_master.c index e97a6648..d6c99cbd 100644 --- a/src/drivers/ao_packet_master.c +++ b/src/drivers/ao_packet_master.c @@ -18,10 +18,15 @@ #include "ao.h" static char -ao_packet_getchar(void) __critical +ao_packet_getchar(void) { - char c; - while ((c = ao_packet_pollchar()) == AO_READ_AGAIN) { + int c; + + /* No need to block interrupts in this function as + * all packet variables are only modified from task + * context, not an interrupt handler + */ + while ((c = _ao_packet_pollchar()) == AO_READ_AGAIN) { if (!ao_packet_enable) break; if (ao_packet_master_sleeping) @@ -35,7 +40,7 @@ ao_packet_getchar(void) __critical static void ao_packet_echo(void) __reentrant { - char c; + int c; while (ao_packet_enable) { c = ao_packet_getchar(); if (c != AO_READ_AGAIN) @@ -140,7 +145,7 @@ ao_packet_forward(void) __reentrant static void ao_packet_signal(void) { - printf ("RSSI: %d\n", AO_RSSI_FROM_RADIO(ao_packet_last_rssi)); + printf ("RSSI: %d\n", ao_radio_rssi); } __code struct ao_cmds ao_packet_master_cmds[] = { diff --git a/src/drivers/ao_packet_slave.c b/src/drivers/ao_packet_slave.c index fd5d443e..e75df0d6 100644 --- a/src/drivers/ao_packet_slave.c +++ b/src/drivers/ao_packet_slave.c @@ -22,6 +22,7 @@ ao_packet_slave(void) { ao_tx_packet.addr = ao_serial_number; ao_tx_packet.len = AO_PACKET_SYN; + ao_packet_restart = 1; while (ao_packet_enable) { if (ao_packet_recv()) { ao_xmemcpy(&ao_tx_packet.callsign, &ao_rx_packet.packet.callsign, AO_MAX_CALLSIGN); @@ -58,7 +59,7 @@ ao_packet_slave_stop(void) void ao_packet_slave_init(uint8_t enable) { - ao_add_stdio(ao_packet_pollchar, + ao_add_stdio(_ao_packet_pollchar, ao_packet_putchar, NULL); if (enable) diff --git a/src/drivers/ao_pad.c b/src/drivers/ao_pad.c index 55e6289d..6cec98ab 100644 --- a/src/drivers/ao_pad.c +++ b/src/drivers/ao_pad.c @@ -40,24 +40,62 @@ static __pdata uint8_t ao_pad_debug; #endif static void +ao_siren(uint8_t v) +{ +#ifdef AO_SIREN + ao_gpio_set(AO_SIREN_PORT, AO_SIREN_PIN, AO_SIREN, v); +#else + ao_beep(v ? AO_BEEP_MID : 0); +#endif +} + +static void +ao_strobe(uint8_t v) +{ +#ifdef AO_STROBE + ao_gpio_set(AO_STROBE_PORT, AO_STROBE_PIN, AO_STROBE, v); +#endif +} + +static void ao_pad_run(void) { + uint8_t pins; + 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; + pins = 0; +#if AO_PAD_NUM > 0 + if (ao_pad_ignite & (1 << 0)) + pins |= (1 << AO_PAD_PIN_0); +#endif +#if AO_PAD_NUM > 1 + if (ao_pad_ignite & (1 << 1)) + pins |= (1 << AO_PAD_PIN_1); +#endif +#if AO_PAD_NUM > 2 + if (ao_pad_ignite & (1 << 2)) + pins |= (1 << AO_PAD_PIN_2); +#endif +#if AO_PAD_NUM > 3 + if (ao_pad_ignite & (1 << 3)) + pins |= (1 << AO_PAD_PIN_3); +#endif + AO_PAD_PORT = (AO_PAD_PORT & (~AO_PAD_ALL_PINS)) | pins; 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 +#define AO_PAD_ARM_SIREN_INTERVAL 200 static void ao_pad_monitor(void) @@ -139,22 +177,27 @@ ao_pad_monitor(void) prev = cur; } + if (ao_pad_armed && (int16_t) (ao_time() - ao_pad_arm_time) > AO_PAD_ARM_TIME) + ao_pad_armed = 0; + if (ao_pad_armed) { + ao_strobe(1); if (sample & 2) - ao_beep(AO_BEEP_HIGH); + ao_siren(1); else - ao_beep(AO_BEEP_LOW); + ao_siren(0); 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; + arm_beep_time = AO_PAD_ARM_SIREN_INTERVAL; beeping = 1; - ao_beep(AO_BEEP_HIGH); + ao_siren(1); } --arm_beep_time; } else if (beeping) { beeping = 0; - ao_beep(0); + ao_siren(0); + ao_strobe(0); } } } @@ -181,7 +224,6 @@ 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); @@ -197,9 +239,6 @@ ao_pad(void) 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) { @@ -207,7 +246,7 @@ ao_pad(void) break; } - if (command.channels & ~(AO_PAD_ALL_PINS)) + if (command.channels & ~(AO_PAD_ALL_CHANNELS)) break; time_difference = command.tick - ao_time(); @@ -232,7 +271,7 @@ ao_pad(void) query.tick = ao_time(); query.box = ao_pad_box; - query.channels = AO_PAD_ALL_PINS; + query.channels = AO_PAD_ALL_CHANNELS; 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, @@ -349,6 +388,12 @@ ao_pad_init(void) #if AO_PAD_NUM > 3 ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_3, AO_PAD_3, 0); #endif +#ifdef AO_STROBE + ao_enable_output(AO_STROBE_PORT, AO_STROBE_PIN, AO_STROBE, 0); +#endif +#ifdef AO_SIREN + ao_enable_output(AO_SIREN_PORT, AO_SIREN_PIN, AO_SIREN, 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"); diff --git a/src/drivers/ao_radio_master.c b/src/drivers/ao_radio_master.c index 73ac3c03..128fcf32 100644 --- a/src/drivers/ao_radio_master.c +++ b/src/drivers/ao_radio_master.c @@ -53,7 +53,7 @@ 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); + AO_SPI_SPEED_2MHz); } static void @@ -75,7 +75,7 @@ ao_radio_master_send(void) */ PRINTD("Waiting radio ready\n"); - cli(); + ao_arch_block_interrupts(); ao_radio_ready = ao_gpio_get(AO_RADIO_INT_PORT, AO_RADIO_INT_PIN, AO_RADIO_INT); ret = 0; @@ -84,7 +84,7 @@ ao_radio_master_send(void) if (ret) break; } - sei(); + ao_arch_release_interrupts(); if (ret) return 0; @@ -99,11 +99,11 @@ ao_radio_master_send(void) AO_RADIO_SPI_BUS); ao_radio_master_stop(); PRINTD("waiting for send done %d\n", ao_radio_done); - cli(); + ao_arch_block_interrupts(); while (!ao_radio_done) if (ao_sleep((void *) &ao_radio_done)) break; - sei(); + ao_arch_release_interrupts(); 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; } @@ -156,7 +156,7 @@ ao_radio_send(const void *d, uint8_t size) uint8_t -ao_radio_recv(__xdata void *d, uint8_t size) +ao_radio_recv(__xdata void *d, uint8_t size, uint8_t timeout) { int8_t ret; uint8_t recv; @@ -166,6 +166,7 @@ ao_radio_recv(__xdata void *d, uint8_t size) ao_radio_get(AO_RADIO_SPI_RECV, 0); ao_radio_spi_request.recv_len = size; + ao_radio_spi_request.timeout = timeout; recv = ao_radio_master_send(); if (!recv) { ao_radio_put(); diff --git a/src/drivers/ao_radio_slave.c b/src/drivers/ao_radio_slave.c index 1d1f16fe..9a0612e5 100644 --- a/src/drivers/ao_radio_slave.c +++ b/src/drivers/ao_radio_slave.c @@ -65,7 +65,8 @@ ao_radio_slave_spi(void) 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_radio_spi_request.recv_len, + ao_radio_spi_request.timeout); ao_led_off(AO_LED_RX); ao_radio_spi_reply.rssi = 0; ao_spi_send(&ao_radio_spi_reply, diff --git a/src/drivers/ao_rfpa0133.c b/src/drivers/ao_rfpa0133.c new file mode 100644 index 00000000..a98e261a --- /dev/null +++ b/src/drivers/ao_rfpa0133.c @@ -0,0 +1,47 @@ +/* + * Copyright © 2013 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 void +ao_rfpa0133_set_power(uint8_t power) +{ + ao_gpio_set(AO_PA_GAIN_8_GPIO, AO_PA_GAIN_8_PIN, AO_PA_GAIN_8, power & 1); + ao_gpio_set(AO_PA_GAIN_16_GPIO, AO_PA_GAIN_16_PIN, AO_PA_GAIN_16, (power >> 1) & 1); +} + +void +ao_radio_pa_on(void) +{ + ao_rfpa0133_set_power(ao_config.radio_amp); + ao_gpio_set(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 1); +} + +void +ao_radio_pa_off(void) +{ + ao_gpio_set(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 0); + ao_rfpa0133_set_power(0); +} + +void +ao_radio_pa_init(void) +{ + ao_enable_output(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 0); + ao_enable_output(AO_PA_GAIN_8_GPIO, AO_PA_GAIN_8_PIN, AO_PA_GAIN_8, 0); + ao_enable_output(AO_PA_GAIN_16_GPIO, AO_PA_GAIN_16_PIN, AO_PA_GAIN_16, 0); +} diff --git a/src/drivers/ao_rfpa0133.h b/src/drivers/ao_rfpa0133.h new file mode 100644 index 00000000..2ba7f699 --- /dev/null +++ b/src/drivers/ao_rfpa0133.h @@ -0,0 +1,30 @@ +/* + * Copyright © 2013 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_RFPA0133_H_ +#define _AO_RFPA0133_H_ + +void +ao_rfpa0133_on(void); + +void +ao_rfpa0133_off(void); + +void +ao_rfpa0133_init(void); + +#endif /* _AO_RFPA0133_H_ */ diff --git a/src/drivers/ao_sdcard.c b/src/drivers/ao_sdcard.c new file mode 100644 index 00000000..7806bc19 --- /dev/null +++ b/src/drivers/ao_sdcard.c @@ -0,0 +1,707 @@ +/* + * Copyright © 2013 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_sdcard.h" + +#if HAS_RADIO +extern uint8_t ao_radio_mutex; +#define get_radio() ao_mutex_get(&ao_radio_mutex) +#define put_radio() ao_mutex_put(&ao_radio_mutex) +#else +#define get_radio() +#define put_radio() +#endif + +#define ao_sdcard_get_slow() do { get_radio(); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_250kHz); } while (0) +#define ao_sdcard_get() do { get_radio(); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_FAST); } while (0) +#define ao_sdcard_put() do { ao_spi_put(AO_SDCARD_SPI_BUS); put_radio(); } while (0) +#define ao_sdcard_send_fixed(d,l) ao_spi_send_fixed((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_send(d,l) ao_spi_send((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_recv(d,l) ao_spi_recv((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_select() ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,0) +#define ao_sdcard_deselect() ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,1) + +/* Include SD card commands */ +#define SDCARD_DEBUG 0 + +/* Spew SD tracing */ +#define SDCARD_TRACE 0 + +/* Emit error and warning messages */ +#define SDCARD_WARN 0 + +static uint8_t initialized; +static uint8_t present; +static uint8_t mutex; +static enum ao_sdtype sdtype; + +#define ao_sdcard_lock() ao_mutex_get(&mutex) +#define ao_sdcard_unlock() ao_mutex_put(&mutex) + +#if SDCARD_TRACE +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +#if SDCARD_WARN +#define WARN(...) printf(__VA_ARGS__) +#else +#define WARN(...) +#endif + +#define later(x,y) ((int16_t) ((x) - (y)) >= 0) + +/* + * Wait while the card is busy. The card will return a stream of 0xff + * when it is ready to accept a command + */ + +static uint8_t +ao_sdcard_wait_busy(void) +{ + uint16_t timeout = ao_time() + SDCARD_BUSY_TIMEOUT; + uint8_t reply; + for (;;) { + ao_sdcard_recv(&reply, 1); + DBG("\t\twait busy %02x\n", reply); + if (reply == 0xff) + break; + if (later(ao_time(), timeout)) { + WARN("wait busy timeout\n"); + return 0; + } + } + return 1; +} + + +/* + * Send an SD command and await the status reply + */ + +static uint8_t +ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg) +{ + uint8_t data[6]; + uint8_t reply; + int i; + uint16_t timeout; + + DBG ("\tsend_cmd %d arg %08x\n", cmd, arg); + + /* Wait for the card to not be busy */ + if (cmd != SDCARD_GO_IDLE_STATE) { + if (!ao_sdcard_wait_busy()) + return SDCARD_STATUS_TIMEOUT; + } + + data[0] = cmd & 0x3f | 0x40; + data[1] = arg >> 24; + data[2] = arg >> 16; + data[3] = arg >> 8; + data[4] = arg; + if (cmd == SDCARD_GO_IDLE_STATE) + data[5] = 0x95; /* Valid for 0 arg */ + else if (cmd == SDCARD_SEND_IF_COND) + data[5] = 0x87; /* Valid for 0x1aa arg */ + else + data[5] = 0xff; /* no CRC */ + ao_sdcard_send(data, 6); + + /* The first reply byte will be the status, + * which must have the high bit clear + */ + timeout = ao_time() + SDCARD_CMD_TIMEOUT; + for (;;) { + ao_sdcard_recv(&reply, 1); + DBG ("\t\tgot byte %02x\n", reply); + if ((reply & 0x80) == 0) + break; + if (later(ao_time(), timeout)) { + WARN("send_cmd %02x timeout\n", cmd); + return SDCARD_STATUS_TIMEOUT; + } + } +#if SDCARD_WARN + if (reply != SDCARD_STATUS_READY_STATE && reply != SDCARD_STATUS_IDLE_STATE) + WARN("send_cmd %d failed %02x\n", cmd, reply); +#endif + return reply; +} + +/* + * Retrieve any reply, discarding the trailing CRC byte + */ +static void +ao_sdcard_recv_reply(uint8_t *reply, int len) +{ + uint8_t discard; + + if (len) + ao_sdcard_recv(reply, len); + /* trailing byte */ + ao_sdcard_recv(&discard, 1); +} + +/* + * Switch to 'idle' state. This is used to get the card into SPI mode + */ +static uint8_t +ao_sdcard_go_idle_state(void) +{ + uint8_t ret; + + DBG ("go_idle_state\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_GO_IDLE_STATE, 0); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + DBG ("\tgo_idle_state status %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_send_op_cond(void) +{ + uint8_t ret; + + DBG ("send_op_cond\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SEND_OP_COND, 0); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + DBG ("\tsend_op_cond %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_send_if_cond(uint32_t arg, uint8_t send_if_cond_response[4]) +{ + uint8_t ret; + + DBG ("send_if_cond\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SEND_IF_COND, arg); + if (ret != SDCARD_STATUS_IDLE_STATE) { + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret; + } + ao_sdcard_recv_reply(send_if_cond_response, 4); + DBG ("send_if_cond status %02x response %02x %02x %02x %02x\n", + ret, + send_if_cond_response[0], + send_if_cond_response[1], + send_if_cond_response[2], + send_if_cond_response[3]); + ao_sdcard_deselect(); + return ret; +} + +/* + * _ao_sdcard_send_status + * + * Get the 2-byte status value. + * + * Called from other functions with CS held low already, + * hence prefixing the name with '_' + */ +static uint16_t +_ao_sdcard_send_status(void) +{ + uint8_t ret; + uint8_t extra; + + DBG ("send_status\n"); + ret = ao_sdcard_send_cmd(SDCARD_SEND_STATUS, 0); + ao_sdcard_recv_reply(&extra, 1); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret | (extra << 8); +} + +/* + * ao_sdcard_set_blocklen + * + * Set the block length for future read and write commands + */ +static uint8_t +ao_sdcard_set_blocklen(uint32_t blocklen) +{ + uint8_t ret; + + DBG ("set_blocklen %d\n", blocklen); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SET_BLOCKLEN, blocklen); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret; +} + +/* + * _ao_sdcard_app_cmd + * + * Send the app command prefix + * + * Called with the CS held low, hence + * the '_' prefix + */ +static uint8_t +_ao_sdcard_app_cmd(void) +{ + uint8_t ret; + + DBG ("app_cmd\n"); + ret = ao_sdcard_send_cmd(SDCARD_APP_CMD, 0); + ao_sdcard_recv_reply(NULL, 0); + DBG ("\tapp_cmd status %02x\n"); + return ret; +} + +static uint8_t +ao_sdcard_app_send_op_cond(uint32_t arg) +{ + uint8_t ret; + + DBG("send_op_comd\n"); + ao_sdcard_select(); + ret = _ao_sdcard_app_cmd(); + if (ret != SDCARD_STATUS_IDLE_STATE) + goto bail; + ret = ao_sdcard_send_cmd(SDCARD_APP_SEND_OP_COMD, arg); + ao_sdcard_recv_reply(NULL, 0); +bail: + ao_sdcard_deselect(); + DBG ("\tapp_send_op_cond status %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_read_ocr(uint8_t read_ocr_response[4]) +{ + uint8_t ret; + + DBG ("read_ocr\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_READ_OCR, 0); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tread_ocr failed %02x\n", ret); + else { + ao_sdcard_recv_reply(read_ocr_response, 4); + DBG ("\tread_ocr status %02x response %02x %02x %02x %02x\n", ret, + read_ocr_response[0], read_ocr_response[1], + read_ocr_response[2], read_ocr_response[3]); + } + ao_sdcard_deselect(); + return ret; +} + +/* + * Follow the flow-chart defined by the SD group to + * initialize the card and figure out what kind it is + */ +static void +ao_sdcard_setup(void) +{ + int i; + uint8_t ret; + uint8_t response[10]; + + DBG ("Testing sdcard\n"); + + ao_sdcard_get_slow(); + /* + * min 74 clocks with CS high + */ + ao_sdcard_send_fixed(0xff, 10); + + /* Reset the card and get it into SPI mode */ + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) + break; + } + if (i == SDCARD_IDLE_RETRY) + goto bail; + + /* Figure out what kind of card we have */ + sdtype = ao_sdtype_unknown; + + if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { + uint32_t arg = 0; + uint8_t sdver2 = 0; + + /* Check for SD version 2 */ + if ((response[2] & 0xf) == 1 && response[3] == 0xaa) { + arg = 0x40000000; + sdver2 = 1; + } + + for (i = 0; i < SDCARD_OP_COND_RETRY; i++) { + ao_delay(AO_MS_TO_TICKS(10)); + ret = ao_sdcard_app_send_op_cond(arg); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) { + /* MMC */ + for (i = 0; i < SDCARD_OP_COND_RETRY; i++) { + ao_delay(AO_MS_TO_TICKS(10)); + ret = ao_sdcard_send_op_cond(); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + sdtype = ao_sdtype_mmc3; + } else { + /* SD */ + if (sdver2 != 0) { + ret = ao_sdcard_read_ocr(response); + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + if ((response[0] & 0xc0) == 0xc0) + sdtype = ao_sdtype_sd2block; + else + sdtype = ao_sdtype_sd2byte; + } else { + sdtype = ao_sdtype_sd1; + } + } + + /* For everything but SDHC cards, set the block length */ + if (sdtype != ao_sdtype_sd2block) { + ret = ao_sdcard_set_blocklen(512); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("set_blocklen failed, ignoring\n"); + } + } + + DBG ("SD card detected, type %d\n", sdtype); +bail: + ao_sdcard_put(); +} + +static uint8_t +_ao_sdcard_reset(void) +{ + int i; + uint8_t ret; + uint8_t response[10]; + + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) + break; + } + if (i == SDCARD_IDLE_RETRY) { + ret = 0x3f; + goto bail; + } + + /* Follow the setup path to get the card out of idle state and + * up and running again + */ + if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { + uint32_t arg = 0; + uint8_t sdver2 = 0; + + /* Check for SD version 2 */ + if ((response[2] & 0xf) == 1 && response[3] == 0xaa) { + arg = 0x40000000; + sdver2 = 1; + } + + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + ret = ao_sdcard_app_send_op_cond(arg); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + + if (ret != SDCARD_STATUS_READY_STATE) { + /* MMC */ + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + ret = ao_sdcard_send_op_cond(); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + } + + /* For everything but SDHC cards, set the block length */ + if (sdtype != ao_sdtype_sd2block) { + ret = ao_sdcard_set_blocklen(512); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("set_blocklen failed, ignoring\n"); + } + } +bail: + return ret; +} + +/* + * The card will send 0xff until it is ready to send + * the data block at which point it will send the START_BLOCK + * marker followed by the data. This function waits while + * the card is sending 0xff + */ +static uint8_t +ao_sdcard_wait_block_start(void) +{ + uint8_t v; + uint16_t timeout = ao_time() + SDCARD_BLOCK_TIMEOUT; + + DBG ("\twait_block_start\n"); + for (;;) { + ao_sdcard_recv(&v, 1); + DBG("\t\trecv %02x\n", v); + if (v != 0xff) + break; + if (later(ao_time(), timeout)) { + printf ("wait block start timeout\n"); + return 0xff; + } + } + return v; +} + +/* + * Read a block of 512 bytes from the card + */ +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data) +{ + uint8_t ret; + uint8_t start_block; + uint8_t crc[2]; + int tries; + + ao_sdcard_lock(); + if (!initialized) { + ao_sdcard_setup(); + initialized = 1; + if (sdtype != ao_sdtype_unknown) + present = 1; + } + if (!present) { + ao_sdcard_unlock(); + return 0; + } + DBG("read block %d\n", block); + if (sdtype != ao_sdtype_sd2block) + block <<= 9; + + ao_sdcard_get(); + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); + + ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) { + uint16_t status; + WARN ("read block command failed %d status %02x\n", block, ret); + status = _ao_sdcard_send_status(); + WARN ("\tstatus now %04x\n", status); + goto bail; + } + + ao_sdcard_send_fixed(0xff, 1); + + /* Wait for the data start block marker */ + start_block = ao_sdcard_wait_block_start(); + if (start_block != SDCARD_DATA_START_BLOCK) { + WARN ("wait block start failed %02x\n", start_block); + ret = 0x3f; + goto bail; + } + + ao_sdcard_recv(data, 512); + ao_sdcard_recv(crc, 2); + bail: + ao_sdcard_deselect(); + if (ret == SDCARD_STATUS_READY_STATE) + break; + if (ret == SDCARD_STATUS_IDLE_STATE) { + ret = _ao_sdcard_reset(); + if (ret != SDCARD_STATUS_READY_STATE) + break; + } + } + ao_sdcard_put(); + ao_sdcard_unlock(); + +#if SDCARD_WARN + if (ret != SDCARD_STATUS_READY_STATE) + WARN("read failed\n"); + else if (tries) + WARN("took %d tries to read %d\n", tries + 1, block); +#endif + + DBG("read %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + return ret == SDCARD_STATUS_READY_STATE; +} + +/* + * Write a block of 512 bytes to the card + */ +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data) +{ + uint8_t ret; + uint8_t response[1]; + uint8_t start_block[8]; + uint16_t status; + static uint8_t check_data[512]; + int i; + int tries; + + ao_sdcard_lock(); + if (!initialized) { + ao_sdcard_setup(); + initialized = 1; + if (sdtype != ao_sdtype_unknown) + present = 1; + } + if (!present) { + ao_sdcard_unlock(); + return 0; + } + DBG("write block %d\n", block); + if (sdtype != ao_sdtype_sd2block) + block <<= 9; + + ao_sdcard_get(); + + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); + + ret = ao_sdcard_send_cmd(SDCARD_WRITE_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + + /* Write a pad byte followed by the data start block marker */ + start_block[0] = 0xff; + start_block[1] = SDCARD_DATA_START_BLOCK; + ao_sdcard_send(start_block, 2); + + /* Send the data */ + ao_sdcard_send(data, 512); + + /* Fake the CRC */ + ao_sdcard_send_fixed(0xff, 2); + + /* See if the card liked the data */ + ao_sdcard_recv(response, sizeof (response)); + if ((response[0] & SDCARD_DATA_RES_MASK) != SDCARD_DATA_RES_ACCEPTED) { + int i; + WARN("Data not accepted, response"); + for (i = 0; i < sizeof (response); i++) + WARN(" %02x", response[i]); + WARN("\n"); + ret = 0x3f; + goto bail; + } + + /* Wait for the bus to go idle (should be done with an interrupt?) */ + if (!ao_sdcard_wait_busy()) { + ret = 0x3f; + goto bail; + } + + /* Check the current status after the write completes */ + status = _ao_sdcard_send_status(); + if ((status & 0xff) != SDCARD_STATUS_READY_STATE) { + WARN ("send status after write %04x\n", status); + ret = status & 0xff; + goto bail; + } + bail: + ao_sdcard_deselect(); + DBG("write %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + if (ret == SDCARD_STATUS_READY_STATE) + break; + } + ao_sdcard_put(); + ao_sdcard_unlock(); + if (tries) + WARN("took %d tries to write %d\n", tries + 1, block); + + return ret == SDCARD_STATUS_READY_STATE; +} + +#if SDCARD_DEBUG +static uint8_t test_data[512]; + +static void +ao_sdcard_test_read(void) +{ + int i; + + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + + for (i = 0; i < 100; i++) { + printf ("."); flush(); + if (!ao_sdcard_read_block(ao_cmd_lex_u32+i, test_data)) { + printf ("read error %d\n", i); + return; + } + } + printf ("data:"); + for (i = 0; i < 18; i++) + printf (" %02x", test_data[i]); + printf ("\n"); +} + +static void +ao_sdcard_test_write(void) +{ + int i; + printf ("data:"); + for (i = 0; i < 16; i++) { + test_data[i]++; + printf (" %02x", test_data[i]); + } + printf ("\n"); + if (!ao_sdcard_write_block(1, test_data)) { + printf ("write error\n"); + return; + } +} + +static const struct ao_cmds ao_sdcard_cmds[] = { + { ao_sdcard_test_read, "x\0Test read" }, + { ao_sdcard_test_write, "y\0Test read" }, + { 0, NULL }, +}; +#endif + +void +ao_sdcard_init(void) +{ + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_SCK_PIN, STM_PUPDR_PULL_UP); + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MISO_PIN, STM_PUPDR_PULL_UP); + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MOSI_PIN, STM_PUPDR_PULL_UP); + ao_spi_init_cs(AO_SDCARD_SPI_CS_PORT, (1 << AO_SDCARD_SPI_CS_PIN)); +#if SDCARD_DEBUG + ao_cmd_register(&ao_sdcard_cmds[0]); +#endif +} diff --git a/src/drivers/ao_sdcard.h b/src/drivers/ao_sdcard.h new file mode 100644 index 00000000..50b70c73 --- /dev/null +++ b/src/drivers/ao_sdcard.h @@ -0,0 +1,82 @@ +/* + * Copyright © 2013 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_SDCARD_H_ +#define _AO_SDCARD_H_ + +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data); + +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data); + +void +ao_sdcard_init(void); + +/* Commands */ +#define SDCARD_GO_IDLE_STATE 0 +#define SDCARD_SEND_OP_COND 1 +#define SDCARD_SEND_IF_COND 8 +#define SDCARD_SEND_CSD 9 +#define SDCARD_SEND_CID 10 +#define SDCARD_SEND_STATUS 13 +#define SDCARD_SET_BLOCKLEN 16 +#define SDCARD_READ_BLOCK 17 +#define SDCARD_WRITE_BLOCK 24 +#define SDCARD_WRITE_MULTIPLE_BLOCK 25 +#define SDCARD_ERASE_WR_BLK_START 32 +#define SDCARD_ERASE_WR_BLK_END 33 +#define SDCARD_ERASE 38 +#define SDCARD_APP_CMD 55 +#define SDCARD_READ_OCR 58 + +/* App commands */ +#define SDCARD_APP_SET_WR_BLK_ERASE_COUNT 23 +#define SDCARD_APP_SEND_OP_COMD 41 + +/* Status */ +#define SDCARD_STATUS_READY_STATE 0x00 +#define SDCARD_STATUS_IDLE_STATE 0x01 +#define SDCARD_STATUS_ERASE_RESET 0x02 +#define SDCARD_STATUS_ILLEGAL_COMMAND 0x04 +#define SDCARD_STATUS_COM_CRC_ERROR 0x08 +#define SDCARD_STATUS_ERASE_SEQ_ERROR 0x10 +#define SDCARD_STATUS_ADDRESS_ERROR 0x20 +#define SDCARD_STATUS_PARAMETER_ERROR 0x40 +#define SDCARD_STATUS_TIMEOUT 0xff + +#define SDCARD_DATA_START_BLOCK 0xfe +#define SDCARD_STOP_TRAN_TOKEN 0xfd +#define SDCARD_WRITE_MULTIPLE_TOKEN 0xfc +#define SDCARD_DATA_RES_MASK 0x1f +#define SDCARD_DATA_RES_ACCEPTED 0x05 + +#define SDCARD_CMD_TIMEOUT AO_MS_TO_TICKS(20) +#define SDCARD_BUSY_TIMEOUT AO_MS_TO_TICKS(20) +#define SDCARD_BLOCK_TIMEOUT AO_MS_TO_TICKS(200) +#define SDCARD_IDLE_RETRY 10 +#define SDCARD_OP_COND_RETRY 10 + +enum ao_sdtype { + ao_sdtype_unknown, + ao_sdtype_mmc3, + ao_sdtype_sd1, + ao_sdtype_sd2byte, + ao_sdtype_sd2block, +}; + +#endif /* _AO_SDCARD_H_ */ diff --git a/src/drivers/ao_seven_segment.c b/src/drivers/ao_seven_segment.c index b3b5f878..961fbb84 100644 --- a/src/drivers/ao_seven_segment.c +++ b/src/drivers/ao_seven_segment.c @@ -34,134 +34,138 @@ * */ +#ifndef SEVEN_SEGMENT_DEBUG +#define SEVEN_SEGMENT_DEBUG 0 +#endif + 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 */ + (1 << 0) | + (1 << 1) | + (1 << 2) | + (0 << 3) | + (1 << 4) | + (1 << 5) | + (1 << 6), /* 0 */ + + (0 << 0) | + (0 << 1) | + (1 << 2) | + (0 << 3) | + (0 << 4) | + (1 << 5) | + (0 << 6), /* 1 */ + + (1 << 0) | + (0 << 1) | + (1 << 2) | + (1 << 3) | + (1 << 4) | + (0 << 5) | + (1 << 6), /* 2 */ + + (1 << 0) | + (0 << 1) | + (1 << 2) | + (1 << 3) | + (0 << 4) | + (1 << 5) | + (1 << 6), /* 3 */ + + (0 << 0) | + (1 << 1) | + (1 << 2) | + (1 << 3) | + (0 << 4) | + (1 << 5) | + (0 << 6), /* 4 */ + + (1 << 0) | + (1 << 1) | + (0 << 2) | + (1 << 3) | + (0 << 4) | + (1 << 5) | + (1 << 6), /* 5 */ + + (1 << 0) | + (1 << 1) | + (0 << 2) | + (1 << 3) | + (1 << 4) | + (1 << 5) | + (1 << 6), /* 6 */ + + (1 << 0) | + (0 << 1) | + (1 << 2) | + (0 << 3) | + (0 << 4) | + (1 << 5) | + (0 << 6), /* 7 */ + + (1 << 0) | + (1 << 1) | + (1 << 2) | + (1 << 3) | + (1 << 4) | + (1 << 5) | + (1 << 6), /* 8 */ + + (1 << 0) | + (1 << 1) | + (1 << 2) | + (1 << 3) | + (0 << 4) | + (1 << 5) | + (1 << 6), /* 9 */ + + (1 << 0) | + (1 << 1) | + (1 << 2) | + (1 << 3) | + (1 << 4) | + (1 << 5) | + (0 << 6), /* A */ + + (0 << 0) | + (1 << 1) | + (0 << 2) | + (1 << 3) | + (1 << 4) | + (1 << 5) | + (1 << 6), /* b */ + + (1 << 0) | + (1 << 1) | + (0 << 2) | + (0 << 3) | + (1 << 4) | + (0 << 5) | + (1 << 6), /* c */ + + (0 << 0) | + (0 << 1) | + (1 << 2) | + (1 << 3) | + (1 << 4) | + (1 << 5) | + (1 << 6), /* d */ + + (1 << 0) | + (1 << 1) | + (0 << 2) | + (1 << 3) | + (1 << 4) | + (0 << 5) | + (1 << 6), /* E */ + + (1 << 0) | + (1 << 1) | + (0 << 2) | + (1 << 3) | + (1 << 4) | + (0 << 5) | + (0 << 6), /* F */ }; void @@ -177,7 +181,7 @@ ao_seven_segment_set(uint8_t digit, uint8_t value) /* Check for decimal point */ if (value & 0x10) - segments |= (1 << AO_SEGMENT_7); + segments |= (1 << 7); } for (s = 0; s <= 7; s++) @@ -192,7 +196,7 @@ ao_seven_segment_clear(void) } -#if 0 +#if SEVEN_SEGMENT_DEBUG static void ao_seven_segment_show(void) { @@ -214,7 +218,7 @@ static const struct ao_cmds ao_seven_segment_cmds[] = { void ao_seven_segment_init(void) { -#if 0 +#if SEVEN_SEGMENT_DEBUG ao_cmd_register(ao_seven_segment_cmds); #endif } |
