364 lines
7.9 KiB
C
364 lines
7.9 KiB
C
|
/*
|
||
|
* Single-File-Header for SSD1306 I2C interface
|
||
|
* 05-07-2023 E. Brombaugh
|
||
|
*/
|
||
|
|
||
|
#ifndef _SSD1306_I2C_H
|
||
|
#define _SSD1306_I2C_H
|
||
|
|
||
|
#include <string.h>
|
||
|
|
||
|
// 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
|