#include <stdint.h>
#define I2C_BASE 0x40005400
#define RCC_BASE 0x40021000
#define GPIOB_BASE 0x50000400
// I2C Registers
#define I2C_CR1 (*((volatile uint32_t *)(I2C_BASE + 0x00)))
#define I2C_CR2 (*((volatile uint32_t *)(I2C_BASE + 0x04)))
#define I2C_TIMINGR (*((volatile uint32_t *)(I2C_BASE + 0x10)))
#define I2C_TXDR (*((volatile uint32_t *)(I2C_BASE + 0x28)))
#define I2C_RXDR (*((volatile uint32_t *)(I2C_BASE + 0x24)))
#define I2C_ISR (*((volatile uint32_t *)(I2C_BASE + 0x18)))
#define RCC_IOPENR (*((volatile uint32_t *)(RCC_BASE + 0x34)))
#define RCC_APBENR1 (*((volatile uint32_t *)(RCC_BASE + 0x3C)))
#define GPIOB_MODER (*((volatile uint32_t *)(GPIOB_BASE + 0x00)))
#define GPIOB_OTYPER (*((volatile uint32_t *)(GPIOB_BASE + 0x04)))
#define GPIOB_OSPEEDR (*((volatile uint32_t *)(GPIOB_BASE + 0x08)))
#define GPIOB_PUPDR (*((volatile uint32_t *)(GPIOB_BASE + 0x0C)))
#define GPIOB_AFRL (*((volatile uint32_t *)(GPIOB_BASE + 0x20)))
// Macros for I2C configuration
#define I2C_CR1_PE (1 << 0) // Peripheral Enable
#define I2C_CR2_START (1 << 13) // Generate Start Condition
#define I2C_CR2_STOP (1 << 14) // Generate Stop Condition
#define I2C_ISR_TXE (1 << 0) // Transmit Data Register Empty
#define I2C_ISR_TXIS (1 << 1) // Transmit Interrupt Status
#define I2C_ISR_TC (1 << 6) // Transfer Complete
#define I2C_ISR_NACKF (1 << 4) // NACK flag
// LCD Configuration
#define I2C_ADDR 0x27 // I2C address for the LCD
#define LCD_BACKLIGHT 0x08 // Backlight bit
#define LCD_ENABLE_BIT 0x04 // Enable bit
#define LCD_COLUMNS 20 // Number of LCD columns
#define LCD_LINES 4 // Number of LCD lines
// Delay function
void delay_ms(uint32_t ms) {
while (ms--) {
volatile uint32_t i = 1000;
while (i--);
}
}
// GPIO Initialization for I2C
void GPIO_Init_I2C(void) {
// Enable GPIOB clock
RCC_IOPENR |= (1 << 1);
// Configure PB8 (SCL) and PB9 (SDA) as Alternate Function mode
GPIOB_MODER &= ~((0x3 << (8 * 2)) | (0x3 << (9 * 2))); // Clear MODER
GPIOB_MODER |= (0x2 << (8 * 2)) | (0x2 << (9 * 2)); // Set AF mode (0b10)
// Set open-drain output type
GPIOB_OTYPER |= (1 << 8) | (1 << 9);
// Set high speed
GPIOB_OSPEEDR |= (0x3 << (8 * 2)) | (0x3 << (9 * 2));
// Enable internal pull-up resistors
GPIOB_PUPDR &= ~((0x3 << (8 * 2)) | (0x3 << (9 * 2)));
GPIOB_PUPDR |= (0x1 << (8 * 2)) | (0x1 << (9 * 2));
// Configure Alternate Function for PB8 and PB9 (AF4 for I2C)
GPIOB_AFRL &= ~((0xF << ((8 - 8) * 4)) | (0xF << ((9 - 8) * 4)));
GPIOB_AFRL |= (0x4 << ((8 - 8) * 4)) | (0x4 << ((9 - 8) * 4));
}
// I2C Initialization
void I2C_Init(void) {
// Enable I2C1 clock
RCC_APBENR1 |= (1 << 21);
// Reset I2C
I2C_CR1 &= ~I2C_CR1_PE; // Disable peripheral
while (I2C_CR1 & I2C_CR1_PE); // Ensure PE = 0
for (volatile int i = 0; i < 3; i++); // Wait for 3 APB clock cycles
// Configure timing for 100kHz I2C (16 MHz system clock)
I2C_TIMINGR |= (0x9<<0)|(0x3<<8)|(0x3<<16)|(0x3<<20)|(0x5<<28); // Adjust as needed
// Enable I2C peripheral
I2C_CR1 |= I2C_CR1_PE;
}
// I2C Start Condition
void I2C_Start(uint8_t slaveAddr, uint8_t numBytes, uint8_t direction) {
// Wait for transfer to be ready
while (I2C_ISR & I2C_ISR_BUSY);
// Configure slave address, number of bytes, and direction
I2C_CR2 = (slaveAddr << 1) | // 7-bit slave address
(numBytes << 16) | // Number of bytes
(direction << 10); // Direction: 0 = Write, 1 = Read
// Generate Start condition
I2C_CR2 |= I2C_CR2_START;
// Wait for the TXIS flag (ready to send data) or NACK flag
while (!(I2C_ISR & (I2C_ISR_TXIS | I2C_ISR_NACKF))) {
if (I2C_ISR & I2C_ISR_NACKF) {
// Handle NACK error
I2C_Stop();
return;
}
}
}
// I2C Write Data
void I2C_Write(uint8_t data) {
// Wait until TXIS is set (ready to transmit)
while (!(I2C_ISR & I2C_ISR_TXIS));
// Write data to TXDR
I2C_TXDR = data;
// Wait until TXE (Transmit Buffer Empty)
while (!(I2C_ISR & I2C_ISR_TXE));
}
// I2C Stop Condition
void I2C_Stop(void) {
// Generate STOP condition
I2C_CR2 |= I2C_CR2_STOP;
// Wait until the STOP flag is cleared
while (I2C_CR2 & I2C_CR2_STOP);
}
// LCD Functions
void lcd_expanderWrite(uint8_t data) {
I2C_Start(I2C_ADDR, 1, 0);
I2C_Write(data | LCD_BACKLIGHT); // Write data with backlight
I2C_Stop();
}
void lcd_pulseEnable(uint8_t data) {
lcd_expanderWrite(data | LCD_ENABLE_BIT); // Enable pulse
delay_ms(1); // Small delay
lcd_expanderWrite(data & ~LCD_ENABLE_BIT); // Disable pulse
delay_ms(1); // Small delay
}
void lcd_write4Bits(uint8_t value) {
lcd_expanderWrite(value);
lcd_pulseEnable(value);
}
void lcd_send(uint8_t value, uint8_t mode) {
uint8_t highNibble = value & 0xF0;
uint8_t lowNibble = (value << 4) & 0xF0;
lcd_write4Bits(highNibble | mode);
lcd_write4Bits(lowNibble | mode);
}
void lcd_sendCommand(uint8_t command) {
lcd_send(command, 0); // Command mode
}
void lcd_sendData(uint8_t data) {
lcd_send(data, 0x01); // Data mode
}
void lcd_init() {
GPIO_Init_I2C();
I2C_Init();
delay_ms(50); // Wait for LCD to power up
lcd_sendCommand(0x33); // Initialize in 8-bit mode
lcd_sendCommand(0x32); // Switch to 4-bit mode
lcd_sendCommand(0x28); // 4-bit mode, 2-line, 5x8 dots
lcd_sendCommand(0x0C); // Display ON, cursor OFF
lcd_sendCommand(0x06); // Entry mode: Increment cursor
lcd_sendCommand(0x01); // Clear display
delay_ms(2);
}
void lcd_setCursor(uint8_t col, uint8_t row) {
uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
lcd_sendCommand(0x80 | (col + row_offsets[row]));
}
void lcd_print(const char* str) {
while (*str) {
lcd_sendData(*str++);
}
}
void lcd_backlight() {
lcd_expanderWrite(0);
}
// Main Setup Function
void setup1() {
lcd_init();
lcd_backlight();
lcd_setCursor(3, 0);
lcd_print("STM32 I2C LCD");
}
// Main Function
int main(void) {
setup1();
while (1) {
// Main loop does nothing in this example
}
}