aboutsummaryrefslogtreecommitdiff
path: root/stm32/src
diff options
context:
space:
mode:
Diffstat (limited to 'stm32/src')
-rw-r--r--stm32/src/adc_rec_usb.c85
-rw-r--r--stm32/src/dac_ut.c57
-rw-r--r--stm32/src/debugblinky.c57
-rw-r--r--stm32/src/memtools.c67
-rw-r--r--stm32/src/menu.c98
-rw-r--r--stm32/src/morse.c175
-rw-r--r--stm32/src/sfx.c67
-rw-r--r--stm32/src/sm1000_leds_switches.c229
-rw-r--r--stm32/src/sm1000_leds_switches_ut.c41
-rw-r--r--stm32/src/sm1000_main.c1476
-rw-r--r--stm32/src/sounds.c62
-rw-r--r--stm32/src/startup_stm32f4xx.s526
-rw-r--r--stm32/src/stm32f4_adc.c286
-rw-r--r--stm32/src/stm32f4_dac.c427
-rw-r--r--stm32/src/stm32f4_machdep.c92
-rw-r--r--stm32/src/stm32f4_usart.c71
-rw-r--r--stm32/src/stm32f4_usb_vcp.c90
-rw-r--r--stm32/src/stm32f4_vrom.c724
-rw-r--r--stm32/src/system_stm32f4xx.c585
-rw-r--r--stm32/src/tone.c151
-rw-r--r--stm32/src/tot.c90
-rw-r--r--stm32/src/usart_ut.c30
-rw-r--r--stm32/src/usb_vcp_ut.c96
-rw-r--r--stm32/src/usb_vsp_ut.c192
24 files changed, 5774 insertions, 0 deletions
diff --git a/stm32/src/adc_rec_usb.c b/stm32/src/adc_rec_usb.c
new file mode 100644
index 0000000..e5ab084
--- /dev/null
+++ b/stm32/src/adc_rec_usb.c
@@ -0,0 +1,85 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: adc_rec_usb.c
+ AUTHOR......: David Rowe
+ DATE CREATED: Nov 2015
+
+ Records a 16 kHz sample rate raw file from one of the ADC channels,
+ which are connected to pins PA1 (ADC1) and PA2 (ADC2). Uploads to the
+ host PC via the STM32F4 USB port, which appears as /dev/ttyACM0.
+
+ On the SM1000:
+ ADC1 -> PA1 -> "from radio"
+ ADC2 -> PA2 -> "mic amp"
+
+ I used this to record:
+ $ sudo dd if=/dev/ttyACM0 of=test.raw count=100
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2015 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <math.h>
+#include "stm32f4_adc.h"
+#include "stm32f4_usb_vcp.h"
+#include "sm1000_leds_switches.h"
+
+#define N (ADC_BUF_SZ*6)
+
+/* test tone parameters */
+
+#define FREQ 999.0 /* make sure no alignment with frame boundaries */
+#define FS 16000.0
+#define AMP 10000.0
+
+extern int adc_overflow1;
+extern int adc_overflow2;
+
+int main(void){
+ short buf[N];
+ #ifdef TEST_TONE
+ float phase = 0.0;
+ float sam;
+ int i;
+ #endif
+
+ usb_vcp_init();
+ adc_open(ADC_FS_96KHZ, 4*N, NULL, NULL);
+ sm1000_leds_switches_init();
+
+ /* set up test buffer, lets us test USB comms indep of ADC, record to a file
+ then play back/examine waveform to make sure no clicks */
+
+ while(1) {
+ while(adc1_read(buf, N) == -1);
+
+ #ifdef TEST_TONE
+ for(i=0; i<N; i++) {
+ phase += 2.0*M_PI*FREQ/FS;
+ phase -= 2.0*M_PI*floor(phase/(2.0*M_PI));
+ sam = AMP*cos(phase);
+ buf[i] = (short)sam;
+ }
+ #endif
+
+ led_pwr(1);
+ VCP_send_buffer((uint8_t*)buf, sizeof(buf));
+ led_pwr(0);
+ }
+}
diff --git a/stm32/src/dac_ut.c b/stm32/src/dac_ut.c
new file mode 100644
index 0000000..2edceb0
--- /dev/null
+++ b/stm32/src/dac_ut.c
@@ -0,0 +1,57 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: dac_ut.c
+ AUTHOR......: David Rowe
+ DATE CREATED: May 31 2013
+
+ Plays a 500 Hz sine wave sampled at 16 kHz out of PA5 on a Discovery board,
+ or the speaker output of the SM1000.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2013 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include "stm32f4_dac.h"
+
+#define SINE_SAMPLES 32
+
+/* 32 sample sine wave which at Fs=16kHz will be 500Hz. Note samples
+ are 16 bit 2's complement, the DAC driver convertsto 12 bit
+ unsigned. */
+
+short aSine[] = {
+ -16, 6384, 12528, 18192, 23200, 27232, 30256, 32128,
+ 32752, 32128, 30256, 27232, 23152, 18192, 12528, 6384,
+ -16, -6416, -12560, -18224, -23184, -27264, -30288, -32160,
+ -32768, -32160, -30288, -27264, -23184, -18224, -12560, -6416
+};
+
+int main(void) {
+
+ dac_open(DAC_FS_16KHZ, 4*DAC_BUF_SZ, 0, 0);
+
+ while (1) {
+
+ /* keep DAC FIFOs topped up */
+
+ dac1_write((short*)aSine, SINE_SAMPLES, 0);
+ dac2_write((short*)aSine, SINE_SAMPLES, 0);
+ }
+
+}
diff --git a/stm32/src/debugblinky.c b/stm32/src/debugblinky.c
new file mode 100644
index 0000000..992c8b0
--- /dev/null
+++ b/stm32/src/debugblinky.c
@@ -0,0 +1,57 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: debugblinky.c
+ AUTHOR......: David Rowe
+ DATE CREATED: 12 August 2014
+
+ Configures GPIO pins used for debug blinkies
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2014 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stm32f4xx.h"
+
+void init_debug_blinky(void) {
+ GPIO_InitTypeDef GPIO_InitStruct;
+
+ /* PE0-3 used to indicate activity, PE4-5 for SM2000 +12V rail switching */
+
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
+
+ GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
+ GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
+ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
+ GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
+ GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
+ GPIO_Init(GPIOE, &GPIO_InitStruct);
+}
+
+/* SM2000: 0 for +12V RX power, 1 for +12V TX power */
+
+void txrx_12V(int state) {
+ if (state) {
+ GPIOE->ODR &= ~(1 << 5); /* +12VRXENB off */
+ GPIOE->ODR |= (1 << 4); /* +12VTXENB on */
+ }
+ else {
+ GPIOE->ODR &= ~(1 << 4); /* +12VTXENB off */
+ GPIOE->ODR |= (1 << 5); /* +12VRXENB on */
+ }
+}
+
diff --git a/stm32/src/memtools.c b/stm32/src/memtools.c
new file mode 100644
index 0000000..431ceaf
--- /dev/null
+++ b/stm32/src/memtools.c
@@ -0,0 +1,67 @@
+/*
+ memtools.h
+ June 2019
+
+ Tools for looking at memory on the stm32. See also debug_alloc.h
+*/
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <math.h>
+#include "memtools.h"
+
+/* Required memory allocation wrapper for embedded platforms. For SM1000, we can just use stdlib's memory functions. */
+void* codec2_malloc(size_t size)
+{
+ return malloc(size);
+}
+
+void* codec2_calloc(size_t nmemb, size_t size)
+{
+ return calloc(nmemb, size);
+}
+
+void codec2_free(void* ptr)
+{
+ free(ptr);
+}
+
+/* startup_stm32f4xx.s has been modified to fill RAM segment from bss up with 0x0x55555555 */
+
+void memtools_find_unused( int (*printf_func)(const char *fmt, ...) ) {
+ int32_t *p, *start;
+ int found = 0;
+
+ (*printf_func)("chunks of RAM segment > 256 bytes containing start up pattern:\n");
+
+ /* count down from top of memory through stack, empty memory, then to heap */
+ for (p =(int32_t*)0x20000000; p<(int32_t*)0x20020000; p++) {
+ if (found == 0) {
+ if (*p == 0x55555555) {
+ start = p;
+ found = 1;
+ }
+ }
+
+ if (found == 1) {
+ if (*p != 0x55555555) {
+ found = 0;
+ int bytes = (void*)p - (void*)start;
+ if (bytes >= 0x100)
+ (*printf_func)(" start: 0x%x end: 0x%x bytes: %d\n", (int) start, (int)p, bytes);
+ }
+ }
+ }
+
+}
+
+void memtools_isnan(float *vec, int n, char *label, int (*printf_func)(const char *fmt, ...)) {
+ int count = 0;
+ for(int i=0; i<n; i++) {
+ if (isnan(vec[i])) {
+ (*printf_func)("%s memtools_isnan: %d %p\n", label, i, &vec[i]);
+ if (count++ == 5) return;
+ }
+ }
+}
+
diff --git a/stm32/src/menu.c b/stm32/src/menu.c
new file mode 100644
index 0000000..cb8ba97
--- /dev/null
+++ b/stm32/src/menu.c
@@ -0,0 +1,98 @@
+/*!
+ * Callback driven menu handler.
+ *
+ * The following is an implementation of a callback-driven menu system.
+ * It supports arbitrary levels of menus (limited by size of return stack)
+ * and supports arbitrary user events.
+ *
+ * Author Stuart Longland <[email protected]>
+ * Copyright (C) 2015 FreeDV project.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation. 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 Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "menu.h"
+#include <stdlib.h>
+
+/*!
+ * Return the Nth item on the stack.
+ */
+static const struct menu_stack_item_t* const menu_stack(
+ const struct menu_t* const menu,
+ uint8_t index)
+{
+ if (menu->stack_depth <= index)
+ return NULL;
+
+ return &(menu->stack[menu->stack_depth - index - 1]);
+}
+
+/*!
+ * Return the Nth item on the stack.
+ */
+const struct menu_item_t* const menu_item(
+ const struct menu_t* const menu, uint8_t index)
+{
+ const struct menu_stack_item_t* const current
+ = menu_stack(menu, index);
+
+ if (!current)
+ return NULL;
+ return current->item;
+}
+
+/*!
+ * Enter a (sub)-menu.
+ */
+int menu_enter(struct menu_t* const menu,
+ const struct menu_item_t* const item)
+{
+ if (menu->stack_depth == MENU_STACK_SZ)
+ return -1;
+
+ menu->stack[menu->stack_depth].item = item;
+ menu->stack[menu->stack_depth].index = menu->current;
+ menu->stack_depth++;
+
+ (item->event_cb)(menu, MENU_EVT_ENTERED);
+
+ return 0;
+}
+
+/*!
+ * Return from a (sub)-menu.
+ */
+void menu_leave(struct menu_t* const menu)
+{
+ if (!menu->stack_depth)
+ return; /* Already out of the menu */
+
+ menu->last = menu_item(menu, 0);
+ menu->stack_depth--;
+
+ const struct menu_stack_item_t* current = menu_stack(menu, 0);
+ if (current && current->item) {
+ menu->current = current->index;
+ (current->item->event_cb)(menu, MENU_EVT_RETURNED);
+ }
+}
+
+/*!
+ * Execute the callback for the current item with a user-supplied event.
+ */
+void menu_exec(struct menu_t* const menu, uint32_t event)
+{
+ const struct menu_item_t* item = menu_item(menu, 0);
+ if (item && item->event_cb)
+ (item->event_cb)(menu, event);
+}
diff --git a/stm32/src/morse.c b/stm32/src/morse.c
new file mode 100644
index 0000000..2522993
--- /dev/null
+++ b/stm32/src/morse.c
@@ -0,0 +1,175 @@
+/*!
+ * Morse code library.
+ *
+ * This implements a state machine for playing back morse code messages.
+ *
+ * Author Stuart Longland <[email protected]>
+ * Copyright (C) 2015 FreeDV project.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation. 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 Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include "morse.h"
+
+#include <stdio.h>
+
+/*! Symbol table element definition */
+struct morse_sym_table_t {
+ uint8_t code; uint8_t len;
+};
+
+/*! Symbol table: "digits" */
+static const struct morse_sym_table_t morse_digits[] = {
+ { .code = 0xf8, .len = 5 }, /* 0: ----- */
+ { .code = 0x78, .len = 5 }, /* 1: .---- */
+ { .code = 0x38, .len = 5 }, /* 2: ..--- */
+ { .code = 0x18, .len = 5 }, /* 3: ...-- */
+ { .code = 0x08, .len = 5 }, /* 4: ....- */
+ { .code = 0x00, .len = 5 }, /* 5: ..... */
+ { .code = 0x80, .len = 5 }, /* 6: -.... */
+ { .code = 0xc0, .len = 5 }, /* 7: --... */
+ { .code = 0xe0, .len = 5 }, /* 8: ---.. */
+ { .code = 0xf0, .len = 5 }, /* 9: ----. */
+};
+
+/*! Symbol table: "letters" */
+static const struct morse_sym_table_t morse_letters[] = {
+ { .code = 0x40, .len = 2 }, /* A: .- */
+ { .code = 0x80, .len = 4 }, /* B: -... */
+ { .code = 0xa0, .len = 4 }, /* C: -.-. */
+ { .code = 0x80, .len = 3 }, /* D: -.. */
+ { .code = 0x00, .len = 1 }, /* E: . */
+ { .code = 0x20, .len = 4 }, /* F: ..-. */
+ { .code = 0xc0, .len = 3 }, /* G: --. */
+ { .code = 0x00, .len = 4 }, /* H: .... */
+ { .code = 0x00, .len = 2 }, /* I: .. */
+ { .code = 0x70, .len = 4 }, /* J: .--- */
+ { .code = 0xa0, .len = 3 }, /* K: -.- */
+ { .code = 0x40, .len = 4 }, /* L: .-.. */
+ { .code = 0xc0, .len = 2 }, /* M: -- */
+ { .code = 0x80, .len = 2 }, /* N: -. */
+ { .code = 0xe0, .len = 3 }, /* O: --- */
+ { .code = 0x60, .len = 4 }, /* P: .--. */
+ { .code = 0xd0, .len = 4 }, /* Q: --.- */
+ { .code = 0x40, .len = 3 }, /* R: .-. */
+ { .code = 0x00, .len = 3 }, /* S: ... */
+ { .code = 0x80, .len = 1 }, /* T: - */
+ { .code = 0x20, .len = 3 }, /* U: ..- */
+ { .code = 0x10, .len = 4 }, /* V: ...- */
+ { .code = 0x60, .len = 3 }, /* W: .-- */
+ { .code = 0x90, .len = 4 }, /* X: -..- */
+ { .code = 0xb0, .len = 4 }, /* Y: -.-- */
+ { .code = 0xc0, .len = 4 }, /* Z: --.. */
+};
+
+static void morse_next_sym(struct morse_player_t* const morse_player)
+{
+ struct sfx_player_t* sfx_player = &(morse_player->sfx_player);
+
+ if (!morse_player->msg) {
+ sfx_play(sfx_player, NULL);
+ return;
+ }
+
+ uint8_t sym_rem = 0;
+ uint8_t sym_code = 0;
+ const struct morse_sym_table_t* sym = NULL;
+ const char* c = morse_player->msg;
+
+ while(!sym) {
+ if ((*c >= 'A') && (*c <= 'Z'))
+ /* Play a letter. (capitals) */
+ sym = &morse_letters[*c - 'A'];
+ else if ((*c >= 'a') && (*c <= 'z'))
+ /* Play a letter. (lowercase) */
+ sym = &morse_letters[*c - 'a'];
+ else if ((*c >= '0') && (*c <= '9'))
+ /* Play a digit. */
+ sym = &morse_digits[*c - '0'];
+ else if (*c == 0) {
+ morse_player->msg = NULL;
+ return;
+ }
+ c++;
+ }
+ morse_player->msg = c;
+
+ struct sfx_note_t* note = morse_player->sym;
+ sym_rem = sym->len;
+ sym_code = sym->code;
+
+ while(sym_rem) {
+ note->freq = morse_player->freq;
+ if (sym_code & 0x80)
+ /* Play a "dah" */
+ note->duration = morse_player->dit_time*3;
+ else
+ /* Play a "dit" */
+ note->duration = morse_player->dit_time;
+ note++;
+ sym_code <<= 1;
+ sym_rem--;
+
+ /* A gap follows */
+ note->freq = 0;
+
+ if (sym_rem) {
+ /* More of the character */
+ note->duration = morse_player->dit_time;
+ note++;
+ }
+ }
+
+ /* What comes next? */
+ if (*c == ' ') {
+ /* End of word */
+ note->duration = morse_player->dit_time*7;
+ note++;
+ } else if (*c) {
+ /* End of character */
+ note->duration = morse_player->dit_time*3;
+ note++;
+ }
+
+ /* Terminate the sequence */
+ note->freq = 0;
+ note->duration = 0;
+
+ /* Set the player up */
+ sfx_play(sfx_player, morse_player->sym);
+}
+
+/*!
+ * Start playing a particular effect.
+ * @param sfx_player Effect player state machine
+ * @param effect Pointer to sound effect (NULL == stop)
+ */
+void morse_play(struct morse_player_t* const morse_player,
+ const char* msg)
+{
+ morse_player->msg = msg;
+ morse_next_sym(morse_player);
+}
+
+/*!
+ * Retrieve the next sample to be played.
+ */
+int16_t morse_next(struct morse_player_t* const morse_player)
+{
+ if (!morse_player)
+ return(0);
+ if (!morse_player->sfx_player.note)
+ morse_next_sym(morse_player);
+ return sfx_next(&(morse_player->sfx_player));
+}
diff --git a/stm32/src/sfx.c b/stm32/src/sfx.c
new file mode 100644
index 0000000..f80c8d1
--- /dev/null
+++ b/stm32/src/sfx.c
@@ -0,0 +1,67 @@
+/*!
+ * Sound effect player library.
+ *
+ * This implements a state machine for playing back various monophonic
+ * sound effects such as morse code symbols, clicks and alert tones.
+ *
+ * Author Stuart Longland <[email protected]>
+ * Copyright (C) 2015 FreeDV project.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation. 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 Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include "sfx.h"
+
+static void sfx_next_tone(struct sfx_player_t* const sfx_player)
+{
+ struct tone_gen_t* tone_gen = &(sfx_player->tone_gen);
+ const struct sfx_note_t* note = sfx_player->note;
+
+ if (!note) {
+ tone_reset(tone_gen, 0, 0);
+ } else {
+ tone_reset(tone_gen, note->freq, note->duration);
+
+ if (!note->duration)
+ /* We are done */
+ sfx_player->note = NULL;
+ else
+ /* Move to next note */
+ sfx_player->note++;
+ }
+}
+
+/*!
+ * Start playing a particular effect.
+ * @param sfx_player Effect player state machine
+ * @param effect Pointer to sound effect (NULL == stop)
+ */
+void sfx_play(struct sfx_player_t* const sfx_player,
+ const struct sfx_note_t* effect)
+{
+ sfx_player->note = effect;
+ sfx_next_tone(sfx_player);
+}
+
+/*!
+ * Retrieve the next sample to be played.
+ */
+int16_t sfx_next(struct sfx_player_t* const sfx_player)
+{
+ if (!sfx_player)
+ return(0);
+ if (!sfx_player->tone_gen.remain)
+ sfx_next_tone(sfx_player);
+ return tone_next(&(sfx_player->tone_gen));
+}
diff --git a/stm32/src/sm1000_leds_switches.c b/stm32/src/sm1000_leds_switches.c
new file mode 100644
index 0000000..e8f6ffc
--- /dev/null
+++ b/stm32/src/sm1000_leds_switches.c
@@ -0,0 +1,229 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: sm1000_leds_switches.c
+ AUTHOR......: David Rowe
+ DATE CREATED: 18 July 2014
+
+ Functions for controlling LEDs and reading switches on the SM1000.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2014 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _CPTT GPIO_Pin_10
+#define LED_PWR GPIO_Pin_12
+#define LED_PTT GPIO_Pin_13
+#define LED_RT GPIO_Pin_14
+#define LED_ERR GPIO_Pin_15
+#define SWITCH_PTT GPIO_Pin_7
+#define SWITCH_SELECT GPIO_Pin_0
+#define SWITCH_BACK GPIO_Pin_1
+#define EXT_PTT GPIO_Pin_8
+
+#include <stm32f4xx.h>
+#include <stm32f4xx_gpio.h>
+#include "sm1000_leds_switches.h"
+
+void sm1000_leds_switches_init(void) {
+ GPIO_InitTypeDef GPIO_InitStruct;
+
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
+
+ /* output pins */
+
+ GPIO_InitStruct.GPIO_Pin = LED_PWR | LED_PTT | LED_RT | LED_ERR | _CPTT;
+ GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
+ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
+ GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
+ GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
+ GPIO_Init(GPIOD, &GPIO_InitStruct);
+
+ /* input pins */
+
+ GPIO_InitStruct.GPIO_Pin = SWITCH_PTT | SWITCH_SELECT | SWITCH_BACK;
+ GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
+ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
+ GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; /* we have our own external pull ups */
+ GPIO_Init(GPIOD, &GPIO_InitStruct);
+
+ GPIO_InitStruct.GPIO_Pin = EXT_PTT;
+ GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
+ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
+ GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; /* use internal pull up */
+ GPIO_Init(GPIOD, &GPIO_InitStruct);
+
+
+}
+
+void led_pwr(int state) {
+ if (state > 0)
+ GPIOD->ODR |= (1 << 12);
+ else if (state < 0)
+ GPIOD->ODR ^= (1 << 12);
+ else
+ GPIOD->ODR &= ~(1 << 12);
+}
+
+void led_ptt(int state) {
+ if (state > 0)
+ GPIOD->ODR |= (1 << 13);
+ else if (state < 0)
+ GPIOD->ODR |= (1 << 13);
+ else
+ GPIOD->ODR &= ~(1 << 13);
+}
+
+void led_rt(int state) {
+ if (state > 0)
+ GPIOD->ODR |= (1 << 14);
+ else if (state < 0)
+ GPIOD->ODR ^= (1 << 14);
+ else
+ GPIOD->ODR &= ~(1 << 14);
+}
+
+void led_err(int state) {
+ if (state > 0)
+ GPIOD->ODR |= (1 << 15);
+ else if (state < 0)
+ GPIOD->ODR ^= (1 << 15);
+ else
+ GPIOD->ODR &= ~(1 << 15);
+}
+
+void not_cptt(int state) {
+ if (state)
+ GPIOD->ODR |= (1 << 10);
+ else
+ GPIOD->ODR &= ~(1 << 10);
+}
+
+int switch_ptt(void) {
+ return GPIOD->IDR & (1 << 7);
+}
+
+int switch_select(void) {
+ return GPIOD->IDR & (1 << 0);
+}
+
+int switch_back(void) {
+ return GPIOD->IDR & (1 << 1);
+}
+
+int ext_ptt(void) {
+ return GPIOD->IDR & (1 << 8);
+}
+
+/*
+ FUNCTION: ColorfulRingOfDeath()
+ AUTHOR..: xenovacivus
+
+ Colourful ring of death, blink LEDs like crazy forever if something
+ really nasty happens. Adapted from USB Virtual COM Port (VCP)
+ module adapted from code I found here:
+
+ https://github.com/xenovacivus/STM32DiscoveryVCP
+
+ Call this to indicate a failure. Blinks the STM32F4 discovery LEDs
+ in sequence. At 168Mhz, the blinking will be very fast - about 5
+ Hz. Keep that in mind when debugging, knowing the clock speed
+ might help with debugging.
+*/
+
+int mycode; /* examine this with debugger if it dies */
+
+void ColorfulRingOfDeath(int code) {
+ mycode = code;
+ uint16_t ring = 1;
+ while (1) {
+ uint32_t count = 0;
+ while (count++ < 5000000);
+
+ GPIOD->BSRRH = (ring << 12);
+ ring = ring << 1;
+ if (ring >= 1<<4) {
+ ring = 1;
+ }
+ GPIOD->BSRRL = (ring << 12);
+ }
+}
+void HardFault_Handler(void) { ColorfulRingOfDeath(1); }
+void MemManage_Handler(void) { ColorfulRingOfDeath(2); }
+void BusFault_Handler(void) { ColorfulRingOfDeath(3); }
+void UsageFault_Handler(void){ ColorfulRingOfDeath(4); }
+
+
+void switch_tick(struct switch_t* const sw)
+{
+ if (sw->sw != sw->raw) {
+ /* State transition, reset timer */
+ if (sw->state == SW_STEADY)
+ sw->last = sw->sw;
+ sw->state = SW_DEBOUNCE;
+ sw->timer = DEBOUNCE_DELAY;
+ sw->sw = sw->raw;
+ } else if (sw->state == SW_DEBOUNCE) {
+ if (sw->timer > 0) {
+ /* Steady so far, keep waiting */
+ sw->timer--;
+ } else {
+ /* Steady state reached */
+ sw->state = SW_STEADY;
+ }
+ } else if (sw->sw) {
+ /* Hold state. Yes this will wrap, but who cares? */
+ sw->timer++;
+ }
+}
+
+void switch_update(struct switch_t* const sw, uint8_t state)
+{
+ sw->raw = state;
+ if (sw->raw == sw->sw)
+ return;
+
+ if (sw->state == SW_STEADY)
+ sw->last = sw->sw;
+ sw->timer = DEBOUNCE_DELAY;
+ sw->sw = sw->raw;
+ sw->state = SW_DEBOUNCE;
+}
+
+uint32_t switch_pressed(const struct switch_t* const sw)
+{
+ if ((sw->state == SW_STEADY) && sw->sw)
+ return sw->timer;
+ return 0;
+}
+
+int switch_released(const struct switch_t* const sw)
+{
+ if (sw->state != SW_STEADY)
+ return 0;
+ if (!sw->last)
+ return 0;
+ if (sw->sw)
+ return 0;
+ return 1;
+}
+
+void switch_ack(struct switch_t* const sw)
+{
+ if (sw->state == SW_STEADY)
+ sw->last = sw->sw;
+}
diff --git a/stm32/src/sm1000_leds_switches_ut.c b/stm32/src/sm1000_leds_switches_ut.c
new file mode 100644
index 0000000..9209bfc
--- /dev/null
+++ b/stm32/src/sm1000_leds_switches_ut.c
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: sm1000_leds_switches_ut.c
+ AUTHOR......: David Rowe
+ DATE CREATED: August 5 2014
+
+ Unit Test program for the SM1000 switches and LEDs driver.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2014 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include "sm1000_leds_switches.h"
+
+int main(void) {
+ sm1000_leds_switches_init();
+
+ while(1) {
+ led_pwr(switch_select());
+ led_ptt(switch_ptt());
+ led_rt(switch_back());
+ led_err(!switch_back());
+ }
+}
+
diff --git a/stm32/src/sm1000_main.c b/stm32/src/sm1000_main.c
new file mode 100644
index 0000000..6e5ec3b
--- /dev/null
+++ b/stm32/src/sm1000_main.c
@@ -0,0 +1,1476 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: sm1000_main.c
+ AUTHOR......: David Rowe
+ DATE CREATED: August 5 2014
+
+ Main program for SM1000.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2014 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stm32f4_adc.h"
+#include "stm32f4_dac.h"
+#include "stm32f4_vrom.h"
+#include "stm32f4_usart.h"
+#include "freedv_api.h"
+#include "codec2_fdmdv.h"
+#include "sm1000_leds_switches.h"
+#include "memtools.h"
+#include <assert.h>
+#include <stm32f4xx_gpio.h>
+#include <stm32f4xx_rcc.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "sfx.h"
+#include "sounds.h"
+#include "morse.h"
+#include "menu.h"
+#include "tot.h"
+
+#define VERSION "V5"
+#define FORTY_MS_16K (0.04*16000) /* 40ms of samples at 16 kHz */
+#define FREEDV_NSAMPLES_16K (2*FREEDV_NSAMPLES)
+#define CCM (void*)0x10000000 /* start of 64k CCM memory */
+#define CCM_LEN 0x10000 /* start of 64k CCM memory */
+
+#define MENU_LED_PERIOD 100
+#define ANNOUNCE_DELAY 1500
+#define HOLD_DELAY 1000
+#define MENU_DELAY 1000
+
+#define STATE_RX 0x00 /*!< Receive state: normal operation */
+#define STATE_TX 0x10 /*!< Transmit state: normal operation */
+#define STATE_RX_TOT 0x01 /*!< Receive state: after time-out */
+#define STATE_MENU 0x20 /*!< Menu state: normal operation */
+
+/*!
+ * State machine states. We consider our state depending on what events
+ * are in effect at the start of the main() loop. For buttons, we have
+ * the following events:
+ *
+ * PRESS: Short-succession down-and-up event. (<1 second)
+ * DOWN: Button press event with no release.
+ * UP: Button release event.
+ * HOLD: Button press for a minimum duration of 1 second without
+ * release.
+ *
+ * We also have some other state machines:
+ * TOT:
+ * IDLE: No time-out event
+ * WARN: Warning period reached event
+ * WARN_TICK: Next warning tick due event
+ * TIMEOUT: Cease transmit event
+ *
+ * We consider ourselves to be in one of a few finite states:
+ *
+ * STATE_RX: Normal receive state.
+ * Conditions: !PTT.DOWN, !SELECT.HOLD
+ *
+ * We receive samples via the TRX ADC and pass those
+ * to SPEAKER DAC after demodulation/filtering.
+ *
+ * On SELECT.HOLD: go to STATE_MENU
+ * On SELECT.PRESS: next mode, stay in STATE_RX
+ * On BACK.PRESS: prev mode, stay in STATE_RX
+ * On PTT.DOWN: reset TOT, go to STATE_TX
+ *
+ * STATE_TX: Normal transmit state.
+ * Conditions: PTT.DOWN, !TOT.TIMEOUT
+ *
+ * We receive samples via the MIC ADC and pass those
+ * to TRX DAC after modulation/filtering.
+ *
+ * On PTT.UP: reset TOT, go to STATE_RX
+ * On TOT.WARN_TICK: play tick noise,
+ * reset WARN_TICK event,
+ * stay in STATE_TX
+ * On TOT.TIMEOUT: play timeout tune,
+ * reset TIMEOUT event
+ * go to STATE_RX_TOT.
+ *
+ * STATE_RX_TOT: Receive after time-out state.
+ * Conditions: PTT.DOWN
+ *
+ * We receive samples via the TRX ADC and pass those
+ * to SPEAKER DAC after demodulation/filtering.
+ *
+ * On PTT.UP: reset TOT, go to STATE_RX
+ *
+ * STATE_MENU: Menu operation state. Operation is dictated by
+ * the menu state machine, when we exit that state
+ * machine, we return to STATE_RX.
+ *
+ * On SELECT.HOLD: select the current menu entry,
+ * if it is a submenu then make that the current level
+ * On SELECT.PRESS: next entry in the current menu level
+ * On BACK.PRESS: prev mode in the current menu level
+ * On BACK.HOLD: go up to the previous menu
+ * save any changes to NV memory
+ * This may exit the menu system
+ * On PTT.DOWN: Exit menu system, do not save to NVM
+ *
+ * See the "Menu data" section of this file for the menu structure
+ *
+ */
+uint8_t core_state = STATE_RX;
+
+#define MAX_MODES 4
+#define ANALOG 0
+#define DV1600 1
+#define DV700D 2
+#define DV700E 3
+
+struct switch_t sw_select; /*!< Switch driver for SELECT button */
+struct switch_t sw_back; /*!< Switch driver for BACK button */
+struct switch_t sw_ptt; /*!< Switch driver for PTT buttons */
+
+struct tot_t tot; /*!< Time-out timer */
+
+unsigned int announceTicker = 0;
+unsigned int menuLEDTicker = 0;
+unsigned int menuTicker = 0;
+unsigned int menuExit = 0;
+
+uint32_t ms = 0; /* increments once per ms */
+
+/*!
+ * User preferences
+ */
+static struct prefs_t {
+ /*! Serial number */
+ uint64_t serial;
+ /*! Time-out timer period, in seconds increment */
+ uint16_t tot_period;
+ /*! Time-out timer warning period, in seconds increment */
+ uint16_t tot_warn_period;
+ /*! Menu frequency */
+ uint16_t menu_freq;
+ /*! Menu speed */
+ uint8_t menu_speed;
+ /*! Menu volume (attenuation) */
+ uint8_t menu_vol;
+ /*! Default operating mode */
+ uint8_t op_mode;
+} prefs;
+
+/*! Preferences changed flag */
+int prefs_changed = 0;
+
+/*! Number of preference images kept */
+#define PREFS_IMG_NUM (2)
+/*! Base ROM ID for preferences */
+#define PREFS_IMG_BASE (0)
+/*! Minimum serial number */
+#define PREFS_SERIAL_MIN 8
+/*! Maximum serial number */
+#define PREFS_SERIAL_MAX UINT64_MAX
+
+/*! Preference serial numbers, by slot */
+static uint64_t prefs_serial[PREFS_IMG_NUM];
+
+struct tone_gen_t tone_gen;
+struct sfx_player_t sfx_player;
+struct morse_player_t morse_player;
+
+void SysTick_Handler(void);
+
+/*! Menu item root */
+static const struct menu_item_t menu_root;
+
+#define MENU_EVT_NEXT 0x10 /*!< Increment the current item */
+#define MENU_EVT_PREV 0x11 /*!< Decrement the current item */
+#define MENU_EVT_SELECT 0x20 /*!< Select current item */
+#define MENU_EVT_BACK 0x21 /*!< Go back one level */
+#define MENU_EVT_EXIT 0x30 /*!< Exit menu */
+
+/*!
+ * Software-mix two 16-bit samples.
+ */
+int16_t software_mix(int16_t a, int16_t b) {
+ int32_t s = a + b;
+ if (s < INT16_MIN)
+ return INT16_MIN; /* Clip! */
+ if (s > INT16_MAX)
+ return INT16_MAX; /* Clip! */
+ return s;
+}
+
+/*! Compare current serial with oldest and newest */
+void compare_prefs(int* const oldest, int* const newest, int idx)
+{
+ if (newest && prefs_serial[idx]) {
+ if ((*newest < 0)
+ || (prefs_serial[idx] > prefs_serial[*newest])
+ || ((prefs_serial[idx] == PREFS_SERIAL_MIN)
+ && (prefs_serial[*newest] == PREFS_SERIAL_MAX)))
+ *newest = idx;
+ }
+
+ if (oldest) {
+ if ((*oldest < 0)
+ || (!prefs_serial[idx])
+ || (prefs_serial[idx] < prefs_serial[*oldest])
+ || ((prefs_serial[idx] == PREFS_SERIAL_MAX)
+ && (prefs_serial[*oldest] == PREFS_SERIAL_MIN)))
+ *oldest = idx;
+ }
+}
+
+/*! Find oldest and newest images */
+void find_prefs(int* const oldest, int* const newest)
+{
+ int i;
+ if (newest) *newest = -1;
+ if (oldest) *oldest = -1;
+ for (i = 0; i < PREFS_IMG_NUM; i++)
+ compare_prefs(oldest, newest, i);
+}
+
+/*! Load preferences from flash */
+int load_prefs()
+{
+ struct prefs_t image[PREFS_IMG_NUM];
+ int newest = -1;
+ int i;
+
+ /* Load all copies into RAM */
+ for (i = 0; i < PREFS_IMG_NUM; i++) {
+ int res = vrom_read(PREFS_IMG_BASE + i, 0,
+ sizeof(image[i]), &image[i]);
+ if (res == sizeof(image[i])) {
+ prefs_serial[i] = image[i].serial;
+ compare_prefs(NULL, &newest, i);
+ } else {
+ prefs_serial[i] = 0;
+ }
+ }
+
+ if (newest < 0)
+ /* No newest image was found */
+ return -ENOENT;
+
+ /* Load from the latest image */
+ memcpy(&prefs, &image[newest], sizeof(prefs));
+ return 0;
+}
+
+void print_prefs(struct prefs_t *prefs) {
+ usart_printf("serial: %d\n", (int)prefs->serial);
+ usart_printf("tot_period: %d\n", (int)prefs->tot_period);
+ usart_printf("tot_warn_period: %d\n", (int)prefs->tot_warn_period);
+ usart_printf("menu_freq: %d\n", (int)prefs->menu_freq);
+ usart_printf("menu_speed: %d\n", (int)prefs->menu_speed);
+ usart_printf("menu_vol: %d\n", (int)prefs->menu_vol);
+ usart_printf("op_mode: %d\n", (int)prefs->op_mode);
+ usart_printf("prefs_changed: %d\n", prefs_changed);
+}
+
+struct freedv *set_freedv_mode(int op_mode, int *n_samples) {
+ struct freedv *f = NULL;
+ switch(op_mode) {
+ case ANALOG:
+ usart_printf("Analog\n");
+ *n_samples = FORTY_MS_16K/4;
+ f = NULL;
+ break;
+ case DV1600:
+ usart_printf("FreeDV 1600\n");
+ f = freedv_open(FREEDV_MODE_1600);
+ assert(f != NULL);
+ *n_samples = freedv_get_n_speech_samples(f);
+ break;
+ case DV700D:
+ usart_printf("FreeDV 700D\n");
+ f = freedv_open(FREEDV_MODE_700D);
+ assert(f != NULL);
+ freedv_set_snr_squelch_thresh(f, -2.0); /* squelch at -2.0 dB */
+ freedv_set_squelch_en(f, 1);
+ freedv_set_eq(f, 1); /* equaliser on by default */
+
+ /* Clipping and TXBPF nice to have for 700D. */
+ freedv_set_clip(f, 1);
+ freedv_set_tx_bpf(f, 1);
+
+ *n_samples = freedv_get_n_speech_samples(f);
+ break;
+ case DV700E:
+ usart_printf("FreeDV 700E\n");
+ f = freedv_open(FREEDV_MODE_700E);
+ assert(f != NULL);
+ freedv_set_snr_squelch_thresh(f, 0.0); /* squelch at 0.0 dB */
+ freedv_set_squelch_en(f, 1);
+ freedv_set_eq(f, 1); /* equaliser on by default */
+
+ /* Clipping and TXBPF needed for 700E. */
+ freedv_set_clip(f, 1);
+ freedv_set_tx_bpf(f, 1);
+
+ *n_samples = freedv_get_n_speech_samples(f);
+ break;
+ }
+ return f;
+}
+
+int process_core_state_machine(int core_state, struct menu_t *menu, int *op_mode);
+
+int main(void) {
+ struct freedv *f;
+ int nin, nout, i;
+ int n_samples, n_samples_16k;
+
+ usart_init(); usart_printf("SM1000 VERSION: %s\n", VERSION);
+ usart_printf("SM1000 main()... stack 0x%x (%d)\n", &n_samples_16k, (uint32_t)0x2001ffff - (uint32_t)&n_samples_16k);
+ memtools_find_unused(usart_printf);
+
+ /* Menu data */
+ struct menu_t menu;
+
+ /* Outgoing sample counter */
+ int spk_nsamples = 0;
+
+ /* Current runtime operation mode */
+ int op_mode = ANALOG;
+
+ /* init all the drivers for various peripherals */
+
+ SysTick_Config(SystemCoreClock/1000); /* 1 kHz SysTick */
+ sm1000_leds_switches_init();
+
+ /* Enable CRC clock */
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE);
+
+ /* Briefly open FreeDV 700D to determine buffer sizes we need
+ (700D has the largest buffers) */
+
+ f = freedv_open(FREEDV_MODE_700D);
+ int n_speech_samples = freedv_get_n_speech_samples(f);
+ int n_speech_samples_16k = 2*n_speech_samples;
+ int n_modem_samples = freedv_get_n_max_modem_samples(f);
+ int n_modem_samples_16k = 2*n_modem_samples;
+ freedv_close(f); f = NULL;
+ usart_printf("n_speech_samples: %d n_modem_samples: %d\n",
+ n_speech_samples, n_modem_samples);
+
+ /* both speech and modem buffers will be about the same size, but
+ choose the largest and add a little extra padding */
+ if (n_speech_samples_16k > n_modem_samples_16k)
+ n_samples_16k = n_speech_samples_16k;
+ else
+ n_samples_16k = n_modem_samples_16k;
+ n_samples_16k += FORTY_MS_16K;
+ usart_printf("n_samples_16k: %d storage for 4 FIFOs: %d bytes\n",
+ n_samples_16k, 4*2*n_samples_16k);
+
+ /* Set up ADCs/DACs and their FIFOs, note storage is in CCM memory */
+ short *pccm = CCM;
+ usart_printf("pccm before dac/adc open: %p\n", pccm);
+ n_samples = n_samples_16k/2;
+ dac_open(DAC_FS_16KHZ, n_samples_16k, pccm, pccm+n_samples_16k);
+ pccm += 2*n_samples_16k;
+ adc_open(ADC_FS_16KHZ, n_samples_16k, pccm, pccm+n_samples_16k);
+ pccm += 2*n_samples_16k;
+ usart_printf("pccm after dac/adc open: %p\n", pccm);
+ assert((void*)pccm < CCM+CCM_LEN);
+
+ short *adc16k = pccm; pccm += FDMDV_OS_TAPS_16K+n_samples_16k;
+ short *dac16k = pccm; pccm += n_samples_16k;
+ short adc8k[n_samples];
+ short dac8k[FDMDV_OS_TAPS_8K+n_samples];
+ usart_printf("pccm after buffers: %p\n", pccm);
+ assert((void*)pccm < CCM+CCM_LEN);
+
+ /* clear buffers */
+ for(i=0; i<FDMDV_OS_TAPS_16K+n_samples_16k; i++)
+ adc16k[i] = 0;
+ for(i=0; i<n_samples_16k; i++)
+ dac16k[i] = 0;
+ for(i=0; i<n_samples; i++)
+ adc8k[i] = 0;
+ for(i=0; i<FDMDV_OS_TAPS_8K+n_samples; i++)
+ dac8k[i] = 0;
+
+ usart_printf("drivers initialised...stack: %p\n", memtools_sp);
+ memtools_find_unused(usart_printf);
+
+ /* put outputs into a known state */
+ led_pwr(1); led_ptt(0); led_rt(0); led_err(0); not_cptt(1);
+
+ if (!switch_back()) {
+ /* Play tone to acknowledge, wait for release */
+ tone_reset(&tone_gen, 1200, 1000);
+ while(!switch_back()) {
+ int dac_rem = dac2_free();
+ if (dac_rem) {
+ // TODO this might need fixing for larger FIFOs
+ if (dac_rem > n_samples_16k)
+ dac_rem = n_samples_16k;
+
+ for (i = 0; i < dac_rem; i++)
+ dac16k[i] = tone_next(&tone_gen);
+ dac2_write(dac16k, dac_rem, 0);
+ }
+ if (!menuLEDTicker) {
+ menuLEDTicker = MENU_LED_PERIOD;
+ led_rt(LED_INV);
+ }
+ }
+
+ /* Button released, do an EEPROM erase */
+ for (i = 0; i < PREFS_IMG_NUM; i++)
+ vrom_erase(i + PREFS_IMG_BASE);
+ }
+ led_rt(LED_OFF);
+ tone_reset(&tone_gen, 0, 0);
+ tot_reset(&tot);
+
+ usart_printf("loading preferences from flash....\n");
+
+ /* Try to load preferences from flash */
+ if (load_prefs() < 0) {
+ usart_printf("loading default preferences....\n");
+ /* Fail! Load defaults. */
+ memset(&prefs, 0, sizeof(prefs));
+ prefs.op_mode = ANALOG;
+ prefs.menu_vol = 2;
+ prefs.menu_speed = 60; /* 20 WPM */
+ prefs.menu_freq = 800;
+ prefs.tot_period = 0; /* Disable time-out timer */
+ prefs.tot_warn_period = 15;
+ }
+ print_prefs(&prefs);
+
+ /* Set up time-out timer, 100msec ticks */
+ tot.tick_period = 100;
+ tot.remain_warn_ticks = 10;
+
+ /* Clear out switch states */
+ memset(&sw_select, 0, sizeof(sw_select));
+ memset(&sw_back, 0, sizeof(sw_back));
+ memset(&sw_ptt, 0, sizeof(sw_ptt));
+
+ /* Clear out menu state */
+ memset(&menu, 0, sizeof(menu));
+
+ morse_player.freq = prefs.menu_freq;
+ morse_player.dit_time = prefs.menu_speed;
+ morse_player.msg = NULL;
+ op_mode = prefs.op_mode;
+
+ /* default op-mode */
+ f = set_freedv_mode(op_mode, &n_samples);
+ n_samples_16k = 2*n_samples;
+
+ /* play VERSION and op mode at start-up. Morse player can't queue
+ so we assemble a concatenated string here */
+ char startup_announcement[16];
+ if (op_mode == ANALOG)
+ snprintf(startup_announcement, 16, VERSION " ANA");
+ else if (op_mode == DV1600)
+ snprintf(startup_announcement, 16, VERSION " 1600");
+ else if (op_mode == DV700D)
+ snprintf(startup_announcement, 16, VERSION " 700D");
+ else if (op_mode == DV700E)
+ snprintf(startup_announcement, 16, VERSION " 700E");
+ morse_play(&morse_player, startup_announcement);
+
+ usart_printf("entering main loop...\n");
+ uint32_t lastms = ms;
+ while(1) {
+ /* Read switch states */
+ switch_update(&sw_select, (!switch_select()) ? 1 : 0);
+ switch_update(&sw_back, (!switch_back()) ? 1 : 0);
+ switch_update(&sw_ptt, (switch_ptt() || (!ext_ptt())) ? 1 : 0);
+
+ /* Update time-out timer state */
+ tot_update(&tot);
+
+ /* iterate core state machine based on switch events */
+ int prev_op_mode = op_mode;
+ int prev_core_state = core_state;
+ core_state = process_core_state_machine(core_state, &menu, &op_mode);
+
+ /* Acknowledge switch events */
+ switch_ack(&sw_select);
+ switch_ack(&sw_back);
+ switch_ack(&sw_ptt);
+
+ /* if mode has changed, re-open freedv */
+ if (op_mode != prev_op_mode) {
+ usart_printf("Mode change prev_op_mode: %d op_mode: %d\n", prev_op_mode, op_mode);
+ if (f) { freedv_close(f); } f = NULL;
+ f = set_freedv_mode(op_mode, &n_samples);
+ n_samples_16k = 2*n_samples;
+ usart_printf("FreeDV f = 0x%x n_samples: %d n_samples_16k: %d\n", (int)f, n_samples, n_samples_16k);
+
+ /* clear buffers */
+
+ for(i=0; i<FDMDV_OS_TAPS_16K+n_samples_16k; i++)
+ adc16k[i] = 0;
+ for(i=0; i<n_samples_16k; i++)
+ dac16k[i] = 0;
+ for(i=0; i<n_samples; i++)
+ adc8k[i] = 0;
+ for(i=0; i<FDMDV_OS_TAPS_8K+n_samples; i++)
+ dac8k[i] = 0;
+ }
+
+ /* if we have moved from tx to rx reset sync state of rx so we re-start acquisition */
+ if ((op_mode == DV1600) || (op_mode == DV700D) || (op_mode == DV700E))
+ if ((prev_core_state == STATE_TX) && (core_state == STATE_RX))
+ freedv_set_sync(f, FREEDV_SYNC_UNSYNC);
+
+ /* perform signal processing based on core state */
+ switch (core_state) {
+ case STATE_MENU:
+ if (!menuLEDTicker) {
+ led_pwr(LED_INV);
+ menuLEDTicker = MENU_LED_PERIOD;
+ }
+ break;
+ case STATE_TX:
+ /* Transmit -------------------------------------------------------------------------*/
+
+ /* ADC2 is the SM1000 microphone, DAC1 is the modulator signal we send to radio tx */
+
+ if (adc2_read(&adc16k[FDMDV_OS_TAPS_16K], n_samples_16k) == 0) {
+ GPIOE->ODR = (1 << 3);
+
+ /* clipping indicator */
+
+ led_err(0);
+ for (i=0; i<n_samples_16k; i++) {
+ if (abs(adc16k[FDMDV_OS_TAPS_16K+i]) > 28000)
+ led_err(1);
+ }
+
+ fdmdv_16_to_8_short(adc8k, &adc16k[FDMDV_OS_TAPS_16K], n_samples);
+
+ if (op_mode == ANALOG) {
+ for(i=0; i<n_samples; i++)
+ dac8k[FDMDV_OS_TAPS_8K+i] = adc8k[i];
+ fdmdv_8_to_16_short(dac16k, &dac8k[FDMDV_OS_TAPS_8K], n_samples);
+ dac1_write(dac16k, n_samples_16k, 0);
+ }
+ else {
+ freedv_tx(f, &dac8k[FDMDV_OS_TAPS_8K], adc8k);
+ for(i=0; i<n_samples; i++)
+ dac8k[FDMDV_OS_TAPS_8K+i] *= 0.398; /* 8dB back off from peak */
+ fdmdv_8_to_16_short(dac16k, &dac8k[FDMDV_OS_TAPS_8K], n_samples);
+ dac1_write(dac16k, n_samples_16k, 0);
+ }
+
+ led_ptt(1); led_rt(0); led_err(0); not_cptt(0);
+ GPIOE->ODR &= ~(1 << 3);
+ }
+ break;
+
+ case STATE_RX:
+ case STATE_RX_TOT:
+ /* Receive --------------------------------------------------------------------------*/
+
+ not_cptt(1);
+ led_ptt(0);
+
+ /* ADC1 is the demod in signal from the radio rx, DAC2 is the SM1000 speaker */
+
+ if (op_mode == ANALOG) {
+ if (ms > lastms+5000) {
+ usart_printf("Analog\n");
+ lastms = ms;
+ }
+
+ if (adc1_read(&adc16k[FDMDV_OS_TAPS_16K], n_samples_16k) == 0) {
+ fdmdv_16_to_8_short(adc8k, &adc16k[FDMDV_OS_TAPS_16K], n_samples);
+ for(i=0; i<n_samples; i++)
+ dac8k[FDMDV_OS_TAPS_8K+i] = adc8k[i];
+ fdmdv_8_to_16_short(dac16k, &dac8k[FDMDV_OS_TAPS_8K], n_samples);
+ spk_nsamples = n_samples_16k;
+ led_rt(0); led_err(0);
+ }
+ }
+ else {
+ if (ms > lastms+5000) {
+ usart_printf("Digital Voice\n");
+ lastms = ms;
+ }
+
+ /* 1600 or 700D/E DV mode */
+
+ nin = freedv_nin(f);
+ nout = nin;
+ freedv_set_total_bit_errors(f, 0);
+ if (adc1_read(&adc16k[FDMDV_OS_TAPS_16K], 2*nin) == 0) {
+ GPIOE->ODR = (1 << 3);
+ fdmdv_16_to_8_short(adc8k, &adc16k[FDMDV_OS_TAPS_16K], nin);
+ nout = freedv_rx(f, &dac8k[FDMDV_OS_TAPS_8K], adc8k);
+ fdmdv_8_to_16_short(dac16k, &dac8k[FDMDV_OS_TAPS_8K], nout);
+ spk_nsamples = 2*nout;
+ led_rt(freedv_get_sync(f)); led_err(freedv_get_total_bit_errors(f));
+ GPIOE->ODR &= ~(1 << 3);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* Write audio to speaker output */
+ if (spk_nsamples || sfx_player.note || morse_player.msg) {
+ /* Make a note of our playback position */
+ int16_t* play_ptr = dac16k;
+
+ if (!spk_nsamples)
+ spk_nsamples = dac2_free();
+
+ /*
+ * There is audio to play on the external speaker. If there
+ * is a sound or announcement, software-mix it into the outgoing
+ * buffer.
+ */
+ if (sfx_player.note) {
+ int i;
+ if (menu.stack_depth) {
+ /* Exclusive */
+ for (i = 0; i < spk_nsamples; i++)
+ dac16k[i] = sfx_next(&sfx_player) >> prefs.menu_vol;
+ } else {
+ /* Software mix */
+ for (i = 0; i < spk_nsamples; i++)
+ dac16k[i] = software_mix(dac16k[i],
+ sfx_next(&sfx_player) >> prefs.menu_vol);
+ }
+ if (!sfx_player.note && morse_player.msg)
+ announceTicker = ANNOUNCE_DELAY;
+ } else if (!announceTicker && morse_player.msg) {
+ int i;
+ if (menu.stack_depth) {
+ for (i = 0; i < spk_nsamples; i++)
+ dac16k[i] = morse_next(&morse_player) >> prefs.menu_vol;
+ } else {
+ for (i = 0; i < spk_nsamples; i++)
+ dac16k[i] = software_mix(dac16k[i],
+ morse_next(&morse_player) >> prefs.menu_vol);
+ }
+ }
+
+ while (spk_nsamples) {
+ /* Get the number of samples to be played this time around */
+ int n_rem = dac2_free();
+ if (spk_nsamples < n_rem)
+ n_rem = spk_nsamples;
+ /* Play the audio */
+ dac2_write(play_ptr, n_rem, 0);
+ spk_nsamples -= n_rem;
+ play_ptr += n_rem;
+ }
+
+ /* Clear out buffer */
+ memset(dac16k, 0, n_samples_16k*sizeof(short));
+ }
+
+ } /* while(1) ... */
+}
+
+/*
+ * SysTick Interrupt Handler
+ */
+
+void SysTick_Handler(void)
+{
+ ms++;
+ switch_tick(&sw_select);
+ switch_tick(&sw_back);
+ switch_tick(&sw_ptt);
+ if (menuTicker > 0) {
+ menuTicker--;
+ }
+ if (menuLEDTicker > 0) {
+ menuLEDTicker--;
+ }
+ if (announceTicker > 0) {
+ announceTicker--;
+ }
+ tot_tick(&tot);
+}
+
+
+int process_core_state_machine(int core_state, struct menu_t *menu, int *op_mode) {
+ /* State machine updates */
+ switch(core_state) {
+ case STATE_RX:
+ {
+ uint8_t mode_changed = 0;
+
+ if (!menuTicker) {
+ if (menuExit) {
+ /* We've just exited a menu, wait for release of BACK */
+ if (switch_released(&sw_back))
+ menuExit = 0;
+ } else if (switch_pressed(&sw_ptt)) {
+ /* Cancel any announcement if scheduled */
+ if (announceTicker && morse_player.msg) {
+ announceTicker = 0;
+ morse_play(&morse_player, NULL);
+ }
+ /* Start time-out timer if enabled */
+ if (prefs.tot_period)
+ tot_start(&tot, prefs.tot_period*10,
+ prefs.tot_warn_period*10);
+ /* Enter transmit state */
+ core_state = STATE_TX;
+ } else if (switch_pressed(&sw_select) > HOLD_DELAY) {
+ /* Enter the menu */
+ led_pwr(1); led_ptt(0); led_rt(0);
+ led_err(0); not_cptt(1);
+
+ menu_enter(menu, &menu_root);
+ menuTicker = MENU_DELAY;
+ core_state = STATE_MENU;
+ prefs_changed = 0;
+ usart_printf("Entering menu ...\n");
+ print_prefs(&prefs);
+
+ } else if (switch_released(&sw_select)) {
+ /* Shortcut: change current mode */
+ *op_mode = (*op_mode + 1) % MAX_MODES;
+ mode_changed = 1;
+ } else if (switch_released(&sw_back)) {
+ /* Shortcut: change current mode */
+ *op_mode = *op_mode - 1;
+ if (*op_mode < 0)
+ {
+ // Loop back around to the end of the mode list if we reach 0.
+ *op_mode = MAX_MODES - 1;
+ }
+ mode_changed = 1;
+ }
+
+ if (mode_changed) {
+ /* Announce the new mode */
+ if (*op_mode == ANALOG)
+ morse_play(&morse_player, "ANA");
+ else if (*op_mode == DV1600)
+ morse_play(&morse_player, "1600");
+ else if (*op_mode == DV700D)
+ morse_play(&morse_player, "700D");
+ else if (*op_mode == DV700E)
+ morse_play(&morse_player, "700E");
+ sfx_play(&sfx_player, sound_click);
+ }
+ }
+ }
+ break;
+ case STATE_TX:
+ {
+ if (!switch_pressed(&sw_ptt)) {
+ /* PTT released, leave transmit mode */
+ tot_reset(&tot);
+ core_state = STATE_RX;
+ } else if (tot.event & TOT_EVT_TIMEOUT) {
+ /* Time-out reached */
+ sfx_play(&sfx_player, sound_death_march);
+ tot.event &= ~TOT_EVT_TIMEOUT;
+ core_state = STATE_RX_TOT;
+ } else if (tot.event & TOT_EVT_WARN_NEXT) {
+ /* Re-set warning flag */
+ tot.event &= ~TOT_EVT_WARN_NEXT;
+ /* Schedule a click tone */
+ sfx_play(&sfx_player, sound_click);
+ }
+ }
+ break;
+ case STATE_RX_TOT:
+ if (switch_released(&sw_ptt)) {
+ /* PTT released, leave transmit mode */
+ tot_reset(&tot);
+ core_state = STATE_RX;
+ }
+ break;
+ case STATE_MENU:
+ if (!menuTicker) {
+ /* We are in a menu */
+ static uint8_t press_ack = 0;
+ uint8_t save_settings = 0;
+
+ if (press_ack == 1) {
+ if ((sw_select.state == SW_STEADY)
+ && (!sw_select.sw))
+ press_ack = 0;
+ } else if (press_ack == 2) {
+ if ((sw_back.state == SW_STEADY)
+ && (!sw_back.sw))
+ press_ack = 0;
+ } else {
+ if (switch_pressed(&sw_select) > HOLD_DELAY) {
+ menu_exec(menu, MENU_EVT_SELECT);
+ press_ack = 1;
+ menuTicker = MENU_DELAY;
+ } else if (switch_pressed(&sw_back) > HOLD_DELAY) {
+ menu_exec(menu, MENU_EVT_BACK);
+ press_ack = 2;
+ menuTicker = MENU_DELAY;
+
+ usart_printf("Leaving menu ... stack_depth: %d \n", menu->stack_depth);
+ print_prefs(&prefs);
+ if (!menu->stack_depth)
+ save_settings = prefs_changed;
+
+ } else if (switch_released(&sw_select)) {
+ menu_exec(menu, MENU_EVT_NEXT);
+ menuTicker = MENU_DELAY;
+ } else if (switch_released(&sw_back)) {
+ menu_exec(menu, MENU_EVT_PREV);
+ menuTicker = MENU_DELAY;
+ } else if (switch_released(&sw_ptt)) {
+ while(menu->stack_depth > 0)
+ menu_exec(menu, MENU_EVT_EXIT);
+ sfx_play(&sfx_player, sound_returned);
+ }
+
+ /* If exited, put the LED back */
+ if (!menu->stack_depth) {
+ menuLEDTicker = 0;
+ menuTicker = 0;
+ led_pwr(LED_ON);
+ morse_play(&morse_player, NULL);
+ menuExit = 1;
+ if (save_settings) {
+ int oldest = -1;
+ int res;
+ /* Copy the morse settings in */
+ prefs.menu_freq = morse_player.freq;
+ prefs.menu_speed = morse_player.dit_time;
+ /* make sure we have same op mode as power on prefs */
+ *op_mode = prefs.op_mode;
+ /* Increment serial number */
+ prefs.serial++;
+ /* Find the oldest image */
+ find_prefs(&oldest, NULL);
+ if (oldest < 0)
+ oldest = 0; /* No current image */
+
+ /* Write new settings over it */
+ usart_printf("vrom_write\n");
+ res = vrom_write(oldest + PREFS_IMG_BASE, 0,
+ sizeof(prefs), &prefs);
+ if (res >= 0)
+ prefs_serial[oldest] = prefs.serial;
+ }
+ /* Go back to receive state */
+ core_state = STATE_RX;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return core_state;
+}
+
+
+/* ---------------------------- Menu data ---------------------------
+ *
+ * MENU -
+ * |- "MODE" Select operating mode
+ * | |- "ANA" - Analog
+ * | |- "DV1600" - FreeDV 1600
+ * | |- "DV700D" - FreeDV 700D
+ * | |- "DV700E" - FreeDV 700E
+ * |
+ * |- "TOT" Timer Out Timer options
+ * | |- "TIME" - Set timeout time (a sub menu)
+ * | | |- - SELECT.PRESS add 5 sec
+ * | | |- - BACK.PRESS subtracts 5 sec
+ * | |
+ * | |- "WARN" - Set warning time (a sub menu)
+ * | | |- - SELECT.PRESS add 5 sec
+ * | | |- - BACK.PRESS subtracts 5 sec
+ * |
+ * |- "UI" UI (morse code announcements) parameters
+ * | |- "FREQ" - Set tone
+ * | | |- - SELECT.PRESS add 50 Hz
+ * | | |- - BACK.PRESS subtracts 50 Hz
+ * | |
+ * | |- "WPMQ" - Set speed
+ * | | |- - SELECT.PRESS add 5 WPM
+ * | | |- - BACK.PRESS subtracts 5 WPM
+ * | |
+ * | |- "VOL" - Set volume
+ * | | |- - SELECT.PRESS -> quieter
+ * | | |- - BACK.PRESS -> louder
+ */
+
+/*!
+ * Default handler for menu callback.
+ */
+static void menu_default_cb(struct menu_t* const menu, uint32_t event)
+{
+ /* Get the current menu item */
+ const struct menu_item_t* item = menu_item(menu, 0);
+ uint8_t announce = 0;
+
+ switch(event) {
+ case MENU_EVT_ENTERED:
+ sfx_play(&sfx_player, sound_startup);
+ /* Choose first item */
+ menu->current = 0;
+ case MENU_EVT_RETURNED:
+ announce = 1;
+ break;
+ case MENU_EVT_NEXT:
+ sfx_play(&sfx_player, sound_click);
+ menu->current = (menu->current + 1) % item->num_children;
+ announce = 1;
+ break;
+ case MENU_EVT_PREV:
+ sfx_play(&sfx_player, sound_click);
+ if (menu->current == 0)
+ {
+ menu->current = item->num_children - 1;
+ }
+ else
+ {
+ menu->current = menu->current - 1;
+ }
+ announce = 1;
+ break;
+ case MENU_EVT_SELECT:
+ /* Enter the sub-menu */
+ menu_enter(menu, item->children[menu->current]);
+ break;
+ case MENU_EVT_BACK:
+ /* Exit the menu */
+ sfx_play(&sfx_player, sound_returned);
+ case MENU_EVT_EXIT:
+ menu_leave(menu);
+ break;
+ default:
+ break;
+ }
+
+ if (announce) {
+ /* Announce the label of the selected child */
+ morse_play(&morse_player,
+ item->children[menu->current]->label);
+ }
+}
+
+/* Root menu item forward declarations */
+static const struct menu_item_t* menu_root_children[];
+/* Root item definition */
+static const struct menu_item_t menu_root = {
+ .label = "MENU",
+ .event_cb = menu_default_cb,
+ .children = menu_root_children,
+ .num_children = 3,
+};
+
+/* Child declarations */
+static const struct menu_item_t menu_op_mode;
+static const struct menu_item_t menu_tot;
+static const struct menu_item_t menu_ui;
+static const struct menu_item_t * menu_root_children[] = {
+ &menu_op_mode,
+ &menu_tot,
+ &menu_ui,
+};
+
+
+/* Operation Mode menu forward declarations */
+static void menu_op_mode_cb(struct menu_t* const menu, uint32_t event);
+static struct menu_item_t const* menu_op_mode_children[];
+/* Operation mode menu */
+static const struct menu_item_t menu_op_mode = {
+ .label = "MODE",
+ .event_cb = menu_op_mode_cb,
+ .children = menu_op_mode_children,
+ .num_children = 4,
+};
+/* Children */
+static const struct menu_item_t menu_op_mode_analog = {
+ .label = "ANA",
+ .event_cb = NULL,
+ .children = NULL,
+ .num_children = 0,
+ .data = {
+ .ui = ANALOG,
+ },
+};
+static const struct menu_item_t menu_op_mode_dv1600 = {
+ .label = "1600",
+ .event_cb = NULL,
+ .children = NULL,
+ .num_children = 0,
+ .data = {
+ .ui = DV1600,
+ },
+};
+static const struct menu_item_t menu_op_mode_dv700D = {
+ .label = "700D",
+ .event_cb = NULL,
+ .children = NULL,
+ .num_children = 0,
+ .data = {
+ .ui = DV700D,
+ },
+};
+static const struct menu_item_t menu_op_mode_dv700E = {
+ .label = "700E",
+ .event_cb = NULL,
+ .children = NULL,
+ .num_children = 0,
+ .data = {
+ .ui = DV700E,
+ },
+};
+static struct menu_item_t const* menu_op_mode_children[] = {
+ &menu_op_mode_analog,
+ &menu_op_mode_dv1600,
+ &menu_op_mode_dv700D,
+ &menu_op_mode_dv700E,
+};
+/* Callback function */
+static void menu_op_mode_cb(struct menu_t* const menu, uint32_t event)
+{
+ const struct menu_item_t* item = menu_item(menu, 0);
+ uint8_t announce = 0;
+
+ switch(event) {
+ case MENU_EVT_ENTERED:
+ sfx_play(&sfx_player, sound_startup);
+ /* Choose current item */
+ switch(prefs.op_mode) {
+ case DV1600:
+ menu->current = 1;
+ break;
+ case DV700D:
+ menu->current = 2;
+ break;
+ case DV700E:
+ menu->current = 3;
+ break;
+ default:
+ menu->current = 0;
+ }
+ case MENU_EVT_RETURNED:
+ /* Shouldn't happen, but we handle it anyway */
+ announce = 1;
+ break;
+ case MENU_EVT_NEXT:
+ sfx_play(&sfx_player, sound_click);
+ menu->current = (menu->current + 1) % item->num_children;
+ announce = 1;
+ break;
+ case MENU_EVT_PREV:
+ sfx_play(&sfx_player, sound_click);
+ if (menu->current == 0)
+ {
+ menu->current = item->num_children - 1;
+ }
+ else
+ {
+ menu->current = menu->current - 1;
+ }
+ announce = 1;
+ break;
+ case MENU_EVT_SELECT:
+ /* Choose the selected mode */
+ prefs.op_mode = item->children[menu->current]->data.ui;
+ /* Play the "selected" tune and return. */
+ sfx_play(&sfx_player, sound_startup);
+ prefs_changed = 1;
+ menu_leave(menu);
+ break;
+ case MENU_EVT_BACK:
+ /* Exit the menu */
+ sfx_play(&sfx_player, sound_returned);
+ case MENU_EVT_EXIT:
+ menu_leave(menu);
+ break;
+ default:
+ break;
+ }
+
+ if (announce) {
+ /* Announce the label of the selected child */
+ morse_play(&morse_player,
+ item->children[menu->current]->label);
+ }
+}
+
+
+/* Time-out timer menu forward declarations */
+static struct menu_item_t const* menu_tot_children[];
+/* Operation mode menu */
+static const struct menu_item_t menu_tot = {
+ .label = "TOT",
+ .event_cb = menu_default_cb,
+ .children = menu_tot_children,
+ .num_children = 2,
+};
+/* Children */
+static const struct menu_item_t menu_tot_time;
+static const struct menu_item_t menu_tot_warn;
+static struct menu_item_t const* menu_tot_children[] = {
+ &menu_tot_time,
+ &menu_tot_warn,
+};
+
+/* TOT time menu forward declarations */
+static void menu_tot_time_cb(struct menu_t* const menu, uint32_t event);
+/* TOT time menu */
+static const struct menu_item_t menu_tot_time = {
+ .label = "TIME",
+ .event_cb = menu_tot_time_cb,
+ .children = NULL,
+ .num_children = 0,
+};
+
+/* Callback function */
+static void menu_tot_time_cb(struct menu_t* const menu, uint32_t event)
+{
+ uint8_t announce = 0;
+
+ switch(event) {
+ case MENU_EVT_ENTERED:
+ sfx_play(&sfx_player, sound_startup);
+ /* Get the current period */
+ menu->current = prefs.tot_period;
+ case MENU_EVT_RETURNED:
+ /* Shouldn't happen, but we handle it anyway */
+ announce = 1;
+ break;
+ case MENU_EVT_NEXT:
+ sfx_play(&sfx_player, sound_click);
+ /* Adjust the frequency up by 50 Hz */
+ if (prefs.tot_period < 600)
+ prefs.tot_period += 5;
+ announce = 1;
+ break;
+ case MENU_EVT_PREV:
+ sfx_play(&sfx_player, sound_click);
+ if (prefs.tot_period > 0)
+ prefs.tot_period -= 5;
+ announce = 1;
+ break;
+ case MENU_EVT_SELECT:
+ /* Play the "selected" tune and return. */
+ sfx_play(&sfx_player, sound_startup);
+ prefs_changed = 1;
+ menu_leave(menu);
+ break;
+ case MENU_EVT_BACK:
+ /* Restore the mode and exit the menu */
+ sfx_play(&sfx_player, sound_returned);
+ case MENU_EVT_EXIT:
+ prefs.tot_period = menu->current;
+ menu_leave(menu);
+ break;
+ default:
+ break;
+ }
+
+ if (announce) {
+ /* Render the text, thankfully we don't need re-entrancy */
+ static char period[6];
+ snprintf(period, 6, "%d", prefs.tot_period);
+ /* Announce the period */
+ morse_play(&morse_player, period);
+ }
+};
+
+/* TOT warning time menu forward declarations */
+static void menu_tot_warn_cb(struct menu_t* const menu, uint32_t event);
+/* TOT warning time menu */
+static const struct menu_item_t menu_tot_warn = {
+ .label = "WARN",
+ .event_cb = menu_tot_warn_cb,
+ .children = NULL,
+ .num_children = 0,
+};
+
+/* Callback function */
+static void menu_tot_warn_cb(struct menu_t* const menu, uint32_t event)
+{
+ uint8_t announce = 0;
+
+ switch(event) {
+ case MENU_EVT_ENTERED:
+ sfx_play(&sfx_player, sound_startup);
+ /* Get the current period */
+ if (prefs.tot_warn_period < prefs.tot_period)
+ menu->current = prefs.tot_warn_period;
+ else
+ menu->current = prefs.tot_period;
+ case MENU_EVT_RETURNED:
+ /* Shouldn't happen, but we handle it anyway */
+ announce = 1;
+ break;
+ case MENU_EVT_NEXT:
+ sfx_play(&sfx_player, sound_click);
+ /* Adjust the frequency up by 50 Hz */
+ if (prefs.tot_warn_period < prefs.tot_period)
+ prefs.tot_warn_period += 5;
+ announce = 1;
+ break;
+ case MENU_EVT_PREV:
+ sfx_play(&sfx_player, sound_click);
+ if (prefs.tot_warn_period > 0)
+ prefs.tot_warn_period -= 5;
+ announce = 1;
+ break;
+ case MENU_EVT_SELECT:
+ /* Play the "selected" tune and return. */
+ sfx_play(&sfx_player, sound_startup);
+ prefs_changed = 1;
+ menu_leave(menu);
+ break;
+ case MENU_EVT_BACK:
+ /* Restore the mode and exit the menu */
+ sfx_play(&sfx_player, sound_returned);
+ case MENU_EVT_EXIT:
+ prefs.tot_warn_period = menu->current;
+ menu_leave(menu);
+ break;
+ default:
+ break;
+ }
+
+ if (announce) {
+ /* Render the text, thankfully we don't need re-entrancy */
+ static char period[6];
+ snprintf(period, 6, "%d", prefs.tot_warn_period);
+ /* Announce the period */
+ morse_play(&morse_player, period);
+ }
+};
+
+/* UI menu forward declarations */
+static struct menu_item_t const* menu_ui_children[];
+/* Operation mode menu */
+static const struct menu_item_t menu_ui = {
+ .label = "UI",
+ .event_cb = menu_default_cb,
+ .children = menu_ui_children,
+ .num_children = 3,
+};
+/* Children */
+static const struct menu_item_t menu_ui_freq;
+static const struct menu_item_t menu_ui_speed;
+static const struct menu_item_t menu_ui_vol;
+static struct menu_item_t const* menu_ui_children[] = {
+ &menu_ui_freq,
+ &menu_ui_speed,
+ &menu_ui_vol,
+};
+
+/* UI Frequency menu forward declarations */
+static void menu_ui_freq_cb(struct menu_t* const menu, uint32_t event);
+/* UI Frequency menu */
+static const struct menu_item_t menu_ui_freq = {
+ .label = "FREQ",
+ .event_cb = menu_ui_freq_cb,
+ .children = NULL,
+ .num_children = 0,
+};
+/* Callback function */
+static void menu_ui_freq_cb(struct menu_t* const menu, uint32_t event)
+{
+ uint8_t announce = 0;
+
+ switch(event) {
+ case MENU_EVT_ENTERED:
+ sfx_play(&sfx_player, sound_startup);
+ /* Get the current frequency */
+ menu->current = morse_player.freq;
+ case MENU_EVT_RETURNED:
+ /* Shouldn't happen, but we handle it anyway */
+ announce = 1;
+ break;
+ case MENU_EVT_NEXT:
+ sfx_play(&sfx_player, sound_click);
+ /* Adjust the frequency up by 50 Hz */
+ if (morse_player.freq < 2000)
+ morse_player.freq += 50;
+ announce = 1;
+ break;
+ case MENU_EVT_PREV:
+ sfx_play(&sfx_player, sound_click);
+ if (morse_player.freq > 50)
+ morse_player.freq -= 50;
+ announce = 1;
+ break;
+ case MENU_EVT_SELECT:
+ /* Play the "selected" tune and return. */
+ sfx_play(&sfx_player, sound_startup);
+ prefs_changed = 1;
+ menu_leave(menu);
+ break;
+ case MENU_EVT_BACK:
+ /* Restore the mode and exit the menu */
+ sfx_play(&sfx_player, sound_returned);
+ case MENU_EVT_EXIT:
+ morse_player.freq = menu->current;
+ menu_leave(menu);
+ break;
+ default:
+ break;
+ }
+
+ if (announce) {
+ /* Render the text, thankfully we don't need re-entrancy */
+ static char freq[6];
+ snprintf(freq, 6, "%d", morse_player.freq);
+ /* Announce the frequency */
+ morse_play(&morse_player, freq);
+ }
+};
+
+/* UI Speed menu forward declarations */
+static void menu_ui_speed_cb(struct menu_t* const menu, uint32_t event);
+/* UI Speed menu */
+static const struct menu_item_t menu_ui_speed = {
+ .label = "WPM",
+ .event_cb = menu_ui_speed_cb,
+ .children = NULL,
+ .num_children = 0,
+};
+/* Callback function */
+static void menu_ui_speed_cb(struct menu_t* const menu, uint32_t event)
+{
+ uint8_t announce = 0;
+
+ /* Get the current WPM */
+ uint16_t curr_wpm = 1200 / morse_player.dit_time;
+
+ switch(event) {
+ case MENU_EVT_ENTERED:
+ sfx_play(&sfx_player, sound_startup);
+ /* Get the current frequency */
+ menu->current = morse_player.dit_time;
+ case MENU_EVT_RETURNED:
+ /* Shouldn't happen, but we handle it anyway */
+ announce = 1;
+ break;
+ case MENU_EVT_NEXT:
+ sfx_play(&sfx_player, sound_click);
+ /* Increment WPM by 5 */
+ if (curr_wpm < 60)
+ curr_wpm += 5;
+ announce = 1;
+ break;
+ case MENU_EVT_PREV:
+ sfx_play(&sfx_player, sound_click);
+ if (curr_wpm > 5)
+ curr_wpm -= 5;
+ announce = 1;
+ break;
+ case MENU_EVT_SELECT:
+ /* Play the "selected" tune and return. */
+ sfx_play(&sfx_player, sound_startup);
+ prefs_changed = 1;
+ menu_leave(menu);
+ break;
+ case MENU_EVT_BACK:
+ /* Restore the mode and exit the menu */
+ sfx_play(&sfx_player, sound_returned);
+ case MENU_EVT_EXIT:
+ morse_player.dit_time = menu->current;
+ menu_leave(menu);
+ break;
+ default:
+ break;
+ }
+
+ if (announce) {
+ /* Render the text, thankfully we don't need re-entrancy */
+ static char wpm[5];
+ snprintf(wpm, 5, "%d", curr_wpm);
+ /* Set the new parameter */
+ morse_player.dit_time = 1200 / curr_wpm;
+ /* Announce the words per minute */
+ morse_play(&morse_player, wpm);
+ }
+};
+
+/* UI volume menu forward declarations */
+static void menu_ui_vol_cb(struct menu_t* const menu, uint32_t event);
+/* UI volume menu */
+static const struct menu_item_t menu_ui_vol = {
+ .label = "VOL",
+ .event_cb = menu_ui_vol_cb,
+ .children = NULL,
+ .num_children = 0,
+};
+/* Callback function */
+static void menu_ui_vol_cb(struct menu_t* const menu, uint32_t event)
+{
+ uint8_t announce = 0;
+
+ switch(event) {
+ case MENU_EVT_ENTERED:
+ sfx_play(&sfx_player, sound_startup);
+ /* Get the current volume */
+ menu->current = prefs.menu_vol;
+ case MENU_EVT_RETURNED:
+ /* Shouldn't happen, but we handle it anyway */
+ announce = 1;
+ break;
+ case MENU_EVT_NEXT:
+ sfx_play(&sfx_player, sound_click);
+ if (prefs.menu_vol > 0)
+ prefs.menu_vol--;
+ announce = 1;
+ break;
+ case MENU_EVT_PREV:
+ sfx_play(&sfx_player, sound_click);
+ if (prefs.menu_vol < 14)
+ prefs.menu_vol++;
+ announce = 1;
+ break;
+ case MENU_EVT_SELECT:
+ /* Play the "selected" tune and return. */
+ sfx_play(&sfx_player, sound_startup);
+ menu_leave(menu);
+ prefs_changed = 1;
+ break;
+ case MENU_EVT_BACK:
+ /* Restore the mode and exit the menu */
+ sfx_play(&sfx_player, sound_returned);
+ case MENU_EVT_EXIT:
+ prefs.menu_vol = menu->current;
+ menu_leave(menu);
+ break;
+ default:
+ break;
+ }
+
+ if (announce) {
+ /* Render the text, thankfully we don't need re-entrancy */
+ static char vol[5];
+ snprintf(vol, 5, "%d", 15 - prefs.menu_vol);
+ /* Announce the volume level */
+ morse_play(&morse_player, vol);
+ }
+};
diff --git a/stm32/src/sounds.c b/stm32/src/sounds.c
new file mode 100644
index 0000000..54848b8
--- /dev/null
+++ b/stm32/src/sounds.c
@@ -0,0 +1,62 @@
+/*!
+ * Sound effect library.
+ *
+ * This provides some sound effects for the SM1000 UI.
+ *
+ * Author Stuart Longland <[email protected]>
+ * Copyright (C) 2015 FreeDV project.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation. 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 Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "sounds.h"
+
+const struct sfx_note_t sound_startup[] = {
+ {.freq = 600, .duration = 80},
+ {.freq = 800, .duration = 80},
+ {.freq = 1000, .duration = 80},
+ {.freq = 0, .duration = 0}
+};
+
+const struct sfx_note_t sound_returned[] = {
+ {.freq = 1000, .duration = 80},
+ {.freq = 800, .duration = 80},
+ {.freq = 600, .duration = 80},
+ {.freq = 0, .duration = 0}
+};
+
+const struct sfx_note_t sound_click[] = {
+ {.freq = 1200, .duration = 10},
+ {.freq = 0, .duration = 0}
+};
+
+const struct sfx_note_t sound_death_march[] = {
+ {.freq = 340, .duration = 400},
+ {.freq = 0, .duration = 80},
+ {.freq = 340, .duration = 400},
+ {.freq = 0, .duration = 80},
+ {.freq = 340, .duration = 400},
+ {.freq = 0, .duration = 80},
+ {.freq = 420, .duration = 400},
+ {.freq = 0, .duration = 80},
+ {.freq = 400, .duration = 300},
+ {.freq = 0, .duration = 80},
+ {.freq = 340, .duration = 120},
+ {.freq = 0, .duration = 80},
+ {.freq = 340, .duration = 120},
+ {.freq = 0, .duration = 80},
+ {.freq = 300, .duration = 200},
+ {.freq = 0, .duration = 80},
+ {.freq = 340, .duration = 400},
+ {.freq = 0, .duration = 0},
+};
diff --git a/stm32/src/startup_stm32f4xx.s b/stm32/src/startup_stm32f4xx.s
new file mode 100644
index 0000000..6a34870
--- /dev/null
+++ b/stm32/src/startup_stm32f4xx.s
@@ -0,0 +1,526 @@
+/**
+ ******************************************************************************
+ * @file startup_stm32f4xx.s
+ * @author MCD Application Team
+ * @version V1.0.0
+ * @date 30-September-2011
+ * @brief STM32F4xx Devices vector table for Atollic TrueSTUDIO toolchain.
+ * This module performs:
+ * - Set the initial SP
+ * - Set the initial PC == Reset_Handler,
+ * - Set the vector table entries with the exceptions ISR address
+ * - Configure the clock system and the external SRAM mounted on
+ * STM324xG-EVAL board to be used as data memory (optional,
+ * to be enabled by user)
+ * - Branches to main in the C library (which eventually
+ * calls main()).
+ * After Reset the Cortex-M4 processor is in Thread mode,
+ * priority is Privileged, and the Stack is set to Main.
+ ******************************************************************************
+ * @attention
+ *
+ * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
+ * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
+ * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
+ * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
+ * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
+ * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
+ *
+ * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
+ ******************************************************************************
+ */
+
+ .syntax unified
+ .cpu cortex-m3
+ .fpu softvfp
+ .thumb
+
+.global g_pfnVectors
+.global Default_Handler
+.global EndofMain
+
+/* start address for the initialization values of the .data section.
+defined in linker script */
+.word _sidata
+/* start address for the .data section. defined in linker script */
+.word _sdata
+/* end address for the .data section. defined in linker script */
+.word _edata
+/* start address for the .bss section. defined in linker script */
+.word _sbss
+/* end address for the .bss section. defined in linker script */
+.word _ebss
+/* stack used for SystemInit_ExtMemCtl; always internal RAM used */
+
+/**
+ * @brief This is the code that gets called when the processor first
+ * starts execution following a reset event. Only the absolutely
+ * necessary set is performed, after which the application
+ * supplied main() routine is called.
+ * @param None
+ * @retval : None
+*/
+
+ .section .text.Reset_Handler
+ .weak Reset_Handler
+ .type Reset_Handler, %function
+Reset_Handler:
+
+/* Copy the data segment initializers from flash to SRAM */
+ movs r1, #0
+ b LoopCopyDataInit
+
+CopyDataInit:
+ ldr r3, =_sidata
+ ldr r3, [r3, r1]
+ str r3, [r0, r1]
+ adds r1, r1, #4
+
+LoopCopyDataInit:
+ ldr r0, =_sdata
+ ldr r3, =_edata
+ adds r2, r0, r1
+ cmp r2, r3
+ bcc CopyDataInit
+ ldr r2, =_sbss
+ b LoopFillZerobss
+
+/* Zero fill all memory from bss up */
+FillZerobss:
+ movs r3, #0
+ str r3, [r2], #4
+
+LoopFillZerobss:
+ ldr r3, = _ebss
+ cmp r2, r3
+ bcc FillZerobss
+
+/* Zero memory from bss up with a sentinel value */
+ b LoopFillsentinel
+Fillsentinel:
+ ldr r3, = 0x55555555 /* sentinel value we put in memory */
+ str r3, [r2], #4
+
+LoopFillsentinel:
+ ldr r3, = 0x2001fffc /* end of ram */
+ cmp r2, r3
+ bcc Fillsentinel
+
+/* Call the clock system initialization function.*/
+ bl SystemInit
+/* Call static constructors */
+ bl __libc_init_array
+/* Call the application's entry point.*/
+ bl main
+EndofMain:
+ bl .
+.size Reset_Handler, .-Reset_Handler
+
+/**
+ * @brief This is the code that gets called when the processor receives an
+ * unexpected interrupt. This simply enters an infinite loop, preserving
+ * the system state for examination by a debugger.
+ * @param None
+ * @retval None
+*/
+ .section .text.Default_Handler,"ax",%progbits
+Default_Handler:
+Infinite_Loop:
+ b Infinite_Loop
+ .size Default_Handler, .-Default_Handler
+/******************************************************************************
+*
+* The minimal vector table for a Cortex M3. Note that the proper constructs
+* must be placed on this to ensure that it ends up at physical address
+* 0x0000.0000.
+*
+*******************************************************************************/
+ .section .isr_vector,"a",%progbits
+ .type g_pfnVectors, %object
+ .size g_pfnVectors, .-g_pfnVectors
+
+
+g_pfnVectors:
+ .word _estack
+ .word Reset_Handler
+ .word NMI_Handler
+ .word HardFault_Handler
+ .word MemManage_Handler
+ .word BusFault_Handler
+ .word UsageFault_Handler
+ .word 0
+ .word 0
+ .word 0
+ .word 0
+ .word SVC_Handler
+ .word DebugMon_Handler
+ .word 0
+ .word PendSV_Handler
+ .word SysTick_Handler
+
+ /* External Interrupts */
+ .word WWDG_IRQHandler /* Window WatchDog */
+ .word PVD_IRQHandler /* PVD through EXTI Line detection */
+ .word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */
+ .word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */
+ .word FLASH_IRQHandler /* FLASH */
+ .word RCC_IRQHandler /* RCC */
+ .word EXTI0_IRQHandler /* EXTI Line0 */
+ .word EXTI1_IRQHandler /* EXTI Line1 */
+ .word EXTI2_IRQHandler /* EXTI Line2 */
+ .word EXTI3_IRQHandler /* EXTI Line3 */
+ .word EXTI4_IRQHandler /* EXTI Line4 */
+ .word DMA1_Stream0_IRQHandler /* DMA1 Stream 0 */
+ .word DMA1_Stream1_IRQHandler /* DMA1 Stream 1 */
+ .word DMA1_Stream2_IRQHandler /* DMA1 Stream 2 */
+ .word DMA1_Stream3_IRQHandler /* DMA1 Stream 3 */
+ .word DMA1_Stream4_IRQHandler /* DMA1 Stream 4 */
+ .word DMA1_Stream5_IRQHandler /* DMA1 Stream 5 */
+ .word DMA1_Stream6_IRQHandler /* DMA1 Stream 6 */
+ .word ADC_IRQHandler /* ADC1, ADC2 and ADC3s */
+ .word CAN1_TX_IRQHandler /* CAN1 TX */
+ .word CAN1_RX0_IRQHandler /* CAN1 RX0 */
+ .word CAN1_RX1_IRQHandler /* CAN1 RX1 */
+ .word CAN1_SCE_IRQHandler /* CAN1 SCE */
+ .word EXTI9_5_IRQHandler /* External Line[9:5]s */
+ .word TIM1_BRK_TIM9_IRQHandler /* TIM1 Break and TIM9 */
+ .word TIM1_UP_TIM10_IRQHandler /* TIM1 Update and TIM10 */
+ .word TIM1_TRG_COM_TIM11_IRQHandler /* TIM1 Trigger and Commutation and TIM11 */
+ .word TIM1_CC_IRQHandler /* TIM1 Capture Compare */
+ .word TIM2_IRQHandler /* TIM2 */
+ .word TIM3_IRQHandler /* TIM3 */
+ .word TIM4_IRQHandler /* TIM4 */
+ .word I2C1_EV_IRQHandler /* I2C1 Event */
+ .word I2C1_ER_IRQHandler /* I2C1 Error */
+ .word I2C2_EV_IRQHandler /* I2C2 Event */
+ .word I2C2_ER_IRQHandler /* I2C2 Error */
+ .word SPI1_IRQHandler /* SPI1 */
+ .word SPI2_IRQHandler /* SPI2 */
+ .word USART1_IRQHandler /* USART1 */
+ .word USART2_IRQHandler /* USART2 */
+ .word USART3_IRQHandler /* USART3 */
+ .word EXTI15_10_IRQHandler /* External Line[15:10]s */
+ .word RTC_Alarm_IRQHandler /* RTC Alarm (A and B) through EXTI Line */
+ .word OTG_FS_WKUP_IRQHandler /* USB OTG FS Wakeup through EXTI line */
+ .word TIM8_BRK_TIM12_IRQHandler /* TIM8 Break and TIM12 */
+ .word TIM8_UP_TIM13_IRQHandler /* TIM8 Update and TIM13 */
+ .word TIM8_TRG_COM_TIM14_IRQHandler /* TIM8 Trigger and Commutation and TIM14 */
+ .word TIM8_CC_IRQHandler /* TIM8 Capture Compare */
+ .word DMA1_Stream7_IRQHandler /* DMA1 Stream7 */
+ .word FSMC_IRQHandler /* FSMC */
+ .word SDIO_IRQHandler /* SDIO */
+ .word TIM5_IRQHandler /* TIM5 */
+ .word SPI3_IRQHandler /* SPI3 */
+ .word UART4_IRQHandler /* UART4 */
+ .word UART5_IRQHandler /* UART5 */
+ .word TIM6_DAC_IRQHandler /* TIM6 and DAC1&2 underrun errors */
+ .word TIM7_IRQHandler /* TIM7 */
+ .word DMA2_Stream0_IRQHandler /* DMA2 Stream 0 */
+ .word DMA2_Stream1_IRQHandler /* DMA2 Stream 1 */
+ .word DMA2_Stream2_IRQHandler /* DMA2 Stream 2 */
+ .word DMA2_Stream3_IRQHandler /* DMA2 Stream 3 */
+ .word DMA2_Stream4_IRQHandler /* DMA2 Stream 4 */
+ .word ETH_IRQHandler /* Ethernet */
+ .word ETH_WKUP_IRQHandler /* Ethernet Wakeup through EXTI line */
+ .word CAN2_TX_IRQHandler /* CAN2 TX */
+ .word CAN2_RX0_IRQHandler /* CAN2 RX0 */
+ .word CAN2_RX1_IRQHandler /* CAN2 RX1 */
+ .word CAN2_SCE_IRQHandler /* CAN2 SCE */
+ .word OTG_FS_IRQHandler /* USB OTG FS */
+ .word DMA2_Stream5_IRQHandler /* DMA2 Stream 5 */
+ .word DMA2_Stream6_IRQHandler /* DMA2 Stream 6 */
+ .word DMA2_Stream7_IRQHandler /* DMA2 Stream 7 */
+ .word USART6_IRQHandler /* USART6 */
+ .word I2C3_EV_IRQHandler /* I2C3 event */
+ .word I2C3_ER_IRQHandler /* I2C3 error */
+ .word OTG_HS_EP1_OUT_IRQHandler /* USB OTG HS End Point 1 Out */
+ .word OTG_HS_EP1_IN_IRQHandler /* USB OTG HS End Point 1 In */
+ .word OTG_HS_WKUP_IRQHandler /* USB OTG HS Wakeup through EXTI */
+ .word OTG_HS_IRQHandler /* USB OTG HS */
+ .word DCMI_IRQHandler /* DCMI */
+ .word CRYP_IRQHandler /* CRYP crypto */
+ .word HASH_RNG_IRQHandler /* Hash and Rng */
+ .word FPU_IRQHandler /* FPU */
+
+
+/*******************************************************************************
+*
+* Provide weak aliases for each Exception handler to the Default_Handler.
+* As they are weak aliases, any function with the same name will override
+* this definition.
+*
+*******************************************************************************/
+ .weak NMI_Handler
+ .thumb_set NMI_Handler,Default_Handler
+
+ .weak HardFault_Handler
+ .thumb_set HardFault_Handler,Default_Handler
+
+ .weak MemManage_Handler
+ .thumb_set MemManage_Handler,Default_Handler
+
+ .weak BusFault_Handler
+ .thumb_set BusFault_Handler,Default_Handler
+
+ .weak UsageFault_Handler
+ .thumb_set UsageFault_Handler,Default_Handler
+
+ .weak SVC_Handler
+ .thumb_set SVC_Handler,Default_Handler
+
+ .weak DebugMon_Handler
+ .thumb_set DebugMon_Handler,Default_Handler
+
+ .weak PendSV_Handler
+ .thumb_set PendSV_Handler,Default_Handler
+
+ .weak SysTick_Handler
+ .thumb_set SysTick_Handler,Default_Handler
+
+ .weak WWDG_IRQHandler
+ .thumb_set WWDG_IRQHandler,Default_Handler
+
+ .weak PVD_IRQHandler
+ .thumb_set PVD_IRQHandler,Default_Handler
+
+ .weak TAMP_STAMP_IRQHandler
+ .thumb_set TAMP_STAMP_IRQHandler,Default_Handler
+
+ .weak RTC_WKUP_IRQHandler
+ .thumb_set RTC_WKUP_IRQHandler,Default_Handler
+
+ .weak FLASH_IRQHandler
+ .thumb_set FLASH_IRQHandler,Default_Handler
+
+ .weak RCC_IRQHandler
+ .thumb_set RCC_IRQHandler,Default_Handler
+
+ .weak EXTI0_IRQHandler
+ .thumb_set EXTI0_IRQHandler,Default_Handler
+
+ .weak EXTI1_IRQHandler
+ .thumb_set EXTI1_IRQHandler,Default_Handler
+
+ .weak EXTI2_IRQHandler
+ .thumb_set EXTI2_IRQHandler,Default_Handler
+
+ .weak EXTI3_IRQHandler
+ .thumb_set EXTI3_IRQHandler,Default_Handler
+
+ .weak EXTI4_IRQHandler
+ .thumb_set EXTI4_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream0_IRQHandler
+ .thumb_set DMA1_Stream0_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream1_IRQHandler
+ .thumb_set DMA1_Stream1_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream2_IRQHandler
+ .thumb_set DMA1_Stream2_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream3_IRQHandler
+ .thumb_set DMA1_Stream3_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream4_IRQHandler
+ .thumb_set DMA1_Stream4_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream5_IRQHandler
+ .thumb_set DMA1_Stream5_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream6_IRQHandler
+ .thumb_set DMA1_Stream6_IRQHandler,Default_Handler
+
+ .weak ADC_IRQHandler
+ .thumb_set ADC_IRQHandler,Default_Handler
+
+ .weak CAN1_TX_IRQHandler
+ .thumb_set CAN1_TX_IRQHandler,Default_Handler
+
+ .weak CAN1_RX0_IRQHandler
+ .thumb_set CAN1_RX0_IRQHandler,Default_Handler
+
+ .weak CAN1_RX1_IRQHandler
+ .thumb_set CAN1_RX1_IRQHandler,Default_Handler
+
+ .weak CAN1_SCE_IRQHandler
+ .thumb_set CAN1_SCE_IRQHandler,Default_Handler
+
+ .weak EXTI9_5_IRQHandler
+ .thumb_set EXTI9_5_IRQHandler,Default_Handler
+
+ .weak TIM1_BRK_TIM9_IRQHandler
+ .thumb_set TIM1_BRK_TIM9_IRQHandler,Default_Handler
+
+ .weak TIM1_UP_TIM10_IRQHandler
+ .thumb_set TIM1_UP_TIM10_IRQHandler,Default_Handler
+
+ .weak TIM1_TRG_COM_TIM11_IRQHandler
+ .thumb_set TIM1_TRG_COM_TIM11_IRQHandler,Default_Handler
+
+ .weak TIM1_CC_IRQHandler
+ .thumb_set TIM1_CC_IRQHandler,Default_Handler
+
+ .weak TIM2_IRQHandler
+ .thumb_set TIM2_IRQHandler,Default_Handler
+
+ .weak TIM3_IRQHandler
+ .thumb_set TIM3_IRQHandler,Default_Handler
+
+ .weak TIM4_IRQHandler
+ .thumb_set TIM4_IRQHandler,Default_Handler
+
+ .weak I2C1_EV_IRQHandler
+ .thumb_set I2C1_EV_IRQHandler,Default_Handler
+
+ .weak I2C1_ER_IRQHandler
+ .thumb_set I2C1_ER_IRQHandler,Default_Handler
+
+ .weak I2C2_EV_IRQHandler
+ .thumb_set I2C2_EV_IRQHandler,Default_Handler
+
+ .weak I2C2_ER_IRQHandler
+ .thumb_set I2C2_ER_IRQHandler,Default_Handler
+
+ .weak SPI1_IRQHandler
+ .thumb_set SPI1_IRQHandler,Default_Handler
+
+ .weak SPI2_IRQHandler
+ .thumb_set SPI2_IRQHandler,Default_Handler
+
+ .weak USART1_IRQHandler
+ .thumb_set USART1_IRQHandler,Default_Handler
+
+ .weak USART2_IRQHandler
+ .thumb_set USART2_IRQHandler,Default_Handler
+
+ .weak USART3_IRQHandler
+ .thumb_set USART3_IRQHandler,Default_Handler
+
+ .weak EXTI15_10_IRQHandler
+ .thumb_set EXTI15_10_IRQHandler,Default_Handler
+
+ .weak RTC_Alarm_IRQHandler
+ .thumb_set RTC_Alarm_IRQHandler,Default_Handler
+
+ .weak OTG_FS_WKUP_IRQHandler
+ .thumb_set OTG_FS_WKUP_IRQHandler,Default_Handler
+
+ .weak TIM8_BRK_TIM12_IRQHandler
+ .thumb_set TIM8_BRK_TIM12_IRQHandler,Default_Handler
+
+ .weak TIM8_UP_TIM13_IRQHandler
+ .thumb_set TIM8_UP_TIM13_IRQHandler,Default_Handler
+
+ .weak TIM8_TRG_COM_TIM14_IRQHandler
+ .thumb_set TIM8_TRG_COM_TIM14_IRQHandler,Default_Handler
+
+ .weak TIM8_CC_IRQHandler
+ .thumb_set TIM8_CC_IRQHandler,Default_Handler
+
+ .weak DMA1_Stream7_IRQHandler
+ .thumb_set DMA1_Stream7_IRQHandler,Default_Handler
+
+ .weak FSMC_IRQHandler
+ .thumb_set FSMC_IRQHandler,Default_Handler
+
+ .weak SDIO_IRQHandler
+ .thumb_set SDIO_IRQHandler,Default_Handler
+
+ .weak TIM5_IRQHandler
+ .thumb_set TIM5_IRQHandler,Default_Handler
+
+ .weak SPI3_IRQHandler
+ .thumb_set SPI3_IRQHandler,Default_Handler
+
+ .weak UART4_IRQHandler
+ .thumb_set UART4_IRQHandler,Default_Handler
+
+ .weak UART5_IRQHandler
+ .thumb_set UART5_IRQHandler,Default_Handler
+
+ .weak TIM6_DAC_IRQHandler
+ .thumb_set TIM6_DAC_IRQHandler,Default_Handler
+
+ .weak TIM7_IRQHandler
+ .thumb_set TIM7_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream0_IRQHandler
+ .thumb_set DMA2_Stream0_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream1_IRQHandler
+ .thumb_set DMA2_Stream1_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream2_IRQHandler
+ .thumb_set DMA2_Stream2_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream3_IRQHandler
+ .thumb_set DMA2_Stream3_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream4_IRQHandler
+ .thumb_set DMA2_Stream4_IRQHandler,Default_Handler
+
+ .weak ETH_IRQHandler
+ .thumb_set ETH_IRQHandler,Default_Handler
+
+ .weak ETH_WKUP_IRQHandler
+ .thumb_set ETH_WKUP_IRQHandler,Default_Handler
+
+ .weak CAN2_TX_IRQHandler
+ .thumb_set CAN2_TX_IRQHandler,Default_Handler
+
+ .weak CAN2_RX0_IRQHandler
+ .thumb_set CAN2_RX0_IRQHandler,Default_Handler
+
+ .weak CAN2_RX1_IRQHandler
+ .thumb_set CAN2_RX1_IRQHandler,Default_Handler
+
+ .weak CAN2_SCE_IRQHandler
+ .thumb_set CAN2_SCE_IRQHandler,Default_Handler
+
+ .weak OTG_FS_IRQHandler
+ .thumb_set OTG_FS_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream5_IRQHandler
+ .thumb_set DMA2_Stream5_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream6_IRQHandler
+ .thumb_set DMA2_Stream6_IRQHandler,Default_Handler
+
+ .weak DMA2_Stream7_IRQHandler
+ .thumb_set DMA2_Stream7_IRQHandler,Default_Handler
+
+ .weak USART6_IRQHandler
+ .thumb_set USART6_IRQHandler,Default_Handler
+
+ .weak I2C3_EV_IRQHandler
+ .thumb_set I2C3_EV_IRQHandler,Default_Handler
+
+ .weak I2C3_ER_IRQHandler
+ .thumb_set I2C3_ER_IRQHandler,Default_Handler
+
+ .weak OTG_HS_EP1_OUT_IRQHandler
+ .thumb_set OTG_HS_EP1_OUT_IRQHandler,Default_Handler
+
+ .weak OTG_HS_EP1_IN_IRQHandler
+ .thumb_set OTG_HS_EP1_IN_IRQHandler,Default_Handler
+
+ .weak OTG_HS_WKUP_IRQHandler
+ .thumb_set OTG_HS_WKUP_IRQHandler,Default_Handler
+
+ .weak OTG_HS_IRQHandler
+ .thumb_set OTG_HS_IRQHandler,Default_Handler
+
+ .weak DCMI_IRQHandler
+ .thumb_set DCMI_IRQHandler,Default_Handler
+
+ .weak CRYP_IRQHandler
+ .thumb_set CRYP_IRQHandler,Default_Handler
+
+ .weak HASH_RNG_IRQHandler
+ .thumb_set HASH_RNG_IRQHandler,Default_Handler
+
+ .weak FPU_IRQHandler
+ .thumb_set FPU_IRQHandler,Default_Handler
+
+/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/
diff --git a/stm32/src/stm32f4_adc.c b/stm32/src/stm32f4_adc.c
new file mode 100644
index 0000000..96e776b
--- /dev/null
+++ b/stm32/src/stm32f4_adc.c
@@ -0,0 +1,286 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: stm32f4_adc.c
+ AUTHOR......: David Rowe
+ DATE CREATED: 4 June 2013
+
+ Two channel ADC driver module for STM32F4. Pin PA1 connects to ADC1, pin
+ PA2 connects to ADC2.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2013 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "stm32f4xx_adc.h"
+#include "stm32f4xx_gpio.h"
+#include "stm32f4xx_rcc.h"
+
+#include "codec2_fifo.h"
+#include "stm32f4_adc.h"
+#include "debugblinky.h"
+
+struct FIFO *adc1_fifo;
+struct FIFO *adc2_fifo;
+unsigned short adc_buf[ADC_BUF_SZ];
+int adc_overflow1, adc_overflow2;
+int half,full;
+
+#define ADCx_DR_ADDRESS ((uint32_t)0x4001204C)
+#define DMA_CHANNELx DMA_Channel_0
+#define DMA_STREAMx DMA2_Stream0
+#define ADCx ADC1
+
+void adc_configure();
+
+static void tim2_config(int fs_divisor);
+
+// You can optionally supply your own storage for the FIFO buffers bu1 and buf2,
+// or set them to NULL and they will be malloc-ed for you
+void adc_open(int fs_divisor, int fifo_sz, short *buf1, short *buf2) {
+ if (buf1 == NULL) {
+ adc1_fifo = codec2_fifo_create(fifo_sz);
+ adc2_fifo = codec2_fifo_create(fifo_sz);
+ } else {
+ adc1_fifo = codec2_fifo_create_buf(fifo_sz, buf1);
+ adc2_fifo = codec2_fifo_create_buf(fifo_sz, buf2);
+ }
+
+ tim2_config(fs_divisor);
+ adc_configure();
+ init_debug_blinky();
+}
+
+/* n signed 16 bit samples in buf[] if return != -1 */
+
+int adc1_read(short buf[], int n) {
+ return codec2_fifo_read(adc1_fifo, buf, n);
+}
+
+/* n signed 16 bit samples in buf[] if return != -1 */
+
+int adc2_read(short buf[], int n) {
+ return codec2_fifo_read(adc2_fifo, buf, n);
+}
+
+/* Returns number of signed 16 bit samples in the FIFO currently */
+int adc1_samps(){
+ return codec2_fifo_used(adc1_fifo);
+}
+
+/* Returns number of signed 16 bit samples in the FIFO currently */
+int adc2_samps(){
+ return codec2_fifo_used(adc2_fifo);
+}
+
+static void tim2_config(int fs_divisor)
+{
+ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
+
+ /* TIM2 Periph clock enable */
+ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
+
+ /* --------------------------------------------------------
+
+ TIM2 input clock (TIM2CLK) is set to 2 * APB1 clock (PCLK1), since
+ APB1 prescaler is different from 1 (see system_stm32f4xx.c and Fig
+ 13 clock tree figure in DM0031020.pdf).
+
+ Sample rate Fs = 2*PCLK1/TIM_ClockDivision
+ = (HCLK/2)/TIM_ClockDivision
+
+ ----------------------------------------------------------- */
+
+ /* Time base configuration */
+
+ TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
+ TIM_TimeBaseStructure.TIM_Period = fs_divisor - 1;
+ TIM_TimeBaseStructure.TIM_Prescaler = 0;
+ TIM_TimeBaseStructure.TIM_ClockDivision = 0;
+ TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
+ TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
+
+ /* TIM2 TRGO selection */
+
+ TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
+
+ /* TIM2 enable counter */
+
+ TIM_Cmd(TIM2, ENABLE);
+}
+
+
+void adc_configure(){
+ ADC_InitTypeDef ADC_init_structure;
+ GPIO_InitTypeDef GPIO_initStructre;
+ DMA_InitTypeDef DMA_InitStructure;
+ NVIC_InitTypeDef NVIC_InitStructure;
+
+ // Clock configuration
+
+ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
+ RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOAEN,ENABLE);
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
+
+ // Analog pin configuration ADC1->PA1, ADC2->PA2
+
+ GPIO_initStructre.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
+ GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN;
+ GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL;
+ GPIO_Init(GPIOA,&GPIO_initStructre);
+
+ // ADC structure configuration
+
+ ADC_DeInit();
+ ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Left;
+ ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;
+ ADC_init_structure.ADC_ContinuousConvMode = DISABLE;
+ ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
+ ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
+ ADC_init_structure.ADC_NbrOfConversion = 2;
+ ADC_init_structure.ADC_ScanConvMode = ENABLE;
+ ADC_Init(ADCx,&ADC_init_structure);
+
+ // Select the channel to be read from
+
+ ADC_RegularChannelConfig(ADCx,ADC_Channel_1,1,ADC_SampleTime_144Cycles);
+ ADC_RegularChannelConfig(ADCx,ADC_Channel_2,2,ADC_SampleTime_144Cycles);
+ //ADC_VBATCmd(ENABLE);
+
+ /* DMA configuration **************************************/
+
+ DMA_DeInit(DMA_STREAMx);
+ DMA_InitStructure.DMA_Channel = DMA_CHANNELx;
+ DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADCx_DR_ADDRESS;
+ DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)adc_buf;
+ DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
+ DMA_InitStructure.DMA_BufferSize = ADC_BUF_SZ;
+ DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
+ DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
+ DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
+ DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
+ DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
+ DMA_InitStructure.DMA_Priority = DMA_Priority_High;
+ DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
+ DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
+ DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
+ DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
+ DMA_Init(DMA_STREAMx, &DMA_InitStructure);
+
+ /* Enable DMA request after last transfer (Single-ADC mode) */
+
+ ADC_DMARequestAfterLastTransferCmd(ADCx, ENABLE);
+
+ /* Enable ADC1 DMA */
+
+ ADC_DMACmd(ADCx, ENABLE);
+
+ /* DMA2_Stream0 enable */
+
+ DMA_Cmd(DMA_STREAMx, ENABLE);
+
+ /* Enable DMA Half & Complete interrupts */
+
+ DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);
+
+ /* Enable the DMA Stream IRQ Channel */
+
+ NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
+ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
+ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
+ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
+ NVIC_Init(&NVIC_InitStructure);
+
+ // Enable and start ADC conversion
+
+ ADC_Cmd(ADC1,ENABLE);
+ ADC_SoftwareStartConv(ADC1);
+}
+
+/*
+ This function handles DMA Stream interrupt request.
+*/
+
+void DMA2_Stream0_IRQHandler(void) {
+ int i, j, sam;
+ short signed_buf1[ADC_BUF_SZ/2];
+ short signed_buf2[ADC_BUF_SZ/2];
+
+ GPIOE->ODR |= (1 << 0);
+
+ /* Half transfer interrupt */
+
+ if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF0) != RESET) {
+ half++;
+
+ /* convert to signed */
+
+ for(i=0, j=0; i<ADC_BUF_SZ/2; i+=2,j++) {
+ sam = (int)adc_buf[i] - 32768;
+ signed_buf1[j] = sam;
+ sam = (int)adc_buf[i+1] - 32768;
+ signed_buf2[j] = sam;
+ }
+ /* write first half to fifo */
+
+ if (codec2_fifo_write(adc1_fifo, signed_buf1, ADC_BUF_SZ/4) == -1) {
+ adc_overflow1++;
+ }
+ if (codec2_fifo_write(adc2_fifo, signed_buf2, ADC_BUF_SZ/4) == -1) {
+ adc_overflow2++;
+ }
+
+ /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+ DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF0);
+ }
+
+ /* Transfer complete interrupt */
+
+ if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0) != RESET) {
+ full++;
+
+ /* convert to signed */
+
+ for(i=0, j=0; i<ADC_BUF_SZ/2; i+=2,j++) {
+ sam = (int)adc_buf[ADC_BUF_SZ/2 + i] - 32768;
+ signed_buf1[j] = sam;
+ sam = (int)adc_buf[ADC_BUF_SZ/2 + i+1] - 32768;
+ signed_buf2[j] = sam;
+ }
+
+ /* write second half to fifo */
+
+ if (codec2_fifo_write(adc1_fifo, signed_buf1, ADC_BUF_SZ/4) == -1) {
+ adc_overflow1++;
+ }
+ if (codec2_fifo_write(adc2_fifo, signed_buf2, ADC_BUF_SZ/4) == -1) {
+ adc_overflow2++;
+ }
+
+ /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+ DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
+ }
+
+ GPIOE->ODR &= ~(1 << 0);
+}
+
diff --git a/stm32/src/stm32f4_dac.c b/stm32/src/stm32f4_dac.c
new file mode 100644
index 0000000..ec61bf4
--- /dev/null
+++ b/stm32/src/stm32f4_dac.c
@@ -0,0 +1,427 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: stm32f4_dac.c
+ AUTHOR......: David Rowe
+ DATE CREATED: 1 June 2013
+
+ DAC driver module for STM32F4. DAC1 is connected to pin PA4, DAC2
+ is connected to pin PA5.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2013 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "stm32f4xx.h"
+#include "codec2_fifo.h"
+#include "stm32f4_dac.h"
+#include "debugblinky.h"
+
+/* write to these registers for 12 bit left aligned data, as per data sheet
+ make sure 4 least sig bits set to 0 */
+
+#define DAC_DHR12R1_ADDRESS 0x40007408
+#define DAC_DHR12R2_ADDRESS 0x40007414
+
+#define DAC_MAX 4096 /* maximum amplitude */
+
+/* y=mx+c mapping of samples16 bit shorts to DAC samples. Table: 74
+ of data sheet indicates With DAC buffer on, DAC range is limited to
+ 0x0E0 to 0xF1C at VREF+ = 3.6 V, we have Vref=3.3V which is close.
+ */
+
+#define M ((3868.0-224.0)/65536.0)
+#define C 2047.0
+
+static struct FIFO *dac1_fifo;
+static struct FIFO *dac2_fifo;
+
+static unsigned short dac1_buf[DAC_BUF_SZ];
+static unsigned short dac2_buf[DAC_BUF_SZ];
+
+static void tim6_config(int fs_divisor);
+static void dac1_config(void);
+static void dac2_config(void);
+
+int dac_underflow;
+
+// You can optionally supply your own storage for the FIFO buffers bu1 and buf2,
+// or set them to NULL and they will be malloc-ed for you
+void dac_open(int fs_divisor, int fifo_size, short *buf1, short *buf2) {
+
+ memset(dac1_buf, 32768, sizeof(short)*DAC_BUF_SZ);
+ memset(dac2_buf, 32768, sizeof(short)*DAC_BUF_SZ);
+
+ /* Create fifos */
+
+ if ((buf1 == NULL) && (buf2 == NULL)) {
+ dac1_fifo = codec2_fifo_create(fifo_size);
+ dac2_fifo = codec2_fifo_create(fifo_size);
+ } else {
+ dac1_fifo = codec2_fifo_create_buf(fifo_size, buf1);
+ dac2_fifo = codec2_fifo_create_buf(fifo_size, buf2);
+ }
+
+ /* Turn on the clocks we need -----------------------------------------------*/
+
+ /* DMA1 clock enable */
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
+ /* GPIOA clock enable (to be used with DAC) */
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
+ /* DAC Periph clock enable */
+ RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
+
+ /* GPIO Pin configuration DAC1->PA.4, DAC2->PA.5 configuration --------------*/
+
+ GPIO_InitTypeDef GPIO_InitStructure;
+ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
+ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
+ GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
+ GPIO_Init(GPIOA, &GPIO_InitStructure);
+
+ /* Timer and DAC 1 & 2 Configuration ----------------------------------------*/
+
+ tim6_config(fs_divisor);
+ dac1_config();
+ dac2_config();
+
+ init_debug_blinky();
+}
+
+/* Call these functions to send samples to the DACs. For your
+ convenience they accept signed 16 bit samples. You can optionally
+ limit how much data to store in the fifo */
+
+int dac1_write(short buf[], int n, int limit) {
+ /* artificial limit < FIFO size */
+ if (limit) {
+ if ((codec2_fifo_used(dac1_fifo) + n) <= limit)
+ return codec2_fifo_write(dac1_fifo, buf, n);
+ else
+ return -1;
+ }
+ /* normal operation */
+ return codec2_fifo_write(dac1_fifo, buf, n);
+}
+
+int dac2_write(short buf[], int n, int limit) {
+ /* artificial limit < FIFO size */
+ if (limit) {
+ if ((codec2_fifo_used(dac2_fifo) + n) <= limit)
+ return codec2_fifo_write(dac2_fifo, buf, n);
+ else
+ return -1;
+ }
+ /* normal operation */
+ return codec2_fifo_write(dac2_fifo, buf, n);
+}
+
+int dac1_free() {
+ return codec2_fifo_free(dac1_fifo);
+}
+
+int dac2_free() {
+ return codec2_fifo_free(dac2_fifo);
+}
+
+static void tim6_config(int fs_divisor)
+{
+ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
+
+ /* TIM6 Periph clock enable */
+ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
+
+ /* --------------------------------------------------------
+
+ TIM6 input clock (TIM6CLK) is set to 2 * APB1 clock (PCLK1), since
+ APB1 prescaler is different from 1 (see system_stm32f4xx.c and Fig
+ 13 clock tree figure in DM0031020.pdf).
+
+ Sample rate Fs = 2*PCLK1/TIM_ClockDivision
+ = (HCLK/2)/TIM_ClockDivision
+
+ ----------------------------------------------------------- */
+
+ /* Time base configuration */
+
+ TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
+ TIM_TimeBaseStructure.TIM_Period = fs_divisor - 1;
+ TIM_TimeBaseStructure.TIM_Prescaler = 0;
+ TIM_TimeBaseStructure.TIM_ClockDivision = 0;
+ TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
+ TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
+
+ /* TIM6 TRGO selection */
+
+ TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);
+
+ /* TIM6 enable counter */
+
+ TIM_Cmd(TIM6, ENABLE);
+}
+
+static void dac1_config(void)
+{
+ DAC_InitTypeDef DAC_InitStructure;
+ DMA_InitTypeDef DMA_InitStructure;
+ NVIC_InitTypeDef NVIC_InitStructure;
+
+ /* DAC channel 1 Configuration */
+
+ /*
+ This line fixed a bug that cost me 5 days, bad wave amplitude
+ value, and some STM32F4 periph library bugs caused triangle wave
+ geneartion to be enable resulting in a low level tone on the
+ SM1000, that we thought was caused by analog issues like layour
+ or power supply biasing
+ */
+ DAC_StructInit(&DAC_InitStructure);
+
+ DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;
+ DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
+ DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
+ DAC_Init(DAC_Channel_1, &DAC_InitStructure);
+
+ /* DMA1_Stream5 channel7 configuration **************************************/
+ /* Table 35 page 219 of the monster data sheet */
+
+ DMA_DeInit(DMA1_Stream5);
+ DMA_InitStructure.DMA_Channel = DMA_Channel_7;
+ DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)DAC_DHR12R1_ADDRESS;
+ DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)dac1_buf;
+ DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
+ DMA_InitStructure.DMA_BufferSize = DAC_BUF_SZ;
+ DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
+ DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
+ DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
+ DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
+ DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
+ DMA_InitStructure.DMA_Priority = DMA_Priority_High;
+ DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
+ DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
+ DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
+ DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
+ DMA_Init(DMA1_Stream5, &DMA_InitStructure);
+
+ /* Enable DMA Half & Complete interrupts */
+
+ DMA_ITConfig(DMA1_Stream5, DMA_IT_TC | DMA_IT_HT, ENABLE);
+
+ /* Enable the DMA Stream IRQ Channel */
+
+ NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn;
+ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
+ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
+ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
+ NVIC_Init(&NVIC_InitStructure);
+
+ /* Enable DMA1_Stream5 */
+
+ DMA_Cmd(DMA1_Stream5, ENABLE);
+
+ /* Enable DAC Channel 1 */
+
+ DAC_Cmd(DAC_Channel_1, ENABLE);
+
+ /* Enable DMA for DAC Channel 1 */
+
+ DAC_DMACmd(DAC_Channel_1, ENABLE);
+}
+
+static void dac2_config(void)
+{
+ DAC_InitTypeDef DAC_InitStructure;
+ DMA_InitTypeDef DMA_InitStructure;
+ NVIC_InitTypeDef NVIC_InitStructure;
+
+ /* DAC channel 2 Configuration (see notes in dac1_config() above) */
+
+ DAC_StructInit(&DAC_InitStructure);
+ DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;
+ DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
+ DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
+ DAC_Init(DAC_Channel_2, &DAC_InitStructure);
+
+ /* DMA1_Stream6 channel7 configuration **************************************/
+
+ DMA_DeInit(DMA1_Stream6);
+ DMA_InitStructure.DMA_Channel = DMA_Channel_7;
+ DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)DAC_DHR12R2_ADDRESS;
+ DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)dac2_buf;
+ DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
+ DMA_InitStructure.DMA_BufferSize = DAC_BUF_SZ;
+ DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
+ DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
+ DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
+ DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
+ DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
+ DMA_InitStructure.DMA_Priority = DMA_Priority_High;
+ DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
+ DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
+ DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
+ DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
+ DMA_Init(DMA1_Stream6, &DMA_InitStructure);
+
+ /* Enable DMA Half & Complete interrupts */
+
+ DMA_ITConfig(DMA1_Stream6, DMA_IT_TC | DMA_IT_HT, ENABLE);
+
+ /* Enable the DMA Stream IRQ Channel */
+
+ NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream6_IRQn;
+ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
+ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
+ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
+ NVIC_Init(&NVIC_InitStructure);
+
+ /* Enable DMA1_Stream6 */
+
+ DMA_Cmd(DMA1_Stream6, ENABLE);
+
+ /* Enable DAC Channel 2 */
+
+ DAC_Cmd(DAC_Channel_2, ENABLE);
+
+ /* Enable DMA for DAC Channel 2 */
+
+ DAC_DMACmd(DAC_Channel_2, ENABLE);
+
+}
+
+/******************************************************************************/
+/* STM32F4xx Peripherals Interrupt Handlers */
+/* Add here the Interrupt Handler for the used peripheral(s) (PPP), for the */
+/* available peripheral interrupt handler's name please refer to the startup */
+/* file (startup_stm32f40xx.s/startup_stm32f427x.s). */
+/******************************************************************************/
+
+/*
+ This function handles DMA1 Stream 5 interrupt request for DAC1.
+*/
+
+void DMA1_Stream5_IRQHandler(void) {
+ int i, j, sam;
+ short signed_buf[DAC_BUF_SZ/2];
+
+ GPIOE->ODR |= (1 << 1);
+
+ /* Transfer half empty interrupt - refill first half */
+
+ if(DMA_GetITStatus(DMA1_Stream5, DMA_IT_HTIF5) != RESET) {
+ /* fill first half from fifo */
+
+ if (codec2_fifo_read(dac1_fifo, signed_buf, DAC_BUF_SZ/2) == -1) {
+ memset(signed_buf, 0, sizeof(short)*DAC_BUF_SZ/2);
+ dac_underflow++;
+ }
+
+ /* convert to unsigned */
+
+ for(i=0; i<DAC_BUF_SZ/2; i++) {
+ sam = (int)(M*(float)signed_buf[i] + C);
+ dac1_buf[i] = (unsigned short)sam;
+ }
+
+ /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+ DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_HTIF5);
+ }
+
+ /* Transfer complete interrupt - refill 2nd half */
+
+ if(DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5) != RESET) {
+ /* fill second half from fifo */
+
+ if (codec2_fifo_read(dac1_fifo, signed_buf, DAC_BUF_SZ/2) == -1) {
+ memset(signed_buf, 0, sizeof(short)*DAC_BUF_SZ/2);
+ dac_underflow++;
+ }
+
+ /* convert to unsigned */
+
+ for(i=0, j=DAC_BUF_SZ/2; i<DAC_BUF_SZ/2; i++,j++) {
+ sam = (int)(M*(float)signed_buf[i] + C);
+ dac1_buf[j] = (unsigned short)sam;
+ }
+
+ /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+ DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5);
+ }
+
+ GPIOE->ODR &= ~(1 << 1);
+}
+
+/*
+ This function handles DMA1 Stream 6 interrupt request for DAC2.
+*/
+
+void DMA1_Stream6_IRQHandler(void) {
+ int i, j, sam;
+ short signed_buf[DAC_BUF_SZ/2];
+
+ GPIOE->ODR |= (1 << 2);
+
+ /* Transfer half empty interrupt - refill first half */
+
+ if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_HTIF6) != RESET) {
+ /* fill first half from fifo */
+
+ if (codec2_fifo_read(dac2_fifo, signed_buf, DAC_BUF_SZ/2) == -1) {
+ memset(signed_buf, 0, sizeof(short)*DAC_BUF_SZ/2);
+ dac_underflow++;
+ }
+
+ /* convert to unsigned */
+
+ for(i=0; i<DAC_BUF_SZ/2; i++) {
+ sam = (int)(M*(float)signed_buf[i] + C);
+ dac2_buf[i] = (unsigned short)sam;
+ }
+
+ /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+ DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_HTIF6);
+ }
+
+ /* Transfer complete interrupt - refill 2nd half */
+
+ if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_TCIF6) != RESET) {
+ /* fill second half from fifo */
+
+ if (codec2_fifo_read(dac2_fifo, signed_buf, DAC_BUF_SZ/2) == -1) {
+ memset(signed_buf, 0, sizeof(short)*DAC_BUF_SZ/2);
+ dac_underflow++;
+ }
+
+ /* convert to unsigned */
+
+ for(i=0, j=DAC_BUF_SZ/2; i<DAC_BUF_SZ/2; i++,j++) {
+ sam = (int)(M*(float)signed_buf[i] + C);
+ dac2_buf[j] = (unsigned short)sam;
+ }
+
+ /* Clear DMA Stream Transfer Complete interrupt pending bit */
+
+ DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);
+ }
+
+ GPIOE->ODR &= ~(1 << 2);
+}
diff --git a/stm32/src/stm32f4_machdep.c b/stm32/src/stm32f4_machdep.c
new file mode 100644
index 0000000..59f9ed7
--- /dev/null
+++ b/stm32/src/stm32f4_machdep.c
@@ -0,0 +1,92 @@
+
+/*---------------------------------------------------------------------------*\
+
+ FILE........: stm32f4_machdep.c
+ AUTHOR......: David Rowe
+ DATE CREATED: May 2 2013
+
+ STM32F4 implementation of the machine dependent timer functions,
+ e.g. profiling using a clock cycle counter..
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2013 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. 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 Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include "machdep.h"
+
+#ifdef SEMIHOST_USE_STDIO
+#include "stdio.h"
+#else
+#include "gdb_stdio.h"
+#define printf gdb_stdio_printf
+#endif
+
+volatile unsigned int *DWT_CYCCNT = (volatile unsigned int *)0xE0001004;
+volatile unsigned int *DWT_CONTROL = (volatile unsigned int *)0xE0001000;
+volatile unsigned int *SCB_DEMCR = (volatile unsigned int *)0xE000EDFC;
+
+#define CORE_CLOCK 168E6
+#define BUF_SZ 4096
+
+static char buf[BUF_SZ];
+
+void machdep_profile_init(void)
+{
+ static int enabled = 0;
+
+ if (!enabled) {
+ *SCB_DEMCR = *SCB_DEMCR | 0x01000000;
+ *DWT_CYCCNT = 0; // reset the counter
+ *DWT_CONTROL = *DWT_CONTROL | 1 ; // enable the counter
+
+ enabled = 1;
+ }
+ *buf = 0;
+}
+
+void machdep_profile_reset(void)
+{
+ *DWT_CYCCNT = 0; // reset the counter
+}
+
+unsigned int machdep_profile_sample(void) {
+ return *DWT_CYCCNT;
+}
+
+/* log to a buffer, we only call printf after timing finished as it is slow */
+
+unsigned int machdep_profile_sample_and_log(unsigned int start, char s[])
+{
+ char tmp[80];
+ float msec;
+
+ unsigned int dwt = *DWT_CYCCNT - start;
+ msec = 1000.0*(float)dwt/CORE_CLOCK;
+ snprintf(tmp, sizeof(tmp), "%s %5.2f msecs\n",s,(double)msec);
+ if ((strlen(buf) + strlen(tmp)) < BUF_SZ)
+ strncat(buf, tmp, sizeof(buf)-1);
+ return *DWT_CYCCNT;
+}
+
+void machdep_profile_print_logged_samples(void)
+{
+ printf("%s", buf);
+ *buf = 0;
+}
+
diff --git a/stm32/src/stm32f4_usart.c b/stm32/src/stm32f4_usart.c
new file mode 100644
index 0000000..4aa0259
--- /dev/null
+++ b/stm32/src/stm32f4_usart.c
@@ -0,0 +1,71 @@
+/*
+ stm32f4_usart.c
+ David Rowe May 2019
+
+ Basic USART tty support for the stm32.
+
+ From:
+ http://stm32projectconsulting.blogspot.com/2013/04/stm32f4-discovery-usart-example.html
+*/
+
+#include <stm32f4xx.h>
+#include <stm32f4xx_usart.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include "stm32f4_usart.h"
+
+#define MAX_FMT_SIZE 256
+
+void usart_init(void){
+
+ GPIO_InitTypeDef GPIO_InitStructure;
+ USART_InitTypeDef USART_InitStructure;
+
+ /* enable peripheral clock for USART3 */
+ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
+
+ /* GPIOB clock enable */
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
+
+ /* GPIOA Configuration: USART3 TX on PB10 */
+ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
+ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
+ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+ GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
+ GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
+ GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+ /* Connect USART3 pins to AF2 */
+ // TX = PB10
+ GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);
+
+ USART_InitStructure.USART_BaudRate = 115200;
+ USART_InitStructure.USART_WordLength = USART_WordLength_8b;
+ USART_InitStructure.USART_StopBits = USART_StopBits_1;
+ USART_InitStructure.USART_Parity = USART_Parity_No;
+ USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
+ USART_InitStructure.USART_Mode = USART_Mode_Tx;
+ USART_Init(USART3, &USART_InitStructure);
+
+ USART_Cmd(USART3, ENABLE); // enable USART3
+
+}
+
+void usart_puts(const char s[]) {
+ for (int i=0; i<strlen(s); i++) {
+ USART_SendData(USART3, s[i]);
+ while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
+ }
+}
+
+int usart_printf(const char *fmt, ...)
+{
+ char s[MAX_FMT_SIZE];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(s, MAX_FMT_SIZE, fmt, ap);
+ va_end(ap);
+ usart_puts(s);
+ return 1;
+}
diff --git a/stm32/src/stm32f4_usb_vcp.c b/stm32/src/stm32f4_usb_vcp.c
new file mode 100644
index 0000000..f30ecfb
--- /dev/null
+++ b/stm32/src/stm32f4_usb_vcp.c
@@ -0,0 +1,90 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: stm32f4_usb_vcp.c
+ AUTHOR......: xenovacivus
+ DATE CREATED: 3 Sep 2014
+
+ USB Virtual COM Port (VCP) module adapted from code I found here:
+
+ https://github.com/xenovacivus/STM32DiscoveryVCP
+
+\*---------------------------------------------------------------------------*/
+
+#include "stm32f4xx_conf.h"
+#include "stm32f4xx.h"
+#include "stm32f4xx_gpio.h"
+#include "stm32f4xx_rcc.h"
+#include "stm32f4xx_exti.h"
+#include "usbd_cdc_core.h"
+#include "usbd_usr.h"
+#include "usbd_desc.h"
+#include "usbd_cdc_vcp.h"
+#include "usb_dcd_int.h"
+#include "sm1000_leds_switches.h"
+#include "stm32f4_usb_vcp.h"
+
+/*
+ * The USB data must be 4 byte aligned if DMA is enabled. This macro handles
+ * the alignment, if necessary (it's actually magic, but don't tell anyone).
+ */
+__ALIGN_BEGIN USB_OTG_CORE_HANDLE USB_OTG_dev __ALIGN_END;
+
+
+/*
+ * Define prototypes for interrupt handlers here. The conditional "extern"
+ * ensures the weak declarations from startup_stm32f4xx.c are overridden.
+ */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+void NMI_Handler(void);
+void HardFault_Handler(void);
+void MemManage_Handler(void);
+void BusFault_Handler(void);
+void UsageFault_Handler(void);
+void SVC_Handler(void);
+void DebugMon_Handler(void);
+void PendSV_Handler(void);
+void OTG_FS_IRQHandler(void);
+void OTG_FS_WKUP_IRQHandler(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+void usb_vcp_init() {
+ /* Setup USB */
+ USBD_Init(&USB_OTG_dev,
+ USB_OTG_FS_CORE_ID,
+ &USR_desc,
+ &USBD_CDC_cb,
+ &USR_cb);
+}
+
+
+/*
+ * Interrupt Handlers
+ */
+
+void NMI_Handler(void) {}
+void SVC_Handler(void) {}
+void DebugMon_Handler(void) {}
+void PendSV_Handler(void) {}
+
+void OTG_FS_IRQHandler(void)
+{
+ USBD_OTG_ISR_Handler (&USB_OTG_dev);
+}
+
+void OTG_FS_WKUP_IRQHandler(void)
+{
+ if(USB_OTG_dev.cfg.low_power)
+ {
+ *(uint32_t *)(0xE000ED10) &= 0xFFFFFFF9 ;
+ SystemInit();
+ USB_OTG_UngateClock(&USB_OTG_dev);
+ }
+ EXTI_ClearITPendingBit(EXTI_Line18);
+}
diff --git a/stm32/src/stm32f4_vrom.c b/stm32/src/stm32f4_vrom.c
new file mode 100644
index 0000000..5b66126
--- /dev/null
+++ b/stm32/src/stm32f4_vrom.c
@@ -0,0 +1,724 @@
+/*!
+ * STM32F4 Virtual EEPROM driver
+ *
+ * This module implements a crude virtual EEPROM device stored in on-board
+ * flash. The STM32F405 has 4 16kB flash sectors starting at address
+ * 0x80000000, followed by a 64kB sector, then 128kB sectors.
+ *
+ * The Cortex M4 core maps these all to address 0x00000000 when booting
+ * from normal flash, so the first sector is reserved for interrupt
+ * vectors.
+ *
+ * Everything else however is free game, and so we use these smaller
+ * sectors to store our configuration.
+ *
+ * Author Stuart Longland <[email protected]>
+ * Copyright (C) 2015 FreeDV project.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation. 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 Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "stm32f4_vrom.h"
+#include "stm32f4xx_flash.h"
+#include "stm32f4xx_crc.h"
+
+#define VROM_SECT_SZ (16384) /*!< Size of a flash sector */
+#define VROM_SECT_CNT (3) /*!< Number of sectors */
+#define VROM_BLOCK_SZ (256) /*!< Size of a flash block */
+
+/*!
+ * Starting address for the flash area
+ */
+#define VROM_START_ADDR (0x08004000)
+
+/*!
+ * Number of blocks we can fit per sector, including the index block.
+ */
+#define VROM_BLOCK_CNT (VROM_SECT_SZ / VROM_BLOCK_SZ)
+
+/*!
+ * Number of application blocks we can fit per sector.
+ */
+#define VROM_SECT_APP_BLOCK_CNT (VROM_BLOCK_CNT - 1)
+
+/*!
+ * Total number of application blocks we can fit in flash.
+ */
+#define VROM_APP_BLOCK_CNT (VROM_SECT_CNT * VROM_SECT_APP_BLOCK_CNT)
+
+/*!
+ * Maximum number of erase cycles per sector.
+ * Table 42 (page 109) of STM32F405 datasheet (DocID022152 Rev 5).
+ */
+#define VROM_MAX_CYCLES (10000)
+
+/*!
+ * EEPROM block header.
+ */
+struct __attribute__ ((__packed__)) vrom_block_hdr_t {
+ /*!
+ * CRC32 checksum of the data, offset, size and ROM ID.
+ * A CRC32 of 0x00000000 indicates an obsoleted block.
+ * A CRC32 of 0xffffffff indicates an erased block.
+ */
+ uint32_t crc32;
+ /*!
+ * ROM ID.
+ */
+ uint8_t rom;
+ /*!
+ * Block number in the virtual EEPROM.
+ */
+ uint8_t idx;
+ /*!
+ * Number of bytes from the virtual EEPROM stored in this block.
+ */
+ uint8_t size;
+ /*!
+ * Reserved for future use.
+ */
+ uint8_t reserved;
+};
+
+/*!
+ * The size of a block header in bytes.
+ */
+#define VROM_BLOCK_HDR_SZ (sizeof(struct vrom_block_hdr_t))
+
+/*!
+ * The amount of data available for application use.
+ */
+#define VROM_DATA_SZ (VROM_BLOCK_SZ - VROM_BLOCK_HDR_SZ)
+
+/*!
+ * EEPROM data block.
+ */
+struct __attribute__ ((__packed__)) vrom_data_block_t {
+ /*! Block header */
+ struct vrom_block_hdr_t header;
+
+ /*! Block data */
+ uint8_t data[VROM_DATA_SZ];
+};
+
+/*!
+ * The first block in a sector is the sector index block. This indicates
+ * the used/free state of the entire block and counts the number of
+ * erase cycles for the sector. The index block has no header.
+ */
+struct __attribute__ ((__packed__)) vrom_sector_idx_t {
+ /*!
+ * Number of erase cycles remaining for the sector.
+ * 0xffffffff == unprogrammed.
+ */
+ uint32_t cycles_remain;
+ /*!
+ * Block metadata flags. One for each data block in the sector.
+ * Does not include the index block.
+ */
+ uint16_t flags[VROM_SECT_APP_BLOCK_CNT];
+};
+
+#define VROM_SFLAGS_USED (1 << 0) /*!< Block in use */
+
+/*!
+ * Return the address of a virtual EEPROM sector header.
+ */
+static const struct vrom_sector_idx_t* vrom_get_sector_hdr(uint8_t sector)
+{
+ return (const struct vrom_sector_idx_t*)(
+ VROM_START_ADDR + (VROM_SECT_SZ * sector));
+}
+
+/*!
+ * Return the address of a virtual EEPROM block.
+ */
+static const struct vrom_data_block_t* vrom_get_block(
+ uint8_t sector, uint8_t block)
+{
+ return (const struct vrom_data_block_t*)(
+ (void*)vrom_get_sector_hdr(sector)
+ + (VROM_BLOCK_SZ * (block + 1)));
+}
+
+/*!
+ * Compute the CRC32 of a block.
+ */
+static uint32_t vrom_crc32(
+ const struct vrom_data_block_t* const block)
+{
+ struct vrom_data_block_t temp_block;
+ uint32_t size = sizeof(temp_block);
+ const uint8_t* in = (const uint8_t*)(&temp_block);
+ uint32_t tmp;
+ uint32_t crc;
+
+ memcpy(&temp_block, block, sizeof(temp_block));
+ temp_block.header.crc32 = 0;
+
+ CRC_ResetDR();
+ while(size) {
+ tmp = 0;
+ if (size) {
+ tmp |= (uint32_t)(*(in++)) << 24;
+ size--;
+ }
+ if (size) {
+ tmp |= (uint32_t)(*(in++)) << 16;
+ size--;
+ }
+ if (size) {
+ tmp |= (uint32_t)(*(in++)) << 8;
+ size--;
+ }
+ if (size) {
+ tmp |= (uint32_t)(*(in++));
+ size--;
+ }
+ crc = CRC_CalcCRC(tmp);
+ }
+ return crc;
+}
+
+/*!
+ * Find the block storing the given index.
+ */
+static const struct vrom_data_block_t* vrom_find(uint8_t rom, uint8_t idx)
+{
+ int sector, block;
+
+ for (sector = 0; sector < VROM_SECT_CNT; sector++) {
+ const struct vrom_sector_idx_t* sect_hdr
+ = vrom_get_sector_hdr(sector);
+ if (sect_hdr->cycles_remain == UINT32_MAX)
+ /* unformatted */
+ continue;
+ for (block = 0; block < VROM_SECT_APP_BLOCK_CNT; block++) {
+ const struct vrom_data_block_t* block_ptr;
+ if (sect_hdr->flags[block] == UINT16_MAX)
+ /* unformatted */
+ continue;
+ if (sect_hdr->flags[block] == 0)
+ /* obsolete */
+ continue;
+
+ block_ptr = vrom_get_block(sector, block);
+
+ /* Verify the content */
+ if (vrom_crc32(block_ptr)
+ != block_ptr->header.crc32)
+ /* corrupt */
+ continue;
+
+ if (block_ptr->header.rom != rom)
+ /* different ROM */
+ continue;
+
+ if (block_ptr->header.idx != idx)
+ /* wrong index */
+ continue;
+
+ return block_ptr;
+ }
+ }
+ return NULL;
+}
+
+/*!
+ * Get the sector number of a given address.
+ */
+static uint8_t vrom_sector_num(const void* address)
+{
+ /* Get the offset from the base address */
+ uint32_t offset = (uint32_t)address - VROM_START_ADDR;
+ return offset / VROM_SECT_SZ;
+}
+
+/*!
+ * Get the block number of a given address.
+ */
+static uint8_t vrom_block_num(const void* address)
+{
+ /* Get the sector number */
+ uint8_t sector = vrom_sector_num(address);
+
+ /* Get the offset from the sector base */
+ uint32_t offset = (uint32_t)(address
+ - (const void*)vrom_get_sector_hdr(sector));
+ offset /= VROM_BLOCK_SZ;
+ return offset - 1;
+}
+
+/*!
+ * (Erase and) Format a sector.
+ *
+ * @retval -EIO Erase failed
+ * @retval -EPERM Erase counter depleted.
+ */
+static int vrom_format_sector(const struct vrom_sector_idx_t* sector)
+{
+ uint8_t sector_num = vrom_sector_num(sector);
+ uint32_t cycles_remain = VROM_MAX_CYCLES;
+ if (sector->cycles_remain != UINT32_MAX) {
+ if (sector->cycles_remain == 0)
+ /* This sector is exhausted */
+ return -EPERM;
+
+ /* This sector has been formatted before */
+ cycles_remain = sector->cycles_remain - 1;
+ if (FLASH_EraseSector(sector_num + 1, VoltageRange_3))
+ /* Erase failed */
+ return -EIO;
+ }
+
+ /* Program the new sector cycle counter */
+ if (FLASH_ProgramWord((uint32_t)sector,
+ cycles_remain) == FLASH_COMPLETE)
+ return 0; /* All good */
+ /* If we get here, then programming failed */
+ return -EIO;
+}
+
+/*!
+ * Find the next available block.
+ */
+static const struct vrom_data_block_t* vrom_find_free(uint8_t run_gc)
+{
+ int sector;
+ if (run_gc) {
+ for (sector = 0; sector < VROM_SECT_CNT; sector++) {
+ uint8_t block;
+ uint8_t used = 0;
+ const struct vrom_sector_idx_t* sect_hdr
+ = vrom_get_sector_hdr(sector);
+ if (sect_hdr->cycles_remain == UINT32_MAX)
+ /* Already erased */
+ continue;
+ if (sect_hdr->cycles_remain == 0)
+ /* Depleted */
+ continue;
+
+ for (block = 0; block < VROM_SECT_APP_BLOCK_CNT;
+ block++) {
+ if (sect_hdr->flags[block]) {
+ used = 1;
+ break;
+ }
+ }
+
+ if (!used) {
+ /* We can format this */
+ vrom_format_sector(sect_hdr);
+ }
+ }
+ }
+
+ for (sector = 0; sector < VROM_SECT_CNT; sector++) {
+ uint8_t block;
+ const struct vrom_sector_idx_t* sect_hdr
+ = vrom_get_sector_hdr(sector);
+ if (sect_hdr->cycles_remain == UINT32_MAX) {
+ /* Unformatted sector. */
+ if (vrom_format_sector(sect_hdr))
+ /* Couldn't format, keep looking */
+ continue;
+ }
+ for (block = 0; block < VROM_SECT_APP_BLOCK_CNT; block++) {
+ if (sect_hdr->flags[block] == UINT16_MAX)
+ /* Success */
+ return vrom_get_block(sector, block);
+ }
+ }
+
+ /* No blocks free, but have we done garbage collection? */
+ if (!run_gc)
+ return vrom_find_free(1);
+
+ /* If we get here, then we weren't able to find a free block */
+ return NULL;
+}
+
+/*!
+ * Set flags for a block
+ */
+static int vrom_set_flags(const struct vrom_data_block_t* block,
+ uint16_t flags)
+{
+ const struct vrom_sector_idx_t* sector =
+ vrom_get_sector_hdr(vrom_sector_num(block));
+ uint8_t block_num = vrom_block_num(block);
+
+ /* Compute the new flags settings */
+ flags = sector->flags[block_num] & ~flags;
+
+ /* Write them */
+ if (FLASH_ProgramHalfWord(
+ (uint32_t)(&(sector->flags[block_num])),
+ flags) != FLASH_COMPLETE)
+ return -EIO;
+ return 0;
+}
+
+/*!
+ * Mark a block as being obsolete
+ */
+static int vrom_mark_obsolete(const struct vrom_data_block_t* block)
+{
+ /* Blank out the CRC */
+ if (FLASH_ProgramWord((uint32_t)(&(block->header.crc32)), 0)
+ != FLASH_COMPLETE)
+ return -EIO;
+ /* Blank out the ROM ID */
+ if (FLASH_ProgramByte((uint32_t)(&(block->header.rom)), 0)
+ != FLASH_COMPLETE)
+ return -EIO;
+ /* Blank out the index */
+ if (FLASH_ProgramByte((uint32_t)(&(block->header.idx)), 0)
+ != FLASH_COMPLETE)
+ return -EIO;
+ /* Blank out the size */
+ if (FLASH_ProgramByte((uint32_t)&(block->header.size), 0)
+ != FLASH_COMPLETE)
+ return -EIO;
+ /* Blank out the reserved byte */
+ if (FLASH_ProgramByte((uint32_t)&(block->header.reserved), 0)
+ != FLASH_COMPLETE)
+ return -EIO;
+ /* Blank out the flags */
+ return vrom_set_flags(block, -1);
+}
+
+/*!
+ * Write a new block.
+ */
+static int vrom_write_block(uint8_t rom, uint8_t idx, uint8_t size,
+ const uint8_t* in)
+{
+ /* Find a new home for the block */
+ const struct vrom_data_block_t* block = vrom_find_free(0);
+ struct vrom_data_block_t new_block;
+ uint8_t* out = (uint8_t*)(block);
+ uint32_t rem = sizeof(new_block);
+ int res;
+
+ if (!block)
+ return -ENOSPC;
+
+ /* Prepare the new block */
+ memset(&new_block, 0xff, sizeof(new_block));
+ new_block.header.rom = rom;
+ new_block.header.idx = idx;
+ new_block.header.size = size;
+ memcpy(new_block.data, in, size);
+ new_block.header.crc32 = vrom_crc32(&new_block);
+
+ /* Start writing out the block */
+ in = (uint8_t*)(&new_block);
+ rem = VROM_BLOCK_SZ;
+ while(rem) {
+ if (*out != *in) {
+ if (FLASH_ProgramByte((uint32_t)out, *in)
+ != FLASH_COMPLETE)
+ /* Failed! */
+ return -EIO;
+ }
+ in++;
+ out++;
+ rem--;
+ }
+ res = vrom_set_flags(block, VROM_SFLAGS_USED);
+ if (res < 0)
+ return res;
+ return size;
+}
+
+/*!
+ * Re-write the given block if needed.
+ */
+static int vrom_rewrite_block(const struct vrom_data_block_t* block,
+ uint8_t size, const uint8_t* in)
+{
+ uint8_t obsolete = 0;
+ uint8_t rom = block->header.rom;
+ uint8_t idx = block->header.idx;
+ const uint8_t* cmp_block = block->data;
+ const uint8_t* cmp_in = in;
+ uint8_t cmp_sz = size;
+ int res;
+ while(cmp_sz) {
+ if (*cmp_block != *cmp_in) {
+ obsolete = 1;
+ break;
+ }
+ cmp_sz--;
+ cmp_block++;
+ cmp_in++;
+ }
+
+ if (!obsolete)
+ /* The block is fine, leave it be. */
+ return size;
+
+ /* Mark the block as obsolete */
+ res = vrom_mark_obsolete(block);
+ if (res)
+ return res;
+ return vrom_write_block(rom, idx, size, in);
+}
+
+/*!
+ * Overwrite the start of a block.
+ */
+static int vrom_overwrite_block(
+ const struct vrom_data_block_t* block,
+ uint8_t offset, uint8_t size, const uint8_t* in)
+{
+ uint8_t data[VROM_DATA_SZ];
+ uint16_t block_sz = block->header.size;
+ int res;
+
+ if (!offset && (size >= block->header.size))
+ /* Complete overwrite */
+ return vrom_rewrite_block(block, size, in);
+
+ if (offset) {
+ /* Overwrite end of block, possible expansion */
+ block_sz = offset + size;
+ if (block_sz > VROM_DATA_SZ)
+ block_sz = VROM_DATA_SZ;
+ memcpy(data, block->data, offset);
+ memcpy(&data[offset], in, block_sz - offset);
+ } else {
+ /* Overwrite start of block, no size change */
+ memcpy(data, in, size);
+ memcpy(&data[size], &(block->data[size]),
+ block_sz - size);
+ }
+
+ res = vrom_rewrite_block(block, block_sz, data);
+ if (res < 0)
+ return res;
+ return block_sz;
+}
+
+/*!
+ * Write data to the virtual EEPROM.
+ */
+static int vrom_write_internal(uint8_t rom,
+ uint16_t offset, uint16_t size, const uint8_t* in)
+{
+ /* Figure out our starting block and offset */
+ uint8_t block_idx = offset / VROM_DATA_SZ;
+ uint8_t block_offset = offset % VROM_DATA_SZ;
+ int count = 0;
+
+ /* Locate the first block */
+ const struct vrom_data_block_t* block = vrom_find(rom, block_idx);
+
+ uint8_t block_sz = VROM_DATA_SZ;
+ if (block_sz > (size + block_offset))
+ block_sz = size + block_offset;
+
+ if (!block) {
+ /* Create a new block */
+ uint8_t data[VROM_DATA_SZ];
+ int res;
+ memset(data, 0xff, sizeof(data));
+ memcpy(&data[block_offset], in,
+ block_sz-block_offset);
+ res = vrom_write_block(rom, block_idx, block_sz, data);
+ if (res < 0)
+ return res;
+ } else {
+ /* Overwrite block */
+ int res = vrom_overwrite_block(block, block_offset,
+ block_sz, in);
+ if (res < 0)
+ return res;
+ count += block_sz;
+ }
+
+ block_idx++;
+ size -= block_sz - block_offset;
+
+ while(size) {
+ /* Work out how much data to write */
+ if (size < VROM_DATA_SZ)
+ block_sz = size;
+ else
+ block_sz = VROM_DATA_SZ;
+
+ int res;
+
+ /* Is there a block covering this range? */
+ block = vrom_find(rom, block_idx);
+ if (block)
+ res = vrom_overwrite_block(
+ block, 0, block_sz, in);
+ else
+ res = vrom_write_block(rom, block_idx,
+ block_sz, in);
+
+ if (res < 0)
+ return res;
+
+ /* Successful write */
+ count += res;
+ size -= res;
+ in += res;
+ offset += res;
+ }
+ return count;
+}
+
+/*!
+ * Read data from a virtual EEPROM.
+ * @param rom ROM ID to start reading.
+ * @param offset Address offset into ROM to start reading.
+ * @param size Number of bytes to read from ROM.
+ * @param out Buffer to write ROM content to.
+ * @returns Number of bytes read from ROM.
+ * @retval -ENXIO ROM not found
+ * @retval -ESPIPE Offset past end of ROM.
+ */
+int vrom_read(uint8_t rom, uint16_t offset, uint16_t size, void* out)
+{
+ /* Figure out our starting block and offset */
+ uint8_t block_idx = offset / VROM_DATA_SZ;
+ uint8_t block_offset = offset % VROM_DATA_SZ;
+ uint8_t block_sz;
+ int count = 0;
+ uint8_t* out_ptr = (uint8_t*)out;
+
+ /* Locate the first block */
+ const struct vrom_data_block_t* block = vrom_find(rom, block_idx);
+
+ if (!block)
+ return -ENXIO;
+
+ if (block_offset >= block->header.size)
+ return -ESPIPE;
+
+ /* Copy the initial bytes */
+ block_sz = block->header.size - block_offset;
+ if (block_sz > size)
+ block_sz = size;
+ memcpy(out_ptr, &(block->data[block_offset]), block_sz);
+ out_ptr += block_sz;
+ size -= block_sz;
+ count += block_sz;
+
+ if (size) {
+ /* Look for the next block */
+ block = vrom_find(rom, ++block_idx);
+ while(size && block) {
+ if (block->header.size <= size)
+ block_sz = block->header.size;
+ else
+ block_sz = size;
+ memcpy(out_ptr, block->data, block_sz);
+ out_ptr += block_sz;
+ size -= block_sz;
+ count += block_sz;
+
+ block = vrom_find(rom, ++block_idx);
+ }
+ }
+
+ return count;
+}
+
+/*!
+ * Write data to a virtual EEPROM.
+ * @param rom ROM ID to start writing.
+ * @param offset Address offset into ROM to start writing.
+ * @param size Number of bytes to write to ROM.
+ * @param in Buffer to write ROM content from.
+ * @returns Number of bytes written to ROM.
+ * @retval -EIO Programming failed
+ * @retval -ENOSPC No free blocks available
+ */
+int vrom_write(uint8_t rom, uint16_t offset, uint16_t size,
+ const void* in)
+{
+ int res;
+ FLASH_Unlock();
+ FLASH_ClearFlag(FLASH_FLAG_EOP
+ | FLASH_FLAG_OPERR
+ | FLASH_FLAG_WRPERR
+ | FLASH_FLAG_PGAERR
+ | FLASH_FLAG_PGPERR
+ | FLASH_FLAG_PGSERR);
+ res = vrom_write_internal(rom, offset, size, in);
+ FLASH_Lock();
+ return res;
+}
+
+/*!
+ * Erase a virtual EEPROM.
+ * @param rom ROM ID to erase.
+ * @returns Number of bytes written to ROM.
+ * @retval -EIO Programming failed
+ * @retval -ENOSPC No free blocks available
+ */
+int vrom_erase(uint8_t rom)
+{
+ int sector, block;
+ FLASH_Unlock();
+ FLASH_ClearFlag(FLASH_FLAG_EOP
+ | FLASH_FLAG_OPERR
+ | FLASH_FLAG_WRPERR
+ | FLASH_FLAG_PGAERR
+ | FLASH_FLAG_PGPERR
+ | FLASH_FLAG_PGSERR);
+ for (sector = 0; sector < VROM_SECT_CNT; sector++) {
+ const struct vrom_sector_idx_t* sect_hdr
+ = vrom_get_sector_hdr(sector);
+ if (sect_hdr->cycles_remain == UINT32_MAX)
+ /* unformatted */
+ continue;
+ for (block = 0; block < VROM_SECT_APP_BLOCK_CNT; block++) {
+ int res;
+ const struct vrom_data_block_t* block_ptr;
+ if (sect_hdr->flags[block] == UINT16_MAX)
+ /* unformatted */
+ continue;
+ if (sect_hdr->flags[block] == 0)
+ /* obsolete */
+ continue;
+
+ block_ptr = vrom_get_block(sector, block);
+
+ /* Verify the content */
+ if (vrom_crc32(block_ptr)
+ != block_ptr->header.crc32)
+ /* corrupt */
+ continue;
+
+ if (block_ptr->header.rom != rom)
+ /* different ROM */
+ continue;
+
+ /*
+ * Block is valid, for the correct ROM. Mark it
+ * obsolete.
+ */
+ res = vrom_mark_obsolete(block_ptr);
+ if (res)
+ return res;
+ }
+ }
+ return 0;
+}
diff --git a/stm32/src/system_stm32f4xx.c b/stm32/src/system_stm32f4xx.c
new file mode 100644
index 0000000..b8bdd96
--- /dev/null
+++ b/stm32/src/system_stm32f4xx.c
@@ -0,0 +1,585 @@
+/**
+ ******************************************************************************
+ * @file system_stm32f4xx.c
+ * @author MCD Application Team
+ * @version V1.0.1
+ * @date 10-July-2012
+ * @brief CMSIS Cortex-M4 Device Peripheral Access Layer System Source File.
+ * This file contains the system clock configuration for STM32F4xx devices,
+ * and is generated by the clock configuration tool
+ * stm32f4xx_Clock_Configuration_V1.0.1.xls
+ *
+ * 1. This file provides two functions and one global variable to be called from
+ * user application:
+ * - SystemInit(): Setups the system clock (System clock source, PLL Multiplier
+ * and Divider factors, AHB/APBx prescalers and Flash settings),
+ * depending on the configuration made in the clock xls tool.
+ * This function is called at startup just after reset and
+ * before branch to main program. This call is made inside
+ * the "startup_stm32f4xx.s" file.
+ *
+ * - SystemCoreClock variable: Contains the core clock (HCLK), it can be used
+ * by the user application to setup the SysTick
+ * timer or configure other parameters.
+ *
+ * - SystemCoreClockUpdate(): Updates the variable SystemCoreClock and must
+ * be called whenever the core clock is changed
+ * during program execution.
+ *
+ * 2. After each device reset the HSI (16 MHz) is used as system clock source.
+ * Then SystemInit() function is called, in "startup_stm32f4xx.s" file, to
+ * configure the system clock before to branch to main program.
+ *
+ * 3. If the system clock source selected by user fails to startup, the SystemInit()
+ * function will do nothing and HSI still used as system clock source. User can
+ * add some code to deal with this issue inside the SetSysClock() function.
+ *
+ * 4. The default value of HSE crystal is set to 25MHz, refer to "HSE_VALUE" define
+ * in "stm32f4xx.h" file. When HSE is used as system clock source, directly or
+ * through PLL, and you are using different crystal you have to adapt the HSE
+ * value to your own configuration.
+ *
+ * 5. This file configures the system clock as follows:
+ *=============================================================================
+ *=============================================================================
+ * Supported STM32F4xx device revision | Rev A
+ *-----------------------------------------------------------------------------
+ * System Clock source | PLL (HSE)
+ *-----------------------------------------------------------------------------
+ * SYSCLK(Hz) | 168000000
+ *-----------------------------------------------------------------------------
+ * HCLK(Hz) | 168000000
+ *-----------------------------------------------------------------------------
+ * AHB Prescaler | 1
+ *-----------------------------------------------------------------------------
+ * APB1 Prescaler | 4
+ *-----------------------------------------------------------------------------
+ * APB2 Prescaler | 2
+ *-----------------------------------------------------------------------------
+ * HSE Frequency(Hz) | 8000000
+ *-----------------------------------------------------------------------------
+ * PLL_M | 8
+ *-----------------------------------------------------------------------------
+ * PLL_N | 336
+ *-----------------------------------------------------------------------------
+ * PLL_P | 2
+ *-----------------------------------------------------------------------------
+ * PLL_Q | 7
+ *-----------------------------------------------------------------------------
+ * PLLI2S_N | 352
+ *-----------------------------------------------------------------------------
+ * PLLI2S_R | 2
+ *-----------------------------------------------------------------------------
+ * I2S input clock(Hz) | 176000000
+ * |
+ * To achieve the following I2S config: |
+ * - Master clock output (MCKO): OFF |
+ * - Frame wide : 16bit |
+ * - Error % : 0,0000 |
+ * - Prescaler Odd factor (ODD): 1 |
+ * - Linear prescaler (DIV) : 14 |
+ *-----------------------------------------------------------------------------
+ * VDD(V) | 3,3
+ *-----------------------------------------------------------------------------
+ * Main regulator output voltage | Scale1 mode
+ *-----------------------------------------------------------------------------
+ * Flash Latency(WS) | 5
+ *-----------------------------------------------------------------------------
+ * Prefetch Buffer | OFF
+ *-----------------------------------------------------------------------------
+ * Instruction cache | ON
+ *-----------------------------------------------------------------------------
+ * Data cache | ON
+ *-----------------------------------------------------------------------------
+ * Require 48MHz for USB OTG FS, | Enabled
+ * SDIO and RNG clock |
+ *-----------------------------------------------------------------------------
+ *=============================================================================
+ ******************************************************************************
+ * @attention
+ *
+ * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
+ * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
+ * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
+ * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
+ * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
+ * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
+ *
+ * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
+ ******************************************************************************
+ */
+
+/** @addtogroup CMSIS
+ * @{
+ */
+
+/** @addtogroup stm32f4xx_system
+ * @{
+ */
+
+/** @addtogroup STM32F4xx_System_Private_Includes
+ * @{
+ */
+
+#include "stm32f4xx.h"
+
+/**
+ * @}
+ */
+
+/** @addtogroup STM32F4xx_System_Private_TypesDefinitions
+ * @{
+ */
+
+/**
+ * @}
+ */
+
+/** @addtogroup STM32F4xx_System_Private_Defines
+ * @{
+ */
+
+/************************* Miscellaneous Configuration ************************/
+/*!< Uncomment the following line if you need to use external SRAM mounted
+ on STM324xG_EVAL board as data memory */
+/* #define DATA_IN_ExtSRAM */
+
+/*!< Uncomment the following line if you need to relocate your vector Table in
+ Internal SRAM. */
+/* #define VECT_TAB_SRAM */
+#define VECT_TAB_OFFSET 0x00 /*!< Vector Table base offset field.
+ This value must be a multiple of 0x200. */
+/******************************************************************************/
+
+/************************* PLL Parameters *************************************/
+/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
+#define PLL_M 8
+#define PLL_N 336
+
+/* SYSCLK = PLL_VCO / PLL_P */
+#define PLL_P 2
+
+/* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */
+#define PLL_Q 7
+
+/* PLLI2S_VCO = (HSE_VALUE Or HSI_VALUE / PLL_M) * PLLI2S_N
+ I2SCLK = PLLI2S_VCO / PLLI2S_R */
+#define START_I2SCLOCK 0
+#define PLLI2S_N 352
+#define PLLI2S_R 2
+
+/******************************************************************************/
+
+/**
+ * @}
+ */
+
+/** @addtogroup STM32F4xx_System_Private_Macros
+ * @{
+ */
+
+/**
+ * @}
+ */
+
+/** @addtogroup STM32F4xx_System_Private_Variables
+ * @{
+ */
+
+uint32_t SystemCoreClock = 168000000;
+
+__I uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
+
+/**
+ * @}
+ */
+
+/** @addtogroup STM32F4xx_System_Private_FunctionPrototypes
+ * @{
+ */
+
+static void SetSysClock(void);
+#ifdef DATA_IN_ExtSRAM
+static void SystemInit_ExtMemCtl(void);
+#endif /* DATA_IN_ExtSRAM */
+
+/**
+ * @}
+ */
+
+/** @addtogroup STM32F4xx_System_Private_Functions
+ * @{
+ */
+
+/**
+ * @brief Setup the microcontroller system
+ * Initialize the Embedded Flash Interface, the PLL and update the
+ * SystemFrequency variable.
+ * @param None
+ * @retval None
+ */
+void SystemInit(void)
+{
+ /* FPU settings ------------------------------------------------------------*/
+#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
+ SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
+#endif
+ /* Reset the RCC clock configuration to the default reset state ------------*/
+ /* Set HSION bit */
+ RCC->CR |= (uint32_t)0x00000001;
+
+ /* Reset CFGR register */
+ RCC->CFGR = 0x00000000;
+
+ /* Reset HSEON, CSSON and PLLON bits */
+ RCC->CR &= (uint32_t)0xFEF6FFFF;
+
+ /* Reset PLLCFGR register */
+ RCC->PLLCFGR = 0x24003010;
+
+ /* Reset HSEBYP bit */
+ RCC->CR &= (uint32_t)0xFFFBFFFF;
+
+ /* Disable all interrupts */
+ RCC->CIR = 0x00000000;
+
+#ifdef DATA_IN_ExtSRAM
+ SystemInit_ExtMemCtl();
+#endif /* DATA_IN_ExtSRAM */
+
+ /* Configure the System clock source, PLL Multiplier and Divider factors,
+ AHB/APBx prescalers and Flash settings ----------------------------------*/
+ SetSysClock();
+
+ /* Configure the Vector Table location add offset address ------------------*/
+#ifdef VECT_TAB_SRAM
+ SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
+#else
+ SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
+#endif
+}
+
+/**
+ * @brief Update SystemCoreClock variable according to Clock Register Values.
+ * The SystemCoreClock variable contains the core clock (HCLK), it can
+ * be used by the user application to setup the SysTick timer or configure
+ * other parameters.
+ *
+ * @note Each time the core clock (HCLK) changes, this function must be called
+ * to update SystemCoreClock variable value. Otherwise, any configuration
+ * based on this variable will be incorrect.
+ *
+ * @note - The system frequency computed by this function is not the real
+ * frequency in the chip. It is calculated based on the predefined
+ * constant and the selected clock source:
+ *
+ * - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*)
+ *
+ * - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(**)
+ *
+ * - If SYSCLK source is PLL, SystemCoreClock will contain the HSE_VALUE(**)
+ * or HSI_VALUE(*) multiplied/divided by the PLL factors.
+ *
+ * (*) HSI_VALUE is a constant defined in stm32f4xx.h file (default value
+ * 16 MHz) but the real value may vary depending on the variations
+ * in voltage and temperature.
+ *
+ * (**) HSE_VALUE is a constant defined in stm32f4xx.h file (default value
+ * 25 MHz), user has to ensure that HSE_VALUE is same as the real
+ * frequency of the crystal used. Otherwise, this function may
+ * have wrong result.
+ *
+ * - The result of this function could be not correct when using fractional
+ * value for HSE crystal.
+ *
+ * @param None
+ * @retval None
+ */
+void SystemCoreClockUpdate(void)
+{
+ uint32_t tmp = 0, pllvco = 0, pllp = 2, pllsource = 0, pllm = 2;
+
+ /* Get SYSCLK source -------------------------------------------------------*/
+ tmp = RCC->CFGR & RCC_CFGR_SWS;
+
+ switch (tmp)
+ {
+ case 0x00: /* HSI used as system clock source */
+ SystemCoreClock = HSI_VALUE;
+ break;
+ case 0x04: /* HSE used as system clock source */
+ SystemCoreClock = HSE_VALUE;
+ break;
+ case 0x08: /* PLL used as system clock source */
+
+ /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N
+ SYSCLK = PLL_VCO / PLL_P
+ */
+ pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) >> 22;
+ pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM;
+
+ if (pllsource != 0)
+ {
+ /* HSE used as PLL clock source */
+ pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);
+ }
+ else
+ {
+ /* HSI used as PLL clock source */
+ pllvco = (HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);
+ }
+
+ pllp = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >>16) + 1 ) *2;
+ SystemCoreClock = pllvco/pllp;
+ break;
+ default:
+ SystemCoreClock = HSI_VALUE;
+ break;
+ }
+ /* Compute HCLK frequency --------------------------------------------------*/
+ /* Get HCLK prescaler */
+ tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)];
+ /* HCLK frequency */
+ SystemCoreClock >>= tmp;
+}
+
+/**
+ * @brief Configures the System clock source, PLL Multiplier and Divider factors,
+ * AHB/APBx prescalers and Flash settings
+ * @Note This function should be called only once the RCC clock configuration
+ * is reset to the default reset state (done in SystemInit() function).
+ * @param None
+ * @retval None
+ */
+static void SetSysClock(void)
+{
+ /******************************************************************************/
+ /* PLL (clocked by HSE) used as System clock source */
+ /******************************************************************************/
+ __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+ /* Enable HSE */
+ RCC->CR |= ((uint32_t)RCC_CR_HSEON);
+
+ /* Wait till HSE is ready and if Time out is reached exit */
+ do
+ {
+ HSEStatus = RCC->CR & RCC_CR_HSERDY;
+ StartUpCounter++;
+ } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+ if ((RCC->CR & RCC_CR_HSERDY) != RESET)
+ {
+ HSEStatus = (uint32_t)0x01;
+ }
+ else
+ {
+ HSEStatus = (uint32_t)0x00;
+ }
+
+ if (HSEStatus == (uint32_t)0x01)
+ {
+ /* Select regulator voltage output Scale 1 mode, System frequency up to 168 MHz */
+ RCC->APB1ENR |= RCC_APB1ENR_PWREN;
+ PWR->CR |= PWR_CR_VOS;
+
+ /* HCLK = SYSCLK / 1*/
+ RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
+
+ /* PCLK2 = HCLK / 2*/
+ RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
+
+ /* PCLK1 = HCLK / 4*/
+ RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
+
+ /* Configure the main PLL */
+ RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
+ (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
+
+ /* Enable the main PLL */
+ RCC->CR |= RCC_CR_PLLON;
+
+ /* Wait till the main PLL is ready */
+ while((RCC->CR & RCC_CR_PLLRDY) == 0)
+ {
+ }
+
+ /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
+ FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
+
+ /* Select the main PLL as system clock source */
+ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
+ RCC->CFGR |= RCC_CFGR_SW_PLL;
+
+ /* Wait till the main PLL is used as system clock source */
+ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL)
+ {
+ asm("nop");
+ }
+ }
+ else
+ { /* If HSE fails to start-up, the application will have wrong clock
+ configuration. User can add here some code to deal with this error */
+ }
+
+
+ /******************************************************************************/
+ /* I2S clock configuration */
+ /******************************************************************************/
+
+#if START_I2SCLOCK
+ /* PLLI2S clock used as I2S clock source */
+ RCC->CFGR &= ~RCC_CFGR_I2SSRC;
+
+ /* Configure PLLI2S */
+ RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);
+
+ /* Enable PLLI2S */
+ RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);
+
+ /* Wait till PLLI2S is ready */
+ while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)
+ {
+ }
+#endif
+}
+
+/**
+ * @brief Setup the external memory controller. Called in startup_stm32f4xx.s
+ * before jump to __main
+ * @param None
+ * @retval None
+ */
+#ifdef DATA_IN_ExtSRAM
+/**
+ * @brief Setup the external memory controller.
+ * Called in startup_stm32f4xx.s before jump to main.
+ * This function configures the external SRAM mounted on STM324xG_EVAL board
+ * This SRAM will be used as program data memory (including heap and stack).
+ * @param None
+ * @retval None
+ */
+void SystemInit_ExtMemCtl(void)
+{
+ /*-- GPIOs Configuration -----------------------------------------------------*/
+ /*
+ +-------------------+--------------------+------------------+------------------+
+ + SRAM pins assignment +
+ +-------------------+--------------------+------------------+------------------+
+ | PD0 <-> FSMC_D2 | PE0 <-> FSMC_NBL0 | PF0 <-> FSMC_A0 | PG0 <-> FSMC_A10 |
+ | PD1 <-> FSMC_D3 | PE1 <-> FSMC_NBL1 | PF1 <-> FSMC_A1 | PG1 <-> FSMC_A11 |
+ | PD4 <-> FSMC_NOE | PE3 <-> FSMC_A19 | PF2 <-> FSMC_A2 | PG2 <-> FSMC_A12 |
+ | PD5 <-> FSMC_NWE | PE4 <-> FSMC_A20 | PF3 <-> FSMC_A3 | PG3 <-> FSMC_A13 |
+ | PD8 <-> FSMC_D13 | PE7 <-> FSMC_D4 | PF4 <-> FSMC_A4 | PG4 <-> FSMC_A14 |
+ | PD9 <-> FSMC_D14 | PE8 <-> FSMC_D5 | PF5 <-> FSMC_A5 | PG5 <-> FSMC_A15 |
+ | PD10 <-> FSMC_D15 | PE9 <-> FSMC_D6 | PF12 <-> FSMC_A6 | PG9 <-> FSMC_NE2 |
+ | PD11 <-> FSMC_A16 | PE10 <-> FSMC_D7 | PF13 <-> FSMC_A7 |------------------+
+ | PD12 <-> FSMC_A17 | PE11 <-> FSMC_D8 | PF14 <-> FSMC_A8 |
+ | PD13 <-> FSMC_A18 | PE12 <-> FSMC_D9 | PF15 <-> FSMC_A9 |
+ | PD14 <-> FSMC_D0 | PE13 <-> FSMC_D10 |------------------+
+ | PD15 <-> FSMC_D1 | PE14 <-> FSMC_D11 |
+ | | PE15 <-> FSMC_D12 |
+ +-------------------+--------------------+
+ */
+ /* Enable GPIOD, GPIOE, GPIOF and GPIOG interface clock */
+ RCC->AHB1ENR = 0x00000078;
+
+ /* Connect PDx pins to FSMC Alternate function */
+ GPIOD->AFR[0] = 0x00cc00cc;
+ GPIOD->AFR[1] = 0xcc0ccccc;
+ /* Configure PDx pins in Alternate function mode */
+ GPIOD->MODER = 0xaaaa0a0a;
+ /* Configure PDx pins speed to 100 MHz */
+ GPIOD->OSPEEDR = 0xffff0f0f;
+ /* Configure PDx pins Output type to push-pull */
+ GPIOD->OTYPER = 0x00000000;
+ /* No pull-up, pull-down for PDx pins */
+ GPIOD->PUPDR = 0x00000000;
+
+ /* Connect PEx pins to FSMC Alternate function */
+ GPIOE->AFR[0] = 0xc00cc0cc;
+ GPIOE->AFR[1] = 0xcccccccc;
+ /* Configure PEx pins in Alternate function mode */
+ GPIOE->MODER = 0xaaaa828a;
+ /* Configure PEx pins speed to 100 MHz */
+ GPIOE->OSPEEDR = 0xffffc3cf;
+ /* Configure PEx pins Output type to push-pull */
+ GPIOE->OTYPER = 0x00000000;
+ /* No pull-up, pull-down for PEx pins */
+ GPIOE->PUPDR = 0x00000000;
+
+ /* Connect PFx pins to FSMC Alternate function */
+ GPIOF->AFR[0] = 0x00cccccc;
+ GPIOF->AFR[1] = 0xcccc0000;
+ /* Configure PFx pins in Alternate function mode */
+ GPIOF->MODER = 0xaa000aaa;
+ /* Configure PFx pins speed to 100 MHz */
+ GPIOF->OSPEEDR = 0xff000fff;
+ /* Configure PFx pins Output type to push-pull */
+ GPIOF->OTYPER = 0x00000000;
+ /* No pull-up, pull-down for PFx pins */
+ GPIOF->PUPDR = 0x00000000;
+
+ /* Connect PGx pins to FSMC Alternate function */
+ GPIOG->AFR[0] = 0x00cccccc;
+ GPIOG->AFR[1] = 0x000000c0;
+ /* Configure PGx pins in Alternate function mode */
+ GPIOG->MODER = 0x00080aaa;
+ /* Configure PGx pins speed to 100 MHz */
+ GPIOG->OSPEEDR = 0x000c0fff;
+ /* Configure PGx pins Output type to push-pull */
+ GPIOG->OTYPER = 0x00000000;
+ /* No pull-up, pull-down for PGx pins */
+ GPIOG->PUPDR = 0x00000000;
+
+ /*-- FSMC Configuration ------------------------------------------------------*/
+ /* Enable the FSMC interface clock */
+ RCC->AHB3ENR = 0x00000001;
+
+ /* Configure and enable Bank1_SRAM2 */
+ FSMC_Bank1->BTCR[2] = 0x00001015;
+ FSMC_Bank1->BTCR[3] = 0x00010603;
+ FSMC_Bank1E->BWTR[2] = 0x0fffffff;
+ /*
+ Bank1_SRAM2 is configured as follow:
+
+ p.FSMC_AddressSetupTime = 3;
+ p.FSMC_AddressHoldTime = 0;
+ p.FSMC_DataSetupTime = 6;
+ p.FSMC_BusTurnAroundDuration = 1;
+ p.FSMC_CLKDivision = 0;
+ p.FSMC_DataLatency = 0;
+ p.FSMC_AccessMode = FSMC_AccessMode_A;
+
+ FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM2;
+ FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
+ FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_PSRAM;
+ FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
+ FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
+ FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
+ FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
+ FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
+ FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
+ FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
+ FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
+ FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
+ FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
+ FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p;
+ FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p;
+ */
+}
+#endif /* DATA_IN_ExtSRAM */
+
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/
diff --git a/stm32/src/tone.c b/stm32/src/tone.c
new file mode 100644
index 0000000..ab56792
--- /dev/null
+++ b/stm32/src/tone.c
@@ -0,0 +1,151 @@
+/*!
+ * Fixed-point tone generator.
+ *
+ * The code here implements a simple fixed-point tone generator that uses
+ * integer arithmetic to generate a sinusoid at a fixed sample rate of
+ * 16kHz.
+ *
+ * To set the initial state of the state machine, you specify a frequency
+ * and duration using tone_reset. The corresponding C file embeds a
+ * sinusoid look-up table. The total number of samples is computed for
+ * the given time and used to initialise 'remain', 'time' is initialised
+ * to 0, and 'step' gives the amount to increment 'time' by each iteration.
+ *
+ * The samples are retrieved by repeatedly calling tone_next. This
+ * advances 'time' and decrements 'remain'. The tone is complete when
+ * 'remain' is zero.
+ *
+ * Author Stuart Longland <[email protected]>
+ * Copyright (C) 2015 FreeDV project.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation. 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 Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "tone.h"
+
+/*! Fixed-point shift factor */
+#define TONE_SHIFT (12)
+
+/*! Static compiled quarter-sinusoid. */
+static const int16_t partial_sine[] = {
+ 830, 2488, 4140, 5781, 7407, 9014, 10598, 12155,
+ 13681, 15171, 16623, 18031, 19394, 20707, 21967, 23170,
+ 24314, 25395, 26411, 27360, 28238, 29043, 29774, 30429,
+ 31006, 31503, 31919, 32253, 32504, 32672, 32756
+};
+
+/*! Length of quarter-sinusoid in samples */
+#define TONE_PART_SINE_LEN (sizeof(partial_sine)\
+ /sizeof(partial_sine[0]))
+
+/*! Total length of sinusoid */
+#define TONE_SINE_LEN ((TONE_PART_SINE_LEN*4)+4)
+
+/*!
+ * Generate a sine from the quarter-waveform.
+ */
+static int16_t tone_sine(uint8_t sample)
+{
+ /* Key points */
+ if ((sample % (TONE_SINE_LEN/2)) == 0)
+ /* Zero crossings */
+ return 0;
+ if (sample == TONE_SINE_LEN/4)
+ /* Maximum */
+ return INT16_MAX;
+ if (sample == (3*TONE_SINE_LEN)/4)
+ /* Minimum */
+ return INT16_MIN;
+
+ if (sample < TONE_SINE_LEN/4)
+ /* First quarter of sine wave */
+ return partial_sine[sample-1];
+
+ if (sample < (TONE_SINE_LEN/2))
+ /* Second quarter */
+ return partial_sine[(TONE_SINE_LEN/2)-sample-1];
+ if (sample < ((3*TONE_SINE_LEN)/4))
+ /* Third quarter */
+ return -partial_sine[(sample-3) % TONE_PART_SINE_LEN];
+ if (sample < TONE_SINE_LEN)
+ /* Final quarter */
+ return -partial_sine[TONE_SINE_LEN-sample-1];
+ /* We should not get here */
+ return 0;
+}
+
+/*!
+ * Re-set the tone generator.
+ *
+ * @param tone_gen Tone generator to reset.
+ * @param freq Frequency in Hz, 0 = silence.
+ * @param duration Duration in milliseconds. 0 to stop.
+ */
+void tone_reset(
+ struct tone_gen_t* const tone_gen,
+ uint16_t freq, uint16_t duration)
+{
+ if (freq)
+ /* Compute the time step */
+ tone_gen->step = (((2*freq*TONE_SINE_LEN) << TONE_SHIFT)
+ / ((2*TONE_FS) + 1) + 1);
+ else
+ /* DC tone == silence */
+ tone_gen->step = 0;
+
+ /* Compute remaining samples */
+ tone_gen->remain = (uint16_t)(
+ ((uint32_t)(TONE_FS * duration)) / 1000);
+
+ /* Initialise the sample counter */
+ tone_gen->sample = 0;
+}
+
+/*!
+ * Retrieve the next sample from the tone generator.
+ * @param tone_gen Tone generator to update.
+ */
+int16_t tone_next(
+ struct tone_gen_t* const tone_gen)
+{
+ if (!tone_gen)
+ return 0;
+ if (!tone_gen->remain)
+ return 0;
+ if (!tone_gen->step) {
+ /* Special case, emit silence */
+ tone_gen->remain--;
+ return 0;
+ }
+
+ /* Compute sample index */
+ uint16_t sample_int = ((tone_gen->sample) >> TONE_SHIFT)
+ % TONE_SINE_LEN;
+
+ /* Advance tone generator state */
+ tone_gen->sample += tone_gen->step;
+ tone_gen->remain--;
+
+ return tone_sine(sample_int);
+}
+
+/*!
+ * Retrieve the current time in milliseconds.
+ */
+uint32_t tone_msec(const struct tone_gen_t* const tone_gen)
+{
+ uint64_t ms = tone_gen->sample;
+ ms *= 1000;
+ ms /= TONE_FS;
+ return ms >> TONE_SHIFT;
+}
diff --git a/stm32/src/tot.c b/stm32/src/tot.c
new file mode 100644
index 0000000..1fce495
--- /dev/null
+++ b/stm32/src/tot.c
@@ -0,0 +1,90 @@
+/*!
+ * Time-out timer.
+ *
+ * This is a simple time-out timer for ensuring a maximum transmission
+ * time is observed. The time-out timer is configured with a total time
+ * in "ticks", which get counted down in an interrupt.
+ *
+ * When the "warning" level is reached, a flag is repeatedly set permit
+ * triggering of LEDs/sounds to warn the user that time is nearly up.
+ *
+ * Upon timeout, a separate flag is set to indicate timeout has taken
+ * place.
+ *
+ * Author Stuart Longland <[email protected]>
+ * Copyright (C) 2015 FreeDV project.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation. 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 Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "tot.h"
+
+/*!
+ * Reset the time-out timer. This zeroes the counter and event flags.
+ */
+void tot_reset(struct tot_t * const tot)
+{
+ tot->event = 0;
+ tot->remaining = 0;
+ tot->warn_remain = 0;
+ tot->ticks = 0;
+}
+
+/*!
+ * Start the time-out timer ticking.
+ */
+void tot_start(struct tot_t * const tot, uint32_t tot_ticks,
+ uint16_t warn_ticks)
+{
+ tot->event = TOT_EVT_START;
+ tot->warn_remain = tot_ticks - warn_ticks;
+ tot->remaining = tot_ticks;
+ tot->ticks = tot->tick_period;
+}
+
+/*!
+ * Update the time-out timer state.
+ */
+void tot_update(struct tot_t * const tot)
+{
+ if (!tot->event)
+ /* We are not active */
+ return;
+
+ if (tot->event & TOT_EVT_DONE)
+ /* We are done, do not process */
+ return;
+
+ if (tot->ticks)
+ /* Wait for a tick to pass */
+ return;
+
+ /* One "tick" has passed */
+ if (!tot->remaining) {
+ /* Time-out reached, reset all flags except timeout */
+ tot->event |= TOT_EVT_TIMEOUT | TOT_EVT_DONE;
+ return;
+ } else {
+ tot->remaining--;
+ }
+
+ if (!tot->warn_remain) {
+ /* Warning period has passed */
+ tot->event |= TOT_EVT_WARN | TOT_EVT_WARN_NEXT;
+ tot->warn_remain = tot->remain_warn_ticks;
+ } else {
+ tot->warn_remain--;
+ }
+
+ tot->ticks = tot->tick_period;
+}
diff --git a/stm32/src/usart_ut.c b/stm32/src/usart_ut.c
new file mode 100644
index 0000000..b8b229f
--- /dev/null
+++ b/stm32/src/usart_ut.c
@@ -0,0 +1,30 @@
+/*
+ usart_ut.c
+ David Rowe May 2019
+
+ Unit test for stm32 USART support.
+
+ tio is useful to receive the serial strings:
+
+ $ tio -m INLCRNL /dev/ttyUSB0
+*/
+
+#include <stm32f4xx.h>
+#include "stm32f4_usart.h"
+
+void Delay(uint32_t nCount)
+{
+ while(nCount--)
+ {
+ }
+}
+
+int main(void){
+
+ usart_init();
+
+ while(1){
+ usart_puts("Hello, World\n");
+ Delay(0x3FFFFF);
+ }
+}
diff --git a/stm32/src/usb_vcp_ut.c b/stm32/src/usb_vcp_ut.c
new file mode 100644
index 0000000..ff6ebd3
--- /dev/null
+++ b/stm32/src/usb_vcp_ut.c
@@ -0,0 +1,96 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: usb_vcp_ut.c
+ AUTHOR......: xenovacivus
+ DATE CREATED: 31 August 2014
+
+ USB Virtual COM Port (VCP) unit test that I found here:
+
+ https://github.com/xenovacivus/STM32DiscoveryVCP
+
+ Remarkably, it compiled and ran first time, and even the LEDs blink
+ as advertised, they just happen to match the LEDs on the SM1000!
+ However the speed was capped at about 130 kB/s. After a lot of
+ messing around I found suggestions in the comments from a similar
+ library here:
+
+ http://stm32f4-discovery.com/2014/08/library-24-virtual-com-port-vcp-stm32f4xx/
+
+ The key was changing APP_RX_DATA_SIZE in usbd_conf.h to 10000. I
+ guess the previous size of 2048 was constraining the length of USB
+ packets, and the USB overhead meant slow throughput. I could
+ achieve a max of 450 kB/s with this change, about 1/3 of the
+ theoretical 1.5 MB/s max for USB FS (12 Mbit/s).
+
+ I used this to test grabbing data from the STM32F4 Discovery:
+ $ sudo dd if=/dev/ttyACM0 of=/dev/null count=100
+ 4+96 records in
+ 44+1 records out
+ 22615 bytes (23 kB) copied, 0.150884 s, 150 kB/s
+
+ However I occasionally see:
+ $ sudo dd if=/dev/ttyACM0 of=/dev/null count=100
+ dd: failed to open ‘/dev/ttyACM0’: Device or resource busy
+
+ Googling found some suggestion that this is due to "modem manager", however I
+ removed MM and the problem still exists.
+
+\*---------------------------------------------------------------------------*/
+
+#include <stm32f4xx.h>
+#include <stm32f4xx_gpio.h>
+#include "stm32f4_usb_vcp.h"
+#include "sm1000_leds_switches.h"
+
+volatile uint32_t ticker, buf_ticker;
+
+#define N 640*6
+
+short buf[N];
+
+int main(void) {
+ int i;
+
+ for(i=0; i<N; i++)
+ buf[i] = 0;
+
+ sm1000_leds_switches_init();
+ usb_vcp_init();
+ SysTick_Config(SystemCoreClock/1000);
+
+ while (1) {
+
+ /* Blink the discovery red LED at 1Hz */
+
+ if (ticker > 500) {
+ GPIOD->BSRRH = GPIO_Pin_13;
+ }
+ if (ticker > 1000) {
+ ticker = 0;
+ GPIOD->BSRRL = GPIO_Pin_13;
+ }
+
+ /* Every 40ms send a buffer, simulates 16 bit samples at Fs=96kHz */
+
+ if (buf_ticker > 40) {
+ buf_ticker = 0;
+ led_pwr(1);
+ VCP_send_buffer((uint8_t*)buf, sizeof(buf));
+ led_pwr(0);
+ }
+
+ }
+
+ return 0;
+}
+
+/*
+ * Interrupt Handler
+ */
+
+void SysTick_Handler(void)
+{
+ ticker++;
+ buf_ticker++;
+}
+
diff --git a/stm32/src/usb_vsp_ut.c b/stm32/src/usb_vsp_ut.c
new file mode 100644
index 0000000..8f0c9f4
--- /dev/null
+++ b/stm32/src/usb_vsp_ut.c
@@ -0,0 +1,192 @@
+
+#define HSE_VALUE ((uint32_t)8000000) /* STM32 discovery uses a 8Mhz external crystal */
+
+#include "stm32f4xx_conf.h"
+#include "stm32f4xx.h"
+#include "stm32f4xx_gpio.h"
+#include "stm32f4xx_rcc.h"
+#include "stm32f4xx_exti.h"
+#include "usbd_cdc_core.h"
+#include "usbd_usr.h"
+#include "usbd_desc.h"
+#include "usbd_cdc_vcp.h"
+#include "usb_dcd_int.h"
+
+volatile uint32_t ticker, downTicker;
+
+/*
+ * The USB data must be 4 byte aligned if DMA is enabled. This macro handles
+ * the alignment, if necessary (it's actually magic, but don't tell anyone).
+ */
+__ALIGN_BEGIN USB_OTG_CORE_HANDLE USB_OTG_dev __ALIGN_END;
+
+
+void init();
+void ColorfulRingOfDeath(void);
+
+/*
+ * Define prototypes for interrupt handlers here. The conditional "extern"
+ * ensures the weak declarations from startup_stm32f4xx.c are overridden.
+ */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+void SysTick_Handler(void);
+void NMI_Handler(void);
+void HardFault_Handler(void);
+void MemManage_Handler(void);
+void BusFault_Handler(void);
+void UsageFault_Handler(void);
+void SVC_Handler(void);
+void DebugMon_Handler(void);
+void PendSV_Handler(void);
+void OTG_FS_IRQHandler(void);
+void OTG_FS_WKUP_IRQHandler(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+int main(void)
+{
+ /* Set up the system clocks */
+ SystemInit();
+
+ /* Initialize USB, IO, SysTick, and all those other things you do in the morning */
+ init();
+
+
+ while (1)
+ {
+ /* Blink the orange LED at 1Hz */
+ if (500 == ticker)
+ {
+ GPIOD->BSRRH = GPIO_Pin_13;
+ }
+ else if (1000 == ticker)
+ {
+ ticker = 0;
+ GPIOD->BSRRL = GPIO_Pin_13;
+ }
+
+
+ /* If there's data on the virtual serial port:
+ * - Echo it back
+ * - Turn the green LED on for 10ms
+ */
+ uint8_t theByte;
+ if (VCP_get_char(&theByte))
+ {
+ VCP_put_char(theByte);
+
+
+ GPIOD->BSRRL = GPIO_Pin_12;
+ downTicker = 10;
+ }
+ if (0 == downTicker)
+ {
+ GPIOD->BSRRH = GPIO_Pin_12;
+ }
+ }
+
+ return 0;
+}
+
+
+void init()
+{
+ /* STM32F4 discovery LEDs */
+ GPIO_InitTypeDef LED_Config;
+
+ /* Always remember to turn on the peripheral clock... If not, you may be up till 3am debugging... */
+ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
+ LED_Config.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13| GPIO_Pin_14| GPIO_Pin_15;
+ LED_Config.GPIO_Mode = GPIO_Mode_OUT;
+ LED_Config.GPIO_OType = GPIO_OType_PP;
+ LED_Config.GPIO_Speed = GPIO_Speed_25MHz;
+ LED_Config.GPIO_PuPd = GPIO_PuPd_NOPULL;
+ GPIO_Init(GPIOD, &LED_Config);
+
+
+
+ /* Setup SysTick or CROD! */
+ if (SysTick_Config(SystemCoreClock / 1000))
+ {
+ ColorfulRingOfDeath();
+ }
+
+
+ /* Setup USB */
+ USBD_Init(&USB_OTG_dev,
+ USB_OTG_FS_CORE_ID,
+ &USR_desc,
+ &USBD_CDC_cb,
+ &USR_cb);
+
+ return;
+}
+
+/*
+ * Call this to indicate a failure. Blinks the STM32F4 discovery LEDs
+ * in sequence. At 168Mhz, the blinking will be very fast - about 5 Hz.
+ * Keep that in mind when debugging, knowing the clock speed might help
+ * with debugging.
+ */
+void ColorfulRingOfDeath(void)
+{
+ uint16_t ring = 1;
+ while (1)
+ {
+ uint32_t count = 0;
+ while (count++ < 500000);
+
+ GPIOD->BSRRH = (ring << 12);
+ ring = ring << 1;
+ if (ring >= 1<<4)
+ {
+ ring = 1;
+ }
+ GPIOD->BSRRL = (ring << 12);
+ }
+}
+
+/*
+ * Interrupt Handlers
+ */
+
+void SysTick_Handler(void)
+{
+ ticker++;
+ if (downTicker > 0)
+ {
+ downTicker--;
+ }
+}
+
+void NMI_Handler(void) {}
+void HardFault_Handler(void) { ColorfulRingOfDeath(); }
+void MemManage_Handler(void) { ColorfulRingOfDeath(); }
+void BusFault_Handler(void) { ColorfulRingOfDeath(); }
+void UsageFault_Handler(void){ ColorfulRingOfDeath(); }
+void SVC_Handler(void) {}
+void DebugMon_Handler(void) {}
+void PendSV_Handler(void) {}
+
+void OTG_FS_IRQHandler(void)
+{
+ USBD_OTG_ISR_Handler (&USB_OTG_dev);
+}
+
+void OTG_FS_WKUP_IRQHandler(void)
+{
+ if(USB_OTG_dev.cfg.low_power)
+ {
+ *(uint32_t *)(0xE000ED10) &= 0xFFFFFFF9 ;
+ SystemInit();
+ USB_OTG_UngateClock(&USB_OTG_dev);
+ }
+ EXTI_ClearITPendingBit(EXTI_Line18);
+}