diff --git a/oled_gol.c b/oled_gol.c index 7e6dfa2..cd4fc6d 100644 --- a/oled_gol.c +++ b/oled_gol.c @@ -1,23 +1,133 @@ -/* Template app on which you can build your own. */ +#define SSD1306_128X32 #include "ch32v003fun.h" #include +#include "ssd1306_i2c.h" +#include "ssd1306.h" #define PIN_LED PC4 +#define PIN_BTN PC7 + +#define u8 uint8_t +#define u16 uint16_t + +uint8_t gol_tmp[sizeof(ssd1306_buffer)]; + +uint32_t lfsr = 1; // PRNG state + +u8 rand8(); +u8 get_pixel(u8 x, u8 y); +void gol_step(); +void r_pentomino(); +void randomise(); int main() { SystemInit(); - funGpioInitAll(); + funPinMode(PIN_LED, GPIO_CFGLR_OUT_10Mhz_PP); + funPinMode(PIN_BTN, GPIO_CFGLR_IN_PUPD); - funPinMode(PIN_LED, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); + ssd1306_i2c_init(); + ssd1306_init(); + randomise(); while (1) { - funDigitalWrite(PIN_LED, FUN_HIGH); - Delay_Ms(150); - funDigitalWrite(PIN_LED, FUN_LOW); - Delay_Ms(150); + ssd1306_refresh(); + gol_step(); + memcpy(ssd1306_buffer, gol_tmp, sizeof(ssd1306_buffer)); + + if (funDigitalRead(PIN_BTN)) + { + randomise(); + ssd1306_refresh(); + funDigitalWrite(PIN_LED, FUN_HIGH); + Delay_Ms(100); + funDigitalWrite(PIN_LED, FUN_LOW); + } } } + +void r_pentomino() +{ + ssd1306_setbuf(0); + ssd1306_drawPixel(20, 20, 1); + ssd1306_drawPixel(21, 20, 1); + ssd1306_drawPixel(21, 21, 1); + ssd1306_drawPixel(22, 21, 1); + ssd1306_drawPixel(21, 22, 1); +} + +void randomise() +{ + lfsr = SysTick->CNT; + for (u16 b = 0; b < sizeof(ssd1306_buffer); b++) + { + ssd1306_buffer[b] = rand8(); + } +} + +u8 get_pixel(u8 x, u8 y) +{ + x = x & 127; + y = y & 31; + u8 slice = ssd1306_buffer[(y / 8) * 128 + x]; + return (slice & (1 << (y & 7))) != 0; +} + +void gol_step() +{ + // slow pixel-by-pixel implementation + for (u8 x = 0; x < 128; x++) + { + for (u8 y = 0; y < 32; y++) + { + u8 this = get_pixel(x, y); + u8 sum = 0; + sum += get_pixel(x + 127, y + 31); + sum += get_pixel(x + 127, y); + sum += get_pixel(x + 127, y + 1); + sum += get_pixel(x, y + 31); + sum += get_pixel(x, y + 1); + sum += get_pixel(x + 1, y + 31); + sum += get_pixel(x + 1, y); + sum += get_pixel(x + 1, y + 1); + + u8 new_state = (sum == 3) | ((sum == 2) & this); + u16 addr = x + (y / 8) * 128; + + if (new_state) + gol_tmp[addr] |= (1 << (y & 7)); + else + gol_tmp[addr] &= ~(1 << (y & 7)); + } + } +} + +/* 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/ssd1306.h b/ssd1306.h new file mode 100644 index 0000000..e613481 --- /dev/null +++ b/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/ssd1306_i2c.h b/ssd1306_i2c.h new file mode 100644 index 0000000..023157f --- /dev/null +++ b/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