diff options
Diffstat (limited to 'stm32/src')
| -rw-r--r-- | stm32/src/adc_rec_usb.c | 85 | ||||
| -rw-r--r-- | stm32/src/dac_ut.c | 57 | ||||
| -rw-r--r-- | stm32/src/debugblinky.c | 57 | ||||
| -rw-r--r-- | stm32/src/memtools.c | 67 | ||||
| -rw-r--r-- | stm32/src/menu.c | 98 | ||||
| -rw-r--r-- | stm32/src/morse.c | 175 | ||||
| -rw-r--r-- | stm32/src/sfx.c | 67 | ||||
| -rw-r--r-- | stm32/src/sm1000_leds_switches.c | 229 | ||||
| -rw-r--r-- | stm32/src/sm1000_leds_switches_ut.c | 41 | ||||
| -rw-r--r-- | stm32/src/sm1000_main.c | 1476 | ||||
| -rw-r--r-- | stm32/src/sounds.c | 62 | ||||
| -rw-r--r-- | stm32/src/startup_stm32f4xx.s | 526 | ||||
| -rw-r--r-- | stm32/src/stm32f4_adc.c | 286 | ||||
| -rw-r--r-- | stm32/src/stm32f4_dac.c | 427 | ||||
| -rw-r--r-- | stm32/src/stm32f4_machdep.c | 92 | ||||
| -rw-r--r-- | stm32/src/stm32f4_usart.c | 71 | ||||
| -rw-r--r-- | stm32/src/stm32f4_usb_vcp.c | 90 | ||||
| -rw-r--r-- | stm32/src/stm32f4_vrom.c | 724 | ||||
| -rw-r--r-- | stm32/src/system_stm32f4xx.c | 585 | ||||
| -rw-r--r-- | stm32/src/tone.c | 151 | ||||
| -rw-r--r-- | stm32/src/tot.c | 90 | ||||
| -rw-r--r-- | stm32/src/usart_ut.c | 30 | ||||
| -rw-r--r-- | stm32/src/usb_vcp_ut.c | 96 | ||||
| -rw-r--r-- | stm32/src/usb_vsp_ut.c | 192 |
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>© 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>© 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); +} |
