stm32f1/application.c

634 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** STM32F1 application example
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities
#include <time.h> // date/time utilities
#include <ctype.h> // utilities to check chars
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/scb.h> // vector table definition
#include <libopencm3/cm3/nvic.h> // interrupt utilities
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/exti.h> // external interrupt utilities
#include <libopencm3/stm32/rtc.h> // real time clock utilities
#include <libopencm3/stm32/iwdg.h> // independent watchdog utilities
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
/* own libraries */
#include "global.h" // board definitions
#include "print.h" // printing utilities
#if !defined(STLINKV2)
#include "uart.h" // USART utilities
#endif
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "terminal.h" // handle the terminal interface
#include "menu.h" // menu utilities
/** watchdog period in ms */
#define WATCHDOG_PERIOD 10000
/** set to 0 if the RTC is reset when the board is powered on, only indicates the uptime
* set to 1 if VBAT can keep the RTC running when the board is unpowered, indicating the date and time
*/
#if defined(CORE_BOARD)
#define RTC_DATE_TIME 1
#else
#define RTC_DATE_TIME 0
#endif
/** number of RTC ticks per second
* @note use integer divider of oscillator to keep second precision
*/
#define RTC_TICKS_SECOND 4
/** RTC time when device is started */
static time_t time_start = 0;
/** @defgroup main_flags flag set in interrupts to be processed in main task
* @{
*/
static volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
/** @} */
size_t putc(char c)
{
size_t length = 0; // number of characters printed
static char last_c = 0; // to remember on which character we last sent
if ('\n' == c) { // send carriage return (CR) + line feed (LF) newline for each LF
if ('\r' != last_c) { // CR has not already been sent
#if !defined(STLINKV2)
uart_putchar_nonblocking('\r'); // send CR over USART
#endif
usb_cdcacm_putchar('\r'); // send CR over USB
length++; // remember we printed 1 character
}
}
#if !defined(STLINKV2)
uart_putchar_nonblocking(c); // send byte over USART
#endif
usb_cdcacm_putchar(c); // send byte over USB
length++; // remember we printed 1 character
last_c = c; // remember last character
return length; // return number of characters printed
}
/** display available commands
* @param[in] argument no argument required
*/
static void command_help(void* argument);
/** show software and hardware version
* @param[in] argument no argument required
*/
static void command_version(void* argument);
/** show uptime
* @param[in] argument no argument required
*/
static void command_uptime(void* argument);
#if RTC_DATE_TIME
/** show date and time
* @param[in] argument date and time to set
*/
static void command_datetime(void* argument);
#endif
/** reset board
* @param[in] argument no argument required
*/
static void command_reset(void* argument);
/** switch to DFU bootloader
* @param[in] argument no argument required
*/
static void command_bootloader(void* argument);
/** list of all supported commands */
static const struct menu_command_t menu_commands[] = {
{
.shortcut = 'h',
.name = "help",
.command_description = "display help",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_help,
},
{
.shortcut = 'v',
.name = "version",
.command_description = "show software and hardware version",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_version,
},
{
.shortcut = 'u',
.name = "uptime",
.command_description = "show uptime",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_uptime,
},
#if RTC_DATE_TIME
{
.shortcut = 'd',
.name = "date",
.command_description = "show/set date and time",
.argument = MENU_ARGUMENT_STRING,
.argument_description = "[YYYY-MM-DD HH:MM:SS]",
.command_handler = &command_datetime,
},
#endif
{
.shortcut = 'r',
.name = "reset",
.command_description = "reset board",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_reset,
},
{
.shortcut = 'b',
.name = "bootloader",
.command_description = "reboot into DFU bootloader",
.argument = MENU_ARGUMENT_NONE,
.argument_description = NULL,
.command_handler = &command_bootloader,
},
};
static void command_help(void* argument)
{
(void)argument; // we won't use the argument
printf("available commands:\n");
menu_print_commands(menu_commands, LENGTH(menu_commands)); // print global commands
}
static void command_version(void* argument)
{
(void)argument; // we won't use the argument
printf("firmware date: %04u-%02u-%02u\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
// show flash size
puts("flash size: ");
if (0xffff == DESIG_FLASH_SIZE) {
puts("unknown (probably a defective micro-controller\n");
} else {
printf("%u KB\n", DESIG_FLASH_SIZE);
}
const uint16_t dev_id = DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK;
const uint16_t rev_id = DBGMCU_IDCODE >> 16;
printf("MCUID: DEV_ID=0x%03x REV_ID=0x%04x\n", dev_id, rev_id);
// display device identity
printf("device id: %08x%08x%04x%04x\n", DESIG_UNIQUE_ID2, DESIG_UNIQUE_ID1, DESIG_UNIQUE_ID0 & 0xffff, DESIG_UNIQUE_ID0 >> 16);
#if DEBUG
bool fake = false; // if details indicate it's not an STM32
puts("chip family: ");
switch (dev_id) {
case 0: // DBGMCU_IDCODE is only accessible in debug mode (this is a known issue documented in STM32F10xxC/D/E Errata sheet, without workaround)
puts("not readable, retry with debug attached");
break;
// from RM0008 STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx
case 0x412:
puts("STM32F10x low-density");
break;
case 0x410:
puts("STM32F10x medium-density");
break;
case 0x414:
puts("STM32F10x high-density");
break;
case 0x430:
puts("STM32F10x XL-density");
break;
case 0x418:
puts("STM32F10xconnectivity");
break;
// from RM0091 STM32F0x8
case 0x444:
puts("STM32F03x");
break;
case 0x445:
puts("STM32F04x");
break;
case 0x440:
puts("STM32F05x");
break;
case 0x448:
puts("STM32F07x");
break;
case 0x442:
puts("STM32F09x");
break;
// from RM0444 STM32G0x1
case 0x460:
puts("STM32G071xx/STM32G081xx");
break;
case 0x466:
puts("STM32G031xx/STM32G041xx");
break;
// from RM0090 STM32F4x5/STM32F4x7
case 0x413:
puts("STM32F405/STM32F407/STM32F415/STM32F417");
break;
case 0x419:
puts("STM32F42x/STM32F43x");
break;
// from RM0368
case 0x423:
puts("STM32F401xB/C");
break;
case 0x433:
puts("STM32F401xD/E");
break;
// from RM0383
case 0x431:
puts("STM32F411xC/E");
break;
default:
puts("unknown");
fake = true;
break;
}
putc('\n');
puts("chip revision: ");
switch (DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK) {
case 0x412:
if (0x1000 == rev_id) {
putc('A');
} else {
puts("unknown");
}
break;
case 0x410:
if (0x0000 == rev_id) {
putc('A');
} else if (0x2000 == rev_id) {
putc('B');
} else if (0x2001 == rev_id) {
putc('Z');
} else if (0x2003 == rev_id) {
puts("1/2/3/X/Y");
} else {
puts("unknown");
}
break;
case 0x414:
if (0x1000 == rev_id) {
puts("A/1");
} else if (0x1001 == rev_id) {
putc('Z');
} else if (0x1003 == rev_id) {
puts("1/2/3/X/Y");
} else {
puts("unknown");
}
break;
case 0x430:
if (0x1003 == rev_id) {
puts("A/1");
} else {
puts("unknown");
}
break;
case 0x418:
if (0x1000 == rev_id) {
putc('A');
} else if (0x1001 == rev_id) {
putc('Z');
} else {
puts("unknown");
}
break;
default:
printf("unknown");
break;
}
putc('\n');
// from RM0091 STM32F0x8 reference manual (not sure if it applies to F1)
puts("manufacturing information:\n");
printf("- X,Y wafer coordinate: %08x\n", DESIG_UNIQUE_ID0);
printf("- lot number: %c%c%c%c%c%c%c\n", DESIG_UNIQUE_ID2 >> 24, DESIG_UNIQUE_ID2 >> 16, DESIG_UNIQUE_ID2 >> 8, DESIG_UNIQUE_ID2 >> 0, DESIG_UNIQUE_ID1 >> 24, DESIG_UNIQUE_ID1 >> 16, DESIG_UNIQUE_ID1 >> 8);
printf("- wafer number: %u\n", DESIG_UNIQUE_ID1 & 0xff);
// from ARMv7-M and Cortex-M3 TRM
// ARMv7-M B3.2.3
printf("CPUID: 0x%08x\n", SCB_CPUID);
const uint8_t cpuid_implementer = (SCB_CPUID & SCB_CPUID_IMPLEMENTER) >> SCB_CPUID_IMPLEMENTER_LSB;
printf("- implementer: %s (0x%02x)\n", 0x41 == cpuid_implementer ? "ARM" : "unknown", cpuid_implementer);
const uint8_t cpuid_architecture = (SCB_CPUID & SCB_CPUID_CONSTANT) >> SCB_CPUID_CONSTANT_LSB;
puts("- architecture: ");
switch (cpuid_architecture) {
case 0xc:
puts("ARMv6-M");
break;
case 0xf:
puts("ARMv7-M");
break;
default:
fake = true;
puts("unknown");
}
printf(" (0x%x)\n", cpuid_architecture);
const uint16_t cpuid_partno = (SCB_CPUID & SCB_CPUID_PARTNO) >> SCB_CPUID_PARTNO_LSB;
puts("- part number: ");
switch (cpuid_partno) {
case 0xC60:
puts("Cortex-M0+");
break;
case 0xC20:
puts("CortexM0");
break;
case 0xC23: // the ARM spec actually mentions 0xC24
puts("CortexM3");
break;
case 0xC24:
puts("CortexM4");
break;
case 0xC27:
puts("CortexM7");
break;
default:
fake = true;
puts("unknown");
}
printf(" (0x%03x)\n", cpuid_partno);
const uint8_t cpuid_variant = (SCB_CPUID & SCB_CPUID_VARIANT) >> SCB_CPUID_VARIANT_LSB;
printf("- variant: %u\n", cpuid_variant);
const uint8_t cpuid_revision = (SCB_CPUID & SCB_CPUID_REVISION) >> SCB_CPUID_REVISION_LSB;
printf("- revision: %u\n", cpuid_revision);
// ARM CoreSight B2.2.2
const uint8_t jep106_continuation = *(uint32_t*)0xE00FFFD0 & 0x0f; // DES_2, PIDR4 bits[3:0]
const uint8_t jep106_identification = ((*(uint32_t*)0xE00FFFE8 & 0x7) << 4) + ((*(uint32_t*)0xE00FFFE4 >> 4) & 0xf); // DES_0, PIDR1 bits[7:4] JEP106 identification code bits[3:0], DES_1, PIDR2 bits[2:0] JEP106 identification code bits[6:4]
const uint16_t pidr_partno = ((*(uint32_t*)0xE00FFFE4 & 0xf) << 8) + (*(uint32_t*)0xE00FFFE0 & 0xff); // PART_0, PIDR0 bits[7:0] Part number bits[7:0], PART_1, PIDR1 bits[3:0] Part number bits[11:8]
puts("JEP106 ID: ");
if (0 == jep106_continuation && 0x20 == jep106_identification) {
puts("STM");
} else if (7 == jep106_continuation && 0x51 == jep106_identification) {
puts("GigaDevice");
} else if (4 == jep106_continuation && 0x3b == jep106_identification) {
puts("ARM");
} else {
puts("unknown");
}
printf(" (cont.=%u, ID=0x%02x), part=0x%03x\n", jep106_continuation, jep106_identification, pidr_partno);
// guess the micro-controller
puts("MCU: ");
if (1 == cpuid_variant && 1 == cpuid_revision && 0 == jep106_continuation && 0x20 == jep106_identification) { // STM32 uses Cortex-M3 r1p1 and the right JEP106 ID
puts("STM32");
} else if (2 == cpuid_variant && 1 == cpuid_revision && 7 == jep106_continuation && 0x51 == jep106_identification) { // GD32 uses Cortex-M3 r2p1 and the right JEP106 ID
puts("GD32");
fake = true;
} else if (2 == cpuid_variant && 1 == cpuid_revision && 4 == jep106_continuation && 0x3b == jep106_identification) { // GD32 uses Cortex-M3 r2p1 and ARM JEP106 ID
puts("CS32");
fake = true;
} else {
puts("unknown");
fake = true;
}
putc('\n');
// detect fake STM32
if (0x412 == dev_id || 0x410 == dev_id || 0x414 == dev_id || 0x430 == dev_id || 0x418 == dev_id) { // STM32F10x
// the original STM32F10x uses a Cortex-M3 r1p1
if (0xC23 != cpuid_partno) { // Cortex-M3
fake = true;
}
if (1 != cpuid_variant) { // r1
fake = true;
}
if (1 != cpuid_revision) { // p1
fake = true;
}
}
printf("this %s to be a genuine STM32\n", fake ? "does not seem" : "seems");
#endif
}
static void command_uptime(void* argument)
{
(void)argument; // we won't use the argument
const uint32_t uptime = (rtc_get_counter_val() - time_start) / RTC_TICKS_SECOND; // get time from internal RTC
printf("uptime: %u.%02u:%02u:%02u\n", uptime / (24 * 60 * 60), (uptime / (60 * 60)) % 24, (uptime / 60) % 60, uptime % 60);
}
#if RTC_DATE_TIME
static void command_datetime(void* argument)
{
char* datetime = (char*)argument; // argument is optional date time
if (NULL == argument) { // no date and time provided, just show the current day and time
time_t time_rtc = rtc_get_counter_val() / RTC_TICKS_SECOND; // get time from internal RTC
struct tm* time_tm = localtime(&time_rtc); // convert time
printf("date: %d-%02d-%02d %02d:%02d:%02d\n", 1900 + time_tm->tm_year, time_tm->tm_mon, time_tm->tm_mday, time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec);
} else { // date and time provided, set it
const char* malformed = "date and time malformed, expecting YYYY-MM-DD HH:MM:SS\n";
struct tm time_tm; // to store the parsed date time
if (strlen(datetime) != (4 + 1 + 2 + 1 + 2) + 1 + (2 + 1 + 2 + 1 + 2)) { // verify date/time is long enough
printf(malformed);
return;
}
if (!(isdigit((int8_t)datetime[0]) && isdigit((int8_t)datetime[1]) && isdigit((int8_t)datetime[2]) && isdigit((int8_t)datetime[3]) && '-' == datetime[4] && isdigit((int8_t)datetime[5]) && isdigit((int8_t)datetime[6]) && '-' == datetime[7] && isdigit((int8_t)datetime[8]) && isdigit((int8_t)datetime[9]) && ' ' == datetime[10] && isdigit((int8_t)datetime[11]) && isdigit((int8_t)datetime[12]) && ':' == datetime[13] && isdigit((int8_t)datetime[14]) && isdigit((int8_t)datetime[15]) && ':' == datetime[16] && isdigit((int8_t)datetime[17]) && isdigit((int8_t)datetime[18]))) { // verify format (good enough to not fail parsing)
printf(malformed);
return;
}
time_tm.tm_year = strtol(&datetime[0], NULL, 10) - 1900; // parse year
time_tm.tm_mon = strtol(&datetime[5], NULL, 10); // parse month
time_tm.tm_mday = strtol(&datetime[8], NULL, 10); // parse day
time_tm.tm_hour = strtol(&datetime[11], NULL, 10); // parse hour
time_tm.tm_min = strtol(&datetime[14], NULL, 10); // parse minutes
time_tm.tm_sec = strtol(&datetime[17], NULL, 10); // parse seconds
time_t time_rtc = mktime(&time_tm); // get back seconds
time_start = time_rtc * RTC_TICKS_SECOND + (rtc_get_counter_val() - time_start); // update uptime with current date
rtc_set_counter_val(time_rtc * RTC_TICKS_SECOND); // save date/time to internal RTC
printf("date and time saved: %d-%02d-%02d %02d:%02d:%02d\n", 1900 + time_tm.tm_year, time_tm.tm_mon, time_tm.tm_mday, time_tm.tm_hour, time_tm.tm_min, time_tm.tm_sec);
}
}
#endif
static void command_reset(void* argument)
{
(void)argument; // we won't use the argument
scb_reset_system(); // reset device
while (true); // wait for the reset to happen
}
static void command_bootloader(void* argument)
{
(void)argument; // we won't use the argument
// set DFU magic to specific RAM location
__dfu_magic[0] = 'D';
__dfu_magic[1] = 'F';
__dfu_magic[2] = 'U';
__dfu_magic[3] = '!';
scb_reset_system(); // reset system (core and peripherals)
while (true); // wait for the reset to happen
}
/** process user command
* @param[in] str user command string (\0 ended)
*/
static void process_command(char* str)
{
// ensure actions are available
if (NULL == menu_commands || 0 == LENGTH(menu_commands)) {
return;
}
// don't handle empty lines
if (!str || 0 == strlen(str)) {
return;
}
bool command_handled = false;
if (!command_handled) {
command_handled = menu_handle_command(str, menu_commands, LENGTH(menu_commands)); // try if this is not a global command
}
if (!command_handled) {
printf("command not recognized. enter help to list commands\n");
}
}
/** program entry point
* this is the firmware function started by the micro-controller
*/
void main(void);
void main(void)
{
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock
#if DEBUG
// enable functionalities for easier debug
DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted
DBGMCU_CR |= DBGMCU_CR_WWDG_STOP; // stop window watchdog counter when code is halted
DBGMCU_CR |= DBGMCU_CR_STANDBY; // allow debug also in standby mode (keep digital part and clock powered)
DBGMCU_CR |= DBGMCU_CR_STOP; // allow debug also in stop mode (keep clock powered)
DBGMCU_CR |= DBGMCU_CR_SLEEP; // allow debug also in sleep mode (keep clock powered)
#else
// setup watchdog to reset in case we get stuck (i.e. when an error occurred)
iwdg_set_period_ms(WATCHDOG_PERIOD); // set independent watchdog period
iwdg_start(); // start independent watchdog
#endif
board_setup(); // setup board
#if !defined(STLINKV2)
uart_setup(); // setup USART (for printing)
#endif
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
puts("\nwelcome to the CuVoodoo STM32F1 example application\n"); // print welcome message
#if DEBUG
// show reset cause
if (RCC_CSR & (RCC_CSR_LPWRRSTF | RCC_CSR_WWDGRSTF | RCC_CSR_IWDGRSTF | RCC_CSR_SFTRSTF | RCC_CSR_PORRSTF | RCC_CSR_PINRSTF)) {
puts("reset cause(s):");
if (RCC_CSR & RCC_CSR_LPWRRSTF) {
puts(" low-power");
}
if (RCC_CSR & RCC_CSR_WWDGRSTF) {
puts(" window-watchdog");
}
if (RCC_CSR & RCC_CSR_IWDGRSTF) {
puts(" independent-watchdog");
}
if (RCC_CSR & RCC_CSR_SFTRSTF) {
puts(" software");
}
if (RCC_CSR & RCC_CSR_PORRSTF) {
puts(" POR/PDR");
}
if (RCC_CSR & RCC_CSR_PINRSTF) {
puts(" pin");
}
putc('\n');
RCC_CSR |= RCC_CSR_RMVF; // clear reset flags
}
#endif
#if !(DEBUG)
// show watchdog information
printf("setup watchdog: %.2fs", WATCHDOG_PERIOD / 1000.0);
if (FLASH_OBR & FLASH_OBR_OPTERR) {
puts(" (option bytes not set in flash: software wachtdog used, not automatically started at reset)\n");
} else if (FLASH_OBR & FLASH_OBR_WDG_SW) {
puts(" (software watchdog used, not automatically started at reset)\n");
} else {
puts(" (hardware watchdog used, automatically started at reset)\n");
}
#endif
// setup RTC
puts("setup internal RTC: ");
#if defined(BLUE_PILL) || defined(STLINKV2) || defined(BLASTER) // for boards without a Low Speed External oscillator
// note: the blue pill LSE oscillator is affected when toggling the onboard LED, thus prefer the HSE
rtc_auto_awake(RCC_HSE, 8000000 / 128 / RTC_TICKS_SECOND - 1); // use High Speed External oscillator (8 MHz / 128) as RTC clock (VBAT can't be used to keep the RTC running)
#else // for boards with an precise Low Speed External oscillator
rtc_auto_awake(RCC_LSE, 32768 / RTC_TICKS_SECOND - 1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
#endif
rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
time_start = rtc_get_counter_val(); // get start time from internal RTC
puts("OK\n");
// setup terminal
terminal_prefix = ""; // set default prefix
terminal_process = &process_command; // set central function to process commands
terminal_setup(); // start terminal
// start main loop
bool action = false; // if an action has been performed don't go to sleep
button_flag = false; // reset button flag
while (true) { // infinite loop
iwdg_reset(); // kick the dog
if (user_input_available) { // user input is available
action = true; // action has been performed
led_toggle(); // toggle LED
char c = user_input_get(); // store receive character
terminal_send(c); // send received character to terminal
}
if (button_flag) { // user pressed button
action = true; // action has been performed
puts("button pressed\n");
led_toggle(); // toggle LED
sleep_ms(100); // wait a bit to remove noise and double trigger
button_flag = false; // reset flag
}
if (rtc_internal_tick_flag) { // the internal RTC ticked
rtc_internal_tick_flag = false; // reset flag
action = true; // action has been performed
if (0 == (rtc_get_counter_val() % RTC_TICKS_SECOND)) { // one seond has passed
led_toggle(); // toggle LED (good to indicate if main function is stuck)
}
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity
action = false;
} else {
__WFI(); // go to sleep
}
} // main loop
}
/** @brief interrupt service routine called when tick passed on RTC */
void rtc_isr(void)
{
rtc_clear_flag(RTC_SEC); // clear flag
rtc_internal_tick_flag = true; // notify to show new time
}