mirror of
https://github.com/ClusterM/fdskey.git
synced 2025-12-16 19:15:54 +01:00
515 lines
16 KiB
C
515 lines
16 KiB
C
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <math.h>
|
|
#include "main.h"
|
|
#include "oled.h"
|
|
|
|
static OLED_CONTROLLER controller;
|
|
static uint8_t rotate = 0;
|
|
static uint8_t current_line = 0;
|
|
static uint8_t image[OLED_HEIGHT * 2 * OLED_WIDTH];
|
|
|
|
// init OLED and buffer
|
|
void oled_init(OLED_CONTROLLER oled_controller, uint8_t rotate_screen, uint8_t reverse, uint8_t contrast)
|
|
{
|
|
controller = oled_controller;
|
|
rotate = rotate_screen;
|
|
uint8_t padding_top = rotate_screen ? 32 : 0;
|
|
|
|
oled_send_commands(11,
|
|
OLED_CMD_SET_OFF,
|
|
0x8D, 0x14, // enable charge pump (???)
|
|
reverse ? OLED_CMD_SET_REVERSE_ON : OLED_CMD_SET_REVERSE_OFF,
|
|
OLED_CMD_SET_START_LINE(current_line + padding_top),
|
|
OLED_CMD_SET_PADS_MODE, OLED_CMD_SET_PADS_MODE_SEQUENTIAL,
|
|
rotate_screen ?
|
|
OLED_CMD_SET_VERTICAL_FLIP_ON :
|
|
OLED_CMD_SET_VERTICAL_FLIP_OFF,
|
|
rotate_screen ?
|
|
OLED_CMD_SET_HORIZONTAL_FLIP_ON :
|
|
OLED_CMD_SET_HORIZONTAL_FLIP_OFF,
|
|
OLED_CMD_SET_CONTRAST_MODE, contrast);
|
|
memset(image, 0, sizeof(image));
|
|
oled_update_full();
|
|
oled_send_commands(1, OLED_CMD_SET_ON);
|
|
}
|
|
|
|
// return pointer to a pixel
|
|
uint8_t* oled_pixel(int x, int y) {
|
|
return image + (y % (OLED_HEIGHT * 2)) * OLED_WIDTH + (x % OLED_WIDTH);
|
|
}
|
|
|
|
// set single pixel
|
|
void oled_set_pixel(int x, int y, uint8_t value) {
|
|
*oled_pixel(x, y) = value;
|
|
}
|
|
|
|
// get pixel value
|
|
uint8_t oled_get_pixel(int x, int y) {
|
|
return *oled_pixel(x, y);
|
|
}
|
|
|
|
// send commads to OLED controller
|
|
HAL_StatusTypeDef oled_send_commands(int len, ...) {
|
|
uint8_t buffer[len * 2];
|
|
va_list valist;
|
|
va_start(valist, len);
|
|
|
|
int i;
|
|
for (i = 0; i < len; i++) {
|
|
buffer[i * 2] = i + 1 < len ? OLED_COMMAND_NOT_LAST : OLED_COMMAND_LAST;
|
|
buffer[i * 2 + 1] = (uint8_t) va_arg(valist, int);
|
|
}
|
|
HAL_StatusTypeDef r = HAL_I2C_Master_Transmit(&OLED_I2C, OLED_ADDRESS << 1, buffer,
|
|
sizeof(buffer), OLED_TIMEOUT);
|
|
return r;
|
|
}
|
|
|
|
// send single command to OLED controller
|
|
HAL_StatusTypeDef oled_send_command(uint8_t command) {
|
|
return oled_send_commands(1, command);
|
|
}
|
|
|
|
// write data to OLED
|
|
HAL_StatusTypeDef oled_write_data(uint8_t *data, uint8_t len) {
|
|
uint8_t buffer[len + 1];
|
|
buffer[0] = OLED_COMMAND_DATA;
|
|
memcpy(&buffer[1], data, len);
|
|
return HAL_I2C_Master_Transmit(&OLED_I2C, OLED_ADDRESS << 1, buffer, sizeof(buffer),
|
|
OLED_TIMEOUT);
|
|
}
|
|
|
|
// transfer data from our buffer to OLED buffer
|
|
HAL_StatusTypeDef oled_update(uint8_t start_page, uint8_t end_page) {
|
|
uint8_t p, x, y, l, bt, buffer[256], bpos;
|
|
HAL_StatusTypeDef r = HAL_OK;
|
|
uint8_t padding_left = rotate ? 0 : (controller == OLED_CONTROLLER_SSD1306 ? 0 : 4);
|
|
|
|
start_page = start_page % (OLED_HEIGHT * 2 / 8);
|
|
end_page = end_page % (OLED_HEIGHT * 2 / 8);
|
|
if (end_page < start_page) end_page += (OLED_HEIGHT * 2 / 8);
|
|
|
|
for (p = start_page; p < end_page + 1; p++) {
|
|
oled_send_commands(3, OLED_CMD_SET_PAGE(p % 8),
|
|
OLED_CMD_SET_COLUMN_LOW(padding_left),
|
|
OLED_CMD_SET_COLUMN_HIGH(padding_left));
|
|
bpos = 0;
|
|
for (x = 0; x < OLED_WIDTH; x++) {
|
|
bt = 0;
|
|
for (l = 0; l < 8; l++) {
|
|
y = (p * 8 + l) % (OLED_HEIGHT * 2);
|
|
bt >>= 1;
|
|
if (*oled_pixel(x, y))
|
|
bt |= 0x80;
|
|
}
|
|
buffer[bpos++] = bt;
|
|
if (bpos >= sizeof(buffer) || x == OLED_WIDTH - 1) {
|
|
r = oled_write_data(buffer, bpos);
|
|
if (r != HAL_OK)
|
|
return r;
|
|
bpos = 0;
|
|
}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// transfer all data from our buffer to OLED buffer
|
|
HAL_StatusTypeDef oled_update_full() {
|
|
return oled_update(0, OLED_HEIGHT * 2 / 8 - 1);
|
|
}
|
|
|
|
// transfer invisible data from our buffer to OLED buffer
|
|
HAL_StatusTypeDef oled_update_invisible() {
|
|
uint8_t start_page = ((current_line + OLED_HEIGHT) % (OLED_HEIGHT * 2)) / 8;
|
|
uint8_t end_page = start_page
|
|
+ ((current_line % 8 == 0) ? OLED_HEIGHT / 8 - 1 : OLED_HEIGHT / 8);
|
|
return oled_update(start_page, end_page);
|
|
}
|
|
|
|
// scroll to line
|
|
HAL_StatusTypeDef oled_set_line(int y) {
|
|
uint8_t padding_top = rotate ? 32 : 0;
|
|
current_line = y % (OLED_HEIGHT * 2);
|
|
return oled_send_command(
|
|
OLED_CMD_SET_START_LINE(current_line + padding_top));
|
|
}
|
|
|
|
// get current scrolling
|
|
uint8_t oled_get_line() {
|
|
return current_line;
|
|
}
|
|
|
|
// copy visible buffer to invisible
|
|
void oled_copy_to_invisible() {
|
|
int y;
|
|
for (y = 0; y < OLED_HEIGHT; y++) {
|
|
memcpy(oled_pixel(0, current_line + y + OLED_HEIGHT),
|
|
oled_pixel(0, current_line + y), OLED_WIDTH);
|
|
}
|
|
}
|
|
|
|
// switch OLED to invisible buffer
|
|
void oled_switch_to_invisible() {
|
|
oled_set_line(current_line + OLED_HEIGHT);
|
|
}
|
|
|
|
// draw rectangle
|
|
void oled_draw_rectangle(int x1, int y1, int x2, int y2,
|
|
uint8_t fill, uint8_t value) {
|
|
int x, y;
|
|
|
|
if (x2 < x1) {
|
|
x = x1;
|
|
x1 = x2;
|
|
x2 = x;
|
|
}
|
|
if (y2 < y1) {
|
|
y = y1;
|
|
y1 = y2;
|
|
y2 = y;
|
|
}
|
|
|
|
for (y = y1; y <= y2; y++) {
|
|
if (fill || y == y1 || y == y2) {
|
|
for (x = x1; x <= x2; x++) {
|
|
*oled_pixel(x, y) = value;
|
|
}
|
|
} else {
|
|
*oled_pixel(x1, y) = *oled_pixel(x2, y) = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// draw line
|
|
void oled_draw_line(int x1, int y1, int x2, int y2,
|
|
uint8_t value) {
|
|
int x, y;
|
|
|
|
if (x1 == x2 || y1 == y2) {
|
|
oled_draw_rectangle(x1, y1, x2, y2, 0, value);
|
|
} else if (abs(x2 - x1) >= abs(y2 - y1)) {
|
|
if (x2 > x1)
|
|
for (x = x1; x <= x2; x++)
|
|
*oled_pixel(x,
|
|
(int) round(
|
|
(float) y1
|
|
+ ((float) y2 - (float) y1)
|
|
* ((float) x - (float) x1)
|
|
/ ((float) x2 - (float) x1))) =
|
|
value;
|
|
else
|
|
for (x = x2; x <= x1; x++)
|
|
*oled_pixel(x,
|
|
(int) round(
|
|
(float) y2
|
|
+ ((float) y2 - (float) y1)
|
|
* ((float) x2 - (float) x)
|
|
/ ((float) x1 - (float) x2))) =
|
|
value;
|
|
} else {
|
|
if (y2 > y1)
|
|
for (y = y1; y <= y2; y++)
|
|
*oled_pixel(
|
|
(int) round(
|
|
(float) x1
|
|
+ ((float) x2 - (float) x1)
|
|
* ((float) y - (float) y1)
|
|
/ ((float) y2 - (float) y1)), y) =
|
|
value;
|
|
else
|
|
for (y = y2; y <= y1; y++)
|
|
*oled_pixel(
|
|
(int) round(
|
|
(float) x2
|
|
+ ((float) x2 - (float) x1)
|
|
* ((float) y2 - (float) y)
|
|
/ ((float) y1 - (float) y2)), y) =
|
|
value;
|
|
}
|
|
}
|
|
|
|
// draw text
|
|
void oled_draw_text(const DotMatrixFont *font, char *text, int x, int y,
|
|
uint8_t replace, uint8_t invert) {
|
|
oled_draw_text_cropped(font, text, x, y, 0, 0, 0, 0, replace, invert);
|
|
}
|
|
|
|
// draw cropped text
|
|
void oled_draw_text_cropped(const DotMatrixFont *font, char *text, int x, int y,
|
|
uint8_t start_x, uint8_t max_width, uint8_t start_y, uint8_t max_height,
|
|
uint8_t replace, uint8_t invert) {
|
|
int xpos = x; // current x position
|
|
int len = 0; // total text length
|
|
int c, l; // column/line of current character
|
|
int xp, yp; // temporary variables for dot coordinates
|
|
uint8_t *char_data; // pointer for character data
|
|
uint64_t char_data_casted; // unboxed character data
|
|
uint8_t char_width; // current character width
|
|
uint8_t size_offset = font->font_type;
|
|
while (*text) {
|
|
char ch = *text;
|
|
// replace unknown characters with underscore
|
|
if (ch < font->start_char || ch >= font->start_char + font->font_length)
|
|
ch = '_';
|
|
|
|
// get character length
|
|
if (font->char_height <= 8)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width + size_offset)];
|
|
else if (font->char_height <= 16)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * sizeof(uint16_t) + size_offset)];
|
|
else if (font->char_height <= 24)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 3 + size_offset)];
|
|
else if (font->char_height <= 32)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * sizeof(uint32_t) + size_offset)];
|
|
else if (font->char_height <= 40)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 5 + size_offset)];
|
|
else if (font->char_height <= 48)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 6 + size_offset)];
|
|
else if (font->char_height <= 56)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 7 + size_offset)];
|
|
else
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * sizeof(uint64_t) + size_offset)];
|
|
|
|
// resize spaces if need
|
|
if (font->font_type == 0) // monospaced
|
|
{
|
|
char_width = font->char_width;
|
|
} else {
|
|
char_width =
|
|
(ch != ' ' || !font->space_length) ?
|
|
char_data[0] : font->space_length;
|
|
}
|
|
|
|
// for every column of character
|
|
for (c = 0; c < char_width; c++) {
|
|
// stop if maximum length meet
|
|
if (max_width && len - start_x > max_width)
|
|
return;
|
|
// skip columns
|
|
if (start_x && start_x > len) {
|
|
len++;
|
|
continue;
|
|
}
|
|
|
|
// unbox character data
|
|
if (font->char_height <= 8)
|
|
char_data_casted = *((uint8_t*) (char_data + c + size_offset));
|
|
else if (font->char_height <= 16)
|
|
{
|
|
uint8_t b1 = *(char_data + c * 2 + size_offset);
|
|
uint8_t b2 = *(char_data + c * 2 + size_offset + 1);
|
|
char_data_casted = b1 | (b2 << 8);
|
|
} else if (font->char_height <= 24) {
|
|
uint8_t b1 = *(char_data + c * 3 + size_offset);
|
|
uint8_t b2 = *(char_data + c * 3 + size_offset + 1);
|
|
uint8_t b3 = *(char_data + c * 3 + size_offset + 2);
|
|
char_data_casted = b1 | (b2 << 8) | (b3 << 16);
|
|
} else if (font->char_height <= 32) {
|
|
uint8_t b1 = *(char_data + c * 4 + size_offset);
|
|
uint8_t b2 = *(char_data + c * 4 + size_offset + 1);
|
|
uint8_t b3 = *(char_data + c * 4 + size_offset + 2);
|
|
uint8_t b4 = *(char_data + c * 4 + size_offset + 3);
|
|
char_data_casted = b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
|
|
} else if (font->char_height <= 40) {
|
|
uint8_t b1 = *(char_data + c * 5 + size_offset);
|
|
uint8_t b2 = *(char_data + c * 5 + size_offset + 1);
|
|
uint8_t b3 = *(char_data + c * 5 + size_offset + 2);
|
|
uint8_t b4 = *(char_data + c * 5 + size_offset + 3);
|
|
uint8_t b5 = *(char_data + c * 5 + size_offset + 4);
|
|
char_data_casted = (uint64_t)b1 | ((uint64_t)b2 << 8) | ((uint64_t)b3 << 16) | ((uint64_t)b4 << 24) | ((uint64_t)b5 << 32);
|
|
} else if (font->char_height <= 48) {
|
|
uint8_t b1 = *(char_data + c * 6 + size_offset);
|
|
uint8_t b2 = *(char_data + c * 6 + size_offset + 1);
|
|
uint8_t b3 = *(char_data + c * 6 + size_offset + 2);
|
|
uint8_t b4 = *(char_data + c * 6 + size_offset + 3);
|
|
uint8_t b5 = *(char_data + c * 6 + size_offset + 4);
|
|
uint8_t b6 = *(char_data + c * 6 + size_offset + 5);
|
|
char_data_casted = (uint64_t)b1 | ((uint64_t)b2 << 8) | ((uint64_t)b3 << 16) | ((uint64_t)b4 << 24) | ((uint64_t)b5 << 32) | ((uint64_t)b6 << 40);
|
|
} else if (font->char_height <= 56) {
|
|
uint8_t b1 = *(char_data + c * 7 + size_offset);
|
|
uint8_t b2 = *(char_data + c * 7 + size_offset + 1);
|
|
uint8_t b3 = *(char_data + c * 7 + size_offset + 2);
|
|
uint8_t b4 = *(char_data + c * 7 + size_offset + 3);
|
|
uint8_t b5 = *(char_data + c * 7 + size_offset + 4);
|
|
uint8_t b6 = *(char_data + c * 7 + size_offset + 5);
|
|
uint8_t b7 = *(char_data + c * 7 + size_offset + 6);
|
|
char_data_casted = (uint64_t)b1 | ((uint64_t)b2 << 8) | ((uint64_t)b3 << 16) | ((uint64_t)b4 << 24) | ((uint64_t)b5 << 32) | ((uint64_t)b6 << 40) | ((uint64_t)b7 << 48);
|
|
} else {
|
|
uint8_t b1 = *(char_data + c * 8 + size_offset);
|
|
uint8_t b2 = *(char_data + c * 8 + size_offset + 1);
|
|
uint8_t b3 = *(char_data + c * 8 + size_offset + 2);
|
|
uint8_t b4 = *(char_data + c * 8 + size_offset + 3);
|
|
uint8_t b5 = *(char_data + c * 8 + size_offset + 4);
|
|
uint8_t b6 = *(char_data + c * 8 + size_offset + 5);
|
|
uint8_t b7 = *(char_data + c * 8 + size_offset + 6);
|
|
uint8_t b8 = *(char_data + c * 8 + size_offset + 7);
|
|
char_data_casted = (uint64_t)b1 | ((uint64_t)b2 << 8) | ((uint64_t)b3 << 16) | ((uint64_t)b4 << 24) | ((uint64_t)b5 << 32) | ((uint64_t)b6 << 40) | ((uint64_t)b7 << 48) | ((uint64_t)b8 << 56);
|
|
}
|
|
|
|
// for every line of character (or until max height)
|
|
for (l = start_y;
|
|
(l < font->char_height)
|
|
&& (!max_height || l - start_y < max_height); l++) {
|
|
xp = xpos + c - start_x;
|
|
yp = y + l - start_y;
|
|
if (char_data_casted & (1 << l))
|
|
*oled_pixel(xp, yp) = !invert;
|
|
else if (replace)
|
|
*oled_pixel(xp, yp) = invert;
|
|
}
|
|
len++;
|
|
}
|
|
|
|
text++;
|
|
// fill gap between characters
|
|
if (font->spacing > 0 && replace && *text)
|
|
oled_draw_rectangle(xpos + char_width, y,
|
|
xpos + char_width + font->spacing - 1, y + font->char_height - 1,
|
|
1, invert);
|
|
xpos += char_width + (*text ? font->spacing : 0);
|
|
if (*text)
|
|
len += font->spacing;
|
|
}
|
|
}
|
|
|
|
// calculate text length
|
|
int oled_get_text_length(const DotMatrixFont *font, char *text) {
|
|
int len = 0; // total text length
|
|
uint8_t *char_data; // pointer for character data
|
|
uint8_t char_width; // current character width
|
|
uint8_t size_offset = font->font_type;
|
|
while (*text) {
|
|
char ch = *text;
|
|
// replace unknown characters with underscore
|
|
if (ch < font->start_char || ch >= font->start_char + font->font_length)
|
|
ch = '_';
|
|
|
|
// get character length
|
|
if (font->char_height <= 8)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width + size_offset)];
|
|
else if (font->char_height <= 16)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * sizeof(uint16_t) + size_offset)];
|
|
else if (font->char_height <= 24)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 3 + size_offset)];
|
|
else if (font->char_height <= 32)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * sizeof(uint32_t) + size_offset)];
|
|
else if (font->char_height <= 40)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 5 + size_offset)];
|
|
else if (font->char_height <= 48)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 6 + size_offset)];
|
|
else if (font->char_height <= 56)
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * 7 + size_offset)];
|
|
else
|
|
char_data = &((uint8_t*) font->font_data)[(ch - font->start_char)
|
|
* (font->char_width * sizeof(uint64_t) + size_offset)];
|
|
|
|
// resize spaces if need
|
|
if (font->font_type == 0) // monospaced
|
|
{
|
|
char_width = font->char_width;
|
|
} else {
|
|
char_width =
|
|
(ch != ' ' || !font->space_length) ?
|
|
char_data[0] : font->space_length;
|
|
}
|
|
|
|
text++;
|
|
len += char_width + (*text ? font->spacing : 0);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
// draw image
|
|
void oled_draw_image(const DotMatrixImage *img, int x, int y,
|
|
uint8_t replace, uint8_t invert) {
|
|
oled_draw_image_cropped(img, x, y, 0, 0, 0, 0, replace, invert);
|
|
int c, l;
|
|
uint8_t bit = 0;
|
|
int pos = 0;
|
|
|
|
for (l = 0; l < img->height; l++) {
|
|
for (c = 0; c < img->width; c++) {
|
|
if (img->image_data[pos] & (1 << bit))
|
|
*oled_pixel(x + c, y + l) = !invert;
|
|
else if (replace)
|
|
*oled_pixel(x + c, y + l) = invert;
|
|
bit++;
|
|
if (bit >= 8) {
|
|
bit = 0;
|
|
pos++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// draw cropped image
|
|
void oled_draw_image_cropped(const DotMatrixImage *img, int x, int y,
|
|
uint8_t start_x, uint8_t max_width, uint8_t start_y, uint8_t max_height,
|
|
uint8_t replace, uint8_t invert) {
|
|
int c, l;
|
|
uint8_t bit = 0;
|
|
int pos = 0;
|
|
if (!max_width)
|
|
max_width = img->width;
|
|
if (!max_height)
|
|
max_height = img->height;
|
|
|
|
for (l = 0; l < img->height; l++) {
|
|
for (c = 0; c < img->width; c++) {
|
|
if (c >= start_x && l >= start_y && c < max_width + start_x
|
|
&& l < max_height + start_y) {
|
|
if (img->image_data[pos] & (1 << bit))
|
|
*oled_pixel(x + c - start_x, y + l - start_y) = !invert;
|
|
else if (replace)
|
|
*oled_pixel(x + c - start_x, y + l - start_y) = invert;
|
|
}
|
|
bit++;
|
|
if (bit >= 8) {
|
|
bit = 0;
|
|
pos++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// rotate screen, keep buffer and update screen
|
|
void oled_rotate(uint8_t rotate_screen)
|
|
{
|
|
if (!!rotate == !!rotate_screen)
|
|
return;
|
|
|
|
uint8_t padding_top = rotate_screen ? 32 : 0;
|
|
|
|
oled_send_command(OLED_CMD_SET_OFF);
|
|
oled_send_command(OLED_CMD_SET_START_LINE(current_line + padding_top));
|
|
oled_send_commands(2,
|
|
rotate_screen ?
|
|
OLED_CMD_SET_VERTICAL_FLIP_ON :
|
|
OLED_CMD_SET_VERTICAL_FLIP_OFF,
|
|
rotate_screen ?
|
|
OLED_CMD_SET_HORIZONTAL_FLIP_ON :
|
|
OLED_CMD_SET_HORIZONTAL_FLIP_OFF
|
|
);
|
|
oled_update_full();
|
|
oled_send_command(OLED_CMD_SET_ON);
|
|
rotate = rotate_screen;
|
|
}
|