diff --git a/examples/i2c_oled/.gdbinit b/examples/i2c_oled/.gdbinit new file mode 100644 index 0000000000000000000000000000000000000000..193753f4f7e9b58eca31fc50ae6edb2bc868e39e --- /dev/null +++ b/examples/i2c_oled/.gdbinit @@ -0,0 +1,2 @@ +file i2c_oled.elf +target extended-remote localhost:3333 diff --git a/examples/i2c_oled/README.md b/examples/i2c_oled/README.md index 625a07552f3d2e21353334f551caa36d46d824e9..3015423ddfa4536364173b310dfeddcc9386ab28 100644 --- a/examples/i2c_oled/README.md +++ b/examples/i2c_oled/README.md @@ -9,6 +9,23 @@ various drawing primitives. https://user-images.githubusercontent.com/1132011/230734071-dee305de-5aad-4ca0-a422-5fb31d2bb0e0.mp4 +## Build options +There are a few build-time options in the i2c.h source: +* I2C_CLKRATE - defines the I2C bus clock rate. Both 100kHz and 400kHz are supported. +800kHz has been seen to work when I2C_PRERATE is 1000000, but 1MHz did not. To +use higher bus rates you must increase I2C_PRERATE at the expense of higher power +consumption. +* I2C_PRERATE - defines the I2C logic clock rate. Must be higher than I2C_CLKRATE. +Keep this value as low as possible (but not lower than 1000000) to ensure low power +operaton. +* I2C_DUTY - for I2C_CLKRATE > 100kHz this specifies the duty cycle, either 33% or 36%. +* TIMEOUT_MAX - the amount of tries in busy-wait loops before giving up. This value +depends on the I2C_CLKRATE and should not affect normal operation. +* I2C_IRQ - chooses IRQ-based operation instead of busy-wait polling. Useful to +free up CPU resources but should be used carefully since it has more potential +mysterious effects and less error checking. +* IRQ_DIAG - enables timing analysis via GPIO toggling. Don't enable this unless +you know what you're doing. ## Use Connect an SSD1306-based OLED in I2C interface mode to pins PC1 (SCL) and PC2 (SDA) diff --git a/examples/i2c_oled/debug.sh b/examples/i2c_oled/debug.sh new file mode 100755 index 0000000000000000000000000000000000000000..bb05a9465959cb153fad95bba4b2175667830528 --- /dev/null +++ b/examples/i2c_oled/debug.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# before running this you should start OOCD server +#../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/openocd -f ../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/wch-riscv.cfg + +../../../MRS_Toolchain_Linux_x64_V1.70/RISC-V\ Embedded\ GCC/bin/riscv-none-embed-gdb diff --git a/examples/i2c_oled/i2c.h b/examples/i2c_oled/i2c.h index 04b2e1903ff729473c1c123f9a7596f4532ee81c..92cfbdd58ec091024723f96c417a4a96860c0af7 100644 --- a/examples/i2c_oled/i2c.h +++ b/examples/i2c_oled/i2c.h @@ -6,12 +6,29 @@ #ifndef _I2C_H #define _I2C_H -// I2C clock rate -#define I2C_CLKRATE 100000 +// I2C Bus clock rate - must be lower the Logic clock rate +#define I2C_CLKRATE 1000000 + +// I2C Logic clock rate - must be higher than Bus clock rate +#define I2C_PRERATE 2000000 + +// uncomment this for high-speed 36% duty cycle, otherwise 33% +#define I2C_DUTY // I2C Timeout count #define TIMEOUT_MAX 100000 +// uncomment this to enable IRQ-driven operation +#define I2C_IRQ + +#ifdef I2C_IRQ +// some stuff that IRQ mode needs +volatile uint8_t i2c_send_buffer[64], *i2c_send_ptr, i2c_send_sz, i2c_irq_state; + +// uncomment this to enable time diags in IRQ +//#define IRQ_DIAG +#endif + /* * init just I2C */ @@ -26,7 +43,7 @@ void i2c_setup(void) // set freq tempreg = I2C1->CTLR2; tempreg &= ~I2C_CTLR2_FREQ; - tempreg |= (APB_CLOCK/1000000)&I2C_CTLR2_FREQ; + tempreg |= (APB_CLOCK/I2C_PRERATE)&I2C_CTLR2_FREQ; I2C1->CTLR2 = tempreg; // Set clock config @@ -35,9 +52,26 @@ void i2c_setup(void) // standard mode good to 100kHz tempreg = (APB_CLOCK/(2*I2C_CLKRATE))&I2C_CKCFGR_CCR; #else - // fast mode not yet handled here + // fast mode over 100kHz +#ifndef I2C_DUTY + // 33% duty cycle + tempreg = (APB_CLOCK/(3*I2C_CLKRATE))&I2C_CKCFGR_CCR; +#else + // 36% duty cycle + tempreg = (APB_CLOCK/(25*I2C_CLKRATE))&I2C_CKCFGR_CCR; + tempreg |= I2C_CKCFGR_DUTY; +#endif + tempreg |= I2C_CKCFGR_FS; #endif I2C1->CKCFGR = tempreg; + +#ifdef I2C_IRQ + // enable IRQ driven operation + NVIC_EnableIRQ(I2C1_EV_IRQn); + + // initialize the state + i2c_irq_state = 0; +#endif // Enable I2C I2C1->CTLR1 |= I2C_CTLR1_PE; @@ -63,6 +97,16 @@ void i2c_init(void) GPIO_CFGLR_CNF2_1 | GPIO_CFGLR_CNF2_0 | GPIO_CFGLR_MODE2_1 | GPIO_CFGLR_MODE2_0; +#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 i2c_setup(); } @@ -108,8 +152,118 @@ uint8_t i2c_chk_evt(uint32_t event_mask) return (status & event_mask) == event_mask; } +#ifdef I2C_IRQ /* - * packet send + * packet send for IRQ-driven operation + */ +uint8_t 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(i2c_send_buffer)) || !sz) + return 2; + + // wait for previous packet to finish + while(i2c_irq_state); + +#ifdef IRQ_DIAG + GPIOC->BSHR = (1<<(16+3)); + GPIOC->BSHR = (1<<(4)); +#endif + + // init buffer for sending + i2c_send_sz = sz; + i2c_send_ptr = i2c_send_buffer; + memcpy((uint8_t *)i2c_send_buffer, data, sz); + + // wait for not busy + timeout = TIMEOUT_MAX; + while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--)); + if(timeout==-1) + return i2c_error(0); + + // Set START condition + I2C1->CTLR1 |= I2C_CTLR1_START; + + // wait for master mode select + timeout = TIMEOUT_MAX; + while((!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--)); + if(timeout==-1) + return i2c_error(1); + + // send 7-bit address + write flag + I2C1->DATAR = addr<<1; + + // wait for transmit condition + timeout = TIMEOUT_MAX; + while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--)); + if(timeout==-1) + return i2c_error(2); + + // Enable TXE interrupt + I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN; + 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(i2c_send_sz--) + I2C1->DATAR = *i2c_send_ptr++; + + /* was that the last byte? */ + if(!i2c_send_sz) + { + // disable TXE interrupt + I2C1->CTLR2 &= ~(I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN); + + // reset IRQ state + i2c_irq_state = 0; + + // wait for tx complete + while(!i2c_chk_evt(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); + + // set STOP condition + I2C1->CTLR1 |= I2C_CTLR1_STOP; + } + } + +#ifdef IRQ_DIAG + GPIOC->BSHR = (1<<(16+4)); +#endif +} +#else +/* + * packet send for polled operation */ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz) { @@ -164,6 +318,6 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz) // we're happy return 0; } - +#endif #endif