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:
221
test.c
221
test.c
@@ -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++);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user