293 lines
6.8 KiB
C
293 lines
6.8 KiB
C
/*
|
|
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
|
* and is covered by GPLv3 license found in COPYING.
|
|
*/
|
|
#include "oled.h"
|
|
#include "delay.h"
|
|
#include "stm32l4xx_hal_gpio.h"
|
|
#include "stm32l4xx_hal_rcc.h"
|
|
#include "stm32l4xx_hal_dma.h"
|
|
#include "stm32l4xx_hal_spi.h"
|
|
#include <string.h>
|
|
|
|
// Reset and config sequence.
|
|
//
|
|
// As measured! No attempt to understand them here.
|
|
static const uint8_t reset_commands[] = {
|
|
174, 32, 0, 64, 161, 168, 63, 200,
|
|
211, 0, 218, 18, 213, 128, 217, 241,
|
|
219, 48, 129, 255, 164, 166, 141, 20, 175
|
|
};
|
|
|
|
// Bytes to send before sending the 1024 bytes of pixel data.
|
|
//
|
|
static const uint8_t before_show[] = { 33, 0, 127, 34, 0, 7 };
|
|
|
|
// OLED connections:
|
|
// - all on port A
|
|
// - all push/pull outputs
|
|
//
|
|
#define RESET_PIN GPIO_PIN_6
|
|
#define DC_PIN GPIO_PIN_8
|
|
#define CS_PIN GPIO_PIN_4
|
|
#define SPI_SCK GPIO_PIN_5
|
|
#define SPI_MOSI GPIO_PIN_7
|
|
|
|
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
|
|
|
|
static SPI_HandleTypeDef spi_port;
|
|
|
|
// write_bytes()
|
|
//
|
|
static inline void
|
|
write_bytes(int len, const uint8_t *buf)
|
|
{
|
|
// send via SPI(1)
|
|
HAL_SPI_Transmit(&spi_port, (uint8_t *)buf, len, HAL_MAX_DELAY);
|
|
}
|
|
|
|
// oled_write_cmd()
|
|
//
|
|
void
|
|
oled_write_cmd(uint8_t cmd)
|
|
{
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, DC_PIN, 0);
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 0);
|
|
|
|
write_bytes(1, &cmd);
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
}
|
|
|
|
// oled_write_cmd_sequence()
|
|
//
|
|
void
|
|
oled_write_cmd_sequence(int len, const uint8_t *cmds)
|
|
{
|
|
for(int i=0; i<len; i++) {
|
|
oled_write_cmd(cmds[i]);
|
|
}
|
|
}
|
|
|
|
// oled_write_data()
|
|
//
|
|
void
|
|
oled_write_data(int len, const uint8_t *pixels)
|
|
{
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, DC_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 0);
|
|
|
|
write_bytes(len, pixels);
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
}
|
|
|
|
// oled_setup()
|
|
//
|
|
// Ok to call this lots.
|
|
//
|
|
void
|
|
oled_setup(void)
|
|
{
|
|
static uint32_t inited;
|
|
|
|
if(inited == 0x238a572F) {
|
|
return;
|
|
}
|
|
inited = 0x238a572F;
|
|
|
|
// enable some internal clocks
|
|
__HAL_RCC_GPIOA_CLK_ENABLE();
|
|
__HAL_RCC_SPI1_CLK_ENABLE();
|
|
|
|
// simple pins
|
|
GPIO_InitTypeDef setup = {
|
|
.Pin = RESET_PIN | DC_PIN | CS_PIN,
|
|
.Mode = GPIO_MODE_OUTPUT_PP,
|
|
.Pull = GPIO_NOPULL,
|
|
.Speed = GPIO_SPEED_FREQ_MEDIUM,
|
|
.Alternate = 0,
|
|
};
|
|
HAL_GPIO_Init(GPIOA, &setup);
|
|
|
|
// starting values
|
|
HAL_GPIO_WritePin(GPIOA, RESET_PIN | CS_PIN | DC_PIN, 1);
|
|
|
|
// SPI pins
|
|
setup.Pin = SPI_SCK | SPI_MOSI;
|
|
setup.Mode = GPIO_MODE_AF_PP;
|
|
setup.Alternate = GPIO_AF5_SPI1;
|
|
HAL_GPIO_Init(GPIOA, &setup);
|
|
|
|
// lock the RESET pin so that St's DFU code doesn't clear screen
|
|
// it might be trying to use it a MISO signal for SPI loading
|
|
HAL_GPIO_LockPin(GPIOA, RESET_PIN | CS_PIN | DC_PIN);
|
|
|
|
// 10ms low-going pulse on reset pin
|
|
delay_ms(1);
|
|
HAL_GPIO_WritePin(GPIOA, RESET_PIN, 0);
|
|
delay_ms(10);
|
|
HAL_GPIO_WritePin(GPIOA, RESET_PIN, 1);
|
|
|
|
memset(&spi_port, 0, sizeof(spi_port));
|
|
|
|
spi_port.Instance = SPI1;
|
|
|
|
// see SPI_InitTypeDef
|
|
spi_port.Init.Mode = SPI_MODE_MASTER;
|
|
spi_port.Init.Direction = SPI_DIRECTION_2LINES;
|
|
spi_port.Init.DataSize = SPI_DATASIZE_8BIT;
|
|
spi_port.Init.CLKPolarity = SPI_POLARITY_LOW;
|
|
spi_port.Init.CLKPhase = SPI_PHASE_1EDGE;
|
|
spi_port.Init.NSS = SPI_NSS_SOFT;
|
|
spi_port.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // conservative
|
|
spi_port.Init.FirstBit = SPI_FIRSTBIT_MSB;
|
|
spi_port.Init.TIMode = SPI_TIMODE_DISABLED;
|
|
spi_port.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
|
|
|
|
HAL_SPI_Init(&spi_port);
|
|
|
|
// where: SPI1->CR1, CR2, SR
|
|
// mpy settings:
|
|
// '0x354', '0x1700', '0x002'
|
|
// this code:
|
|
// '0x37c', '0x1700', '0x603'
|
|
//SPI1->CR1 = 0x354;
|
|
|
|
// write a sequence to reset things
|
|
oled_write_cmd_sequence(sizeof(reset_commands), reset_commands);
|
|
}
|
|
|
|
// oled_show_raw()
|
|
//
|
|
// No decompression.
|
|
//
|
|
void
|
|
oled_show_raw(uint32_t len, const uint8_t *pixels)
|
|
{
|
|
oled_setup();
|
|
|
|
oled_write_cmd_sequence(sizeof(before_show), before_show);
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, DC_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 0);
|
|
|
|
write_bytes(len, pixels);
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
}
|
|
|
|
// oled_show()
|
|
//
|
|
// Perform simple RLE decompression.
|
|
//
|
|
void
|
|
oled_show(const uint8_t *pixels)
|
|
{
|
|
oled_setup();
|
|
|
|
oled_write_cmd_sequence(sizeof(before_show), before_show);
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, DC_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 0);
|
|
|
|
uint8_t buf[127];
|
|
const uint8_t *p = pixels;
|
|
|
|
// NOTE: must also update code in oled_show_progress, which dups this heavily.
|
|
while(1) {
|
|
uint8_t hdr = *(p++);
|
|
if(!hdr) break;
|
|
|
|
uint8_t len = hdr & 0x7f;
|
|
if(hdr & 0x80) {
|
|
// random bytes follow
|
|
memcpy(buf, p, len);
|
|
p += len;
|
|
} else {
|
|
// repeat same byte
|
|
memset(buf, *p, len);
|
|
p++;
|
|
}
|
|
|
|
write_bytes(len, buf);
|
|
}
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
}
|
|
|
|
// oled_show_progress()
|
|
//
|
|
// Perform simple RLE decompression, and add a bar on final screen line.
|
|
//
|
|
void
|
|
oled_show_progress(const uint8_t *pixels, int progress)
|
|
{
|
|
oled_setup();
|
|
|
|
oled_write_cmd_sequence(sizeof(before_show), before_show);
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, DC_PIN, 1);
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 0);
|
|
|
|
uint8_t buf[127];
|
|
const uint8_t *p = pixels;
|
|
|
|
const uint16_t p_start = 896;
|
|
uint32_t p_count = 1280 * progress / 1000;
|
|
|
|
if(p_count > 128) p_count = 128;
|
|
if(p_count < 0) p_count = 0;
|
|
|
|
bool last_line = false;
|
|
|
|
uint16_t offset = 0;
|
|
while(1) {
|
|
uint8_t hdr = *(p++);
|
|
if(hdr == 0) break;
|
|
|
|
uint8_t len = hdr & 0x7f;
|
|
if(hdr & 0x80) {
|
|
// random bytes follow
|
|
memcpy(buf, p, len);
|
|
p += len;
|
|
} else {
|
|
// repeat same byte
|
|
memset(buf, *p, len);
|
|
p++;
|
|
}
|
|
|
|
if(!last_line && (offset+len) >= p_start) {
|
|
last_line = true;
|
|
|
|
// adjust so we're aligned w/ last line
|
|
int h = p_start - offset;
|
|
if(h) {
|
|
write_bytes(h, buf);
|
|
memmove(buf, buf+h, len-h);
|
|
len -= h;
|
|
offset += h;
|
|
}
|
|
}
|
|
|
|
if(last_line) {
|
|
for(int j=0; (p_count > 0) && (j<len); j++, p_count--) {
|
|
buf[j] |= 0x80;
|
|
}
|
|
}
|
|
|
|
write_bytes(len, buf);
|
|
offset += len;
|
|
}
|
|
|
|
HAL_GPIO_WritePin(GPIOA, CS_PIN, 1);
|
|
}
|
|
|
|
|
|
// EOF
|