/* 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 .
*
*/
/** CuVoodoo clapperboard firmware (for STM32F103Cx micro-controller)
* @file main.c
* @author King Kévin
* @date 2016-2017
*/
/* standard libraries */
#include // standard integer types
#include // boolean type
#include // string 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
#include // timer utilities
/* own libraries */
#include "global.h" // board definitions
#include "print.h" // printing utilities
#include "usart.h" // USART utilities
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "rtc_ds1307.h" // DS1307 RTC utilities
#include "led_tm1637.h" // TM1637 7-segment controller utilities
#include "led_max7219.h" // MAX7219 7-segment controller utilities
#define WATCHDOG_PERIOD 10000 /**< watchdog period in ms */
/** @defgroup main_flags flag set in interrupts to be processed in main task
* @{
*/
volatile bool rtc_tick_flag = false; /**< flag set when RTC ticked */
volatile bool frame_flag = false; /**< flag set when a frame has passed */
volatile bool keep_alive_flag = false; /**< flag to restart shutdown counter on power switch activity */
/** @} */
#define SQUARE_WAVE_PORT B /**< port connected to RTC DS1307 square wave output */
#define SQUARE_WAVE_PIN 0 /**< pin connected to RTC DS1307 square wave output */
volatile uint8_t rtc_seconds = 0; /**< number of seconds passed incremented by the square wave */
#define STANDBY_TIMEOUT 30 /**< number of seconds after last shake before going down */
volatile uint16_t standby_timer = 0; /**< number of seconds since last wake-up/activity */
#define FRAME_TIMER 2 /**< timer to count frame time */
#define FRAME_RATE 25 /**< frame rate */
volatile uint8_t frame_count = 0; /**< number of frames passed */
#define BUZZER_TIMER 1 /**< timer to generate scene and take count */
#define BUZZER_1_PORT A /**< use timer 1 channel 1 (and it's negative) to driver buzzer */
#define BUZZER_1_PIN 7 /**< use timer 1 channel 1 (and it's negative) to driver buzzer */
#define BUZZER_2_PORT A /**< use timer 1 channel 1 (and it's negative) to driver buzzer */
#define BUZZER_2_PIN 8 /**< use timer 1 channel 1 (and it's negative) to driver buzzer */
#define MUX_EN_PORT B /**< port to enable multiplexer */
#define MUX_EN_PIN 9 /**< pin to enable multiplexer */
#define MUX_S0_PORT B /**< port to select multiplexer output */
#define MUX_S0_PIN 3 /**< pin to select multiplexer output */
#define MUX_S1_PORT B /**< port to select multiplexer output */
#define MUX_S1_PIN 4 /**< pin to select multiplexer output */
#define MUX_S2_PORT B /**< port to select multiplexer output */
#define MUX_S2_PIN 5 /**< pin to select multiplexer output */
/** number to be display and changed using the buttons */
struct number_t {
uint16_t number; /**< number to display (0-9999, 0xffff=off) */
uint8_t up; /**< which of the 16 buttons is to increment the number */
uint8_t down; /**< which of the 16 buttons is to decrement the number */
};
/** episode, scene, take, video 1, audio 1, video 2, audio 2 number (does not have to be in this order) */
struct number_t numbers[] = {
{0, 2, 3},
{0, 14, 15},
{0, 10, 11},
{0, 6, 7},
{0, 1, 0},
{0, 13, 12},
{0, 5, 4},
};
#define POWER_SWITCH_PORT B /**< port to switch power of all devices (including this micro-controller) */
#define POWER_SWITCH_PIN 8 /**< pin to switch power of all devices (including this micro-controller) */
#define POWER_BUTTON_PORT B /**< port to detect power switching activity (to keep alive) */
#define POWER_BUTTON_PIN 1 /**< pin to detect power switching activity (to keep alive) */
#define BUTTONS_DRIVE_PORT A
#define BUTTONS_DRIVE_PIN0 0
#define BUTTONS_DRIVE_PIN1 1
#define BUTTONS_DRIVE_PIN2 2
#define BUTTONS_DRIVE_PIN3 3
#define BUTTONS_READ_PORT A
#define BUTTONS_READ_PIN0 4
#define BUTTONS_READ_PIN1 5
#define BUTTONS_READ_PIN2 6
#define BUTTONS_READ_PIN3 15
/** user input command */
static char command[32] = {0};
/** user input command index */
uint8_t command_i = 0;
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)
usart_putchar_nonblocking('\r'); // send CR over USART
cdcacm_putchar('\r'); // send CR over USB
usart_putchar_nonblocking('\n'); // send LF over USART
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 {
usart_putchar_nonblocking(c); // send byte over USART
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
}
/** process user command
* @param[in] str user command string (\0 ended)
*/
static void process_command(char* str)
{
// split command
const char* delimiter = " ";
char* word = strtok(str,delimiter);
if (!word) {
goto error;
}
// parse command
if (0==strcmp(word,"help")) {
printf("available commands:\n");
printf("led [on|off|toggle]\n");
printf("time [HH:MM:SS]\n");
printf("date [YYYY-MM-DD]\n");
} else if (0==strcmp(word,"led")) {
word = strtok(NULL,delimiter);
if (!word) {
goto error;
} else if (0==strcmp(word,"on")) {
led_on(); // switch LED on
printf("LED switched on\n"); // notify user
} else if (0==strcmp(word,"off")) {
led_off(); // switch LED off
printf("LED switched off\n"); // notify user
} else if (0==strcmp(word,"toggle")) {
led_toggle(); // toggle LED
printf("LED toggled\n"); // notify user
} else {
goto error;
}
} else if (0==strcmp(word,"time")) {
word = strtok(NULL,delimiter);
if (!word) {
printf("current time: %02u:%02u:%02u\n", rtc_ds1307_read_hours(), rtc_ds1307_read_minutes(), rtc_ds1307_read_seconds()); // get and print time from external RTC
} else if (strlen(word)!=8 || word[0]<'0' || word[0]>'2' || word[1]<'0' || word[1]>'9' || word[3]<'0' || word[3]>'5' || word[4]<'0' || word[4]>'9' || word[6]<'0' || word[6]>'5' || word[7]<'0' || word[7]>'9') { // time format is incorrect
goto error;
} else {
if (!rtc_ds1307_write_hours((word[0]-'0')*10+(word[1]-'0')*1)) {
printf("setting hours failed\n");
} else if (!rtc_ds1307_write_minutes((word[3]-'0')*10+(word[4]-'0')*1)) {
printf("setting minutes failed\n");
} else if (!rtc_ds1307_write_seconds((word[6]-'0')*10+(word[7]-'0')*1)) {
printf("setting seconds failed\n");
} else {
rtc_ds1307_oscillator_enable(); // be sure the oscillation is enabled
printf("time set\n");
}
}
} else if (0==strcmp(word,"date")) {
word = strtok(NULL,delimiter);
if (!word) {
printf("current date: 20%02u-%02u-%02u\n", rtc_ds1307_read_year(), rtc_ds1307_read_month(), rtc_ds1307_read_date());
} else if (strlen(word)!=10 || word[0]!='2' || word[1]!='0' || word[2]<'0' || word[2]>'9' || word[3]<'0' || word[3]>'9' || word[5]<'0' || word[5]>'1' || word[6]<'0' || word[6]>'9' || word[8]<'0' || word[8]>'3' || word[9]<'0' || word[9]>'9') {
goto error;
} else {
if (!rtc_ds1307_write_year((word[2]-'0')*10+(word[3]-'0')*1)) {
printf("setting year failed\n");
} else if (!rtc_ds1307_write_month((word[5]-'0')*10+(word[6]-'0')*1)) {
printf("setting month failed\n");
} else if (!rtc_ds1307_write_date((word[8]-'0')*10+(word[9]-'0')*1)) {
printf("setting day failed\n");
} else {
printf("date set\n");
}
}
} else {
goto error;
}
return; // command successfully processed
error:
printf("command not recognized. enter help to list commands\n");
return;
}
/** select output for TM1637 display using the multiplexer
* @param[in] output clock output
*/
static void mux_select(uint8_t output)
{
if (output>7) { // multiplexer is only controlling 8 outputs
return;
}
gpio_clear(GPIO(MUX_EN_PORT), GPIO(MUX_EN_PIN)); // enable multiplexer
switch (output) {
case 0: // output on channel C0
gpio_clear(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_clear(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_clear(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
case 1: // output on channel C1
gpio_set(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_clear(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_clear(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
case 2: // output on channel C2
gpio_clear(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_set(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_clear(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
case 3: // output on channel C3
gpio_set(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_set(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_clear(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
case 4: // output on channel C4
gpio_clear(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_clear(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_set(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
case 5: // output on channel C5
gpio_set(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_clear(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_set(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
case 6: // output on channel C6
gpio_clear(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_set(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_set(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
case 7: // output on channel C7
gpio_set(GPIO(MUX_S0_PORT), GPIO(MUX_S0_PIN));
gpio_set(GPIO(MUX_S1_PORT), GPIO(MUX_S1_PIN));
gpio_set(GPIO(MUX_S2_PORT), GPIO(MUX_S2_PIN));
break;
default:
break;
}
}
/** unit Morse code duration in frames */
#define MORSE_DOT 10
uint8_t morse[1*4*5*2] = {0};
uint8_t morse_i = 0;
static void encode_morse(void)
{
bool not_zero = false;
for (uint8_t digit=0; digit<4; digit++) {
uint16_t number = 42;
// get only the digit from the number
for (uint8_t divide=digit; divide<3; divide++) {
number /= 10;
}
number %= 10;
// remember when we found the first non-zero digit
if (number!=0 || digit==3) {
not_zero = true;
}
// encode the number in Morse code (1=short, 3=long)
switch (number) {
case 1:
morse[digit*2*5+0] = 1*MORSE_DOT;
morse[digit*2*5+2] = 3*MORSE_DOT;
morse[digit*2*5+4] = 3*MORSE_DOT;
morse[digit*2*5+6] = 3*MORSE_DOT;
morse[digit*2*5+8] = 3*MORSE_DOT;
break;
case 2:
morse[digit*2*5+0] = 1*MORSE_DOT;
morse[digit*2*5+2] = 1*MORSE_DOT;
morse[digit*2*5+4] = 3*MORSE_DOT;
morse[digit*2*5+6] = 3*MORSE_DOT;
morse[digit*2*5+8] = 3*MORSE_DOT;
break;
case 3:
morse[digit*2*5+0] = 1*MORSE_DOT;
morse[digit*2*5+2] = 1*MORSE_DOT;
morse[digit*2*5+4] = 1*MORSE_DOT;
morse[digit*2*5+6] = 3*MORSE_DOT;
morse[digit*2*5+8] = 3*MORSE_DOT;
break;
case 4:
morse[digit*2*5+0] = 1*MORSE_DOT;
morse[digit*2*5+2] = 1*MORSE_DOT;
morse[digit*2*5+4] = 1*MORSE_DOT;
morse[digit*2*5+6] = 1*MORSE_DOT;
morse[digit*2*5+8] = 3*MORSE_DOT;
break;
case 5:
morse[digit*2*5+0] = 1*MORSE_DOT;
morse[digit*2*5+2] = 1*MORSE_DOT;
morse[digit*2*5+4] = 1*MORSE_DOT;
morse[digit*2*5+6] = 1*MORSE_DOT;
morse[digit*2*5+8] = 1*MORSE_DOT;
break;
case 6:
morse[digit*2*5+0] = 3*MORSE_DOT;
morse[digit*2*5+2] = 1*MORSE_DOT;
morse[digit*2*5+4] = 1*MORSE_DOT;
morse[digit*2*5+6] = 1*MORSE_DOT;
morse[digit*2*5+8] = 1*MORSE_DOT;
break;
case 7:
morse[digit*2*5+0] = 3*MORSE_DOT;
morse[digit*2*5+2] = 3*MORSE_DOT;
morse[digit*2*5+4] = 1*MORSE_DOT;
morse[digit*2*5+6] = 1*MORSE_DOT;
morse[digit*2*5+8] = 1*MORSE_DOT;
break;
case 8:
morse[digit*2*5+0] = 3*MORSE_DOT;
morse[digit*2*5+2] = 3*MORSE_DOT;
morse[digit*2*5+4] = 3*MORSE_DOT;
morse[digit*2*5+6] = 1*MORSE_DOT;
morse[digit*2*5+8] = 1*MORSE_DOT;
break;
case 9:
morse[digit*2*5+0] = 3*MORSE_DOT;
morse[digit*2*5+2] = 3*MORSE_DOT;
morse[digit*2*5+4] = 3*MORSE_DOT;
morse[digit*2*5+6] = 3*MORSE_DOT;
morse[digit*2*5+8] = 1*MORSE_DOT;
break;
case 0:
if (not_zero) {
morse[digit*2*5+0] = 3*MORSE_DOT;
morse[digit*2*5+2] = 3*MORSE_DOT;
morse[digit*2*5+4] = 3*MORSE_DOT;
morse[digit*2*5+6] = 3*MORSE_DOT;
morse[digit*2*5+8] = 3*MORSE_DOT;
} else { //don't encode the first digits if they are zero
morse[digit*2*5+0] = 0;
morse[digit*2*5+2] = 0;
morse[digit*2*5+4] = 0;
morse[digit*2*5+6] = 0;
morse[digit*2*5+8] = 0;
}
break;
}
// fill the spaces between the dots
for (uint8_t space=0; space<5; space++) {
if (0==morse[digit*2*5+space*2]) {
morse[digit*2*5+space*2+1] = 0;
} else {
morse[digit*2*5+space*2+1] = 1*MORSE_DOT;
}
}
}
morse_i = 0; // reset Morse index
}
/** 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
// keep power on a soon as possible
rcc_periph_clock_enable(RCC_GPIO(POWER_SWITCH_PORT)); // enable clock for GPIO
gpio_set_mode(GPIO(POWER_SWITCH_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(POWER_SWITCH_PIN)); // set as output to control power
gpio_set(GPIO(POWER_SWITCH_PORT), GPIO(POWER_SWITCH_PIN)); // enable power by saturating nMOS controlling power
rcc_periph_clock_enable(RCC_GPIO(POWER_BUTTON_PORT)); // enable clock for GPIO domain
gpio_set_mode(GPIO(POWER_BUTTON_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(POWER_BUTTON_PIN)); // set pin as input to detect power switching activity
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
exti_select_source(EXTI(POWER_BUTTON_PIN), GPIO(POWER_BUTTON_PORT)); // mask external interrupt of this pin only for this port
exti_set_trigger(EXTI(POWER_BUTTON_PIN), EXTI_TRIGGER_BOTH); // trigger on any activity of the power switch
exti_enable_request(EXTI(POWER_BUTTON_PIN)); // enable external interrupt
nvic_enable_irq(NVIC_EXTI_IRQ(POWER_BUTTON_PIN)); // enable interrupt
#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
usart_setup(); // setup USART for user communication
cdcacm_setup(); // setup USB ACM (serial) for user communication
gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON,0); // disable JTAG (but leave SWD on) since we need most of the GPIOs
printf("\nwelcome to the CuVoodoo clapperboard\n"); // print welcome message
#if !(DEBUG)
// show watchdog information
printf("watchdog set to (%2u.%2us)\n",WATCHDOG_PERIOD/1000, (WATCHDOG_PERIOD/10)%100);
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 external RTC
printf("setup external RTC: ");
rtc_ds1307_setup(); // setup external RTC module
// enable square wave output and configure input interrupt
rtc_ds1307_write_square_wave(1); // enable 1 Hz square wave output to sync on seconds
rcc_periph_clock_enable(RCC_GPIO(SQUARE_WAVE_PORT)); // enable clock for GPIO
gpio_set_mode(GPIO(SQUARE_WAVE_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(SQUARE_WAVE_PIN)); // set button pin to input
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
exti_select_source(EXTI(SQUARE_WAVE_PIN), GPIO(SQUARE_WAVE_PORT)); // mask external interrupt of this pin only for this port
exti_set_trigger(EXTI(SQUARE_WAVE_PIN), EXTI_TRIGGER_FALLING); // trigger on falling edge of square wave (this is also when the RTC register are updated)
exti_enable_request(EXTI(SQUARE_WAVE_PIN)); // enable external interrupt
nvic_enable_irq(NVIC_EXTI_IRQ(SQUARE_WAVE_PIN)); // enable interrupt
printf("OK\n");
// verify is external RTC is running
if (rtc_ds1307_oscillator_disabled()) {
printf("/!\\ RTC oscillator is disabled: the battery may be empty\n");
rtc_ds1307_oscillator_enable(); // enable oscillator again
}
// display date
uint8_t* rtc_ds1307_time = rtc_ds1307_read_time(); // get time/date from external RTC
if (rtc_ds1307_time==NULL) {
printf("could not get time from DS1307\n");
} else {
rtc_seconds = rtc_ds1307_time[0]; // remember seconds of minute
printf("current date: 20%02u-%02u-%02u %02u:%02u:%02u\n", rtc_ds1307_time[6], rtc_ds1307_time[5], rtc_ds1307_time[4], rtc_ds1307_time[2], rtc_ds1307_time[1], rtc_ds1307_time[0]);
}
// setup analog multiplexer for TM1637 clock
printf("setup multiplexer: ");
rcc_periph_clock_enable(RCC_GPIO(MUX_EN_PORT)); // enable clock for GPIO port domain
gpio_set_mode(GPIO(MUX_EN_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MUX_EN_PIN)); // to enable/disable the multiplexer
gpio_set(GPIO(MUX_EN_PORT), GPIO(MUX_EN_PIN)); // disable multiplexer
rcc_periph_clock_enable(RCC_GPIO(MUX_S0_PORT)); // enable clock for GPIO port domain
gpio_set_mode(GPIO(MUX_S0_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MUX_S0_PIN)); // to select output channel
rcc_periph_clock_enable(RCC_GPIO(MUX_S1_PORT)); // enable clock for GPIO port domain
gpio_set_mode(GPIO(MUX_S1_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MUX_S1_PIN)); // to select output channel
rcc_periph_clock_enable(RCC_GPIO(MUX_S2_PORT)); // enable clock for GPIO port domain
gpio_set_mode(GPIO(MUX_S2_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MUX_S2_PIN)); // to select output channel
printf("OK\n");
// setup TM1637 4-digit 7-segment displays to display the numbers
printf("setup 7-segment displays: ");
led_tm1637_setup(); // setup TM1637
for (uint8_t tm1637=0; tm16370) { // there is a command to process
command[command_i] = 0; // end string
command_i = 0; // prepare for next command
process_command(command); // process user command
}
} else { // user command input
command[command_i] = c; // save command input
if (command_i=LENGTH(morse)) { // all codes done
announce = false; // announce finished
}
}
// read button inputs (drive and read each row since they are multiplexed)
uint16_t buttons_new = 0;
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(BUTTONS_DRIVE_PIN0));
gpio_set(GPIO(BUTTONS_DRIVE_PORT), GPIO(BUTTONS_DRIVE_PIN0));
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(BUTTONS_DRIVE_PIN1)|GPIO(BUTTONS_DRIVE_PIN2)|GPIO(BUTTONS_DRIVE_PIN3));
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN0)) ? 1 : 0)<<0);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN1)) ? 1 : 0)<<1);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN2)) ? 1 : 0)<<2);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN3)) ? 1 : 0)<<3);
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(BUTTONS_DRIVE_PIN1));
gpio_set(GPIO(BUTTONS_DRIVE_PORT), GPIO(BUTTONS_DRIVE_PIN1));
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(BUTTONS_DRIVE_PIN0)|GPIO(BUTTONS_DRIVE_PIN2)|GPIO(BUTTONS_DRIVE_PIN3));
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN0)) ? 1 : 0)<<4);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN1)) ? 1 : 0)<<5);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN2)) ? 1 : 0)<<6);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN3)) ? 1 : 0)<<7);
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(BUTTONS_DRIVE_PIN2));
gpio_set(GPIO(BUTTONS_DRIVE_PORT), GPIO(BUTTONS_DRIVE_PIN2));
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(BUTTONS_DRIVE_PIN0)|GPIO(BUTTONS_DRIVE_PIN1)|GPIO(BUTTONS_DRIVE_PIN3));
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN0)) ? 1 : 0)<<8);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN1)) ? 1 : 0)<<9);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN2)) ? 1 : 0)<<10);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN3)) ? 1 : 0)<<11);
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(BUTTONS_DRIVE_PIN3));
gpio_set(GPIO(BUTTONS_DRIVE_PORT), GPIO(BUTTONS_DRIVE_PIN3));
gpio_set_mode(GPIO(BUTTONS_DRIVE_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(BUTTONS_DRIVE_PIN0)|GPIO(BUTTONS_DRIVE_PIN1)|GPIO(BUTTONS_DRIVE_PIN2));
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN0)) ? 1 : 0)<<12);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN1)) ? 1 : 0)<<13);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN2)) ? 1 : 0)<<14);
buttons_new |= ((gpio_get(GPIO(BUTTONS_READ_PORT), GPIO(BUTTONS_READ_PIN3)) ? 1 : 0)<<15);
uint16_t buttons_diff = buttons^buttons_new; // get difference
buttons = buttons_new; // save new state
if (buttons_diff) { // only do something if there is a change
printf("button pressed: %016b\n", buttons);
for (uint8_t number=0; number>8, numbers[number].number};
if (!rtc_ds1307_write_ram(ram, number*sizeof(ram), sizeof(ram))) {
printf("could not set number on DS1307\n");
}
}
if ((buttons_diff&(1<<9) && buttons&(1<<9)) || (buttons_diff&(1<<8) && buttons&(1<<8))) { // buttons for time pressed
rtc_ds1307_time = rtc_ds1307_read_time(); // get time/date from external RTC
if (rtc_ds1307_time==NULL) {
printf("could not get time from DS1307: resetting\n");
rtc_ds1307_setup(); // resetting peripheral
} else {
// adjust time
if (buttons_diff&(1<<9) && buttons&(1<<9)) { // up pressed
if (!(23==rtc_ds1307_time[2] && 59==rtc_ds1307_time[1] && 59==rtc_ds1307_time[0])) { // don't handle date changes since it's too complex
rtc_ds1307_time[0] += 1;
while (rtc_ds1307_time[0]>=60) {
rtc_ds1307_time[0] -= 60;
rtc_ds1307_time[1] += 1;
}
while (rtc_ds1307_time[1]>=60) {
rtc_ds1307_time[1] -= 60;
rtc_ds1307_time[2] += 1;
}
while (rtc_ds1307_time[2]>=24) {
rtc_ds1307_time[2] -= 24;
}
}
} else if (buttons_diff&(1<<8) && buttons&(1<<8)) { // down pressed
if (!(0==rtc_ds1307_time[2] && 0==rtc_ds1307_time[1] && 0==rtc_ds1307_time[0])) { // don't handle date changes since it's too complex
if (rtc_ds1307_time[0]>0) {
rtc_ds1307_time[0] -= 1;
} else {
rtc_ds1307_time[0] = 59;
if (rtc_ds1307_time[1]>0) {
rtc_ds1307_time[1] -= 1;
} else {
rtc_ds1307_time[1] = 59;
rtc_ds1307_time[2] -= 1; // it can't be 0
}
}
}
}
rtc_seconds = rtc_ds1307_time[0]; // save seconds since this is only updated every minute
if (!rtc_ds1307_write_time(rtc_ds1307_time[0], rtc_ds1307_time[1], rtc_ds1307_time[2], rtc_ds1307_time[3], rtc_ds1307_time[4], rtc_ds1307_time[5], rtc_ds1307_time[6])) {
printf("could not set time on DS1307: resetting\n");
rtc_ds1307_setup(); // resetting peripheral
}
}
}
}
// display time and frame number
char time[] = "00000000"; // time to display
time[0] += (rtc_ds1307_time[2]/10)%10; // display hours
time[1] += (rtc_ds1307_time[2])%10; // display hours
time[1] |= 0x80; // dot for minutes on display
time[2] += (rtc_ds1307_time[1]/10)%10; // display minutes
time[3] += (rtc_ds1307_time[1])%10; // display minutes
if (0==(rtc_seconds%2)) { // even seconds
time[3] |= 0x80; // add dot for seconds
}
time[4] += (rtc_seconds/10)%10; // display seconds
time[5] += (rtc_seconds)%10; // display seconds
if (0==(frame_count%2)) { // even frames
time[5] |= 0x80; // add dot for frame
}
time[6] += (frame_count/10)%10; // display frame
time[7] += (frame_count)%10; // display frame
led_max7219_text(time, 0); // display frame time on 1st display
}
while (rtc_tick_flag) { // the external RTC ticked
rtc_tick_flag = false; // reset flag
action = true; // action has been performed
led_toggle(); // toggle LED (good to indicate if main function is stuck)
if (standby_timer>=STANDBY_TIMEOUT && false) { // standby timeout complete
// go into standby mode
printf("shutting down\n");
led_max7219_off(0xff); // switch off MAX7219 displays
for (uint8_t tm1637=0; tm1637<7; tm1637++) { // switch off TM1637 displays
mux_select(tm1637); // selecting TM1637 display
led_tm1637_off(); // switch off TM1637 display
}
gpio_clear(GPIO(POWER_SWITCH_PORT), GPIO(POWER_SWITCH_PIN)); // switch power of by disconnecting from battery
SCB_SCR |= SCB_SCR_SLEEPDEEP; // enable deep sleep
pwr_set_standby_mode(); // go to deep sleep
while (true); // we should be shut down at this point
}
if (rtc_seconds>=60) { // one minute passed
rtc_ds1307_time = rtc_ds1307_read_time(); // get time/date from external RTC
if (rtc_ds1307_time==NULL) {
printf("could not get time from DS1307: resetting\n");
rtc_ds1307_setup(); // resetting periph
} else {
rtc_seconds = rtc_ds1307_time[0]; // get actual number of seconds
if (0==rtc_ds1307_time[1] && 0==rtc_ds1307_time[2]) { // new day arrived
led_max7219_number(20000000+rtc_ds1307_time[6]*10000+rtc_ds1307_time[5]*100+rtc_ds1307_time[4], 0x14, 1); // display date on 2nd display
}
}
}
}
while (keep_alive_flag) { // power switch is detecting movement to keep clapperboard running
keep_alive_flag = false; // clear flag
standby_timer = 0; // restart standby timer
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity
action = false;
} else {
__WFI(); // go to sleep and wait for interrupt
}
} // main loop
}
/** RTC square wave input ISR to synchronize to seconds and count them */
void EXTI_ISR(SQUARE_WAVE_PIN)(void)
{
exti_reset_request(EXTI(SQUARE_WAVE_PIN)); // reset interrupt
frame_count = 0; // re-sync frame counter to second
rtc_seconds++; // increment number of seconds passed
if (standby_timer