From: Keith Packard Date: Tue, 14 Apr 2009 21:21:56 +0000 (-0700) Subject: Add eeprom driver and command loop X-Git-Tag: sn1-flight1~14 X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=commitdiff_plain;h=ac99982b10fd5772218660137ee21db9b90cd885 Add eeprom driver and command loop This involved adding dma control and a mutex implementation. Signed-off-by: Keith Packard --- diff --git a/25lc1024.h b/25lc1024.h new file mode 100644 index 00000000..7e48f0dd --- /dev/null +++ b/25lc1024.h @@ -0,0 +1,42 @@ +/* + * Copyright © 2009 Keith Packard + * + * 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. + */ + +/* Defines for the 25LC1024 1Mbit SPI Bus Serial EEPROM */ + +#ifndef _25LC1024_H_ +#define _25LC1024_H_ + +#define EE_READ 0x03 +#define EE_WRITE 0x02 +#define EE_WREN 0x06 +#define EE_WRDI 0x04 +#define EE_RDSR 0x05 +#define EE_WRSR 0x01 +#define EE_PE 0x42 +#define EE_SE 0xd8 +#define EE_CE 0xc7 +#define EE_RDID 0xab +#define EE_DPD 0xb9 + +#define EE_STATUS_WIP (1 << 0) +#define EE_STATUS_WEL (1 << 1) +#define EE_STATUS_BP0 (1 << 2) +#define EE_STATUS_BP1 (1 << 3) +#define EE_STATUS_WPEN (1 << 7) + +#endif /* _25LC1024_H_ */ diff --git a/Makefile b/Makefile index 9284ed25..909bb462 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,10 @@ SRC = \ ao_adc.c \ ao_beep.c \ ao_cmd.c \ + ao_dma.c \ + ao_ee.c \ ao_led.c \ + ao_mutex.c \ ao_panic.c \ ao_task.c \ ao_timer.c \ diff --git a/ao.h b/ao.h index 8a344cca..82678595 100644 --- a/ao.h +++ b/ao.h @@ -29,7 +29,7 @@ /* Stack runs from above the allocated __data space to 0xfe, which avoids * writing to 0xff as that triggers the stack overflow indicator */ -#define AO_STACK_START 0x4b +#define AO_STACK_START 0x75 #define AO_STACK_END 0xfe #define AO_STACK_SIZE (AO_STACK_END - AO_STACK_START + 1) @@ -37,10 +37,14 @@ struct ao_task { __xdata void *wchan; /* current wait channel (NULL if running) */ uint8_t stack_count; /* amount of saved stack */ + uint8_t task_id; /* index in the task array */ uint8_t stack[AO_STACK_SIZE]; /* saved stack */ }; +extern __xdata struct ao_task *__data ao_cur_task; + #define AO_NUM_TASKS 10 /* maximum number of tasks */ +#define AO_NO_TASK 0 /* no task id */ /* ao_task.c @@ -71,6 +75,9 @@ ao_start_scheduler(void); */ #define AO_PANIC_NO_TASK 1 /* AO_NUM_TASKS is not large enough */ +#define AO_PANIC_DMA 2 /* Attempt to start DMA while active */ +#define AO_PANIC_MUTEX 3 /* Mis-using mutex API */ +#define AO_PANIC_EE 4 /* Mis-using eeprom API */ /* Stop the operating system, beeping and blinking the reason */ void @@ -257,4 +264,87 @@ ao_usb_init(void); void ao_cmd_init(void); +/* + * ao_dma.c + */ + +/* Allocate a DMA channel. the 'done' parameter will be set to 1 + * when the dma is finished and will be used to wakeup any waiters + */ +uint8_t +ao_dma_alloc(__xdata uint8_t * done); + +/* Setup a DMA channel */ +void +ao_dma_set_transfer(uint8_t id, + void __xdata *srcaddr, + void __xdata *dstaddr, + uint16_t count, + uint8_t cfg0, + uint8_t cfg1); + +/* Start a DMA channel */ +void +ao_dma_start(uint8_t id); + +/* Manually trigger a DMA channel */ +void +ao_dma_trigger(uint8_t id); + +/* Abort a running DMA transfer */ +void +ao_dma_abort(uint8_t id); + +/* DMA interrupt routine */ +void +ao_dma_isr(void) interrupt 8; + +/* + * ao_mutex.c + */ + +void +ao_mutex_get(__xdata uint8_t *ao_mutex); + +void +ao_mutex_put(__xdata uint8_t *ao_mutex); + +/* + * ao_ee.c + */ + +/* + * We reserve the last block on the device for + * configuration space. Writes and reads in this + * area return errors. + */ + +#define AO_EE_BLOCK_SIZE ((uint16_t) (256)) +#define AO_EE_DEVICE_SIZE ((uint32_t) 128 * (uint32_t) 1024) +#define AO_EE_DATA_SIZE (AO_EE_DEVICE_SIZE - (uint32_t) AO_EE_BLOCK_SIZE) +#define AO_EE_CONFIG_BLOCK ((uint16_t) (AO_EE_DATA_SIZE / AO_EE_BLOCK_SIZE)) + +void +ao_ee_flush(void); + +/* Write to the eeprom */ +uint8_t +ao_ee_write(uint32_t pos, uint8_t *buf, uint16_t len); + +/* Read from the eeprom */ +uint8_t +ao_ee_read(uint32_t pos, uint8_t *buf, uint16_t len); + +/* Write the config block (at the end of the eeprom) */ +uint8_t +ao_ee_write_config(uint8_t *buf, uint16_t len); + +/* Read the config block (at the end of the eeprom) */ +uint8_t +ao_ee_read_config(uint8_t *buf, uint16_t len); + +/* Initialize the EEPROM code */ +void +ao_ee_init(void); + #endif /* _AO_H_ */ diff --git a/ao_cmd.c b/ao_cmd.c new file mode 100644 index 00000000..fd87f87e --- /dev/null +++ b/ao_cmd.c @@ -0,0 +1,596 @@ +/* + * Copyright © 2009 Keith Packard + * + * 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. + */ + +#include "ao.h" + +#define LEX_ERROR 1 +#define SYNTAX_ERROR 2 +#define SUCCESS 0 + +static __data uint16_t lex_i; +static __data uint8_t lex_c; +static __data uint8_t lex_status; +static __data uint8_t lex_echo; + +#define CMD_LEN 32 + +static __xdata uint8_t cmd_line[CMD_LEN]; +static __data uint8_t cmd_len; +static __data uint8_t cmd_i; + +void +putchar(char c) +{ + if (c == '\n') + ao_usb_putchar('\r'); + ao_usb_putchar((uint8_t) c); +} + +void +flush(void) +{ + ao_usb_flush(); +} + +char +getchar(void) +{ + return (char) ao_usb_getchar(); +} + +static void +put_string(char *s) +{ + uint8_t c; + while (c = *s++) + putchar(c); +} + +static void +readline(void) +{ + static uint8_t c; + if (lex_echo) + put_string("> "); + cmd_len = 0; + for (;;) { + flush(); + c = getchar(); + /* backspace/delete */ + if (c == '\010' || c == '\177') { + if (cmd_len != 0) { + if (lex_echo) + put_string("\010 \010"); + --cmd_len; + } + continue; + } + + /* ^U */ + if (c == '\025') { + while (cmd_len != 0) { + if (lex_echo) + put_string("\010 \010"); + --cmd_len; + } + continue; + } + + /* map CR to NL */ + if (c == '\r') + c = '\n'; + + if (c == '\n') { + if (lex_echo) + put_string ("\n"); + break; + } + + if (cmd_len >= CMD_LEN - 2) { + if (lex_echo) + putchar('\007'); + continue; + } + cmd_line[cmd_len++] = c; + if (lex_echo) + putchar(c); + } + cmd_line[cmd_len++] = '\n'; + cmd_line[cmd_len++] = '\0'; + cmd_i = 0; +} + +static void +lex(void) +{ + lex_c = '\n'; + if (cmd_i < cmd_len) + lex_c = cmd_line[cmd_i++]; +} + +static void +putnibble(uint8_t v) +{ + if (v < 10) + putchar(v + '0'); + else + putchar(v + ('a' - 10)); +} + +void +put16(uint16_t v) +{ + int8_t i; + for (i = 3; i >= 0; i--) + putnibble((v >> (i << 2)) & 0xf); +} + +void +put8(uint8_t v) +{ + putnibble((v >> 4) & 0xf); + putnibble(v & 0xf); +} + +#define NUM_LEN 7 + +void +puti(int i) +{ + static uint8_t __xdata num_buffer[NUM_LEN]; + uint8_t __xdata *num_ptr = num_buffer + NUM_LEN; + uint8_t neg = 0; + + *--num_ptr = '\0'; + if (i < 0) { + i = -i; + neg = 1; + } + do { + *--num_ptr = '0' + i % 10; + i /= 10; + } while (i); + if (neg) + *--num_ptr = '-'; + while (num_ptr != num_buffer) + *--num_ptr = ' '; + put_string(num_buffer); +} + + +static void +white(void) +{ + while (lex_c == ' ' || lex_c == '\t') + lex(); +} + +static void +hex(void) +{ + uint8_t r = LEX_ERROR; + + lex_i = 0; + white(); + for(;;) { + if ('0' <= lex_c && lex_c <= '9') + lex_i = (lex_i << 4) | (lex_c - '0'); + else if ('a' <= lex_c && lex_c <= 'f') + lex_i = (lex_i << 4) | (lex_c - 'a' + 10); + else if ('A' <= lex_c && lex_c <= 'F') + lex_i = (lex_i << 4) | (lex_c - 'A' + 10); + else + break; + r = SUCCESS; + lex(); + } + if (r != SUCCESS) + lex_status = r; +} + +#if 0 +static void +decimal(void) +{ + uint8_t r = LEX_ERROR; + + lex_i = 0; + white(); + for(;;) { + if ('0' <= lex_c && lex_c <= '9') + lex_i = (lex_i * 10 ) | (lex_c - '0'); + else + break; + r = SUCCESS; + lex(); + } + if (r != SUCCESS) + lex_status = r; +} +#endif + +static void +eol(void) +{ + while (lex_c != '\n') + lex(); +} + +static void +adc_dump(void) +{ + __xdata struct ao_adc packet; + ao_adc_get(&packet); + put_string("tick: "); + puti(packet.tick); + put_string(" accel: "); + puti(packet.accel >> 4); + put_string(" pres: "); + puti(packet.pres >> 4); + put_string(" temp: "); + puti(packet.temp >> 4); + put_string(" batt: "); + puti(packet.v_batt >> 4); + put_string(" drogue: "); + puti(packet.sense_d >> 4); + put_string(" main: "); + puti(packet.sense_m >> 4); + put_string("\n"); +} + +static void +dump(void) +{ + uint16_t c; + uint8_t __xdata *start, *end; + + hex(); + start = (uint8_t __xdata *) lex_i; + hex(); + end = (uint8_t __xdata *) lex_i; + if (lex_status != SUCCESS) + return; + c = 0; + while (start <= end) { + if ((c & 7) == 0) { + if (c) + put_string("\n"); + put16((uint16_t) start); + } + putchar(' '); + put8(*start); + ++c; + start++; + } + put_string("\n"); +} + +static void +ee_dump(void) +{ + uint8_t b; + uint16_t block; + uint8_t i; + + hex(); + block = lex_i; + if (lex_status != SUCCESS) + return; + i = 0; + do { + if ((i & 7) == 0) { + if (i) + put_string("\n"); + put16((uint16_t) i); + } + putchar(' '); + ao_ee_read(((uint32_t) block << 8) | i, &b, 1); + put8(b); + ++i; + } while (i != 0); + put_string("\n"); +} + +static void +ee_store(void) +{ + uint16_t block; + uint8_t i; + uint16_t len; + uint8_t b; + uint32_t addr; + + hex(); + block = lex_i; + hex(); + i = lex_i; + addr = ((uint32_t) block << 8) | i; + hex(); + len = lex_i; + if (lex_status != SUCCESS) + return; + while (len--) { + hex(); + if (lex_status != SUCCESS) + return; + b = lex_i; + ao_ee_write(addr, &b, 1); + addr++; + } + ao_ee_flush(); +} + +static void +echo(void) +{ + hex(); + lex_echo = lex_i != 0; +} + +#if INCLUDE_REMOTE_DEBUG +static void +debug_enable(void) +{ + dbg_debug_mode(); +} + +static void +debug_reset(void) +{ + dbg_reset(); +} + +static void +debug_put(void) +{ + for (;;) { + white (); + if (lex_c == '\n') + break; + hex(); + if (lex_status != SUCCESS) + break; + dbg_send_byte(lex_i); + } +} + +static void +debug_get(void) +{ + uint16_t count; + uint16_t i; + uint8_t byte; + hex(); + if (lex_status != SUCCESS) + return; + count = lex_i; + if (count > 256) { + lex_status = SYNTAX_ERROR; + return; + } + for (i = 0; i < count; i++) { + if (i && (i & 7) == 0) + put_string("\n"); + byte = dbg_recv_byte(); + put8(byte); + putchar(' '); + } + put_string("\n"); +} + +static uint8_t +getnibble(void) +{ + uint8_t c; + + c = getchar(); + if ('0' <= c && c <= '9') + return c - '0'; + if ('a' <= c && c <= 'f') + return c - ('a' - 10); + if ('A' <= c && c <= 'F') + return c - ('A' - 10); + lex_status = LEX_ERROR; + return 0; +} + +static void +debug_input(void) +{ + uint16_t count; + uint16_t addr; + uint8_t b; + uint8_t i; + + hex(); + count = lex_i; + hex(); + addr = lex_i; + if (lex_status != SUCCESS) + return; + dbg_start_transfer(addr); + i = 0; + while (count--) { + if (!(i++ & 7)) + put_string("\n"); + b = dbg_read_byte(); + put8(b); + } + dbg_end_transfer(); + put_string("\n"); +} + +static void +debug_output(void) +{ + uint16_t count; + uint16_t addr; + uint8_t b; + + hex(); + count = lex_i; + hex(); + addr = lex_i; + if (lex_status != SUCCESS) + return; + dbg_start_transfer(addr); + while (count--) { + b = getnibble() << 4; + b |= getnibble(); + if (lex_status != SUCCESS) + return; + dbg_write_byte(b); + } + dbg_end_transfer(); +} +#endif + +static void +dump_log(void) +{ +#if 0 + uint8_t more; + + for (more = log_first(); more; more = log_next()) { + putchar(log_dump.type); + putchar(' '); + put16(log_dump.tick); + putchar(' '); + put16(log_dump.u.anon.d0); + putchar(' '); + put16(log_dump.u.anon.d1); + putchar('\n'); + } +#endif +} + +static const uint8_t help_txt[] = + "All numbers are in hex\n" + "? Print this message\n" + "a Display current ADC values\n" + "d Dump memory\n" + "e Dump a block of EEPROM data\n" + "w ... Write data to EEPROM\n" + "l Dump last flight log\n" + "E <0 off, 1 on> Set command echo mode\n" +#if INCLUDE_REMOTE_DEBUG + "\n" + "Target debug commands:\n" + "D Enable debug mode\n" + "R Reset target\n" + "P ... Put data to debug port\n" + "G Get data from debug port\n" + "O Output bytes to target at \n" + "I Input bytes to target at \n" +#endif +; + +static void +help(void) +{ + put_string(help_txt); +} + +static void +report(void) +{ + switch(lex_status) { + case LEX_ERROR: + case SYNTAX_ERROR: + put_string("Syntax error\n"); + lex_status = 0; + break; + } +} + +void +ao_cmd(void *parameters) +{ + uint8_t c; + (void) parameters; + + ao_led_on(AO_LED_GREEN); + ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(30)); + lex_echo = 1; + for (;;) { + readline(); + lex(); + white(); + c = lex_c; + lex(); + switch (c) { + case '?': + help(); + break; + case 'd': + dump(); + break; + case 'a': + adc_dump(); + break; + case 'e': + ee_dump(); + break; + case 'w': + ee_store(); + break; + case 'l': + dump_log(); + break; + case 'E': + echo(); + break; +#if INCLUDE_REMOTE_DEBUG + case 'D': + debug_enable(); + break; + case 'R': + debug_reset(); + break; + case 'P': + debug_put(); + break; + case 'G': + debug_get(); + break; + case 'I': + debug_input(); + break; + case 'O': + debug_output(); + break; +#endif + case '\r': + case '\n': + break; + default: + lex_status = SYNTAX_ERROR; + break; + } + report(); + } + +} + +struct ao_task __xdata cmd_task; + +void +ao_cmd_init(void) +{ + ao_add_task(&cmd_task, ao_cmd); +} diff --git a/ao_dma.c b/ao_dma.c new file mode 100644 index 00000000..a1092425 --- /dev/null +++ b/ao_dma.c @@ -0,0 +1,131 @@ +/* + * Copyright © 2009 Keith Packard + * + * 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" + +#define NUM_DMA 5 + +/* + * The config address for DMA0 is programmed + * separately from that of DMA1-4, but for simplicity, + * we make them all contiguous. + */ + +static __xdata struct cc_dma_channel ao_dma_config[NUM_DMA]; +static __xdata uint8_t * __xdata ao_dma_done[NUM_DMA]; +static __data uint8_t ao_next_dma; + +uint8_t +ao_dma_alloc(__xdata uint8_t *done) +{ + uint8_t id; + + if (ao_next_dma == NUM_DMA) + ao_panic(AO_PANIC_DMA); + id = ao_next_dma++; + ao_dma_done[id] = done; + + /* When the first dma object is allocated, set up the DMA + * controller + */ + if (id == 0) { + DMAIRQ = 0; + DMAIF = 0; + IEN1 |= IEN1_DMAIE; + } + + return id; +} + +void +ao_dma_set_transfer(uint8_t id, + void __xdata *srcaddr, + void __xdata *dstaddr, + uint16_t count, + uint8_t cfg0, + uint8_t cfg1) +{ + if (DMAARM & (1 << id)) + ao_panic(AO_PANIC_DMA); + ao_dma_config[id].src_high = ((uint16_t) srcaddr) >> 8; + ao_dma_config[id].src_low = ((uint16_t) srcaddr); + ao_dma_config[id].dst_high = ((uint16_t) dstaddr) >> 8; + ao_dma_config[id].dst_low = ((uint16_t) dstaddr); + ao_dma_config[id].len_high = count >> 8; + ao_dma_config[id].len_low = count; + ao_dma_config[id].cfg0 = cfg0; + ao_dma_config[id].cfg1 = cfg1 | DMA_CFG1_IRQMASK; + if (id == 0) { + DMA0CFGH = ((uint16_t) (&ao_dma_config[0])) >> 8; + DMA0CFGL = ((uint16_t) (&ao_dma_config[0])); + } else { + DMA1CFGH = ((uint16_t) (&ao_dma_config[1])) >> 8; + DMA1CFGL = ((uint16_t) (&ao_dma_config[1])); + } +} + +#define nop() _asm nop _endasm; + +void +ao_dma_start(uint8_t id) +{ + uint8_t mask = (1 << id); + DMAIRQ &= ~mask; + DMAARM = 0x80 | mask; + nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); + DMAARM = mask; + nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); + nop(); + *(ao_dma_done[id]) = 0; +} + +void +ao_dma_trigger(uint8_t id) +{ + DMAREQ |= (1 << id); +} + +void +ao_dma_abort(uint8_t id) +{ + uint8_t mask = (1 << id); + DMAARM = 0x80 | mask; + DMAIRQ &= ~mask; +} + +void +ao_dma_isr(void) interrupt 8 +{ + uint8_t id, mask; + + /* Find the first DMA channel which is done */ + mask = 1; + for (id = 0; id < ao_next_dma; id++) { + if (DMAIRQ & mask) { + /* Clear CPU interrupt flag */ + DMAIF = 0; + /* Clear the completed ID */ + DMAIRQ = ~mask; + *(ao_dma_done[id]) = 1; + ao_wakeup(ao_dma_done[id]); + break; + } + mask <<= 1; + } +} diff --git a/ao_ee.c b/ao_ee.c new file mode 100644 index 00000000..8e2e94d5 --- /dev/null +++ b/ao_ee.c @@ -0,0 +1,389 @@ +/* + * Copyright © 2009 Keith Packard + * + * 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. + */ + +#include "ao.h" +#include "25lc1024.h" + +/* + * Using SPI on USART 0, with P1_2 as the chip select + */ + +#define EE_CS P1_2 +#define EE_CS_INDEX 2 + +__xdata uint8_t ao_ee_dma_in_done; +__xdata uint8_t ao_ee_dma_out_done; +__xdata uint8_t ao_ee_mutex; + +uint8_t ao_ee_dma_out_id; +uint8_t ao_ee_dma_in_id; + +static __xdata uint8_t ao_ee_const = 0xff; + +#define ao_ee_delay() do { \ + _asm nop _endasm; \ + _asm nop _endasm; \ + _asm nop _endasm; \ +} while(0) + +void ao_ee_cs_low(void) +{ + ao_ee_delay(); + EE_CS = 0; + ao_ee_delay(); +} + +void ao_ee_cs_high(void) +{ + ao_ee_delay(); + EE_CS = 1; + ao_ee_delay(); +} + +/* Send bytes over SPI. + * + * This sets up two DMA engines, one writing the data and another reading + * bytes coming back. We use the bytes coming back to tell when the transfer + * is complete, as the transmit register is double buffered and hence signals + * completion one byte before the transfer is actually complete + */ +static void +ao_ee_send(void __xdata *block, uint16_t len) +{ + ao_dma_set_transfer(ao_ee_dma_in_id, + &U0DBUFXADDR, + &ao_ee_const, + len, + DMA_CFG0_WORDSIZE_8 | + DMA_CFG0_TMODE_SINGLE | + DMA_CFG0_TRIGGER_URX0, + DMA_CFG1_SRCINC_0 | + DMA_CFG1_DESTINC_0 | + DMA_CFG1_PRIORITY_NORMAL); + + ao_dma_set_transfer(ao_ee_dma_out_id, + block, + &U0DBUFXADDR, + len, + DMA_CFG0_WORDSIZE_8 | + DMA_CFG0_TMODE_SINGLE | + DMA_CFG0_TRIGGER_UTX0, + DMA_CFG1_SRCINC_1 | + DMA_CFG1_DESTINC_0 | + DMA_CFG1_PRIORITY_NORMAL); + + ao_dma_start(ao_ee_dma_in_id); + ao_dma_start(ao_ee_dma_out_id); + ao_dma_trigger(ao_ee_dma_out_id); + __critical while (!ao_ee_dma_in_done) + ao_sleep(&ao_ee_dma_in_done); +} + +/* Receive bytes over SPI. + * + * This sets up tow DMA engines, one reading the data and another + * writing constant values to the SPI transmitter as that is what + * clocks the data coming in. + */ +static void +ao_ee_recv(void __xdata *block, uint16_t len) +{ + ao_dma_set_transfer(ao_ee_dma_in_id, + &U0DBUFXADDR, + block, + len, + DMA_CFG0_WORDSIZE_8 | + DMA_CFG0_TMODE_SINGLE | + DMA_CFG0_TRIGGER_URX0, + DMA_CFG1_SRCINC_0 | + DMA_CFG1_DESTINC_1 | + DMA_CFG1_PRIORITY_NORMAL); + + ao_dma_set_transfer(ao_ee_dma_out_id, + &ao_ee_const, + &U0DBUFXADDR, + len, + DMA_CFG0_WORDSIZE_8 | + DMA_CFG0_TMODE_SINGLE | + DMA_CFG0_TRIGGER_UTX0, + DMA_CFG1_SRCINC_0 | + DMA_CFG1_DESTINC_0 | + DMA_CFG1_PRIORITY_NORMAL); + + ao_dma_start(ao_ee_dma_in_id); + ao_dma_start(ao_ee_dma_out_id); + ao_dma_trigger(ao_ee_dma_out_id); + __critical while (!ao_ee_dma_in_done) + ao_sleep(&ao_ee_dma_in_done); +} + +#define EE_BLOCK 256 + +struct ao_ee_instruction { + uint8_t instruction; + uint8_t address[3]; +} __xdata ao_ee_instruction; + +static void +ao_ee_write_enable(void) +{ + ao_ee_cs_low(); + ao_ee_instruction.instruction = EE_WREN; + ao_ee_send(&ao_ee_instruction, 1); + ao_ee_cs_high(); +} + +static uint8_t +ao_ee_rdsr(void) +{ + ao_ee_cs_low(); + ao_ee_instruction.instruction = EE_RDSR; + ao_ee_send(&ao_ee_instruction, 1); + ao_ee_recv(&ao_ee_instruction, 1); + ao_ee_cs_high(); + return ao_ee_instruction.instruction; +} + +static void +ao_ee_wrsr(uint8_t status) +{ + ao_ee_cs_low(); + ao_ee_instruction.instruction = EE_WRSR; + ao_ee_instruction.address[0] = status; + ao_ee_send(&ao_ee_instruction, 2); + ao_ee_cs_high(); +} + +#define EE_BLOCK_NONE 0xffff + +__xdata uint8_t ao_ee_data[EE_BLOCK]; +__data uint16_t ao_ee_block = EE_BLOCK_NONE; +__data uint8_t ao_ee_block_dirty; + +/* Write the current block to the EEPROM */ +static void +ao_ee_write_block(void) +{ + uint8_t status; + + status = ao_ee_rdsr(); + if (status & (EE_STATUS_BP0|EE_STATUS_BP1|EE_STATUS_WPEN)) { + status &= ~(EE_STATUS_BP0|EE_STATUS_BP1|EE_STATUS_WPEN); + ao_ee_wrsr(status); + } + ao_ee_write_enable(); + ao_ee_cs_low(); + ao_ee_instruction.instruction = EE_WRITE; + ao_ee_instruction.address[0] = ao_ee_block >> 8; + ao_ee_instruction.address[1] = ao_ee_block; + ao_ee_instruction.address[2] = 0; + ao_ee_send(&ao_ee_instruction, 4); + ao_ee_send(ao_ee_data, EE_BLOCK); + ao_ee_cs_high(); + for (;;) { + uint8_t status = ao_ee_rdsr(); + if ((status & EE_STATUS_WIP) == 0) + break; + } +} + +/* Read the current block from the EEPROM */ +static void +ao_ee_read_block(void) +{ + ao_ee_cs_low(); + ao_ee_instruction.instruction = EE_READ; + ao_ee_instruction.address[0] = ao_ee_block >> 8; + ao_ee_instruction.address[1] = ao_ee_block; + ao_ee_instruction.address[2] = 0; + ao_ee_send(&ao_ee_instruction, 4); + ao_ee_recv(ao_ee_data, EE_BLOCK); + ao_ee_cs_high(); +} + +void +ao_ee_flush(void) +{ + if (ao_ee_block_dirty) { + ao_ee_write_block(); + ao_ee_block_dirty = 0; + } +} + +static void +ao_ee_fill(uint16_t block) +{ + if (block != ao_ee_block) { + ao_ee_flush(); + ao_ee_block = block; + ao_ee_read_block(); + } +} + +uint8_t +ao_ee_write(uint32_t pos, uint8_t *buf, uint16_t len) +{ + uint16_t block; + uint16_t this_len; + uint8_t this_off; + + if (pos >= AO_EE_DATA_SIZE || pos + len > AO_EE_DATA_SIZE) + return 0; + while (len) { + + /* Compute portion of transfer within + * a single block + */ + this_off = pos; + this_len = 256 - (uint16_t) this_off; + block = (uint16_t) (pos >> 8); + if (this_len > len) + this_len = len; + if (this_len & 0xff00) + ao_panic(AO_PANIC_EE); + + /* Transfer the data */ + ao_mutex_get(&ao_ee_mutex); { + if (this_len != 256) + ao_ee_fill(block); + else { + ao_ee_flush(); + ao_ee_block = block; + } + memcpy(ao_ee_data + this_off, buf, this_len); + ao_ee_block_dirty = 1; + } ao_mutex_put(&ao_ee_mutex); + + /* See how much is left */ + buf += this_len; + len -= this_len; + } + return 1; +} + +uint8_t +ao_ee_read(uint32_t pos, uint8_t *buf, uint16_t len) +{ + uint16_t block; + uint16_t this_len; + uint8_t this_off; + + if (pos >= AO_EE_DATA_SIZE || pos + len > AO_EE_DATA_SIZE) + return 0; + while (len) { + + /* Compute portion of transfer within + * a single block + */ + this_off = pos; + this_len = 256 - (uint16_t) this_off; + block = (uint16_t) (pos >> 8); + if (this_len > len) + this_len = len; + if (this_len & 0xff00) + ao_panic(AO_PANIC_EE); + + /* Transfer the data */ + ao_mutex_get(&ao_ee_mutex); { + ao_ee_fill(block); + memcpy(buf, ao_ee_data + this_off, this_len); + } ao_mutex_put(&ao_ee_mutex); + + /* See how much is left */ + buf += this_len; + len -= this_len; + } + return 1; +} + +/* + * Read/write the config block, which is in + * the last block of the ao_eeprom + */ +uint8_t +ao_ee_write_config(uint8_t *buf, uint16_t len) +{ + if (len > AO_EE_BLOCK_SIZE) + return 0; + ao_mutex_get(&ao_ee_mutex); { + ao_ee_fill(AO_EE_CONFIG_BLOCK); + memcpy(ao_ee_data, buf, len); + ao_ee_block_dirty = 1; + } ao_mutex_put(&ao_ee_mutex); + return 1; +} + +uint8_t +ao_ee_read_config(uint8_t *buf, uint16_t len) +{ + if (len > AO_EE_BLOCK_SIZE) + return 0; + ao_mutex_get(&ao_ee_mutex); { + ao_ee_fill(AO_EE_CONFIG_BLOCK); + memcpy(buf, ao_ee_data, len); + } ao_mutex_put(&ao_ee_mutex); + return 1; +} + +/* + * To initialize the chip, set up the CS line and + * the SPI interface + */ +void +ao_ee_init(void) +{ + /* set up CS */ + EE_CS = 1; + P1DIR |= (1 << EE_CS_INDEX); + P1SEL &= ~(1 << EE_CS_INDEX); + + /* Set up the USART pin assignment */ + PERCFG = (PERCFG & ~PERCFG_U0CFG_ALT_MASK) | PERCFG_U0CFG_ALT_2; + + /* Ensure that USART0 takes precidence over USART1 for pins that + * they share + */ + P2SEL = (P2SEL & ~P2SEL_PRI3P1_MASK) | P2SEL_PRI3P1_USART0; + + /* Make the SPI pins be controlled by the USART peripheral */ + P1SEL |= ((1 << 5) | (1 << 4) | (1 << 3)); + + /* Set up OUT DMA */ + ao_ee_dma_out_id = ao_dma_alloc(&ao_ee_dma_out_done); + + /* Set up IN DMA */ + ao_ee_dma_in_id = ao_dma_alloc(&ao_ee_dma_in_done); + + /* Set up the USART. + * + * SPI master mode + */ + U0CSR = (UxCSR_MODE_SPI | UxCSR_RE | UxCSR_MASTER); + + /* Set the baud rate and signal parameters + * + * The cc1111 is limited to a 24/8 MHz SPI clock, + * while the 25LC1024 is limited to 20MHz. So, + * use the 3MHz clock (BAUD_E 17, BAUD_M 0) + */ + U0BAUD = 0; + U0GCR = (UxGCR_CPOL_NEGATIVE | + UxGCR_CPHA_FIRST_EDGE | + UxGCR_ORDER_MSB | + (17 << UxGCR_BAUD_E_SHIFT)); +} diff --git a/ao_main.c b/ao_main.c index 2cf6c80d..85fb04f3 100644 --- a/ao_main.c +++ b/ao_main.c @@ -29,6 +29,7 @@ main(void) ao_beep_init(); ao_led_init(); ao_usb_init(); + ao_ee_init(); ao_cmd_init(); ao_start_scheduler(); } diff --git a/ao_mutex.c b/ao_mutex.c new file mode 100644 index 00000000..12c67960 --- /dev/null +++ b/ao_mutex.c @@ -0,0 +1,41 @@ +/* + * Copyright © 2009 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" + +void +ao_mutex_get(__xdata uint8_t *mutex) +{ + if (*mutex == ao_cur_task->task_id) + ao_panic(AO_PANIC_MUTEX); + __critical { + while (*mutex) + ao_sleep(mutex); + *mutex = ao_cur_task->task_id; + } +} + +void +ao_mutex_put(__xdata uint8_t *mutex) +{ + if (*mutex != ao_cur_task->task_id) + ao_panic(AO_PANIC_MUTEX); + __critical { + *mutex = 0; + ao_wakeup(mutex); + } +} diff --git a/ao_task.c b/ao_task.c index 2e0a8b33..f79d6e1f 100644 --- a/ao_task.c +++ b/ao_task.c @@ -17,11 +17,11 @@ #include "ao.h" -#define AO_NO_TASK 0xff +#define AO_NO_TASK_INDEX 0xff __xdata struct ao_task * __xdata ao_tasks[AO_NUM_TASKS]; __data uint8_t ao_num_tasks; -__data uint8_t ao_cur_task_id; +__data uint8_t ao_cur_task_index; __xdata struct ao_task *__data ao_cur_task; void @@ -31,6 +31,7 @@ ao_add_task(__xdata struct ao_task * task, void (*start)(void)) if (ao_num_tasks == AO_NUM_TASKS) ao_panic(AO_PANIC_NO_TASK); ao_tasks[ao_num_tasks++] = task; + task->task_id = ao_num_tasks; /* * Construct a stack frame so that it will 'return' * to the start of the task @@ -94,7 +95,7 @@ ao_yield(void) _naked push _bp _endasm; - if (ao_cur_task_id != AO_NO_TASK) + if (ao_cur_task_index != AO_NO_TASK_INDEX) { /* Save the current stack */ stack_len = SP - (AO_STACK_START - 1); @@ -112,10 +113,10 @@ ao_yield(void) _naked * this loop will run forever, which is just fine */ for (;;) { - ++ao_cur_task_id; - if (ao_cur_task_id == ao_num_tasks) - ao_cur_task_id = 0; - ao_cur_task = ao_tasks[ao_cur_task_id]; + ++ao_cur_task_index; + if (ao_cur_task_index == ao_num_tasks) + ao_cur_task_index = 0; + ao_cur_task = ao_tasks[ao_cur_task_index]; if (ao_cur_task->wchan == NULL) break; } @@ -182,7 +183,7 @@ void ao_start_scheduler(void) { - ao_cur_task_id = AO_NO_TASK; + ao_cur_task_index = AO_NO_TASK_INDEX; ao_cur_task = NULL; ao_yield(); }