app: implement stepper and RGB matrix control

This commit is contained in:
King Kévin 2022-05-24 14:45:56 +02:00
parent 4fba2fd7a4
commit 71564dd4f1
1 changed files with 390 additions and 13 deletions

View File

@ -1,4 +1,4 @@
/** STM32F4 application example
/** firmware to control the cool clock
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -24,6 +24,8 @@
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/timer.h> // timer library
#include <libopencm3/stm32/dma.h> // DMA library
#include <libopencm3/usb/dwc/otg_fs.h> // 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
}
}
}