diff --git a/gol/ssd1306.h b/gol/ssd1306.h index e613481..7fb246d 100644 --- a/gol/ssd1306.h +++ b/gol/ssd1306.h @@ -480,7 +480,7 @@ uint8_t ssd1306_init(void) } // clear display - // ssd1306_setbuf(0); + ssd1306_setbuf(0); ssd1306_refresh(); return 0; diff --git a/soweli/Makefile b/soweli/Makefile new file mode 100644 index 0000000..799aab5 --- /dev/null +++ b/soweli/Makefile @@ -0,0 +1,11 @@ +all : flash + +TARGET:=soweli +CH32V003FUN:=../../ch32v003fun/ch32v003fun + +include ../../ch32v003fun/ch32v003fun/ch32v003fun.mk + +flash : build + gdb-multiarch -x ../gdbinit -ex 'load' -ex 'detach' -ex 'quit' $(TARGET).elf +clean : cv_clean + diff --git a/soweli/data.h b/soweli/data.h new file mode 100644 index 0000000..4655117 --- /dev/null +++ b/soweli/data.h @@ -0,0 +1,80 @@ +#define OBSTACLE_TYPES 5 + +const unsigned char soweli_a[8] = { + 0b01111111, + 0b10000000, + 0b10101000, + 0b10101000, + 0b10000000, + 0b01010101, + 0b01010000, + 0b01010000, +}; + +const unsigned char soweli_b[8] = { + 0b01111111, + 0b10000000, + 0b10101000, + 0b10101000, + 0b10000000, + 0b01010101, + 0b00000101, + 0b00000101, +}; + +const unsigned char obstacle_sprites[OBSTACLE_TYPES][8] = { + { + 0b00000000, + 0b00000000, + 0b01010101, + 0b01010100, + 0b11111110, + 0b01010100, + 0b01010101, + 0b00000000, + }, + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + + 0b00111100, + 0b01100110, + 0b11000011, + 0b10000001, + }, + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b01000010, + 0b01000010, + 0b01000010, + }, + { + 0b01000010, + 0b00011000, + 0b00100100, + 0b01111110, + 0b00100100, + 0b01111110, + 0b00100100, + 0b00011000, + }, + { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00111100, + }, +}; + +const unsigned char obstacle_heights[OBSTACLE_TYPES] = {6, 4, 4, 8, 5}; +const unsigned char obstacle_widths[OBSTACLE_TYPES] = {8, 8, 8, 7, 5}; \ No newline at end of file diff --git a/soweli/funconfig.h b/soweli/funconfig.h new file mode 100644 index 0000000..998cf76 --- /dev/null +++ b/soweli/funconfig.h @@ -0,0 +1,7 @@ +#ifndef _FUNCONFIG_H +#define _FUNCONFIG_H + +#define CH32V003 1 + +#endif + diff --git a/soweli/soweli.c b/soweli/soweli.c new file mode 100644 index 0000000..3e1cad1 --- /dev/null +++ b/soweli/soweli.c @@ -0,0 +1,255 @@ + +#define SSD1306_128X32 +#include "ch32v003fun.h" +#include +#include "ssd1306_i2c.h" +#include "ssd1306.h" + +#include "data.h" + +#define PIN_LED PC4 +#define PIN_BTN PC7 + +// #define JUMP_POWER 48 +// #define GRAVITY 3 +#define JUMP_POWER 36 +#define GRAVITY 2 +#define SCROLL_SPEED 1 +#define FRAME_DELAY 1000 / 60 +#define SCORE_DIGITS 4 +#define RUN_ANIM_SPEED 4 +#define PLAYER_X 16 +#define Y_PRECISION 4 + +u32 time = 0; +u16 score = 0; +u16 highscore = 0; +s16 player_y = 0; +s16 player_vel = 0; +#define MAX_OBSTACLES 16 +u8 obstacle_positions[MAX_OBSTACLES]; +u8 obstacle_types[MAX_OBSTACLES]; // todo +u8 obstacle_count = 0; +u8 is_running = 1; +u8 time_to_obstacle = 0; + +uint32_t lfsr = 1; // PRNG state + +u8 rand8(); +void update(); +void render(); +void restart(); + +int main() +{ + SystemInit(); + funGpioInitAll(); + funPinMode(PIN_LED, GPIO_CFGLR_OUT_10Mhz_PP); + funPinMode(PIN_BTN, GPIO_CFGLR_IN_PUPD); + + ssd1306_i2c_init(); + ssd1306_init(); + + restart(); + while (1) + { + ssd1306_refresh(); + + if (is_running) + update(); + render(); + time += 1; + + if (funDigitalRead(PIN_BTN)) + { + if (is_running) + { + if (player_y == 0 && player_vel == 0) + { + player_vel = JUMP_POWER; + } + } + else + restart(); + } + if (is_running == 0) + { + funDigitalWrite(PIN_LED, (time >> 2) & 1); + } + else + { + funDigitalWrite(PIN_LED, FUN_LOW); + } + Delay_Ms(FRAME_DELAY); + } +} + +void restart() +{ + lfsr = SysTick->CNT; + is_running = 1; + player_vel = 0; + player_y = 0; + obstacle_count = 0; + time_to_obstacle = 0; + if (highscore < score) + highscore = score; + score = 0; +} + +void remove_obstacle() +{ + if (obstacle_count == 0) + return; + for (u8 i = 1; i < obstacle_count; i++) + { + obstacle_positions[i - 1] = obstacle_positions[i]; + obstacle_types[i - 1] = obstacle_types[i]; + } + obstacle_count--; +} + +void spawn_obstacle(u8 pos) +{ + if (obstacle_count >= MAX_OBSTACLES) + return; + obstacle_positions[obstacle_count] = pos; + obstacle_types[obstacle_count] = rand8() % OBSTACLE_TYPES; + obstacle_count++; +} + +void update() +{ + score += (time & 4) != 0; + if (player_vel != 0 || player_y != 0) + { + player_vel -= GRAVITY; + player_y += player_vel; + if (player_y < 0) + { + player_y = 0; + player_vel = 0; + } + } + for (u8 i = 0; i < obstacle_count; i++) + { + obstacle_positions[i] -= SCROLL_SPEED; + u8 type = obstacle_types[i]; + u8 dist_x = gfx_abs((s16)obstacle_positions[i] - (s16)PLAYER_X); + if (dist_x < obstacle_widths[type] && (player_y >> Y_PRECISION) < obstacle_heights[type]) + { + is_running = 0; + } + } + if (obstacle_count > 0 && obstacle_positions[0] == 0) + { + remove_obstacle(); + } + if (time_to_obstacle < 5) + { + u8 count = 1 + (rand8() & rand8() & 1) + (rand8() & rand8() & rand8() & rand8() & 1); + for (u8 i = 0; i < count; i++) + { + spawn_obstacle(128 + i * 8); + } + time_to_obstacle = (rand8() & 31) + 50; + } + time_to_obstacle -= 1; +} + +void render_digit(u8 digit, u8 x, u8 y) +{ + const u16 digit_font[] = { + 0b111101101101111, // 0 + 0b010110010010111, // 1 + 0b111001111100111, // 2 + 0b111001111001111, // 3 + 0b101101111001001, // 4 + 0b111100111001111, // 5 + 0b111100111101111, // 6 + 0b111001001001001, // 7 + 0b111101111101111, // 8 + 0b111101111001111, // 9 + }; + u16 pixels = digit_font[digit]; + + ssd1306_drawPixel(x + 0, y + 0, (pixels >> 14) & 1); + ssd1306_drawPixel(x + 1, y + 0, (pixels >> 13) & 1); + ssd1306_drawPixel(x + 2, y + 0, (pixels >> 12) & 1); + ssd1306_drawPixel(x + 0, y + 1, (pixels >> 11) & 1); + ssd1306_drawPixel(x + 1, y + 1, (pixels >> 10) & 1); + ssd1306_drawPixel(x + 2, y + 1, (pixels >> 9) & 1); + ssd1306_drawPixel(x + 0, y + 2, (pixels >> 8) & 1); + ssd1306_drawPixel(x + 1, y + 2, (pixels >> 7) & 1); + ssd1306_drawPixel(x + 2, y + 2, (pixels >> 6) & 1); + ssd1306_drawPixel(x + 0, y + 3, (pixels >> 5) & 1); + ssd1306_drawPixel(x + 1, y + 3, (pixels >> 4) & 1); + ssd1306_drawPixel(x + 2, y + 3, (pixels >> 3) & 1); + ssd1306_drawPixel(x + 0, y + 4, (pixels >> 2) & 1); + ssd1306_drawPixel(x + 1, y + 4, (pixels >> 1) & 1); + ssd1306_drawPixel(x + 2, y + 4, (pixels >> 0) & 1); +} + +void render_number(u32 s, u8 x, u8 y, u8 digits) +{ + u8 painter_x = x + digits * 4; + u32 val = s; + + for (int i = 0; i < digits; i++) + { + u8 digit = val % 10; + render_digit(digit, painter_x, y); + val /= 10; + painter_x -= 4; + } +} + +void render() +{ + ssd1306_setbuf(0); + ssd1306_drawFastHLine(0, 31, 128, 1); + + render_number(score, 0, 0, SCORE_DIGITS); + render_number(highscore, 0, 6, SCORE_DIGITS); + // render_number(obstacle_count, 20, 0, 2); + // render_number(abs(player_vel), 30, 0, 2); + // render_number(time_to_obstacle, 20, 6, 3); + + for (u8 i = 0; i < obstacle_count; i++) + { + u8 type = obstacle_types[i]; + ssd1306_drawImage(obstacle_positions[i], 31 - 8, obstacle_sprites[type], 8, 8, 0); + } + + if (time & RUN_ANIM_SPEED && is_running) + ssd1306_drawImage(PLAYER_X, 31 - (player_y >> Y_PRECISION) - 8, soweli_a, 8, 8, 0); + else + ssd1306_drawImage(PLAYER_X, 31 - (player_y >> Y_PRECISION) - 8, soweli_b, 8, 8, 0); +} + +/* White Noise Generator State */ +#define NOISE_BITS 8 +#define NOISE_MASK ((1 << NOISE_BITS) - 1) +#define NOISE_POLY_TAP0 31 +#define NOISE_POLY_TAP1 21 +#define NOISE_POLY_TAP2 1 +#define NOISE_POLY_TAP3 0 +/* + * random byte generator, taken from ch32v003fun examples + */ +uint8_t rand8(void) +{ + uint8_t bit; + uint32_t new_data; + + for (bit = 0; bit < NOISE_BITS; bit++) + { + new_data = ((lfsr >> NOISE_POLY_TAP0) ^ + (lfsr >> NOISE_POLY_TAP1) ^ + (lfsr >> NOISE_POLY_TAP2) ^ + (lfsr >> NOISE_POLY_TAP3)); + lfsr = (lfsr << 1) | (new_data & 1); + } + + return lfsr & NOISE_MASK; +} \ No newline at end of file diff --git a/soweli/ssd1306.h b/soweli/ssd1306.h new file mode 100644 index 0000000..7fb246d --- /dev/null +++ b/soweli/ssd1306.h @@ -0,0 +1,489 @@ +/* + * Single-File-Header for using SPI OLED + * 05-05-2023 E. Brombaugh + */ + +#ifndef _SSD1306_H +#define _SSD1306_H + +#include +#include + +// comfortable packet size for this OLED +#define SSD1306_PSZ 32 + +// characteristics of each type +#if !defined(SSD1306_64X32) && !defined(SSD1306_128X32) && !defined(SSD1306_128X64) +#error "Please define the SSD1306_WXH resolution used in your application" +#endif + +#ifdef SSD1306_64X32 +#define SSD1306_W 64 +#define SSD1306_H 32 +#define SSD1306_FULLUSE +#define SSD1306_OFFSET 32 +#endif + +#ifdef SSD1306_128X32 +#define SSD1306_W 128 +#define SSD1306_H 32 +#define SSD1306_OFFSET 0 +#endif + +#ifdef SSD1306_128X64 +#define SSD1306_W 128 +#define SSD1306_H 64 +#define SSD1306_FULLUSE +#define SSD1306_OFFSET 0 +#endif + +/* + * send OLED command byte + */ +uint8_t ssd1306_cmd(uint8_t cmd) +{ + ssd1306_pkt_send(&cmd, 1, 1); + return 0; +} + +/* + * send OLED data packet (up to 32 bytes) + */ +uint8_t ssd1306_data(uint8_t *data, uint8_t sz) +{ + ssd1306_pkt_send(data, sz, 0); + return 0; +} + +#define SSD1306_SETCONTRAST 0x81 +#define SSD1306_SEGREMAP 0xA0 +#define SSD1306_DISPLAYALLON_RESUME 0xA4 +#define SSD1306_DISPLAYALLON 0xA5 +#define SSD1306_NORMALDISPLAY 0xA6 +#define SSD1306_INVERTDISPLAY 0xA7 +#define SSD1306_DISPLAYOFF 0xAE +#define SSD1306_DISPLAYON 0xAF +#define SSD1306_SETDISPLAYOFFSET 0xD3 +#define SSD1306_SETCOMPINS 0xDA +#define SSD1306_SETVCOMDETECT 0xDB +#define SSD1306_SETDISPLAYCLOCKDIV 0xD5 +#define SSD1306_SETPRECHARGE 0xD9 +#define SSD1306_SETMULTIPLEX 0xA8 +#define SSD1306_SETLOWCOLUMN 0x00 +#define SSD1306_SETHIGHCOLUMN 0x10 +#define SSD1306_SETSTARTLINE 0x40 +#define SSD1306_MEMORYMODE 0x20 +#define SSD1306_COLUMNADDR 0x21 +#define SSD1306_PAGEADDR 0x22 +#define SSD1306_COMSCANINC 0xC0 +#define SSD1306_COMSCANDEC 0xC8 +#define SSD1306_CHARGEPUMP 0x8D +#define SSD1306_EXTERNALVCC 0x1 +#define SSD1306_SWITCHCAPVCC 0x2 +#define SSD1306_TERMINATE_CMDS 0xFF + +/* choose VCC mode */ +#define SSD1306_EXTERNALVCC 0x1 +#define SSD1306_SWITCHCAPVCC 0x2 +// #define vccstate SSD1306_EXTERNALVCC +#define vccstate SSD1306_SWITCHCAPVCC + +// OLED initialization commands for 128x32 +const uint8_t ssd1306_init_array[] = + { + SSD1306_DISPLAYOFF, // 0xAE + SSD1306_SETDISPLAYCLOCKDIV, // 0xD5 + 0x80, // the suggested ratio 0x80 + SSD1306_SETMULTIPLEX, // 0xA8 +#ifdef SSD1306_64X32 + 0x1F, // for 64-wide displays +#else + 0x3F, // for 128-wide displays +#endif + SSD1306_SETDISPLAYOFFSET, // 0xD3 + 0x00, // no offset + SSD1306_SETSTARTLINE | 0x0, // 0x40 | line + SSD1306_CHARGEPUMP, // 0x8D + 0x14, // enable? + SSD1306_MEMORYMODE, // 0x20 + 0x00, // 0x0 act like ks0108 + SSD1306_SEGREMAP | 0x1, // 0xA0 | bit + SSD1306_COMSCANDEC, + SSD1306_SETCOMPINS, // 0xDA + 0x12, // + SSD1306_SETCONTRAST, // 0x81 + 0x8F, + SSD1306_SETPRECHARGE, // 0xd9 + 0xF1, + SSD1306_SETVCOMDETECT, // 0xDB + 0x40, + SSD1306_DISPLAYALLON_RESUME, // 0xA4 + SSD1306_NORMALDISPLAY, // 0xA6 + SSD1306_DISPLAYON, // 0xAF --turn on oled panel + SSD1306_TERMINATE_CMDS // 0xFF --fake command to mark end +}; + +// the display buffer +uint8_t ssd1306_buffer[SSD1306_W * SSD1306_H / 8]; + +/* + * set the buffer to a color + */ +void ssd1306_setbuf(uint8_t color) +{ + memset(ssd1306_buffer, color ? 0xFF : 0x00, sizeof(ssd1306_buffer)); +} + +#ifndef SSD1306_FULLUSE +/* + * expansion array for OLED with every other row unused + */ +const uint8_t expand[16] = + { + 0x00, + 0x02, + 0x08, + 0x0a, + 0x20, + 0x22, + 0x28, + 0x2a, + 0x80, + 0x82, + 0x88, + 0x8a, + 0xa0, + 0xa2, + 0xa8, + 0xaa, +}; +#endif + +/* + * Send the frame buffer + */ +void ssd1306_refresh(void) +{ + uint16_t i; + + ssd1306_cmd(SSD1306_COLUMNADDR); + ssd1306_cmd(SSD1306_OFFSET); // Column start address (0 = reset) + ssd1306_cmd(SSD1306_OFFSET + SSD1306_W - 1); // Column end address (127 = reset) + + ssd1306_cmd(SSD1306_PAGEADDR); + ssd1306_cmd(0); // Page start address (0 = reset) + ssd1306_cmd(7); // Page end address + +#ifdef SSD1306_FULLUSE + /* for fully used rows just plow thru everything */ + for (i = 0; i < sizeof(ssd1306_buffer); i += SSD1306_PSZ) + { + /* send PSZ block of data */ + ssd1306_data(&ssd1306_buffer[i], SSD1306_PSZ); + } +#else + /* for displays with odd rows unused expand bytes */ + uint8_t tbuf[SSD1306_PSZ], j, k; + for (i = 0; i < sizeof(ssd1306_buffer); i += 128) // for each page + { + /* low nybble */ + for (j = 0; j < 128; j += SSD1306_PSZ) + { + for (k = 0; k < SSD1306_PSZ; k++) + tbuf[k] = expand[ssd1306_buffer[i + j + k] & 0xf]; + + /* send PSZ block of data */ + ssd1306_data(tbuf, SSD1306_PSZ); + } + + /* high nybble */ + for (j = 0; j < 128; j += SSD1306_PSZ) + { + for (k = 0; k < SSD1306_PSZ; k++) + tbuf[k] = expand[(ssd1306_buffer[i + j + k] >> 4) & 0xf]; + + /* send PSZ block of data */ + ssd1306_data(tbuf, SSD1306_PSZ); + } + } +#endif +} + +/* + * plot a pixel in the buffer + */ +void ssd1306_drawPixel(uint8_t x, uint8_t y, uint8_t color) +{ + uint16_t addr; + + /* clip */ + if (x >= SSD1306_W) + return; + if (y >= SSD1306_H) + return; + + /* compute buffer address */ + addr = x + SSD1306_W * (y / 8); + + /* set/clear bit in buffer */ + if (color) + ssd1306_buffer[addr] |= (1 << (y & 7)); + else + ssd1306_buffer[addr] &= ~(1 << (y & 7)); +} + +/* + * fast vert line + */ +void ssd1306_drawFastVLine(uint8_t x, uint8_t y, uint8_t h, uint8_t color) +{ + // clipping + if ((x >= SSD1306_W) || (y >= SSD1306_H)) + return; + if ((y + h - 1) >= SSD1306_H) + h = SSD1306_H - y; + while (h--) + { + ssd1306_drawPixel(x, y++, color); + } +} + +/* + * fast horiz line + */ +void ssd1306_drawFastHLine(uint8_t x, uint8_t y, uint8_t w, uint8_t color) +{ + // clipping + if ((x >= SSD1306_W) || (y >= SSD1306_H)) + return; + if ((x + w - 1) >= SSD1306_W) + w = SSD1306_W - x; + + while (w--) + { + ssd1306_drawPixel(x++, y, color); + } +} + +/* + * abs() helper function for line drawing + */ +int16_t gfx_abs(int16_t x) +{ + return (x < 0) ? -x : x; +} + +/* + * swap() helper function for line drawing + */ +void gfx_swap(uint16_t *z0, uint16_t *z1) +{ + uint16_t temp = *z0; + *z0 = *z1; + *z1 = temp; +} + +/* + * Bresenham line draw routine swiped from Wikipedia + */ +void ssd1306_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t color) +{ + int16_t steep; + int16_t deltax, deltay, error, ystep, x, y; + + /* flip sense 45deg to keep error calc in range */ + steep = (gfx_abs(y1 - y0) > gfx_abs(x1 - x0)); + + if (steep) + { + gfx_swap(&x0, &y0); + gfx_swap(&x1, &y1); + } + + /* run low->high */ + if (x0 > x1) + { + gfx_swap(&x0, &x1); + gfx_swap(&y0, &y1); + } + + /* set up loop initial conditions */ + deltax = x1 - x0; + deltay = gfx_abs(y1 - y0); + error = deltax / 2; + y = y0; + if (y0 < y1) + ystep = 1; + else + ystep = -1; + + /* loop x */ + for (x = x0; x <= x1; x++) + { + /* plot point */ + if (steep) + /* flip point & plot */ + ssd1306_drawPixel(y, x, color); + else + /* just plot */ + ssd1306_drawPixel(x, y, color); + + /* update error */ + error = error - deltay; + + /* update y */ + if (error < 0) + { + y = y + ystep; + error = error + deltax; + } + } +} + +/* + * draws a circle + */ +void ssd1306_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color) +{ + /* Bresenham algorithm */ + int16_t x_pos = -radius; + int16_t y_pos = 0; + int16_t err = 2 - 2 * radius; + int16_t e2; + + do + { + ssd1306_drawPixel(x - x_pos, y + y_pos, color); + ssd1306_drawPixel(x + x_pos, y + y_pos, color); + ssd1306_drawPixel(x + x_pos, y - y_pos, color); + ssd1306_drawPixel(x - x_pos, y - y_pos, color); + e2 = err; + if (e2 <= y_pos) + { + err += ++y_pos * 2 + 1; + if (-x_pos == y_pos && e2 <= x_pos) + { + e2 = 0; + } + } + if (e2 > x_pos) + { + err += ++x_pos * 2 + 1; + } + } while (x_pos <= 0); +} + +/* + * draws a filled circle + */ +void ssd1306_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color) +{ + /* Bresenham algorithm */ + int16_t x_pos = -radius; + int16_t y_pos = 0; + int16_t err = 2 - 2 * radius; + int16_t e2; + + do + { + ssd1306_drawPixel(x - x_pos, y + y_pos, color); + ssd1306_drawPixel(x + x_pos, y + y_pos, color); + ssd1306_drawPixel(x + x_pos, y - y_pos, color); + ssd1306_drawPixel(x - x_pos, y - y_pos, color); + ssd1306_drawFastHLine(x + x_pos, y + y_pos, 2 * (-x_pos) + 1, color); + ssd1306_drawFastHLine(x + x_pos, y - y_pos, 2 * (-x_pos) + 1, color); + e2 = err; + if (e2 <= y_pos) + { + err += ++y_pos * 2 + 1; + if (-x_pos == y_pos && e2 <= x_pos) + { + e2 = 0; + } + } + if (e2 > x_pos) + { + err += ++x_pos * 2 + 1; + } + } while (x_pos <= 0); +} + +/* + * draw a rectangle + */ +void ssd1306_drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color) +{ + ssd1306_drawFastVLine(x, y, h, color); + ssd1306_drawFastVLine(x + w - 1, y, h, color); + ssd1306_drawFastHLine(x, y, w, color); + ssd1306_drawFastHLine(x, y + h - 1, w, color); +} + +/* + * fill a rectangle + */ +void ssd1306_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color) +{ + uint8_t m, n = y, iw = w; + + /* scan vertical */ + while (h--) + { + m = x; + w = iw; + /* scan horizontal */ + while (w--) + { + /* invert pixels */ + ssd1306_drawPixel(m++, n, color); + } + n++; + } +} + +/* + * invert a rectangle in the buffer + */ +void ssd1306_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h) +{ + uint8_t m, n = y, iw = w; + + /* scan vertical */ + while (h--) + { + m = x; + w = iw; + /* scan horizontal */ + while (w--) + { + /* invert pixels */ + ssd1306_xorPixel(m++, n); + } + n++; + } +} + +/* + * initialize I2C and OLED + */ +uint8_t ssd1306_init(void) +{ + // pulse reset + ssd1306_rst(); + + // initialize OLED + uint8_t *cmd_list = (uint8_t *)ssd1306_init_array; + while (*cmd_list != SSD1306_TERMINATE_CMDS) + { + if (ssd1306_cmd(*cmd_list++)) + return 1; + } + + // clear display + ssd1306_setbuf(0); + ssd1306_refresh(); + + return 0; +} + +#endif diff --git a/soweli/ssd1306_i2c.h b/soweli/ssd1306_i2c.h new file mode 100644 index 0000000..023157f --- /dev/null +++ b/soweli/ssd1306_i2c.h @@ -0,0 +1,363 @@ +/* + * Single-File-Header for SSD1306 I2C interface + * 05-07-2023 E. Brombaugh + */ + +#ifndef _SSD1306_I2C_H +#define _SSD1306_I2C_H + +#include + +// SSD1306 I2C address +#define SSD1306_I2C_ADDR 0x3c + +// I2C Bus clock rate - must be lower the Logic clock rate +#define SSD1306_I2C_CLKRATE 1000000 + +// I2C Logic clock rate - must be higher than Bus clock rate +#define SSD1306_I2C_PRERATE 2000000 + +// uncomment this for high-speed 36% duty cycle, otherwise 33% +#define SSD1306_I2C_DUTY + +// I2C Timeout count +#define TIMEOUT_MAX 100000 + +// uncomment this to enable IRQ-driven operation +//#define SSD1306_I2C_IRQ + +#ifdef SSD1306_I2C_IRQ +// some stuff that IRQ mode needs +volatile uint8_t ssd1306_i2c_send_buffer[64], *ssd1306_i2c_send_ptr, ssd1306_i2c_send_sz, ssd1306_i2c_irq_state; + +// uncomment this to enable time diags in IRQ +//#define IRQ_DIAG +#endif + +/* + * init just I2C + */ +void ssd1306_i2c_setup(void) +{ + uint16_t tempreg; + + // Reset I2C1 to init all regs + RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; + RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1; + + // set freq + tempreg = I2C1->CTLR2; + tempreg &= ~I2C_CTLR2_FREQ; + tempreg |= (FUNCONF_SYSTEM_CORE_CLOCK/SSD1306_I2C_PRERATE)&I2C_CTLR2_FREQ; + I2C1->CTLR2 = tempreg; + + // Set clock config + tempreg = 0; +#if (SSD1306_I2C_CLKRATE <= 100000) + // standard mode good to 100kHz + tempreg = (FUNCONF_SYSTEM_CORE_CLOCK/(2*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR; +#else + // fast mode over 100kHz +#ifndef SSD1306_I2C_DUTY + // 33% duty cycle + tempreg = (FUNCONF_SYSTEM_CORE_CLOCK/(3*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR; +#else + // 36% duty cycle + tempreg = (FUNCONF_SYSTEM_CORE_CLOCK/(25*SSD1306_I2C_CLKRATE))&I2C_CKCFGR_CCR; + tempreg |= I2C_CKCFGR_DUTY; +#endif + tempreg |= I2C_CKCFGR_FS; +#endif + I2C1->CKCFGR = tempreg; + +#ifdef SSD1306_I2C_IRQ + // enable IRQ driven operation + NVIC_EnableIRQ(I2C1_EV_IRQn); + + // initialize the state + ssd1306_i2c_irq_state = 0; +#endif + + // Enable I2C + I2C1->CTLR1 |= I2C_CTLR1_PE; + + // set ACK mode + I2C1->CTLR1 |= I2C_CTLR1_ACK; +} + +/* + * error descriptions + */ +char *errstr[] = +{ + "not busy", + "master mode", + "transmit mode", + "tx empty", + "transmit complete", +}; + +/* + * error handler + */ +uint8_t ssd1306_i2c_error(uint8_t err) +{ + // report error + printf("ssd1306_i2c_error - timeout waiting for %s\n\r", errstr[err]); + + // reset & initialize I2C + ssd1306_i2c_setup(); + + return 1; +} + +// event codes we use +#define SSD1306_I2C_EVENT_MASTER_MODE_SELECT ((uint32_t)0x00030001) /* BUSY, MSL and SB flag */ +#define SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ((uint32_t)0x00070082) /* BUSY, MSL, ADDR, TXE and TRA flags */ +#define SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED ((uint32_t)0x00070084) /* TRA, BUSY, MSL, TXE and BTF flags */ + +/* + * check for 32-bit event codes + */ +uint8_t ssd1306_i2c_chk_evt(uint32_t event_mask) +{ + /* read order matters here! STAR1 before STAR2!! */ + uint32_t status = I2C1->STAR1 | (I2C1->STAR2<<16); + return (status & event_mask) == event_mask; +} + +#ifdef SSD1306_I2C_IRQ +/* + * packet send for IRQ-driven operation + */ +uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz) +{ + int32_t timeout; + +#ifdef IRQ_DIAG + GPIOC->BSHR = (1<<(3)); +#endif + + // error out if buffer under/overflow + if((sz > sizeof(ssd1306_i2c_send_buffer)) || !sz) + return 2; + + // wait for previous packet to finish + while(ssd1306_i2c_irq_state); + +#ifdef IRQ_DIAG + GPIOC->BSHR = (1<<(16+3)); + GPIOC->BSHR = (1<<(4)); +#endif + + // init buffer for sending + ssd1306_i2c_send_sz = sz; + ssd1306_i2c_send_ptr = ssd1306_i2c_send_buffer; + memcpy((uint8_t *)ssd1306_i2c_send_buffer, data, sz); + + // wait for not busy + timeout = TIMEOUT_MAX; + while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(0); + + // Set START condition + I2C1->CTLR1 |= I2C_CTLR1_START; + + // wait for master mode select + timeout = TIMEOUT_MAX; + while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(1); + + // send 7-bit address + write flag + I2C1->DATAR = addr<<1; + + // wait for transmit condition + timeout = TIMEOUT_MAX; + while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(2); + + // Enable TXE interrupt + I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN; + ssd1306_i2c_irq_state = 1; + +#ifdef IRQ_DIAG + GPIOC->BSHR = (1<<(16+4)); +#endif + + // exit + return 0; +} + +/* + * IRQ handler for I2C events + */ +void I2C1_EV_IRQHandler(void) __attribute__((interrupt)); +void I2C1_EV_IRQHandler(void) +{ + uint16_t STAR1, STAR2 __attribute__((unused)); + +#ifdef IRQ_DIAG + GPIOC->BSHR = (1<<(4)); +#endif + + // read status, clear any events + STAR1 = I2C1->STAR1; + STAR2 = I2C1->STAR2; + + /* check for TXE */ + if(STAR1 & I2C_STAR1_TXE) + { + /* check for remaining data */ + if(ssd1306_i2c_send_sz--) + I2C1->DATAR = *ssd1306_i2c_send_ptr++; + + /* was that the last byte? */ + if(!ssd1306_i2c_send_sz) + { + // disable TXE interrupt + I2C1->CTLR2 &= ~(I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN); + + // reset IRQ state + ssd1306_i2c_irq_state = 0; + + // wait for tx complete + while(!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED)); + + // set STOP condition + I2C1->CTLR1 |= I2C_CTLR1_STOP; + } + } + +#ifdef IRQ_DIAG + GPIOC->BSHR = (1<<(16+4)); +#endif +} +#else +/* + * low-level packet send for blocking polled operation via i2c + */ +uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz) +{ + int32_t timeout; + + // wait for not busy + timeout = TIMEOUT_MAX; + while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(0); + + // Set START condition + I2C1->CTLR1 |= I2C_CTLR1_START; + + // wait for master mode select + timeout = TIMEOUT_MAX; + while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(1); + + // send 7-bit address + write flag + I2C1->DATAR = addr<<1; + + // wait for transmit condition + timeout = TIMEOUT_MAX; + while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(2); + + // send data one byte at a time + while(sz--) + { + // wait for TX Empty + timeout = TIMEOUT_MAX; + while(!(I2C1->STAR1 & I2C_STAR1_TXE) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(3); + + // send command + I2C1->DATAR = *data++; + } + + // wait for tx complete + timeout = TIMEOUT_MAX; + while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED)) && (timeout--)); + if(timeout==-1) + return ssd1306_i2c_error(4); + + // set STOP condition + I2C1->CTLR1 |= I2C_CTLR1_STOP; + + // we're happy + return 0; +} +#endif + +/* + * high-level packet send for I2C + */ +uint8_t ssd1306_pkt_send(uint8_t *data, uint8_t sz, uint8_t cmd) +{ + uint8_t pkt[33]; + + /* build command or data packets */ + if(cmd) + { + pkt[0] = 0; + pkt[1] = *data; + } + else + { + pkt[0] = 0x40; + memcpy(&pkt[1], data, sz); + } + return ssd1306_i2c_send(SSD1306_I2C_ADDR, pkt, sz+1); +} + +/* + * init I2C and GPIO + */ +uint8_t ssd1306_i2c_init(void) +{ + // Enable GPIOC and I2C + RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; + RCC->APB1PCENR |= RCC_APB1Periph_I2C1; + + // PC1 is SDA, 10MHz Output, alt func, open-drain + GPIOC->CFGLR &= ~(0xf<<(4*1)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*1); + + // PC2 is SCL, 10MHz Output, alt func, open-drain + GPIOC->CFGLR &= ~(0xf<<(4*2)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*2); + +#ifdef IRQ_DIAG + // GPIO diags on PC3/PC4 + GPIOC->CFGLR &= ~(0xf<<(4*3)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*3); + GPIOC->BSHR = (1<<(16+3)); + GPIOC->CFGLR &= ~(0xf<<(4*4)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4); + GPIOC->BSHR = (1<<(16+4)); +#endif + + // load I2C regs + ssd1306_i2c_setup(); + +#if 0 + // test if SSD1306 is on the bus by sending display off command + uint8_t command = 0xAF; + return ssd1306_pkt_send(&command, 1, 1); +#else + return 0; +#endif +} + +/* + * reset is not used for SSD1306 I2C interface + */ +void ssd1306_rst(void) +{ +} +#endif