Enable text display on Sharp display

1. Add basic code for Sharp Display
2. Add embedded font (generated by fontem)
3. Add the worst code possible to print strings to the display
4. Rejoice in the beautiful (small) text.
This commit is contained in:
2019-03-04 00:22:01 -08:00
parent 43d29285ef
commit fab3332c65
6 changed files with 2822 additions and 42 deletions

View File

@@ -80,6 +80,8 @@ CFLAGS += -D$(DEVICE_DEFINE)
CFLAGS += -I./lib/stm32/$(DEVICE_LINE)/Include
CFLAGS += -I./lib/CMSIS/Core/Include
CFLAGS += -I./lib/fonts/
# Startup Definitions
ASFLAGS += -D__STARTUP_CLEAR_BSS
ASFLAGS += -D__HEAP_SIZE=0 # No heap- let the linker decide it all

2468
lib/fonts/font-notomono-10.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
/* AUTOMATICALLY GENERATED FILE! EDITING NOT RECOMMENDED!
*
* This file is distributed under the terms of the MIT License.
* See the LICENSE file at the top of this tree, or if it is missing a copy can
* be found at http://opensource.org/licenses/MIT
*/
#ifndef _FONTEM_notomono_10_H
#define _FONTEM_notomono_10_H
#include "fontem.h"
extern const struct font font_notomono_10;
#endif /* _FONTEM_notomono_10_H */

84
lib/fonts/fontem.h Normal file
View File

@@ -0,0 +1,84 @@
/** Font structure definitions.
*
* This file is distributed under the terms of the MIT License.
* See the LICENSE file at the top of this tree, or if it is missing a copy can
* be found at http://opensource.org/licenses/MIT
*/
#ifndef _FONTEM_H
#define _FONTEM_H
#include <stdint.h>
#include <stdio.h>
/** Alpha compositing "A over B" mechanism */
#define alpha_blend(in, in_alpha, out, out_alpha) \
((0x100 * in * in_alpha * (255 - out_alpha) + out * out_alpha * (255 - in_alpha)) >> 16)
#define blend(a, b, alpha) \
(((a) * (255 - (alpha)) + (b) * (alpha)) >> 8)
/** Extract the Alpha channel from a 32-bit RGBA value */
#define rgba32_get_a(rgba) ((rgba >> 24) & 0xff)
/** Extract the Red channel from a 32-bit RGBA value */
#define rgba32_get_r(rgba) ((rgba >> 16) & 0xff)
/** Extract the Green channel from a 32-bit RGBA value */
#define rgba32_get_g(rgba) ((rgba >> 8) & 0xff)
/** Extract the Blue channel from a 32-bit RGBA value */
#define rgba32_get_b(rgba) (rgba & 0xff)
/** Extract the Red channel from a 16-bit RGB value */
#define rgb16_get_r(rgb) ((rgb >> 8) & 0xf8)
/** Extract the Green channel from a 16-bit RGB value */
#define rgb16_get_g(rgb) ((rgb >> 3) & 0xfc)
/** Extract the Blue channel from a 16-bit RGB value */
#define rgb16_get_b(rgb) ((rgb << 3) & 0xf8)
/** Combine Red, Green and Blue channels into a 16-bit RGB value */
#define rgb16_combine(r, g, b) \
((((r) & 0xf8) << 8) | \
(((g) & 0xfc) << 3) | \
(((b) & 0xf8) >> 3))
/** Glyph character value rype */
typedef uint16_t glyph_t;
/** Description of a glyph; a single character in a font. */
struct glyph {
glyph_t glyph; /** The glyph this entry refers to */
int16_t left; /** Offset of the left edge of the glyph */
int16_t top; /** Offset of the top edge of the glyph */
int16_t advance; /** Horizonal offset when advancing to the next glyph */
uint16_t cols; /** Width of the bitmap */
uint16_t rows; /** Height of the bitmap */
const uint8_t *bitmap; /** Bitmap data */
const struct kerning *kerning; /** Font kerning data */
};
/** Kerning table; for a pair of glyphs, provides the horizontal adjustment. */
struct kerning {
glyph_t left; /** The left-glyph */
int16_t offset; /** The kerning offset for this glyph pair */
};
/** Description of a font. */
struct font {
char *name; /** Name of the font */
char *style; /** Style of the font */
uint16_t size; /** Point size of the font */
uint16_t dpi; /** Resolution of the font */
int16_t ascender; /** Ascender height */
int16_t descender; /** Descender height */
int16_t height; /** Baseline-to-baseline height */
uint16_t count; /** Number of glyphs */
uint16_t max; /** Maximum glyph index */
const struct glyph **glyphs; /** Font glyphs */
char compressed; /** TRUE if glyph bitmaps are RLE compressed */
};
#endif /* _FONTEM_H */

74
macros.h Normal file
View File

@@ -0,0 +1,74 @@
/**
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.
**/
#ifndef _MACROS_H_
#define _MACROS_H_
#include <assert.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
/*
* Bitwise Operations
*/
#define SET(x, y) \
do { \
(x) |= (y); \
} while (0)
#define SET_POS(x, y) \
do { \
(x) |= 1u << (y); \
} while (0)
#define CLEAR(x, y) \
do { \
(x) &= ~(y); \
} while (0)
#define CLEAR_POS(x, y) \
do { \
(x) &= ~(1u << (y)); \
} while (0)
#define FLIP(x, y) \
do { \
(x) ^= (y); \
} while (0)
#define FLIP_POS(x, y) \
do { \
(x) ^= 1u << y; \
} while (0)
#define SET_TO(x, clear_mask, val) \
static_assert((clear_mask & val) == val, \
"'value' in SET_TO() has bits set that are not in clear_mask"); \
do { \
CLEAR(x, clear_mask); \
SET(x, val); \
} while (0)
#endif

221
test.c
View File

@@ -21,10 +21,15 @@
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include "stm32l0xx.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#include "macros.h"
#include "stm32l0xx.h"
#include "font-notomono-10.h"
/* TODO: Start cleaning up code and adding bounds checking! */
void SystemInit()
{
@@ -38,10 +43,10 @@ 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. */
RCC->CR |= RCC_CR_MSION;
SET(RCC->CR, RCC_CR_MSION);
CLEAR(RCC->ICSCR, RCC_ICSCR_MSIRANGE);
RCC->ICSCR &= ~RCC_ICSCR_MSIRANGE;
RCC->ICSCR |= RCC_ICSCR_MSIRANGE_6; // | RCC_ICSCR_MSIRANGE_0;;
SET_TO(RCC->ICSCR, RCC_ICSCR_MSIRANGE, RCC_ICSCR_MSIRANGE_6);
/*!< Reset
* SW[1:0] (use MSI oscillator as system clock),
@@ -50,8 +55,9 @@ void SystemInit()
* PPRE2[2:0] (do not divide APB high-speed clock),
* MCOSEL[2:0] (disable MCO clock),
* MCOPRE[2:0] (disable MCO prescaler) */
RCC->CFGR &= ~RCC_CFGR_SW & ~RCC_CFGR_HPRE & ~RCC_CFGR_PPRE1 &
~RCC_CFGR_PPRE2 & ~RCC_CFGR_MCOSEL & ~RCC_CFGR_MCOPRE;
CLEAR(RCC->CFGR,
RCC_CFGR_SW | ~RCC_CFGR_HPRE | RCC_CFGR_PPRE1 | RCC_CFGR_PPRE2 |
RCC_CFGR_MCOSEL | RCC_CFGR_MCOPRE);
/*!< Reset
* HSION (disable HSI),
@@ -60,8 +66,9 @@ void SystemInit()
* CSSHSEON (disable HSE clock monitoring)
* PLLON (disable PLL)
*/
RCC->CR &= ~RCC_CR_HSION & ~RCC_CR_HSIDIVEN & ~RCC_CR_HSEON &
~RCC_CR_CSSHSEON & ~RCC_CR_PLLON;
CLEAR(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;
@@ -77,8 +84,8 @@ void SystemInit()
/*!< Disable all interrupts */
RCC->CIER = 0x00000000;
/* Configure the Vector Table location add offset address ------------------*/
SCB->VTOR = FLASH_BASE; /* Vector Table Relocation in Internal FLASH */
/* Vector Table Relocation in Internal FLASH */
SCB->VTOR = FLASH_BASE;
}
void init_lptim()
@@ -91,8 +98,7 @@ void init_lptim()
while (!(RCC->CSR & RCC_CSR_LSIRDY)) {};
/*!< Set the LSI clock to be the source of the LPTIM */
RCC->CCIPR &= ~RCC_CCIPR_LPTIM1SEL;
RCC->CCIPR |= 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) */
@@ -134,9 +140,65 @@ void init_lptim()
}
/*!< The buffer that will be used in the future to display on the watch display. */
//static uint8_t display_buffer[144 * 168 / 8];
void init_lptim_toggler() {
#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;
void init_lptim_toggler()
{
init_lptim();
/* Assign LPTIM1_OUT to PA7 */
@@ -159,70 +221,145 @@ void init_spi_display()
/* Assign SPI_MOSI to PA12 (AFRH5), since PA7 is taken by LPTIM_OUT */
GPIOA->AFR[1] &= ~GPIO_AFRH_AFRH4;
GPIOA->MODER &= ~GPIO_MODER_MODE12;
GPIOA->MODER |= 2u << GPIO_MODER_MODE12_Pos;
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
// SPI1 NSS (PA4)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL4;
GPIOA->MODER &= ~GPIO_MODER_MODE4;
GPIOA->MODER |= 2u << GPIO_MODER_MODE4_Pos;
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
// SPI1 SCK (PA5)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL5;
GPIOA->MODER &= ~GPIO_MODER_MODE5;
GPIOA->MODER |= 2u << GPIO_MODER_MODE5_Pos;
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
// SPI1 MISO (PA6)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL6;
GPIOA->MODER &= ~GPIO_MODER_MODE6;
GPIOA->MODER |= 2u << GPIO_MODER_MODE6_Pos;
SET_TO(GPIOA->MODER, GPIO_MODER_MODE6, 2u << GPIO_MODER_MODE6_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_6;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD6;
SPI1->CR1 |= SPI_CR1_SPE | SPI_CR1_MSTR | SPI_CR1_SSOE;// | SPI_CR1_SSM | SPI_CR1_SSI;
// SPI1->CR1 |= SPI_CR1_BR;
// 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;
}
int main() {
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++;
}
int main()
{
/** Enable Port A,B clock */
RCC->IOPENR |= RCC_IOPENR_IOPAEN;
RCC->IOPENR |= RCC_IOPENR_IOPBEN;
/** Enable pin P3 for output */
GPIOB->MODER &= ~GPIO_MODER_MODE3;
GPIOB->MODER |= GPIO_MODER_MODE3_0;
SET_TO(GPIOB->MODER, GPIO_MODER_MODE3, GPIO_MODER_MODE3_0);
GPIOB->OTYPER &= ~GPIO_OTYPER_OT_3;
GPIOB->PUPDR &= GPIO_PUPDR_PUPD3;
CLEAR(GPIOB->OTYPER, GPIO_OTYPER_OT_3);
CLEAR(GPIOB->PUPDR, GPIO_PUPDR_PUPD3);
init_lptim_toggler();
init_spi_display();
// GPIOB->ODR |= GPIO_ODR_OD3;
// for (volatile int i = 0; i < 100000; i++) {}
// GPIOB->ODR &= ~GPIO_ODR_OD3;
display_buffer_init(&buffer);
while (1) {
if (SPI1->SR & SPI_SR_TXE) {
SPI1->DR = 0xA5;
GPIOB->ODR |= GPIO_ODR_OD3;
} else {
GPIOB->ODR &= ~GPIO_ODR_OD3;
}
display_update();
spi_send_blocking(SPI1, (uint8_t *) &buffer, sizeof(buffer));
FLIP(GPIOB->ODR, GPIO_ODR_OD3);
// for (int i = 0; i < 100000; i++);
}
}