/* 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 .
*
*/
/** @file main.c
* @author King Kévin
* @date 2016
* @brief show the time on a LED strip
*
* The LED strip consists of 60 WS2812b LEDs.
* The time is read from a DS1307 RTC module.
*/
/* standard libraries */
#include // standard integer types
#include // standard I/O facilities
#include // standard utilities
#include // standard streams
#include // error number utilities
#include // string utilities
#include // mathematical utilities
/* STM32 (including CM3) libraries */
#include // real-time control clock library
#include // general purpose input output library
#include // vector table definition
#include // Cortex M3 utilities
#include // interrupt utilities
#include // external interrupt utilities
#include // timer utilities
#include // ADC utilities
/* own libraries */
#include "global.h" // board definitions
#include "usart.h" // USART utilities
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "led_ws2812b.h" // WS2812b LEDs utilities
/** @defgroup main_flags flag set in interrupts to be processed in main task
* @{
*/
volatile bool button_flag = false; /**< flag set when board user button has been pressed/released */
volatile bool time_flag = false; /**< flag set when time changed */
/** @} */
#define TICKS_PER_SECOND 256 /**< the number of ticks in one second */
/** @defgroup main_ticks ticks per time units
* @note these are derived from TICKS_PER_SECOND
* @note I have to use type variables because defines would be stored in signed integers, leading to an overflow it later calculations
* @{
*/
/** number of ticks in one second */
const uint32_t ticks_second = TICKS_PER_SECOND;
/** number of ticks in one minute */
const uint32_t ticks_minute = 60*TICKS_PER_SECOND;
/** number of ticks in one hour */
const uint32_t ticks_hour = 60*60*TICKS_PER_SECOND;
/** number of ticks in one midday (12 hours) */
const uint32_t ticks_midday = 12*60*60*TICKS_PER_SECOND;
/** @} */
/** @defgroup battery_adc ADC used to measure battery voltage
* @{
*/
#define BATTERY_ADC ADC1 /**< ADC used to measure battery voltage */
#define BATTERY_ADC_RCC RCC_ADC1 /**< ADC clock peripheral */
#define BATTERY_ADC_CHANNEL ADC_CHANNEL1 /**< ADC channel */
#define BATTERY_PORT GPIOA /**< port on which the battery is connected */
#define BATTERY_PORT_RCC RCC_GPIOA /**< timer port peripheral clock */
#define BATTERY_PIN GPIO1 /**< pin of the port on which the battery is connected */
/** @} */
/** RGB values for the WS2812b clock LEDs */
uint8_t clock_leds[WS2812B_LEDS*3] = {0};
/** current time in tick */
volatile uint32_t current_time = 0;
/** user input command */
char command[32] = {0};
/** user input command index */
uint8_t command_i = 0;
/** gamma correction lookup table (common for all colors) */
uint8_t gamma_correction_lut[256] = {0};
int _write(int file, char *ptr, int len)
{
int i;
if (file == STDOUT_FILENO || file == STDERR_FILENO) {
for (i = 0; i < len; i++) {
if (ptr[i] == '\n') { // add carrier return before line feed. this is recommended for most UART terminals
usart_putchar_nonblocking('\r'); // a second line feed doesn't break the display
cdcacm_putchar('\r'); // a second line feed doesn't break the display
}
usart_putchar_nonblocking(ptr[i]); // send byte over USART
cdcacm_putchar(ptr[i]); // send byte over USB
}
return i;
}
errno = EIO;
return -1;
}
void led_on(void)
{
#if defined(SYSTEM_BOARD) || defined(BLUE_PILL)
gpio_clear(LED_PORT, LED_PIN);
#elif defined(MAPLE_MINI)
gpio_set(LED_PORT, LED_PIN);
#endif
}
void led_off(void)
{
#if defined(SYSTEM_BOARD) || defined(BLUE_PILL)
gpio_set(LED_PORT, LED_PIN);
#elif defined(MAPLE_MINI)
gpio_clear(LED_PORT, LED_PIN);
#endif
}
void led_toggle(void)
{
gpio_toggle(LED_PORT, LED_PIN);
}
/** @brief switch off all clock LEDs
* @note LEDs need to be set separately
*/
static void clock_clear(void)
{
// set all colors of all LEDs to 0
for (uint16_t i=0; i=WS2812B_LEDS*255 || led_minute>=WS2812B_LEDS*255) { // a calculation error occurred
return;
}
// show hours and minutes on LEDs
if (led_hour>led_minute) {
// show hours in blue (and clear other LEDs)
for (uint16_t led=0; led=0xff) { // full hours
clock_leds[led*3+2] = 0xff;
} else { // running hours
clock_leds[led*3+2] = led_hour;
}
led_hour -= clock_leds[led*3+2];
}
// show minutes in green (override hours)
for (uint16_t led=0; led0; led++) {
clock_leds[led*3+0] = 0;
if (led_minute>=0xff) { // full minutes
clock_leds[led*3+1] = 0xff;
} else { // running minutes
clock_leds[led*3+1] = led_minute;
}
led_minute -= clock_leds[led*3+1];
clock_leds[led*3+2] = 0;
}
} else {
// show minutes in green (and clear other LEDs)
for (uint16_t led=0; led=0xff) { // full minutes
clock_leds[led*3+1] = 0xff;
} else { // running minutes
clock_leds[led*3+1] = led_minute;
}
led_minute -= clock_leds[led*3+1];
clock_leds[led*3+2] = 0;
}
// show hours in blue (override minutes)
for (uint16_t led=0; led0; led++) {
clock_leds[led*3+0] = 0;
clock_leds[led*3+1] = 0;
if (led_hour>=0xff) { // full hours
clock_leds[led*3+2] = 0xff;
} else { // running hours
clock_leds[led*3+2] = led_hour;
}
led_hour -= clock_leds[led*3+2];
}
}
// don't show seconds on full minute (better for first time setting, barely visible else)
if (time%ticks_minute==0) {
return;
}
uint16_t led_second = (WS2812B_LEDS*(time%ticks_minute))/ticks_minute; // get LED for seconds
uint8_t brightness_second = (255*(time%ticks_second))/ticks_second; // get brightness for seconds
// set seconds LED
clock_leds[led_second*3+0] = brightness_second;
//clock_leds[led_second*3+1] = 0; // clear other colors (minutes/hours indication)
//clock_leds[led_second*3+2] = 0; // clear other colors (minutes/hours indication)
// set previous seconds LED
led_second = ((led_second==0) ? WS2812B_LEDS-1 : led_second-1); // previous LED
clock_leds[led_second*3+0] = 0xff-brightness_second;
//clock_leds[led_second*3+1] = 0; // clear other colors (minutes/hours indication)
//clock_leds[led_second*3+2] = 0; // clear other colors (minutes/hours indication)
}
/** @brief set the LEDs
* @details set the LED colors on WS2812b LEDs
* @note WS2812b LED color values need to be transmitted separately
*/
static void clock_leds_set(void)
{
static uint8_t clock_leds_old[WS2812B_LEDS*3] = {0}; // backup so to only set when value changed
for (uint16_t i=0; i255 ? 512-i-1 : i); // get fade brightness
for (uint8_t hour=0; hour<12; hour++) { // set all hour colors
uint16_t led = WS2812B_LEDS/12*hour; // get LED four hour mark
clock_leds[led*3+0] = brightness; // set brightness
clock_leds[led*3+1] = brightness; // set brightness
clock_leds[led*3+2] = brightness; // set brightness
}
clock_leds_set(); // set the colors of all LEDs
ws2812b_transmit(); // transmit set color
// delay some time for the animation
for (uint32_t j=0; j<40000; j++) {
__asm__("nop");
}
}
}
/** @brief 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("time [HH:MM:SS]\n");
} else if (0==strcmp(word,"time")) {
word = strtok(NULL,delimiter);
if (!word) {
//printf("current time: %02d:%02d:%02d\n", rtc_read_hours(), rtc_read_minutes(), rtc_read_seconds());
} 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') {
goto error;
} else {
/*
if (!rtc_write_hours((word[0]-'0')*10+(word[1]-'0')*1)) {
printf("setting hours failed\n");
} else if (!rtc_write_minutes((word[3]-'0')*10+(word[4]-'0')*1)) {
printf("setting minutes failed\n");
} else if (!rtc_write_seconds((word[6]-'0')*10+(word[7]-'0')*1)) {
printf("setting seconds failed\n");
} else {
current_time = rtc_read_hours()*ticks_hour+rtc_read_minutes()*ticks_minute+rtc_read_seconds()*ticks_second; // set also internal time
rtc_oscillator_enable(); // be sure the oscillation is enabled
printf("time set\n");
}
*/
}
} else {
goto error;
}
return; // command successfully processed
error:
puts("command not recognized. enter help to list commands");
}
/** @brief program entry point
* this is the firmware function started by the micro-controller
*/
int main(void)
{
SCB_VTOR = (uint32_t) 0x08002000; // relocate vector table because of the bootloader
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock
usart_setup(); // setup USART (for printing)
cdcacm_setup(); // setup USB CDC ACM (for printing)
setbuf(stdout, NULL); // set standard out buffer to NULL to immediately print
setbuf(stderr, NULL); // set standard error buffer to NULL to immediately print
// setup LED
rcc_periph_clock_enable(LED_RCC); // enable clock for LED
gpio_set_mode(LED_PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, LED_PIN); // set LED pin to 'output push-pull'
led_off(); // switch off LED to indicate setup started
// setup button
#if defined(BUTTON_RCC) && defined(BUTTON_PORT) && defined(BUTTON_PIN) && defined(BUTTON_EXTI) && defined(BUTTON_IRQ)
rcc_periph_clock_enable(BUTTON_RCC); // enable clock for button
gpio_set_mode(BUTTON_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, BUTTON_PIN); // set button pin to input
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
exti_select_source(BUTTON_EXTI, BUTTON_PORT); // mask external interrupt of this pin only for this port
exti_set_trigger(BUTTON_EXTI, EXTI_TRIGGER_BOTH); // trigger on both edge
exti_enable_request(BUTTON_EXTI); // enable external interrupt
nvic_enable_irq(BUTTON_IRQ); // enable interrupt
#endif
// generate gamma correction table (with fixed gamma value)
for (uint16_t i=0; i0) { // 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 if (c!='\r') { // user command input
command[command_i] = c; // save command input
if (command_i