diff --git a/src/main.c b/src/main.c index 1f4c74b..f0ad49b 100644 --- a/src/main.c +++ b/src/main.c @@ -237,6 +237,262 @@ void DMA1_Channel2_3_IRQHandler() { } } + +static void i2c_init(void); +static void lcd_init(void); +static void i2c_write(uint8_t addr, size_t n); +static void i2c_read(uint8_t addr, size_t n); +static void i2c_nonblocking_mode(void); +static void i2c_blocking_mode(void); +static void lcd_tx_nibble(uint8_t value, uint8_t flags); +static void lcd_pex_set(uint8_t value); +static void delay_us(int duration_us); +static void lcd_command(uint8_t value); +static void lcd_write_char(char d); +static void lcd_write_str(size_t line, char *str); + + +/* https://www.lcd-module.de/eng/pdf/zubehoer/ks0066.pdf */ +enum LCD_Command { + LCD_CMD_CLEAR = 0x01, + LCD_CMD_HOME = 0x02, + LCD_CMD_ENTRY_MODE = 0x04, + LCD_CMD_ON_OFF = 0x08, + LCD_CMD_SHIFT = 0x10, + LCD_CMD_FUNCTION = 0x20, + LCD_CMD_CGRAM_ADDR = 0x40, + LCD_CMD_DDRAM_ADDR = 0x80, +}; + +enum LCD_ENTRY_MODE_Cmd { + LCD_ENTRY_CUR_RIGHT = 0x02, + LCD_ENTRY_CUR_LEFT = 0x00, + LCD_ENTRY_DSP_RIGHT = 0x03, + LCD_ENTRY_DSP_LEFT = 0x01, +}; + +enum LCD_ON_OFF_Cmd { + LCD_DSP_ON = 0x04, + LCD_DSP_OFF = 0x00, + LCD_CUR_ON = 0x02, + LCD_CUR_OFF = 0x00, + LCD_BLK_ON = 0x01, + LCD_BLK_OFF = 0x00, +}; + +enum LCD_SHIFT_Cmd { + LCD_SH_CUR_LEFT = 0x00, + LCD_SH_CUR_RIGHT = 0x04, + LCD_SH_DSP_LEFT = 0x08, + LCD_SH_DSP_RIGHT = 0x0C, +}; + +enum LCD_FUNCTION_Cmd { + LCD_FN_4BIT = 0x00, + LCD_FN_8BIT = 0x10, + LCD_FN_1LINE = 0x00, + LCD_FN_2LINE = 0x08, + LCD_FN_5X8 = 0x00, + LCD_FN_5X11 = 0x04, +}; + +#define LCD_ENTER_4BIT 0x2 + + +struct { + uint8_t buf[90]; + bool blocking; +} st_i2c; + +bool lcd_led; + +void i2c_init() { + memset(&st_i2c, 0, sizeof(st_i2c)); + SYSCFG->CFGR1 |= SYSCFG_CFGR1_I2C1_DMA_RMP; + + /* Magic value for 100kHz I2C @ 48MHz CLK. Fell out of STMCubeMX. I love downloading 120MB of software to download + * another 100MB of software, only this time over unsecured HTTP, to generate 3.5 bytes of configuration values + * using a Java(TM) GUI. */ + I2C1->TIMINGR = (0<CR1 = I2C_CR1_RXDMAEN | I2C_CR1_TXDMAEN | I2C_CR1_ERRIE | I2C_CR1_PE; + + /* I2C1 TX */ + DMA1_Channel6->CCR = (0<CPAR = (uint32_t)&(I2C1->TXDR); + DMA1_Channel6->CMAR = (uint32_t)&st_i2c.buf; + + /* I2C1 RX */ + DMA1_Channel7->CCR = (0<CPAR = (uint32_t)&(I2C1->RXDR); + DMA1_Channel7->CMAR = (uint32_t)&st_i2c.buf; + + NVIC_EnableIRQ(DMA1_Channel4_5_6_7_IRQn); + NVIC_EnableIRQ(I2C1_IRQn); +} + +/* + |P7|P6|P5|P4|P3|P2|P1|P0| + |B7|B6|B5|B4|LED|E|RW|RS| +*/ + +enum LCD_Pins { + B_LCD_RS = (1<<0), + B_LCD_RW = (1<<1), + B_LCD_E = (1<<2), + B_LCD_LED = (1<<3), +}; + +#define LCD_D_Pos 4 + +enum I2C1_Address { + LCD_PEX_ADDR = 0x4E, +}; + +void lcd_pex_set(uint8_t value) { + if (lcd_led) { + value |= B_LCD_LED; + } + + I2C1->TXDR = value; + I2C1->CR2 = (1 << I2C_CR2_NBYTES_Pos) | I2C_CR2_AUTOEND | (LCD_PEX_ADDR << I2C_CR2_SADD_Pos) | I2C_CR2_START; + while (I2C1->ISR & I2C_ISR_BUSY || !(I2C1->ISR & I2C_ISR_TXE)) { + /* do nothing. */ + } +} + +void lcd_tx_nibble(uint8_t value, uint8_t flags) { + value = (value&0xf)<>4, 0); + lcd_tx_nibble(value, 0); +} + +void lcd_write_char(char d) { + i2c_blocking_mode(); + lcd_tx_nibble(d>>4, B_LCD_RS); + lcd_tx_nibble(d, B_LCD_RS); +} + +void lcd_write_str(size_t line, char *str) { + i2c_blocking_mode(); + uint8_t line_offset[4] = {0, 64, 20, 84}; + delay_us(50); + lcd_command(LCD_CMD_DDRAM_ADDR | line_offset[line]); + while (*str) { + lcd_write_char(*str); + str++; + } +} + +void lcd_init() { + lcd_led = false; + i2c_init(); + i2c_blocking_mode(); + delay_us(40000); + + lcd_tx_nibble(LCD_ENTER_4BIT, 0); + lcd_command(LCD_CMD_FUNCTION | LCD_FN_4BIT | LCD_FN_2LINE | LCD_FN_5X8); + lcd_command(LCD_CMD_ON_OFF | LCD_DSP_ON | LCD_CUR_OFF | LCD_BLK_OFF); + lcd_command(LCD_CMD_CLEAR); + delay_us(2000); + lcd_command(LCD_CMD_ENTRY_MODE | LCD_ENTRY_CUR_RIGHT); + lcd_led = true; + i2c_nonblocking_mode(); +} + +void i2c_write(uint8_t addr, size_t n) { + DMA1_Channel6->CCR &= ~DMA_CCR_EN; + while (DMA1_Channel6->CCR & DMA_CCR_EN) { + /* do nothing */ + } + + DMA1_Channel6->CNDTR = n; + DMA1_Channel6->CCR |= DMA_CCR_EN; + + I2C1->CR2 = (n << I2C_CR2_NBYTES_Pos) | I2C_CR2_AUTOEND | (addr << I2C_CR2_SADD_Pos); + I2C1->CR2 |= I2C_CR2_START; + + if (st_i2c.blocking) { + while ((!(DMA1->ISR & DMA_ISR_TCIF6)) || (I2C1->ISR & I2C_ISR_BUSY)) { + /* do nothing */ + } + } +} + +void i2c_nonblocking_mode() { + DMA1_Channel6->CCR &= ~DMA_CCR_EN; + while (DMA1_Channel6->CCR & DMA_CCR_EN) { + /* do nothing */ + } + DMA1_Channel6->CCR |= DMA_CCR_TCIE; + + st_i2c.blocking = false; +} + +void i2c_blocking_mode() { + DMA1_Channel6->CCR &= ~DMA_CCR_EN; + while (DMA1_Channel6->CCR & DMA_CCR_EN) { + /* do nothing */ + } + DMA1_Channel6->CCR &= ~DMA_CCR_TCIE; + + st_i2c.blocking = true; +} + +void i2c_read(uint8_t addr, size_t n) { + DMA1_Channel7->CCR &= ~DMA_CCR_EN; + while (DMA1_Channel7->CCR & DMA_CCR_EN) { + /* do nothing */ + } + DMA1_Channel7->CNDTR = n; + DMA1_Channel7->CCR |= DMA_CCR_EN; + + I2C1->CR2 = (n << I2C_CR2_NBYTES_Pos) | I2C_CR2_START | I2C_CR2_RD_WRN | (addr << I2C_CR2_SADD_Pos); + + if (st_i2c.blocking) { + while ((!(DMA1->ISR & DMA_ISR_TCIF6)) || (I2C1->ISR & I2C_ISR_BUSY)) { + /* do nothing */ + } + } +} + + +void DMA1_Channel4_5_6_7_IRQHandler() { + int flags = DMA1->ISR; + if (flags & (DMA_ISR_TEIF6 | DMA_ISR_TEIF7)) { + DMA1->IFCR = DMA_IFCR_CTEIF6 | DMA_IFCR_CTEIF7; + asm volatile ("bkpt"); + + } else if (flags & DMA_ISR_TCIF6) { /* I2C TX complete */ + DMA1->IFCR = DMA_IFCR_CTCIF6; + + } else if (flags & DMA_ISR_TCIF7) { /* I2C RX complete */ + DMA1->IFCR = DMA_IFCR_CTCIF7; + } +} + + +void I2C1_IRQHandler() { + int flags = I2C1->ISR; + if (flags & (I2C_ISR_ARLO | I2C_ISR_BERR)) { + I2C1->ICR = I2C_ISR_ARLO | I2C_ISR_BERR; + asm volatile ("bkpt"); + } +} + int main(void) { /* Enable HSE w/ 8 MHz crystal */ /* FIXME */ @@ -250,7 +506,7 @@ int main(void) { (4<CR |= RCC_CR_PLLON; @@ -266,9 +522,10 @@ int main(void) { /* Enable peripheral clocks */ RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_GPIOCEN | RCC_AHBENR_DMAEN; - RCC->APB2ENR |= RCC_APB2ENR_TIM16EN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_ADCEN | RCC_APB2ENR_TIM15EN; + RCC->APB2ENR |= RCC_APB2ENR_TIM16EN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_ADCEN | RCC_APB2ENR_TIM15EN | RCC_APB2ENR_SYSCFGEN; RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_SPI2EN | RCC_APB1ENR_USART3EN | RCC_APB1ENR_I2C1EN | RCC_APB1ENR_USBEN; + RCC->CFGR3 |= RCC_CFGR3_I2C1SW; #define AFRL(pin, val) ((val) << ((pin)*4)) #define AFRH(pin, val) ((val) << (((pin)-8)*4)) @@ -365,6 +622,11 @@ int main(void) { SystemCoreClockUpdate(); adc_init(); + lcd_init(); + lcd_write_str(0, "LINE 1 LINE 1 LINE 1"); + lcd_write_str(1, "LINE 2 LINE 2 LINE 2"); + lcd_write_str(2, "LINE 3 LINE 3 LINE 3"); + lcd_write_str(3, "LINE 4 LINE 4 LINE 4"); // int apb2_clock = SystemCoreClock / APB2_PRESC; // // TIM15->PSC = apb2_clock / 1000000 * 100 - 1; /* 100us ticks */