/* 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 . * */ /** STM32F1 application example * @file application.c * @author King Kévin * @date 2016-2017 */ /* standard libraries */ #include // standard integer types #include // standard utilities #include // string utilities #include // date/time utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // vector table definition #include // interrupt utilities #include // general purpose input output library #include // real-time control clock library #include // external interrupt utilities #include // real time clock utilities #include // independent watchdog utilities #include // debug utilities #include // flash utilities /* own libraries */ #include "global.h" // board definitions #include "print.h" // printing utilities #include "usb_cdcacm.h" // USB CDC ACM utilities #include "sensor_forumslader.h" // forumslader communication #include "radio_gps.h" // GPS communication #include "radio_bluetooth.h" // Bluetooth communication #include "flash_sdcard.h" // to read/write logs on SD card /* FatFs library */ #include "diskio.h" // disk access we have to implement to use FatFs #include "ff.h" // FatFs library #define WATCHDOG_PERIOD 10000 /**< watchdog period in ms */ /** @defgroup main_flags flag set in interrupts to be processed in main task * @{ */ volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */ /** @} */ time_t time_rtc = 0; /**< time (seconds since Unix Epoch) */ struct tm* time_tm; /**< time in tm format (time zones are not handled for non-POSIX environments) */ size_t putc(char c) { size_t length = 0; // number of characters printed static char newline = 0; // to remember on which character we sent the newline if (0==c) { length = 0; // don't print string termination character } else if ('\r' == c || '\n' == c) { // send CR+LF newline for most carriage return and line feed combination if (0==newline || c==newline) { // send newline only if not already send (and only once on \r\n or \n\r) usb_cdcacm_putchar('\r'); // send CR over USB usb_cdcacm_putchar('\n'); // send LF over USB length += 2; // remember we printed 2 characters newline = c; // remember on which character we sent the newline } else { length = 0; // the \r or \n of \n\r or \r\n has already been printed } } else { usb_cdcacm_putchar(c); // send byte over USB newline = 0; // clear new line length++; // remember we printed 1 character } return length; // return number of characters printed } #if !FF_FS_NORTC && !FF_FS_READONLY /** get the current time * @return current local time shall be returned as bit-fields packed into a DWORD value * @note FatFs function to be implement by user */ DWORD get_fattime (void) { time_rtc = rtc_get_counter_val(); // get time from internal RTC time_tm = localtime(&time_rtc); // convert time return ((1900+time_tm->tm_year-1980)<<25)+((time_tm->tm_mon)<<21)+((time_tm->tm_mday)<<16)+((time_tm->tm_hour)<<11)+((time_tm->tm_min)<<5)+((time_tm->tm_sec/2)<<0); // convert time to DWORD } #endif /** SD card status, used by FatFs */ static DSTATUS flash_sdcard_status = STA_NOINIT; /** inquire the current drive status * @param[in] pdrv physical drive number * @return current drive status flags * @note FatFs function to be implement by user */ DSTATUS disk_status (BYTE pdrv) { if (0!=pdrv) { // drive 0 (sd card) is our only drive return STA_NOINIT; } return flash_sdcard_status; } /** initializes the storage device * @param[in] pdrv physical drive number * @return current drive status flags * @note FatFs function to be implement by user */ DSTATUS disk_initialize (BYTE pdrv) { if (0!=pdrv) { // drive 0 (sd card) is our only drive return STA_NOINIT; } if (flash_sdcard_setup()) { flash_sdcard_status &= ~(STA_NOINIT|STA_NODISK); // SD card initialized } else { flash_sdcard_status |= (STA_NOINIT|STA_NODISK); // SD card is not present or failed initialisation } return flash_sdcard_status; } /** control device specific features and miscellaneous functions other than generic read/write * @param[in] drv physical drive number * @param[in] cmd control command code * @param[out] buff pointer to the control data * @return RES_* * @note FatFs function to be implement by user */ DRESULT disk_ioctl (BYTE drv, BYTE cmd, void *buff) { if (0!=drv) { // drive 0 (sd card) is our only drive return RES_PARERR; } if (flash_sdcard_status&STA_NOINIT) { // SD card not initialized return RES_NOTRDY; } DRESULT to_return = RES_ERROR; switch (cmd) { case CTRL_SYNC: // nothing to flush (writes are complete) to_return = RES_OK; break; case GET_SECTOR_COUNT: *(DWORD*)buff = flash_sdcard_size()/512; to_return = RES_OK; break; case GET_SECTOR_SIZE: *(DWORD*)buff = 512; to_return = RES_OK; break; case GET_BLOCK_SIZE: *(DWORD*)buff = flash_sdcard_erase_size()/512; to_return = RES_OK; break; default: to_return = RES_ERROR; } return to_return;; } /** read data from the sector(s) of storage device * @param[in] pdrv physical drive number * @param[out] buff pointer to the read data buffer * @param[in] sector start sector number * @param[in] count number of sectors to read * @return RES_* * @note FatFs function to be implement by user */ DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { if (0!=pdrv) { // drive 0 (sd card) is our only drive return RES_PARERR; } if (flash_sdcard_status&STA_NOINIT) { // SD card not initialized return RES_NOTRDY; } if (NULL==buff || 0==count) { // can't read no data return RES_PARERR; } for (UINT i=0; iLENGTH(log_buffer)) { // message too long to be saved return false; } for (uint16_t i=0; i=512) { // one data block is available for writing FIL log_file; // file to log the data if (FR_OK!=f_open(&log_file, log_name, FA_OPEN_APPEND | FA_WRITE)) { // open file to put log in return false; } UINT written = 0; if (FR_OK!=f_write(&log_file, log_buffer, log_used, &written)) { // write data return false; } if (FR_OK!=f_close(&log_file)) { // close file (and flush data) return false; } if (written!=log_used) { // did not log all bytes return false; } log_used = 0; // reset buffer } return true; } /** 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 usb_cdcacm_setup(); // setup USB CDC ACM (for printing) printf("\nwelcome to the CuVoodoo STM32F1 forumlader-logger\n"); // print welcome message #if !(DEBUG) // show watchdog information printf("watchdog set to (%.2fs)\n",WATCHDOG_PERIOD/1000.0); if (FLASH_OBR&FLASH_OBR_OPTERR) { printf("option bytes not set in flash: software wachtdog used (not started at reset)\n"); } else if (FLASH_OBR&FLASH_OBR_WDG_SW) { printf("software wachtdog used (not started at reset)\n"); } else { printf("hardware wachtdog used (started at reset)\n"); } #endif // setup RTC printf("setup internal RTC: "); rtc_auto_awake(RCC_LSE, 32768-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) rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds" nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt printf("OK\n"); // print time time_rtc= rtc_get_counter_val(); // get time from internal RTC 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); printf("setup forumslader UART: "); sensor_forumslader_setup(); // setup USART printf("OK\n"); printf("setup Bluetooth UART: "); radio_bluetooth_setup(); // setup USART printf("OK\n"); printf("setup GPS: "); radio_gps_setup(); bool gps_rtc_synced = false; // has the RTC been synced with the GPS time printf("OK\n"); printf("setup SD card file system: "); FATFS card_fs; // SD card FAT file system DIR directory; // for the root directory FRESULT result = f_mount(&card_fs, "", 0); // mount file system if (FR_OK!=result) { printf("failed (result=%u)\n", result); } else { result = f_opendir(&directory, ""); if (FR_OK!=result) { printf("failed to open directory (result=%u)\n", result); } else { snprintf(log_name, LENGTH(log_name), "forumslogger_%04d-%02d-%02d_%02d-%02d-%02d.txt", 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); // create file name for log file based on date printf("log will be saved in %s\n", log_name); } } // main loop printf("command input: ready\n"); bool action = false; // if an action has been performed don't go to sleep button_flag = false; // reset button flag char c = '\0'; // to store received character bool char_flag = false; // a new character has been received while (true) { // infinite loop iwdg_reset(); // kick the dog while (usb_cdcacm_received) { // data received over USB action = true; // action has been performed led_toggle(); // toggle LED c = usb_cdcacm_getchar(); // store receive character char_flag = true; // notify character has been received } while (char_flag) { // user data received char_flag = false; // reset flag action = true; // action has been performed printf("%c",c); // echo receive character } while (button_flag) { // user pressed button action = true; // action has been performed printf("button pressed\n"); led_toggle(); // toggle LED for (uint32_t i=0; i<1000000; i++) { // wait a bit to remove noise and double trigger __asm__("nop"); } button_flag = false; // reset flag } while (rtc_internal_tick_flag) { // the internal RTC ticked rtc_internal_tick_flag = false; // reset flag action = true; // action has been performed #if !defined(BLUE_PILL) // on the blue pill the LED is close to the 32.768 kHz oscillator and heavily influences it led_toggle(); // toggle LED (good to indicate if main function is stuck) #endif time_rtc = rtc_get_counter_val(); // get time from internal RTC (seconds since Unix Epoch) time_tm = localtime(&time_rtc); // get time in tm format from Epoch (time zones are not handled for non-POSIX environments) if (0==time_tm->tm_sec) { // new minute printf("time: %02d:%02d:%02d\n", time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec); } } while (sensor_forumslader_received) { // a forumslader message has been received sensor_forumslader_received = false; // clear flag action = true; // action has been performed radio_bluetooth_transmit((const char*)sensor_forumslader_message); // forward message over Bluetooth //printf("Forumslader: %s", sensor_forumslader_message); // print message if (!log((char*)sensor_forumslader_message)) { // log Forumslader message printf("logging Forumslader message failed\n"); } } while (radio_bluetooth_received) { // a message has been received over Bluetooth radio_bluetooth_received = false; // clear flag action = true; // action has been performed if (0==strncmp((char*)radio_bluetooth_message, "$FLL,", 5)) { // check if this is a message for us if (0==strncmp((char*)radio_bluetooth_message, "$FLL,LS;", 8)) { // list files radio_bluetooth_transmit("$FLL,LS,BOL;\r\n"); // notify we will begin the list of files if (strlen(log_name)) { // check is card file system is available FILINFO info; // to get file information result = f_findfirst(&directory, &info, "", "forumslogger_?\??\?-?\?-?\?_?\?-?\?-?\?.txt"); // find files while (FR_OK==result && strlen(info.fname)) { // file found radio_bluetooth_transmit("$FLL,LS,"); // file entry header radio_bluetooth_transmit(info.fname); // file name radio_bluetooth_transmit(";\r\n"); // end for file entry result = f_findnext(&directory, &info); // get next file } } radio_bluetooth_transmit("$FLL,LS,EOL;\r\n"); // notify this is the end of the list } else if (0==strncmp((char*)radio_bluetooth_message, "$FLL,RM,forumslogger_", 21)) { // remove log file char filename[36+1] = {0}; // file to delete for (uint8_t i=0; iLENGTH(buffer)-1) { // end of file break; } buffer[read] = 0; // end string radio_bluetooth_transmit(buffer); // send log data } f_close(&log_file); // close file radio_bluetooth_transmit("$FLL,CAT,EOF;\r\n"); // notify user the file ended } else { radio_bluetooth_transmit("$FLL,CAT,ERROR;\r\n"); // notify user file removal failed } } } else { sensor_forumslader_transmit((const char*)radio_bluetooth_message); // forward message to forumslader if (!log((char*)radio_bluetooth_message)) { // log Bluetooth message printf("logging Bluetooth message failed\n"); } } //printf("Bluetooth: %s", radio_bluetooth_message); // print message } while (radio_gps_received) { // a GPS message has been received radio_gps_received = false; // clear flag action = true; // action has been performed if (!gps_rtc_synced) { // if the RTC has not been synced with the GPS time, try to do it if (0==strncmp((const char *)radio_gps_message,"$GPRMC,",7)) { // get time from GPS uint8_t arg = 0; uint8_t arg_start = 0; for (uint8_t i = 0; i < LENGTH(radio_gps_message) && radio_gps_message[i]!='\0'; i++) { if (','==radio_gps_message[i]) { // argument end if (1==arg) { // got time if (i-arg_start<7) { // time not provided break; } else { time_tm->tm_hour = (radio_gps_message[arg_start+1]-'0')*10+(radio_gps_message[arg_start+2]-'0')*1; // set hours time_tm->tm_min = (radio_gps_message[arg_start+3]-'0')*10+(radio_gps_message[arg_start+4]-'0')*1; // set minutes time_tm->tm_sec = (radio_gps_message[arg_start+5]-'0')*10+(radio_gps_message[arg_start+6]-'0')*1; // set seconds } } else if (2==arg) { // got validity if (i-arg_start<2) { // validity not provided break; } else if ('A'!=radio_gps_message[arg_start+1]) { // not valid break; } } else if (9==arg) { // got date if (i-arg_start<7) { // date not provided break; } else { time_tm->tm_mday = (radio_gps_message[arg_start+1]-'0')*10+(radio_gps_message[arg_start+2]-'0')*1; // set day of month time_tm->tm_mon = (radio_gps_message[arg_start+3]-'0')*10+(radio_gps_message[arg_start+4]-'0')*1; // set month time_tm->tm_year = 2000+(radio_gps_message[arg_start+5]-'0')*10+(radio_gps_message[arg_start+6]-'0')*1-1900; // set year time_rtc = mktime(time_tm); // get back seconds rtc_set_counter_val(time_rtc); // save date/time to internal RTC gps_rtc_synced = true; // remember we synced the time printf("GPS date 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); } } arg++; // next argument starts arg_start = i; // save start of next argument } } } } //printf("GPS: %s", radio_gps_message); // print GPS message if (!log((char*)radio_gps_message)) { // log GPS message printf("logging GPS message failed\n"); } } 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 }