Start beautifying the code

* Split out SPI and display (a little)
* Use more of the common Macros
* Remove wasted bytes from notomono font
* Create a mock "menu"
This commit is contained in:
2019-03-10 18:16:55 -07:00
parent fab3332c65
commit dedcb5af71
10 changed files with 1325 additions and 1103 deletions

View File

@@ -79,7 +79,6 @@ CFLAGS += -D$(DEVICE_DEFINE)
# Includes
CFLAGS += -I./lib/stm32/$(DEVICE_LINE)/Include
CFLAGS += -I./lib/CMSIS/Core/Include
CFLAGS += -I./lib/fonts/
# Startup Definitions
@@ -100,7 +99,7 @@ build: $(OUTPUT_BIN)
%.o: %.c
@echo "CC $@"
@$(CC) $(CFLAGS) -c $< -o $@
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.S
@echo "AS $@"

162
display.c Normal file
View File

@@ -0,0 +1,162 @@
/*
* 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 "display.h"
#include "macros.h"
static void display_buffer_init(struct display_buffer *buffer)
{
for (size_t i = 0; i < ARRAY_SIZE(buffer->lines); i++) {
struct display_line *line = &buffer->lines[i];
line->mode = 1; // Update display
line->line = i;
for (size_t j = 0; j < ARRAY_SIZE(line->data); j++) {
line->data[j] = 0xFF;
}
}
buffer->dummy = 0;
}
static void display_spi_init(struct display *display)
{
// TODO: Not all of this should be in here, probably
RCC->APB2ENR = RCC_APB2ENR_SPI1EN;
GPIOB->OSPEEDR = ~0;
/* Assign SPI_MOSI to PA12 (AFRH5), since PA7 is taken by LPTIM_OUT */
GPIOA->AFR[1] &= ~GPIO_AFRH_AFRH4;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE12, 2u << GPIO_MODER_MODE12_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_12;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD12;
// SPI1 NSS (PA4)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL4;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE4, 2u << GPIO_MODER_MODE4_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_4;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD4;
// enable pullup, since the pin doesn't seem to stay up
GPIOA->PUPDR |= 2u << GPIO_PUPDR_PUPD4_Pos;
// SPI1 SCK (PA5)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL5;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE5, 2u << GPIO_MODER_MODE5_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_5;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD5;
// SPI1 MISO (PA6)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL6;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE6, 2u << GPIO_MODER_MODE6_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_6;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD6;
// Enable Master mode and half the baud rate, so it's set to ~1MHz
display->spi->CR1 |= SPI_CR1_MSTR | SPI_CR1_LSBFIRST;
display->spi->CR1 |= 1u << SPI_CR1_BR_Pos;
display->spi->CR2 |= SPI_CR2_SSOE;
}
void display_init(struct display *display, SPI_TypeDef* spi)
{
display->spi = spi;
display_buffer_init(&display->buffer);
display_spi_init(display);
}
void display_set_bit(struct display *display, unsigned int x, unsigned int y, uint8_t val)
{
if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) {
return;
}
struct display_line *line = &display->buffer.lines[y];
uint8_t *byte = &line->data[x >> 3];
if (val) {
CLR_POS(*byte, x & 7);
} else {
SET_POS(*byte, x & 7);
}
}
void display_set_byte(struct display *display, unsigned int x, unsigned int y, uint8_t val)
{
if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) {
return;
}
if (x & 7) {
return;
}
struct display_line *line = &display->buffer.lines[y];
line->data[x >> 3] = val;
}
void display_char_at(struct display *display, int *x_off, int y_off, char c, const struct font *font)
{
const struct glyph *g = glyph_for_char(font, c);
if (g == NULL) {
return;
}
int byte_cols = 1; // TODO: Don't hardcode this
for (size_t x = 0; x < g->cols; x++) {
for (size_t y = 0; y < g->rows; y++) {
int byte_x = x >> 3;
int byte_y = y;
uint8_t bit = (g->bitmap[byte_y * byte_cols + byte_x] >> (7 - (x & 7))) & 1;
/* 16 is font max height */
display_set_bit(display, g->left + *x_off + x, y_off + y + 16 - g->top, bit);
}
}
*x_off += g->advance;
}
void display_string_at(struct display *display, int x_off, int y_off, const char *string, const struct font *font)
{
int i = 0;
while (string[i]) {
display_char_at(display, &x_off, y_off, string[i], font);
i++;
}
}
void display_refresh(struct display *display)
{
spi_send_blocking(display->spi, (uint8_t *) &display->buffer, sizeof(display->buffer));
}
void display_clear(struct display *display)
{
display_buffer_init(&display->buffer);
}

73
display.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* 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.
*/
#ifndef _DISPLAY_H_
#define _DISPLAY_H_
#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#include "spi.h"
#include "font.h"
#define DISPLAY_WIDTH 144
#define DISPLAY_HEIGHT 168
struct display_line
{
uint8_t mode;
uint8_t line;
uint8_t data[DISPLAY_WIDTH / 8];
};
struct display_buffer
{
struct display_line lines[DISPLAY_HEIGHT];
uint16_t dummy;
};
static_assert(sizeof(struct display_buffer) == (DISPLAY_WIDTH / 8 + 2) * DISPLAY_HEIGHT + 2,
"The display buffer structure must be packed");
struct display
{
SPI_TypeDef *spi;
struct display_buffer buffer;
};
void display_init(struct display *display, SPI_TypeDef *spi);
void display_set_bit(struct display *display, unsigned int x, unsigned int y, uint8_t val);
void display_set_byte(struct display *display, unsigned int x, unsigned int y, uint8_t val);
void display_char_at(struct display *display, int *x_off, int y_off, char c, const struct font *font);
void display_string_at(struct display *display, int x_off, int y_off, const char *string, const struct font *font);
void display_refresh(struct display *display);
// TODO
void display_clear(struct display *display);
#endif

39
font.c Normal file
View File

@@ -0,0 +1,39 @@
/*
* 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 "font.h"
const struct glyph *glyph_for_char(const struct font *font, char c)
{
// TODO: This is almost the least efficient way imaginable to implement this
for (int i = 0; i < font->max; i++) {
const struct glyph *g = font->glyphs[i];
if (g == NULL) {
continue;
}
if (g->glyph == c) {
return g;
}
}
return NULL;
}

29
font.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
#ifndef _FONT_H_
#define _FONT_H_
#include "fontem.h"
const struct glyph *glyph_for_char(const struct font *font, char c);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -43,12 +43,12 @@
(x) |= 1u << (y); \
} while (0)
#define CLEAR(x, y) \
#define CLR(x, y) \
do { \
(x) &= ~(y); \
} while (0)
#define CLEAR_POS(x, y) \
#define CLR_POS(x, y) \
do { \
(x) &= ~(1u << (y)); \
} while (0)
@@ -67,7 +67,7 @@
static_assert((clear_mask & val) == val, \
"'value' in SET_TO() has bits set that are not in clear_mask"); \
do { \
CLEAR(x, clear_mask); \
CLR(x, clear_mask); \
SET(x, val); \
} while (0)

46
spi.c Normal file
View File

@@ -0,0 +1,46 @@
/*
* 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 "spi.h"
#include "stm32l0xx.h"
#include "macros.h"
void spi_send_blocking(SPI_TypeDef *spi, const uint8_t *data, size_t len)
{
if (len <= 0) {
return;
}
spi->CR1 |= SPI_CR1_SPE;
for (size_t i = 0; i < len; i++) {
while (!(spi->SR & SPI_SR_TXE)) {}
spi->DR = data[i];
}
while (!(spi->SR & SPI_SR_TXE)) {}
// Ensure that NSS is held for long enough to meet the display's thSCS
for (int i = 0; i < 4; i++);
spi->CR1 &= ~SPI_CR1_SPE;
}

35
spi.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* 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.
*/
#ifndef _SPI_H_
#define _SPI_H_
#include <stdint.h>
#include <stddef.h>
// TODO: Include something more general (only needed for SPI typedef)
#include "stm32l0xx.h"
void spi_send_blocking(SPI_TypeDef *spi, const uint8_t *data, size_t len);
void spi_send_dma(SPI_TypeDef *spi);
#endif

316
test.c
View File

@@ -1,32 +1,33 @@
/**
Copyright 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.
**/
/*
* 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 <assert.h>
#include <stddef.h>
#include <stdint.h>
#include "macros.h"
#include "stm32l0xx.h"
#include "macros.h"
#include "spi.h"
#include "display.h"
#include "font-notomono-10.h"
/* TODO: Start cleaning up code and adding bounds checking! */
@@ -43,10 +44,12 @@ void SystemInit()
* 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);
CLEAR(RCC->ICSCR, RCC_ICSCR_MSIRANGE);
SET(RCC->CR,
RCC_CR_MSION);
SET_TO(RCC->ICSCR, RCC_ICSCR_MSIRANGE, RCC_ICSCR_MSIRANGE_6);
SET_TO(RCC->ICSCR,
RCC_ICSCR_MSIRANGE,
RCC_ICSCR_MSIRANGE_6);
/*!< Reset
* SW[1:0] (use MSI oscillator as system clock),
@@ -55,7 +58,7 @@ void SystemInit()
* PPRE2[2:0] (do not divide APB high-speed clock),
* MCOSEL[2:0] (disable MCO clock),
* MCOPRE[2:0] (disable MCO prescaler) */
CLEAR(RCC->CFGR,
CLR(RCC->CFGR,
RCC_CFGR_SW | ~RCC_CFGR_HPRE | RCC_CFGR_PPRE1 | RCC_CFGR_PPRE2 |
RCC_CFGR_MCOSEL | RCC_CFGR_MCOPRE);
@@ -66,19 +69,21 @@ void SystemInit()
* CSSHSEON (disable HSE clock monitoring)
* PLLON (disable PLL)
*/
CLEAR(RCC->CR,
CLR(RCC->CR,
RCC_CR_HSION | RCC_CR_HSIDIVEN | RCC_CR_HSEON |
RCC_CR_CSSHSEON | RCC_CR_PLLON);
/*!< Reset HSEBYP bit (disable HSE bypass) */
RCC->CR &= ~RCC_CR_HSEBYP;
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
*/
RCC->CFGR &= ~RCC_CFGR_PLLSRC & ~RCC_CFGR_PLLMUL & ~RCC_CFGR_PLLDIV;
CLR(RCC->CFGR,
RCC_CFGR_PLLSRC | RCC_CFGR_PLLMUL | RCC_CFGR_PLLDIV);
/*!< Disable all interrupts */
@@ -91,14 +96,17 @@ void SystemInit()
void init_lptim()
{
/* Enable APB1 for LPTIM */
RCC->APB1ENR |= RCC_APB1ENR_LPTIM1EN;
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);
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) */
@@ -114,18 +122,20 @@ void init_lptim()
* COUNTMODE (LPTIM counter updated on every clock pulse)
* TRGFLT (Do not debounce triggers)
*/
LPTIM1->CFGR &= ~LPTIM_CFGR_ENC & ~LPTIM_CFGR_TIMOUT & ~LPTIM_CFGR_TRIGEN &
~LPTIM_CFGR_TRIGSEL & ~LPTIM_CFGR_PRELOAD & ~LPTIM_CFGR_CKSEL &
~LPTIM_CFGR_COUNTMODE;
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.
*/
LPTIM1->CFGR &= LPTIM_CFGR_PRESC;
LPTIM1->CFGR |= 7u << LPTIM_CFGR_PRESC_Pos;
SET_TO(LPTIM1->CFGR,
LPTIM_CFGR_PRESC,
7u << LPTIM_CFGR_PRESC_Pos);
LPTIM1->CR |= LPTIM_CR_ENABLE;
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
@@ -136,230 +146,58 @@ void init_lptim()
while(!(LPTIM1->ISR & LPTIM_ISR_CMPOK)) {}
/*!< Enable and start the timer */
LPTIM1->CR |= LPTIM_CR_CNTSTRT;
SET(LPTIM1->CR, LPTIM_CR_CNTSTRT);
}
/*!< The buffer that will be used in the future to display on the watch display. */
#define DISPLAY_WIDTH 144
#define DISPLAY_HEIGHT 168
struct display_line
{
uint8_t mode;
uint8_t line;
uint8_t data[DISPLAY_WIDTH / 8];
};
struct display_buffer
{
struct display_line lines[DISPLAY_HEIGHT];
uint16_t dummy;
};
static_assert(sizeof(struct display_buffer) == (DISPLAY_WIDTH / 8 + 2) * DISPLAY_HEIGHT + 2,
"The display buffer structure must be packed");
void display_buffer_init(struct display_buffer *buffer)
{
for (size_t i = 0; i < ARRAY_SIZE(buffer->lines); i++) {
struct display_line *line = &buffer->lines[i];
line->mode = 1; // Update display
line->line = i;
for (size_t j = 0; j < ARRAY_SIZE(line->data); j++) {
line->data[j] = 0xFF;
}
}
buffer->dummy = 0;
}
void display_set_bit(struct display_buffer *buffer, unsigned int x, unsigned int y, uint8_t val)
{
struct display_line *line = &buffer->lines[y];
uint8_t *byte = &line->data[x >> 3];
if (val) {
CLEAR_POS(*byte, x & 7);
} else {
SET_POS(*byte, x & 7);
}
}
void display_set_byte(struct display_buffer *buffer, unsigned int x, unsigned int y, uint8_t val)
{
if (x & 7) {
return;
}
struct display_line *line = &buffer->lines[y];
line->data[x >> 3] = val;
}
static struct display_buffer buffer;
static struct display display;
void init_lptim_toggler()
{
init_lptim();
/* Assign LPTIM1_OUT to PA7 */
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL7;
GPIOA->AFR[0] |= 1 << GPIO_AFRL_AFRL7_Pos;
SET_TO(GPIOA->AFR[0],
GPIO_AFRL_AFRL7,
1u << GPIO_AFRL_AFRL7_Pos);
GPIOA->MODER &= ~GPIO_MODER_MODE7;
GPIOA->MODER |= 2u << GPIO_MODER_MODE7_Pos;
SET_TO(GPIOA->MODER,
GPIO_MODER_MODE7,
2u << GPIO_MODER_MODE7_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_7;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD7;
}
void init_spi_display()
{
RCC->APB2ENR = RCC_APB2ENR_SPI1EN;
GPIOB->OSPEEDR = ~0;
/* Assign SPI_MOSI to PA12 (AFRH5), since PA7 is taken by LPTIM_OUT */
GPIOA->AFR[1] &= ~GPIO_AFRH_AFRH4;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE12, 2u << GPIO_MODER_MODE12_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_12;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD12;
// SPI1 NSS (PA4)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL4;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE4, 2u << GPIO_MODER_MODE4_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_4;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD4;
// enable pullup, since the pin doesn't seem to stay up
GPIOA->PUPDR |= 2u << GPIO_PUPDR_PUPD4_Pos;
// SPI1 SCK (PA5)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL5;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE5, 2u << GPIO_MODER_MODE5_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_5;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD5;
// SPI1 MISO (PA6)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL6;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE6, 2u << GPIO_MODER_MODE6_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_6;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD6;
// Enable Master mode and half the baud rate, so it's set to ~1MHz
SPI1->CR1 |= SPI_CR1_MSTR | SPI_CR1_LSBFIRST;
SPI1->CR1 |= 1u << SPI_CR1_BR_Pos;
SPI1->CR2 |= SPI_CR2_SSOE;
}
void spi_send_blocking(SPI_TypeDef *spi, const uint8_t *data, size_t len)
{
if (len <= 0) {
return;
}
spi->CR1 |= SPI_CR1_SPE;
for (size_t i = 0; i < len; i++) {
while (!(spi->SR & SPI_SR_TXE)) {}
spi->DR = data[i];
}
while (!(spi->SR & SPI_SR_TXE)) {}
// Ensure that NSS is held for long enough to meet the display's thSCS
for (int i = 0; i < 4; i++);
spi->CR1 &= ~SPI_CR1_SPE;
}
const struct glyph *glyph_for_char(const struct font *font, char c)
{
// TODO: This is almost the least efficient way imaginable to implement this
for (int i = 0; i < font->max; i++) {
const struct glyph *g = font->glyphs[i];
if (g == NULL) {
continue;
}
if (g->glyph == c) {
return g;
}
}
return NULL;
}
void display_char_at(int *x_off, int y_off, char c)
{
const struct glyph *g = glyph_for_char(&font_notomono_10, c);
if (g == NULL) {
return;
}
int byte_cols = 2;
for (size_t x = 0; x < g->cols; x++) {
for (size_t y = 0; y < g->rows; y++) {
int byte_x = x >> 3;
int byte_y = y;
uint8_t bit = (g->bitmap[byte_y * byte_cols + byte_x] >> (7 - (x & 7))) & 1;
/* 16 is font max height */
display_set_bit(&buffer, g->left + *x_off + x, y_off + y + 16 - g->top, bit);
}
}
*x_off += g->advance;
}
void display_string_at(int x_off, int y_off, const char *string)
{
int i = 0;
while (string[i]) {
display_char_at(&x_off, y_off, string[i]);
i++;
}
}
void display_update()
{
static uint16_t count = 0;
// for (size_t x = 0; x < DISPLAY_WIDTH / 8; x++) {
// for (size_t y = 0; y < DISPLAY_HEIGHT; y++) {
// display_set_byte(&buffer, (x << 3), y, (((y + count) >> 4) & 1) ? 0xFF : 0);
// }
// }
display_string_at(0, 0, "Hello world!");
count++;
CLR(GPIOA->OTYPER, GPIO_OTYPER_OT_7);
CLR(GPIOA->PUPDR, GPIO_PUPDR_PUPD7);
}
int main()
{
/** Enable Port A,B clock */
RCC->IOPENR |= RCC_IOPENR_IOPAEN;
RCC->IOPENR |= RCC_IOPENR_IOPBEN;
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);
SET_TO(GPIOB->MODER,
GPIO_MODER_MODE3,
GPIO_MODER_MODE3_0);
CLEAR(GPIOB->OTYPER, GPIO_OTYPER_OT_3);
CLEAR(GPIOB->PUPDR, GPIO_PUPDR_PUPD3);
CLR(GPIOB->OTYPER, GPIO_OTYPER_OT_3);
CLR(GPIOB->PUPDR, GPIO_PUPDR_PUPD3);
init_lptim_toggler();
init_spi_display();
display_buffer_init(&buffer);
display_init(&display, SPI1);
int x = 0;
while (1) {
display_update();
spi_send_blocking(SPI1, (uint8_t *) &buffer, sizeof(buffer));
FLIP(GPIOB->ODR, GPIO_ODR_OD3);
// for (int i = 0; i < 100000; i++);
// for (int i = 0; i < 10000; i++);
display_clear(&display);
display_string_at(&display, 0, font_notomono_10.height * 0, "> Option 1", &font_notomono_10);
display_string_at(&display, 0, font_notomono_10.height * 1, " Option 2", &font_notomono_10);
display_string_at(&display, 0, font_notomono_10.height * 2, " Option 3", &font_notomono_10);
display_refresh(&display);
x++;
if (x == DISPLAY_WIDTH) {
x = 0;
}
}
}