#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "display_fill_pattern.h"
// ------------------------------------------------------------------------------
// - Tlačítka -
// ------------------------------------------------------------------------------
// Definice pinů připojených tlačítek
#define BTN_ESC 12 // Pin tlačítka ESC
#define BTN_UP 13 // Pin tlačítka UP
#define BTN_DN 14 // Pin tlačítka DOWN
#define BTN_ENT 15 // Pin tlačítka ENTER
#define BTN_DEBOUNCE_MS 20 // Debounce zpoždění tlačítka v milisekundách
// Seznam tlačítek bude v poli pro univerzální inicializaci
#define BTN_ARRAY {BTN_ESC, BTN_UP, BTN_DN, BTN_ENT}
// Bzučák
#define BUZZER 16 // Pin pro bzučák
#define BUZ_MS 10 // Délka pípnutí
#define LED_MS 10 // Délka bliknutí
// Proměnné pro sledování tlačítek
volatile uint8_t btn_id = 0;
bool btn_hold = false;
uint32_t btn_time_released = 0;
// Obslužná rutina přerušení GPIO (Interrupt Service Routine - ISR)
void gpio_callback(uint gpio, uint32_t events)
{
btn_id = gpio; // Předá ID tlačítka, které bylo stisknuto
}
// inicializace vstupů tlačítek a callback funkce přerušení
void btn_init(uint8_t *btn_array)
{
for (int i = 0; i < sizeof(btn_array); i++)
{
gpio_init(btn_array[i]); // Inicializujeme piny
gpio_set_dir(btn_array[i], GPIO_IN); // Nastavíme piny na vstupy
gpio_pull_up(btn_array[i]); // Nastavíme vestavěné pull-up rezistory (aktivace tlačítka je z 1 na 0)
if (i == 0)
{
// První tlačítko nastaví callback funkci přerušení
gpio_set_irq_enabled_with_callback(btn_array[i], GPIO_IRQ_EDGE_FALL, true, &gpio_callback);
}
else
{
// Ostatní tlačítka již využijí nastavenou callback funkci
gpio_set_irq_enabled(btn_array[i], GPIO_IRQ_EDGE_FALL, true);
}
}
}
// Inicializace bzučáku
void buzzer_init()
{
gpio_init(BUZZER);
gpio_set_dir(BUZZER, GPIO_OUT);
}
void buzzer_beep_ms(int delay)
{
gpio_put(BUZZER, 1);
sleep_ms(delay);
gpio_put(BUZZER, 0);
}
void led_blink_ms(int delay)
{
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
sleep_ms(delay);
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
}
// ------------------------------------------------------------------------------
// - Displej ssd1306 OLED -
// ------------------------------------------------------------------------------
#define SSD1306_HEIGHT 32
#define SSD1306_WIDTH 128
#define SSD1306_I2C_ADDR _u(0x3C)
#define SSD1306_I2C_CLK 400
// commands (see datasheet)
#define SSD1306_SET_MEM_MODE _u(0x20)
#define SSD1306_SET_COL_ADDR _u(0x21)
#define SSD1306_SET_PAGE_ADDR _u(0x22)
#define SSD1306_SET_HORIZ_SCROLL _u(0x26)
#define SSD1306_SET_SCROLL _u(0x2E)
#define SSD1306_SET_DISP_START_LINE _u(0x40)
#define SSD1306_SET_CONTRAST _u(0x81)
#define SSD1306_SET_CHARGE_PUMP _u(0x8D)
#define SSD1306_SET_SEG_REMAP _u(0xA0)
#define SSD1306_SET_ENTIRE_ON _u(0xA4)
#define SSD1306_SET_ALL_ON _u(0xA5)
#define SSD1306_SET_NORM_DISP _u(0xA6)
#define SSD1306_SET_INV_DISP _u(0xA7)
#define SSD1306_SET_MUX_RATIO _u(0xA8)
#define SSD1306_SET_DISP _u(0xAE)
#define SSD1306_SET_COM_OUT_DIR _u(0xC0)
#define SSD1306_SET_COM_OUT_DIR_FLIP _u(0xC0)
#define SSD1306_SET_DISP_OFFSET _u(0xD3)
#define SSD1306_SET_DISP_CLK_DIV _u(0xD5)
#define SSD1306_SET_PRECHARGE _u(0xD9)
#define SSD1306_SET_COM_PIN_CFG _u(0xDA)
#define SSD1306_SET_VCOM_DESEL _u(0xDB)
#define SSD1306_PAGE_HEIGHT _u(8)
#define SSD1306_NUM_PAGES (SSD1306_HEIGHT / SSD1306_PAGE_HEIGHT)
#define SSD1306_BUF_LEN (SSD1306_NUM_PAGES * SSD1306_WIDTH)
#define SSD1306_WRITE_MODE _u(0xFE)
#define SSD1306_READ_MODE _u(0xFF)
struct render_area {
uint8_t start_col;
uint8_t end_col;
uint8_t start_page;
uint8_t end_page;
int buflen;
};
void calc_render_area_buflen(struct render_area *area) {
// calculate how long the flattened buffer will be for a render area
area->buflen = (area->end_col - area->start_col + 1) * (area->end_page - area->start_page + 1);
}
void SSD1306_send_cmd(uint8_t cmd) {
// I2C write process expects a control byte followed by data
// this "data" can be a command or data to follow up a command
// Co = 1, D/C = 0 => the driver expects a command
uint8_t buf[2] = {0x80, cmd};
i2c_write_blocking(i2c_default, SSD1306_I2C_ADDR, buf, 2, false);
}
void SSD1306_send_cmd_list(uint8_t *buf, int num) {
for (int i = 0; i < num; i++)
SSD1306_send_cmd(buf[i]);
}
void SSD1306_send_buf(uint8_t buf[], int buflen) {
// in horizontal addressing mode, the column address pointer auto-increments
// and then wraps around to the next page, so we can send the entire frame
// buffer in one gooooooo!
// copy our frame buffer into a new buffer because we need to add the control byte
// to the beginning
uint8_t *temp_buf = malloc(buflen + 1);
temp_buf[0] = 0x40;
memcpy(temp_buf + 1, buf, buflen);
i2c_write_blocking(i2c_default, SSD1306_I2C_ADDR, temp_buf, buflen + 1, false);
free(temp_buf);
}
void SSD1306_init() {
// Some of these commands are not strictly necessary as the reset
// process defaults to some of these but they are shown here
// to demonstrate what the initialization sequence looks like
// Some configuration values are recommended by the board manufacturer
uint8_t cmds[] = {
SSD1306_SET_DISP, // set display off
/* memory mapping */
SSD1306_SET_MEM_MODE, // set memory address mode 0 = horizontal, 1 = vertical, 2 = page
0x01, // horizontal addressing mode
/* resolution and layout */
SSD1306_SET_DISP_START_LINE, // set display start line to 0
SSD1306_SET_SEG_REMAP | 0x01, // set segment re-map, column address 127 is mapped to SEG0
SSD1306_SET_MUX_RATIO, // set multiplex ratio
SSD1306_HEIGHT - 1, // Display height - 1
SSD1306_SET_COM_OUT_DIR | 0x08, // set COM (common) output scan direction. Scan from bottom up, COM[N-1] to COM0
SSD1306_SET_DISP_OFFSET, // set display offset
0x00, // no offset
SSD1306_SET_COM_PIN_CFG, // set COM (common) pins hardware configuration. Board specific magic number.
// 0x02 Works for 128x32, 0x12 Possibly works for 128x64. Other options 0x22, 0x32
#if ((SSD1306_WIDTH == 128) && (SSD1306_HEIGHT == 32))
0x02,
#elif ((SSD1306_WIDTH == 128) && (SSD1306_HEIGHT == 64))
0x12,
#else
0x02,
#endif
/* timing and driving scheme */
SSD1306_SET_DISP_CLK_DIV, // set display clock divide ratio
0x80, // div ratio of 1, standard freq
SSD1306_SET_PRECHARGE, // set pre-charge period
0xF1, // Vcc internally generated on our board
SSD1306_SET_VCOM_DESEL, // set VCOMH deselect level
0x30, // 0.83xVcc
/* display */
SSD1306_SET_CONTRAST, // set contrast control
0xFF,
SSD1306_SET_ENTIRE_ON, // set entire display on to follow RAM content
SSD1306_SET_NORM_DISP, // set normal (not inverted) display
SSD1306_SET_CHARGE_PUMP, // set charge pump
0x14, // Vcc internally generated on our board
SSD1306_SET_SCROLL | 0x00, // deactivate horizontal scrolling if set. This is necessary as memory writes will corrupt if scrolling was enabled
SSD1306_SET_DISP | 0x01, // turn display on
};
SSD1306_send_cmd_list(cmds, count_of(cmds));
}
void render(uint8_t *buf, struct render_area *area) {
// update a portion of the display with a render area
uint8_t cmds[] = {
SSD1306_SET_COL_ADDR,
area->start_col,
area->end_col,
SSD1306_SET_PAGE_ADDR,
area->start_page,
area->end_page
};
SSD1306_send_cmd_list(cmds, count_of(cmds));
SSD1306_send_buf(buf, area->buflen);
}
static void SetPixel(uint8_t *buf, int x, int y, bool on) {
assert(x >= 0 && x < SSD1306_WIDTH && y >= 0 && y < SSD1306_HEIGHT);
// The calculation to determine the correct bit to set depends on which address
// mode we are in. This code assumes horizontal
// The video ram on the SSD1306 is split up in to 8 rows, one bit per pixel.
// Each row is 128 long by 8 pixels high, each byte vertically arranged, so byte 0 is x=0, y=0->7,
// byte 1 is x = 1, y=0->7 etc
// This code could be optimised, but is like this for clarity. The compiler
// should do a half decent job optimising it anyway.
const int BytesPerRow = SSD1306_WIDTH ; // x pixels, 1bpp, but each row is 8 pixel high, so (x / 8) * 8
int byte_idx = (y / 8) * BytesPerRow + x;
uint8_t byte = buf[byte_idx];
if (on)
byte |= 1 << (y % 8);
else
byte &= ~(1 << (y % 8));
buf[byte_idx] = byte;
}
static void DrawLine(uint8_t *buf, int x0, int y0, int x1, int y1, bool on) {
int dx = abs(x1 - x0);
int sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0);
int sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
int e2;
while (true) {
SetPixel(buf, x0, y0, on);
if (x0 == x1 && y0 == y1)
break;
e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x0 += sx;
}
if (e2 <= dx) {
err += dx;
y0 += sy;
}
}
}
// ------------------------------------------------------------------------------
// - Simple Menu -
// ------------------------------------------------------------------------------
#define MENU_DISPLAY_LINES 3 // Kolik se zobrazí řádků
#define MENU_PIXELS 11 // Výška řádku v pixelech
#define MENU_LENGHT 13 // Počet položek menu
#define MENU_OFFSET_MAX (MENU_LENGHT - MENU_DISPLAY_LINES)
char *menu[] = { // Definice položek menu
"Vymazat displej",
"Napis jedn. text",
"Fill Test",
"Test bitový posun",
"...",
"...",
"...",
"...",
"...",
"...",
"...",
"...",
"..."
};
uint8_t menu_item = 0; // Aktuálně vybraná položka menu
uint8_t menu_offset = 0; // Posun na dislpeji
uint8_t menu_selected_line = 0; // Aktuálně zvýrazněný řádek
void menu_refresh()
{
for (int line = menu_offset; line < (menu_offset + MENU_DISPLAY_LINES); line++)
{
if (line == (menu_selected_line + menu_offset))
{
printf("--> ");
}
else
{
printf(" ");
}
printf("%s\n", menu[line]);
}
printf("item: %d, offset: %d, sel. line: %d\n", menu_item, menu_offset, menu_selected_line);
}
// ------------------------------------------------------------------------------
// - HLAVNÍ PROGRAM -
// ------------------------------------------------------------------------------
int main()
{
stdio_init_all(); // Inicializace existujících stdio typů
printf("START...\n");
uint8_t btn_array[] = BTN_ARRAY; // Vytvoření pole tlačítek
cyw43_arch_init(); // Inicializace Wi-Fi modulu, aby bylo možné použít LED na desce
btn_init(btn_array); // inicializace vstupů tlačítek
buzzer_init(); // Inicializace bzučáku
led_blink_ms(500); // Krátké bliknutí po resetu
i2c_init(i2c_default, SSD1306_I2C_CLK * 1000);
gpio_set_function(4, GPIO_FUNC_I2C);
gpio_set_function(5, GPIO_FUNC_I2C);
gpio_pull_up(4);
gpio_pull_up(5);
// run through the complete initialization process
SSD1306_init();
// Initialize render area for entire frame (SSD1306_WIDTH pixels by SSD1306_NUM_PAGES pages)
struct render_area frame_area = {
start_col: 0,
end_col : SSD1306_WIDTH - 1,
start_page : 0,
end_page : SSD1306_NUM_PAGES - 1
};
calc_render_area_buflen(&frame_area);
// zero the entire display
uint8_t buf[SSD1306_BUF_LEN];
memset(buf, 255, SSD1306_BUF_LEN);
render(buf, &frame_area);
menu_refresh();
while (true)
{
bool btn_pressed = false; // Vynulování stavu tlačítek
for (int i = 0; i < sizeof(btn_array); i++) // Projdeme celé pole tlačítek
{
btn_pressed = btn_pressed | !gpio_get(btn_array[i]); // Logický součet tlačítek
}
uint32_t now = to_ms_since_boot(get_absolute_time());
if (btn_pressed && !btn_hold && ((now - btn_time_released) > BTN_DEBOUNCE_MS)) // Pokud je stisknuto jakékoliv tlačítko a není drženo z minulého cyklu
{
led_blink_ms(LED_MS); // krátké bliknutí po stlačení tlačítka
// buzzer_beep_ms(BUZ_MS);
printf("\nAktivace vstupu %d\n", btn_id);
if (btn_id == BTN_ESC)
{
printf("Stisknuto ESC\n");
menu_refresh();
}
else if (btn_id == BTN_ENT)
{
printf("Spouští se položka -- %s --\n", menu[menu_item]);
switch (menu_item)
{
case 0:
memset(buf, 0, SSD1306_BUF_LEN);
render(buf, &frame_area);
break;
case 1:
break;
case 2:
{
for (int i = 0; i < 4 * 128; i = i + 4) {
buf[i] = fill_pattern[i + 3];
buf[i + 1] = fill_pattern[i + 2];
buf[i + 2] = fill_pattern[i + 1];
buf[i + 3] = fill_pattern[i];
}
render(buf, &frame_area);
}
break;
case 3:
{
uint32_t velke_cislo = 0xaabbccdd;
uint8_t cisla[4];
for (int i = 0; i < 4; i++)
{
cisla[i] = velke_cislo & 0xff;
velke_cislo = velke_cislo >> 8;
printf("%x %x %x %x\n", cisla[3], cisla[2], cisla[1], cisla[0]);
}
}
break;
case 4:
{
uint8_t data[45] = {0x40,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,};
i2c_write_blocking(i2c_default, SSD1306_I2C_ADDR, data, 45, false);
}
default:
}
}
else if (btn_id == BTN_UP)
{
if (menu_item > 0)
{
menu_item--;
if (menu_selected_line > 0)
{
menu_selected_line--;
}
else if (menu_offset > 0)
{
menu_offset--;
}
}
menu_refresh();
}
else if (btn_id == BTN_DN)
{
if (menu_item < (MENU_LENGHT - 1))
{
menu_item++;
if (menu_selected_line < (MENU_DISPLAY_LINES - 1))
{
menu_selected_line++;
}
else if (menu_offset < (MENU_OFFSET_MAX))
{
menu_offset++;
}
}
menu_refresh();
}
btn_hold = true; // Nastavíme držení tlačítka
}
else if (!btn_pressed)
{
btn_hold = false;
btn_time_released = to_ms_since_boot(get_absolute_time());
}
}
sleep_ms(10);
}