/* * Copyright (C) 2019 Max Regan * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include "stm32l0xx.h" #include "macros.h" #include "rtc.h" #include "spi.h" #include "display.h" #include "font-notomono-10.h" /* TODO: Start cleaning up code and adding bounds checking! */ extern uint32_t system_clk_freq; void SystemInit() { /** * Use the MSI for the system clock, and disable all other clocks. */ /*!< Set MSION bit. Set by hardware to force the MSI oscillator ON * when exiting from Stop or Standby mode, or in case of a failure * of the HSE oscillator used directly or indirectly as system * clock. This bit cannot be cleared if the MSI is used as system * clock. */ SET(RCC->CR, RCC_CR_MSION); SET_TO(RCC->ICSCR, RCC_ICSCR_MSIRANGE, RCC_ICSCR_MSIRANGE_6); /*!< Set internal representation of clock frequency to 4MHz */ system_clk_freq = 4u << 22; /*!< Reset * SW[1:0] (use MSI oscillator as system clock), * HPRE[3:0] (do not divide AHB clock in prescaler) , * PPRE1[2:0] (do not divide APB low-speed clock) * PPRE2[2:0] (do not divide APB high-speed clock), * MCOSEL[2:0] (disable MCO clock), * MCOPRE[2:0] (disable MCO prescaler) */ CLR(RCC->CFGR, RCC_CFGR_SW | ~RCC_CFGR_HPRE | RCC_CFGR_PPRE1 | RCC_CFGR_PPRE2 | RCC_CFGR_MCOSEL | RCC_CFGR_MCOPRE); /*!< Reset * HSION (disable HSI), * HSIDIVEN (disable 18MHz HSI division) * HSEON (disable HSE clock) * CSSHSEON (disable HSE clock monitoring) * PLLON (disable PLL) */ CLR(RCC->CR, RCC_CR_HSION | RCC_CR_HSIDIVEN | RCC_CR_HSEON | RCC_CR_CSSHSEON | RCC_CR_PLLON); /*!< Reset HSEBYP bit (disable HSE bypass) */ CLR(RCC->CR, RCC_CR_HSEBYP); /*!< Reset * PLLSRC (HSI16 is the PLL source), * PLLMUL[3:0] (3x PLL multiplication) * Don't touch PLLDIV[1:0], since 0 is undefined */ CLR(RCC->CFGR, RCC_CFGR_PLLSRC | RCC_CFGR_PLLMUL | RCC_CFGR_PLLDIV); /*!< Disable all interrupts */ RCC->CIER = 0x00000000; /* Vector Table Relocation in Internal FLASH */ SCB->VTOR = FLASH_BASE; } void init_lptim() { /* Enable APB1 for LPTIM */ SET(RCC->APB1ENR, RCC_APB1ENR_LPTIM1EN); // Enable low-speed internal RCC->CSR |= RCC_CSR_LSION; while (!(RCC->CSR & RCC_CSR_LSIRDY)) {}; /*!< Set the LSI clock to be the source of the LPTIM */ SET_TO(RCC->CCIPR, RCC_CCIPR_LPTIM1SEL, RCC_CCIPR_LPTIM1SEL_0); /** Write CR CFGR and IER while LPTIM is disabled (LPTIM_CR_ENABLE not yet set) */ /*!< Disable Interrupts (not needed, this is the default */ LPTIM1->IER = 0; /*!< Reset * ENC (Disable encoder mode) * TIMOUT (disable timeout mode) * TRIGEN (Trigger count start with software only) * PRELOAD (Update ARR and CMP registers immediately after write) * CKSEL (LPTIM is using internal clock source) * COUNTMODE (LPTIM counter updated on every clock pulse) * TRGFLT (Do not debounce triggers) */ CLR(LPTIM1->CFGR, LPTIM_CFGR_ENC | LPTIM_CFGR_TIMOUT | LPTIM_CFGR_TRIGEN | LPTIM_CFGR_TRIGSEL | LPTIM_CFGR_PRELOAD | LPTIM_CFGR_CKSEL | LPTIM_CFGR_COUNTMODE); /*!< Set * PRESC (Set prescaler to 128. Using 32kHz LSI as input, this should * correspond to 250Hz counting. */ SET_TO(LPTIM1->CFGR, LPTIM_CFGR_PRESC, 7u << LPTIM_CFGR_PRESC_Pos); SET(LPTIM1->CR, LPTIM_CR_ENABLE); /*!< Do not modify ARR and CMP until after ENABLE bit is set */ /*!< Produce a 60Hz, 50% duty cycle square wave. These values were * determined experimentally. */ LPTIM1->ARR = 9; LPTIM1->CMP = 4; while(!(LPTIM1->ISR & LPTIM_ISR_ARROK)) {} while(!(LPTIM1->ISR & LPTIM_ISR_CMPOK)) {} /*!< Enable and start the timer */ SET(LPTIM1->CR, LPTIM_CR_CNTSTRT); } static struct display display; void init_lptim_toggler() { init_lptim(); /* Assign LPTIM1_OUT to PA7 */ SET_TO(GPIOA->AFR[0], GPIO_AFRL_AFRL7, 1u << GPIO_AFRL_AFRL7_Pos); SET_TO(GPIOA->MODER, GPIO_MODER_MODE7, 2u << GPIO_MODER_MODE7_Pos); CLR(GPIOA->OTYPER, GPIO_OTYPER_OT_7); CLR(GPIOA->PUPDR, GPIO_PUPDR_PUPD7); } static char get_char_for_digit(uint8_t bcd_digit) { return bcd_digit + '0'; } struct time_bcd time; char time_str[11] = { 0 }; int main() { /** Enable Port A,B clock */ SET(RCC->IOPENR, RCC_IOPENR_IOPAEN); SET(RCC->IOPENR, RCC_IOPENR_IOPBEN); /** Enable pin P3 for output */ SET_TO(GPIOB->MODER, GPIO_MODER_MODE3, GPIO_MODER_MODE3_0); CLR(GPIOB->OTYPER, GPIO_OTYPER_OT_3); CLR(GPIOB->PUPDR, GPIO_PUPDR_PUPD3); init_lptim_toggler(); display_init(&display, SPI1); rtc_init(); int x = 0; while (1) { rtc_get_time_bcd(&time); time_str[0] = get_char_for_digit(time.hour_tens); time_str[1] = get_char_for_digit(time.hour_ones); time_str[2] = ':'; time_str[3] = get_char_for_digit(time.minute_tens); time_str[4] = get_char_for_digit(time.minute_ones); time_str[5] = ':'; time_str[6] = get_char_for_digit(time.second_tens); time_str[7] = get_char_for_digit(time.second_ones); time_str[8] = time.pm ? 'P' : 'A'; time_str[9] = 'M'; time_str[10] = '\0'; FLIP(GPIOB->ODR, GPIO_ODR_OD3); // for (int i = 0; i < 10000; i++); display_clear(&display); display_string_at(&display, 32, font_notomono_10.height * 0, time_str, &font_notomono_10); display_string_at(&display, 0, font_notomono_10.height * 2, "> Option 1", &font_notomono_10); display_string_at(&display, 0, font_notomono_10.height * 3, " Option 2", &font_notomono_10); display_string_at(&display, 0, font_notomono_10.height * 4, " Option 3", &font_notomono_10); display_refresh(&display); x++; if (x == DISPLAY_WIDTH) { x = 0; } } }