diff --git a/application.c b/application.c index 96158e3..f0379c2 100644 --- a/application.c +++ b/application.c @@ -1,4 +1,4 @@ -/** STM32F4 application example +/** firmware to control the cool clock * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later @@ -24,6 +24,8 @@ #include // debug utilities #include // design utilities #include // flash utilities +#include // timer library +#include // DMA library #include // USB definitions /* own libraries */ @@ -50,6 +52,137 @@ static volatile bool second_flag = false; /**< flag set when a second passed */ /** number of seconds since boot */ static uint32_t boot_time = 0; +// DRV8825 stepper motor driver connections +#define DRV8825_ENABLE_PIN PB13 /**< pin to enable output (active low) */ +#define DRV8825_RESET_PIN PB14 /**< pin to reset and put to sleep driver (active low) */ +#define DRV8825_DIRECTION_PIN PB15 /**< pin to set direction (low = clockwise) */ +#define DRV8825_STEP_PIN PA15 /**< pin to move one step forward */ +#define DRV8825_STEP_TIMER 2 /**< timer connected to pin */ +#define DRV8825_STEP_CHANNEL 1 /**< timer channel connected to pin */ +#define DRV8825_STEP_OC TIM_OC1 /**< timer output compare connected to pin */ +#define DRV8825_STEP_AF GPIO_AF1 /**< alternate function for timer channel */ +#define DRV8825_FAULT_PIN PB12 /**< pin pulled low on error (such as over-current) */ +static volatile uint32_t drv8825_steps = 0; /**< incremented with each step */ +static int8_t drv8825_direction = 0; /**< direction of the steps (1 = clockwise, -1 = counter-clockwise) */ +/** maximum speed (in steps/s) before the motor stalls (found empirically) + * @note found empirically 300 @ 9V/180mA, 420 @ 12V/150mA + */ +#define DRV8825_SPEED_LIMIT 420U + +// dials position info +#define DIAL_SWITCH_PIN PB3 /**< pin connected to reed switch, pulled low when the hour dial is nearby */ +#define DIAL_CYCLE_STEPS 11904U /**< number of steps for the hour dial to make a round */ +#define DIAL_MIDNIGHT_STEPS 6557U /**< number of steps after dial detection for dials to show midnight */ +static volatile uint32_t dial_steps = 0; /**< set to drv8825_steps when dial is nearby */ + +// RGB matrix pins +#define RGBMATRIX_OE_PIN PB10 /**< pin to enable output (active low) */ +// A-B: PB0-PB3 +// CLK: PA0 +// LAT: PA1 +// RGB1: PA2-PA4 +// RGB2: PA5-PA7 +#define RGBMATRIX_DMA DMA2 /**< DMA used to send data to the RGB matrix (only DMA2 can be used for memory-to-memory transfer) */ +#define RGBMATRIX_RCC_DMA RCC_DMA2 /**< RCC for DMA used for the RGB matrix */ +#define RGBMATRIX_STREAM DMA_STREAM1 /**< stream used to send data to the RGB matrix (any stream can be used for memory-to-memory transfer) */ +#define RGBMATRIX_CHANNEL DMA_SxCR_CHSEL_0 /**< channel used to send data to the RGB matrix (any channel can be used for memory-to-memory transfer) */ +#define RGBMATRIX_IRQ NVIC_DMA2_STREAM1_IRQ /**< IRQ for when a line transfer is complete */ +#define RGBMATRIX_ISR dma2_stream1_isr /**< ISR for when a line transfer is complete */ +#define RGBMATRIX_HEIGHT 2 /**< number of rows in the RGB matrix */ +#define RGBMATRIX_WIDTH 64 /**< number of columns in the RGB matrix */ +static uint8_t rgbmatrix_data[RGBMATRIX_HEIGHT / 2][RGBMATRIX_WIDTH * 2]; /**< data to be sent to RGB matrix (encodes the PA values) */ +static volatile uint8_t rgbmatrix_line = 0; /**< line currently being displayed */ + +/** set motor speed and direction + * @param[in] speed speed (in Hz) and direction (sign) + */ +static void drv8825_speed(int16_t speed) +{ + if (0 == speed) { + timer_disable_counter(TIM(DRV8825_STEP_TIMER)); // stop PWM output + gpio_set(GPIO_PORT(DRV8825_ENABLE_PIN), GPIO_PIN(DRV8825_ENABLE_PIN)); // disable motor + drv8825_direction = 0; // remember we stopped + } else { + if (speed > 0) { + gpio_clear(GPIO_PORT(DRV8825_DIRECTION_PIN), GPIO_PIN(DRV8825_DIRECTION_PIN)); // set clockwise + drv8825_direction = 1; // remember we go clockwise + } else { + gpio_set(GPIO_PORT(DRV8825_DIRECTION_PIN), GPIO_PIN(DRV8825_DIRECTION_PIN)); // set counter-clockwise + drv8825_direction = -1; // remember we go counter-clockwise + speed = -speed; // get positive speed + } + if (speed > (int16_t)DRV8825_SPEED_LIMIT) { // enforce upper limit + speed = DRV8825_SPEED_LIMIT; + } + timer_set_prescaler(TIM(DRV8825_STEP_TIMER), rcc_ahb_frequency / (UINT16_MAX * speed) - 1); // set the clock frequency + gpio_clear(GPIO_PORT(DRV8825_ENABLE_PIN), GPIO_PIN(DRV8825_ENABLE_PIN)); // enable motor + timer_enable_counter(TIM(DRV8825_STEP_TIMER)); // start PWM output + } +} + +/** transfer all data to RGB matrix */ +static void rgbmatrix_update(void) +{ + while (DMA_SCR(RGBMATRIX_DMA, RGBMATRIX_STREAM) & DMA_SxCR_EN); // wait until current transfer is finished + rgbmatrix_line = 0; // restart from line 0 + dma_enable_stream(RGBMATRIX_DMA, RGBMATRIX_STREAM); // start sending line (the ISR will send the others) +} + +/** switch off all LEDs on the RGB matrix */ +static void rgbmatrix_clear(void) +{ + for (uint8_t i = 0; i < LENGTH(rgbmatrix_data); i++) { // for each line + for (uint8_t j = 0; j < LENGTH(rgbmatrix_data[0]); j += 2) { // for each clock cycle + rgbmatrix_data[i][j + 0] = 1; // clock rising edge + rgbmatrix_data[i][j + 1] = 0; // clock falling edge + } + //rgbmatrix_data[i][LENGTH(rgbmatrix_data[0]) - 2] |= (1 << 1); // latch data (if will also latch the current data, but we don't do it here because write RGB value would remove this latch information) + rgbmatrix_data[i][LENGTH(rgbmatrix_data[0]) - 1] |= (1 << 1); // latch data (next line will remove the latch) + } + rgbmatrix_update(); // send data to matrix +} + +/** set color of the LED on the RGB matrix + * @param[in] x horizontal position (0 = left) + * @param[in] y vertical position (0 = top) + * @param[in] r if the red LED should be on + * @param[in] g if the green LED should be on + * @param[in] b if the blue LED should be on + * @note this does not send the data to the matrix + */ +static void rgbmatrix_set(uint8_t x, uint8_t y, bool r, bool g, bool b) +{ + if (x >= RGBMATRIX_WIDTH) { + return; + } + if (y >= RGBMATRIX_HEIGHT) { + return; + } + uint8_t data = 0x1; // set clock high + if (y < (RGBMATRIX_HEIGHT / 2)) { + if (r) { + data |= (1 << 2); + } + if (g) { + data |= (1 << 3); + } + if (b) { + data |= (1 << 4); + } + } else { + if (r) { + data |= (1 << 5); + } + if (g) { + data |= (1 << 6); + } + if (b) { + data |= (1 << 7); + } + } + rgbmatrix_data[y % LENGTH(rgbmatrix_data)][x * 2] = data; // set the LED data +} + size_t putc(char c) { size_t length = 0; // number of characters printed @@ -268,6 +401,77 @@ static void command_bootloader(void* argument) dfu_bootloader(); // start DFU bootloader } +/** set motor speed and direction + * @param[in] argument speed (in Hz) and direction (sign) + */ +static void command_speed(void* argument) +{ + if (NULL == argument) { + puts("speed argument required"); + return; + } + + int32_t speed = *(int32_t*)argument; + if (0 == speed) { + drv8825_speed(0); // stop motor + puts("motor stopped\n"); + } else { + drv8825_speed(speed); // set speed + printf("motor speed set to %d Hz\n", speed); + } +} + +/** advance motor by n steps + * @param[in] argument number of steps + */ +static void command_advance(void* argument) +{ + if (NULL == argument) { + puts("number of steps required"); + return; + } + + int32_t steps = *(int32_t*)argument; + printf("advancing %d steps\n", steps); + drv8825_speed(0); // stop motor to get precise count + uint32_t start = drv8825_steps; // get current position + // WARNING does not work + if (steps > 0) { + drv8825_speed(100); // advance slowly + if (start + steps < DIAL_CYCLE_STEPS) { + while (drv8825_steps < start + steps); // wait to reach point + } else { + while (drv8825_steps > start); // wait to make round + while (drv8825_steps < (start + steps) % DIAL_CYCLE_STEPS); // wait to reach point + } + } else { + drv8825_speed(-100); // reverse slowly + if ((int32_t)start > -steps) { + while (drv8825_steps > start + steps); // wait to reach point + } else { + while (drv8825_steps < start); // wait to make round + while (drv8825_steps > (start + steps) % DIAL_CYCLE_STEPS); // wait to reach point + } + } + drv8825_speed(0); // stop motor +} + +/** test RGB matrix + * @param[in] argument no argument required + */ +static void command_matrix(void* argument) +{ + (void)argument; // we won't use the argument + + puts("test pattern sent to LED matrix\n"); + rgbmatrix_set(0, 0, true, false, false); + rgbmatrix_set(1, 0, false, true, false); + rgbmatrix_set(2, 0, false, false, true); + rgbmatrix_set(0, 1, true, false, false); + rgbmatrix_set(1, 2, false, true, false); + rgbmatrix_set(2, 3, false, false, true); +} + /** list of all supported commands */ static const struct menu_command_t menu_commands[] = { { @@ -303,7 +507,7 @@ static const struct menu_command_t menu_commands[] = { .command_handler = &command_datetime, }, { - .shortcut = 'r', + .shortcut = 'R', .name = "reset", .command_description = "reset board", .argument = MENU_ARGUMENT_NONE, @@ -311,7 +515,7 @@ static const struct menu_command_t menu_commands[] = { .command_handler = &command_reset, }, { - .shortcut = 's', + .shortcut = 'S', .name = "system", .command_description = "reboot into system memory", .argument = MENU_ARGUMENT_NONE, @@ -319,13 +523,37 @@ static const struct menu_command_t menu_commands[] = { .command_handler = &command_system, }, { - .shortcut = 'b', + .shortcut = 'B', .name = "bootloader", .command_description = "reboot into DFU bootloader", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &command_bootloader, }, + { + .shortcut = 's', + .name = "speed", + .command_description = "set motor step frequency and direction", + .argument = MENU_ARGUMENT_SIGNED, + .argument_description = "Hz", + .command_handler = &command_speed, + }, + { + .shortcut = 'a', + .name = "advance", + .command_description = "advance dial (either direction)", + .argument = MENU_ARGUMENT_SIGNED, + .argument_description = "steps", + .command_handler = &command_advance, + }, + { + .shortcut = 'm', + .name = "matrix", + .command_description = "test RGB matrix", + .argument = MENU_ARGUMENT_NONE, + .argument_description = NULL, + .command_handler = &command_matrix, + }, }; static void command_help(void* argument) @@ -382,7 +610,7 @@ void main(void) usb_cdcacm_setup(); // setup USB CDC ACM (for printing) OTG_FS_GCCFG |= OTG_GCCFG_NOVBUSSENS | OTG_GCCFG_PWRDWN; // disable VBUS sensing OTG_FS_GCCFG &= ~(OTG_GCCFG_VBUSBSEN | OTG_GCCFG_VBUSASEN); // force USB device mode - puts("\nwelcome to the CuVoodoo STM32F4 example firmware\n"); // print welcome message + puts("\nwelcome to the World Clock controller\n"); // print welcome message #if DEBUG // show reset cause @@ -426,7 +654,7 @@ void main(void) rcc_osc_on(RCC_LSI); // enable LSI clock while (!rcc_is_osc_ready(RCC_LSI)); // wait until clock is ready rtc_set_prescaler(250, 128); // set clock prescaler to 32000 - RCC_BDCR = (RCC_BDCR & ~(RCC_BDCR_RTCSEL_MASK << RCC_BDCR_RTCSEL_SHIFT)) | (RCC_BDCR_RTCSEL_LSI << RCC_BDCR_RTCSEL_SHIFT); // select LSI as RTC clock source + RCC_BDCR = (RCC_BDCR & ~(RCC_BDCR_RTCSEL_MASK << RCC_BDCR_RTCSEL_SHIFT)) | (RCC_BDCR_RTCSEL_LSI << RCC_BDCR_RTCSEL_SHIFT); // select LSI as RTC clock source #endif RCC_BDCR |= RCC_BDCR_RTCEN; // enable RTC rtc_lock(); // protect RTC register against writing @@ -451,15 +679,112 @@ void main(void) // important: do not re-enable backup_domain_write_protect, since this will prevent clearing flags (but RTC registers do not need to be unlocked) puts_debug("OK\n"); + puts_debug("setup stepper motor: "); + // motor enable pin + rcc_periph_clock_enable(GPIO_RCC(DRV8825_ENABLE_PIN)); // enable clock for GPIO port peripheral + gpio_set(GPIO_PORT(DRV8825_ENABLE_PIN), GPIO_PIN(DRV8825_ENABLE_PIN)); // disable motor + gpio_mode_setup(GPIO_PORT(DRV8825_ENABLE_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(DRV8825_ENABLE_PIN)); // set pin as output + gpio_set_output_options(GPIO_PORT(DRV8825_ENABLE_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(DRV8825_ENABLE_PIN)); // set pin output as push-pull + // motor reset pin + rcc_periph_clock_enable(GPIO_RCC(DRV8825_RESET_PIN)); // enable clock for GPIO port peripheral + gpio_clear(GPIO_PORT(DRV8825_RESET_PIN), GPIO_PIN(DRV8825_RESET_PIN)); // put motor into reset mode + gpio_mode_setup(GPIO_PORT(DRV8825_RESET_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(DRV8825_RESET_PIN)); // set pin as output + gpio_set_output_options(GPIO_PORT(DRV8825_RESET_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(DRV8825_RESET_PIN)); // set pin output as push-pull + // motor direction pin + rcc_periph_clock_enable(GPIO_RCC(DRV8825_DIRECTION_PIN)); // enable clock for GPIO port peripheral + gpio_clear(GPIO_PORT(DRV8825_DIRECTION_PIN), GPIO_PIN(DRV8825_DIRECTION_PIN)); // set clockwise (not really important) + gpio_mode_setup(GPIO_PORT(DRV8825_DIRECTION_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(DRV8825_DIRECTION_PIN)); // set pin as output + gpio_set_output_options(GPIO_PORT(DRV8825_DIRECTION_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(DRV8825_DIRECTION_PIN)); // set pin output as push-pull + // motor step pin + rcc_periph_clock_enable(GPIO_RCC(DRV8825_STEP_PIN)); // enable clock for GPIO port peripheral + gpio_mode_setup(GPIO_PORT(DRV8825_STEP_PIN), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(DRV8825_STEP_PIN)); // set pin to alternate function (e.g. timer) + gpio_set_output_options(GPIO_PORT(DRV8825_STEP_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN(DRV8825_STEP_PIN)); // set pin to output with fast rising edge + gpio_set_af(GPIO_PORT(DRV8825_STEP_PIN), DRV8825_STEP_AF, GPIO_PIN(DRV8825_STEP_PIN)); // set alternate timer function + rcc_periph_clock_enable(RCC_TIM(DRV8825_STEP_TIMER)); // enable clock for timer peripheral + rcc_periph_reset_pulse(RST_TIM(DRV8825_STEP_TIMER)); // reset timer state + timer_set_mode(TIM(DRV8825_STEP_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up + + timer_set_prescaler(TIM(DRV8825_STEP_TIMER), rcc_ahb_frequency / (UINT16_MAX * 100) - 1); // set the clock frequency to 1.5 kHz (maximum is 250 kHz) + timer_set_period(TIM(DRV8825_STEP_TIMER), UINT16_MAX); // use the whole range as period, even if we can only control up to 100 Hz + timer_set_oc_value(TIM(DRV8825_STEP_TIMER), DRV8825_STEP_OC, UINT16_MAX / 2); // duty cycle to 50% (minimum pulse duration is 1.9 µs) + timer_set_oc_mode(TIM(DRV8825_STEP_TIMER), DRV8825_STEP_OC, TIM_OCM_PWM1); // set timer to generate PWM + timer_enable_oc_output(TIM(DRV8825_STEP_TIMER), DRV8825_STEP_OC); // enable output to generate the PWM signal + timer_enable_break_main_output(TIM(DRV8825_STEP_TIMER)); // required to enable timer, even when no dead time is used + timer_set_counter(TIM(DRV8825_STEP_TIMER), 0); // reset counter + timer_clear_flag(TIM(DRV8825_STEP_TIMER), TIM_SR_UIF); // clear update (overflow) flag + timer_update_on_overflow(TIM(DRV8825_STEP_TIMER)); // only use counter overflow as UEV source (use overflow to count steps)) + timer_enable_irq(TIM(DRV8825_STEP_TIMER), TIM_DIER_UIE); // enable update interrupt for overflow + nvic_enable_irq(NVIC_TIM_IRQ(DRV8825_STEP_TIMER)); // catch interrupt in service routine + + // motor fault pin + rcc_periph_clock_enable(GPIO_RCC(DRV8825_FAULT_PIN)); // enable clock for GPIO port peripheral + gpio_mode_setup(GPIO_PORT(DRV8825_FAULT_PIN), GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN(DRV8825_FAULT_PIN)); // set GPIO to input and pull up (a 10 kOhm external pull-up resistor is still required, the internal is too weak) + bool drv8825_fault = false; // if driver reported fault + puts_debug("OK\n"); + + puts_debug("setup dial position: "); + // dial position detection pin + rcc_periph_clock_enable(GPIO_RCC(DIAL_SWITCH_PIN)); // enable clock for GPIO port peripheral + gpio_mode_setup(GPIO_PORT(DIAL_SWITCH_PIN), GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN(DIAL_SWITCH_PIN)); // set GPIO to input and pull up + exti_select_source(GPIO_EXTI(DIAL_SWITCH_PIN), GPIO_PORT(DIAL_SWITCH_PIN)); // mask external interrupt of this pin only for this port + exti_set_trigger(GPIO_EXTI(DIAL_SWITCH_PIN), EXTI_TRIGGER_FALLING); // trigger when magnet on dial is nearby + exti_enable_request(GPIO_EXTI(DIAL_SWITCH_PIN)); // enable external interrupt + nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(DIAL_SWITCH_PIN)); // enable interrupt + puts_debug("OK\n"); + + puts_debug("setup RGB matrix: "); + // configure pin for output enable + rcc_periph_clock_enable(GPIO_RCC(RGBMATRIX_OE_PIN)); // enable clock for GPIO port peripheral + gpio_set(GPIO_PORT(RGBMATRIX_OE_PIN), GPIO_PIN(RGBMATRIX_OE_PIN)); // disable output + gpio_set_output_options(GPIO_PORT(RGBMATRIX_OE_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN(RGBMATRIX_OE_PIN)); // set fast edge + gpio_mode_setup(GPIO_PORT(RGBMATRIX_OE_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBMATRIX_OE_PIN)); // set pin as output + // configure pins for data and clock lines + rcc_periph_clock_enable(RCC_GPIOA); // enable clock for GPIO port peripheral + gpio_clear(RCC_GPIOA, GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | GPIO4 | GPIO5 | GPIO6 | GPIO7); // disable LEDs + gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | GPIO4 | GPIO5 | GPIO6 | GPIO7); // set fast edge + gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | GPIO4 | GPIO5 | GPIO6 | GPIO7); // set pin as output + // configure pins for address lines + rcc_periph_clock_enable(RCC_GPIOB); // enable clock for GPIO port peripheral + gpio_clear(RCC_GPIOB, GPIO0 | GPIO1 | GPIO2 | GPIO3); // unselect line + gpio_set_output_options(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO0 | GPIO1 | GPIO2 | GPIO3); // set fast edge + gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0 | GPIO1 | GPIO2 | GPIO3); // set pin as output + // configure DMA to sent line data + // because there is no peripheral request for data, this is a memory to memory transfer + rcc_periph_clock_enable(RGBMATRIX_RCC_DMA); // enable clock for DMA peripheral (any DMA and channel can be used) + dma_disable_stream(RGBMATRIX_DMA, RGBMATRIX_STREAM); // disable stream before re-configuring + while (DMA_SCR(RGBMATRIX_DMA, RGBMATRIX_STREAM) & DMA_SxCR_EN); // wait until transfer is finished before we can reconfigure + dma_stream_reset(RGBMATRIX_DMA, RGBMATRIX_STREAM); // use default values + dma_set_peripheral_address(RGBMATRIX_DMA, RGBMATRIX_STREAM, (uint32_t)&rgbmatrix_data[0]); // set memory to read from (for memory-to-memory transfer, the source is the peripheral) + dma_set_peripheral_size(RGBMATRIX_DMA, RGBMATRIX_STREAM, DMA_SxCR_PSIZE_8BIT); // we only write the 8 first bit + dma_enable_peripheral_increment_mode(RGBMATRIX_DMA, RGBMATRIX_STREAM); // increment address of memory to read + dma_set_memory_address(RGBMATRIX_DMA, RGBMATRIX_STREAM, (uint32_t) &GPIOA_ODR); // set GPIOA as destination (for memory-to-memory transfer, the destination is the memory) + dma_set_memory_size(RGBMATRIX_DMA, RGBMATRIX_STREAM, DMA_SxCR_MSIZE_8BIT); // read 8 bits for transfer + dma_disable_memory_increment_mode(RGBMATRIX_DMA, RGBMATRIX_STREAM); // don't increment GPIO address + dma_set_number_of_data(RGBMATRIX_DMA, RGBMATRIX_STREAM, LENGTH(rgbmatrix_data[0])); // set transfer size (one line) + dma_channel_select(RGBMATRIX_DMA, RGBMATRIX_STREAM, RGBMATRIX_CHANNEL); // set the channel for this stream + dma_set_transfer_mode(RGBMATRIX_DMA, RGBMATRIX_STREAM, DMA_SxCR_DIR_MEM_TO_MEM); // set transfer from memory to memory + dma_set_priority(RGBMATRIX_DMA, RGBMATRIX_STREAM, DMA_SxCR_PL_LOW); // there is no need to rush + dma_enable_transfer_complete_interrupt(RGBMATRIX_DMA, RGBMATRIX_STREAM); // interrupt when line transfer is complete + nvic_enable_irq(RGBMATRIX_IRQ); // enable interrupt + rgbmatrix_clear(); // clear matrix + gpio_clear(GPIO_PORT(RGBMATRIX_OE_PIN), GPIO_PIN(RGBMATRIX_OE_PIN)); // enable output + puts_debug("OK\n"); + // setup terminal terminal_prefix = ""; // set default prefix terminal_process = &process_command; // set central function to process commands terminal_setup(); // start terminal + // start motor to figure out position + gpio_set(GPIO_PORT(DRV8825_RESET_PIN), GPIO_PIN(DRV8825_RESET_PIN)); // power up driver + int32_t speed = 300; + command_speed(&speed); + // start main loop bool action = false; // if an action has been performed don't go to sleep button_flag = false; // reset button flag led_on(); // switch LED to indicate booting completed + uint16_t matrix_led = 0; while (true) { // infinite loop iwdg_reset(); // kick the dog if (user_input_available) { // user input is available @@ -468,19 +793,33 @@ void main(void) 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 (wakeup_flag) { // time to do periodic checks wakeup_flag = false; // clear flag } if (second_flag) { // one second passed second_flag = false; // clear flag led_toggle(); // toggle LED to indicate if main function is stuck + rgbmatrix_set(matrix_led % RGBMATRIX_WIDTH, 1, false, false, false); + matrix_led++; + GPIOB_ODR = (GPIOB_ODR & 0xfff0) + ((matrix_led / RGBMATRIX_WIDTH) % RGBMATRIX_HEIGHT); + rgbmatrix_set(matrix_led % RGBMATRIX_WIDTH, 1, true, false, false); + rgbmatrix_update(); // send data to matrix + } + if (0 == gpio_get(GPIO_PORT(DRV8825_FAULT_PIN), GPIO_PIN(DRV8825_FAULT_PIN))) { // DRV8825 stepper motor error reports error + gpio_set(GPIO_PORT(DRV8825_ENABLE_PIN), GPIO_PIN(DRV8825_ENABLE_PIN)); // disable motor + gpio_clear(GPIO_PORT(DRV8825_RESET_PIN), GPIO_PIN(DRV8825_RESET_PIN)); // put motor to sleep + if (!drv8825_fault) { + puts("DRV8825 fault detected\n"); + drv8825_fault = true; // remember new fault + } + } + if (dial_steps) { // hour dial position detected + if (drv8825_steps >= DIAL_MIDNIGHT_STEPS) { // wait for dial to reach midnight + speed = 0; // stop motor + command_speed(&speed); // stop motor + dial_steps = 0; // restart position counter + puts("midnight reached\n"); + } } if (action) { // go to sleep if nothing had to be done, else recheck for activity action = false; @@ -503,3 +842,41 @@ void rtc_wkup_isr(void) tick = WAKEUP_FREQ; // restart count down } } + +/** ISR triggered after a completed step */ +void TIM_ISR(DRV8825_STEP_TIMER)(void) +{ + if (timer_get_flag(TIM(DRV8825_STEP_TIMER), TIM_SR_UIF)) { // overflow update event happened + timer_clear_flag(TIM(DRV8825_STEP_TIMER), TIM_SR_UIF); // clear flag + drv8825_steps += drv8825_direction; // increment number of steps + if (UINT32_MAX == drv8825_steps) { // underflow + drv8825_steps = DIAL_CYCLE_STEPS; // use known circumference + } + } +} + +/** ISR triggered when hour dial is near reed switch + * @note surprisingly there is very little bouncing + */ +void GPIO_EXTI_ISR(DIAL_SWITCH_PIN)(void) +{ + exti_reset_request(GPIO_EXTI(DIAL_SWITCH_PIN)); // reset interrupt + if (drv8825_steps > dial_steps + 1000) { // ignore going away debounce + dial_steps = drv8825_steps; // remember on which step we are + drv8825_steps = 0; // restart step counter + } +} + +/** ISR triggered when the data for the line of the RGB matrix has been transferred + */ +void RGBMATRIX_ISR(void) +{ + if (dma_get_interrupt_flag(RGBMATRIX_DMA, RGBMATRIX_STREAM, DMA_TCIF)) { + dma_clear_interrupt_flags(RGBMATRIX_DMA, RGBMATRIX_STREAM, DMA_TCIF); + if (rgbmatrix_line < (RGBMATRIX_HEIGHT / 2)) { // there are still other lines to update (we update 2 lines per transfer) + rgbmatrix_line++; // go to next line + GPIOB_ODR = (GPIOB_ODR & 0xfff0) + (rgbmatrix_line % (RGBMATRIX_HEIGHT / 2)); // select line + dma_enable_stream(RGBMATRIX_DMA, RGBMATRIX_STREAM); // start sending next line + } + } +}