#include <stm32c0xx.h>
#include <stdio.h>
// İsim çakışmasını önlemek için adını my_delay yaptık
void my_delay(volatile uint32_t count) {
while(count--);
}
// --- I2C & LCD DRIVER FUNCTIONS (BARE METAL) ---
// Sends a single byte over I2C to the LCD module (Address 0x27)
void I2C_Write(uint8_t data) {
I2C1->CR2 = (0x27 << 1) | (1 << 16); // Set Slave Address to 0x27, 1 byte to send
I2C1->CR2 |= (1 << 13); // Generate START condition
while(!(I2C1->ISR & (1 << 1))); // Wait for TXIS (Transmit Interrupt Status) flag
I2C1->TXDR = data; // Push data to Transmit Data Register
while(!(I2C1->ISR & (1 << 6))); // Wait for TC (Transfer Complete) flag
I2C1->CR2 |= (1 << 14); // Generate STOP condition
}
// Sends data/command nibbles to the HD44780 controller via PCF8574
void LCD_Send(uint8_t data, uint8_t rs) {
uint8_t high_nibble = (data & 0xF0) | 0x08 | rs; // 0x08 keeps backlight ON
uint8_t low_nibble = ((data << 4) & 0xF0) | 0x08 | rs;
I2C_Write(high_nibble | 0x04); // EN = 1
my_delay(100);
I2C_Write(high_nibble & ~0x04); // EN = 0
my_delay(100);
I2C_Write(low_nibble | 0x04); // EN = 1
my_delay(100);
I2C_Write(low_nibble & ~0x04); // EN = 0
my_delay(100);
}
void LCD_Cmd(uint8_t cmd) { LCD_Send(cmd, 0); }
void LCD_Data(uint8_t data) { LCD_Send(data, 1); }
void LCD_Init() {
my_delay(10000); // Wait for system stabilization
LCD_Cmd(0x33); // Initialization sequence for 4-bit mode
LCD_Cmd(0x32);
LCD_Cmd(0x28); // 4-bit mode, 2 lines, 5x8 font
LCD_Cmd(0x0C); // Display ON, Cursor OFF
LCD_Cmd(0x01); // Clear display
my_delay(10000);
}
void LCD_String(char* str) {
while(*str) {
LCD_Data(*str++);
}
}
void LCD_SetCursor(uint8_t row, uint8_t col) {
uint8_t addr = (row == 0) ? 0x80 : 0xC0;
LCD_Cmd(addr + col);
}
int main(void) {
// 1. CLOCK CONFIGURATION
RCC->IOPENR |= (1 << 0) | (1 << 1); // Enable GPIOA and GPIOB clocks
RCC->APBENR2 |= (1 << 11) | (1 << 20); // Enable TIM1 and ADC clocks
RCC->APBENR1 |= (1 << 21); // Enable I2C1 clock
// 2. GPIO CONFIGURATION
// PA0 (Potentiometer) -> Analog Mode (11)
GPIOA->MODER |= (3 << (0 * 2));
// PA8 (Servo Motor) -> Alternate Function Mode (10) for TIM1_CH1
GPIOA->MODER &= ~(3 << (8 * 2));
GPIOA->MODER |= (2 << (8 * 2));
GPIOA->AFR[1] &= ~(0xF << ((8 - 8) * 4));
GPIOA->AFR[1] |= (2 << ((8 - 8) * 4));
// PB8 (SCL), PB9 (SDA) -> Alternate Function Mode (10) for I2C1
GPIOB->MODER &= ~(0xF << (8 * 2)) & ~(0xF << (9 * 2));
GPIOB->MODER |= (0xA << (8 * 2)); // 1010 shifted
GPIOB->OTYPER |= (3 << 8); // Set PB8 and PB9 as Open-Drain (required for I2C)
GPIOB->AFR[1] &= ~(0xFF << ((8 - 8) * 4));
GPIOB->AFR[1] |= (0x66 << ((8 - 8) * 4)); // AF6 mapping for I2C1
// 3. I2C CONFIGURATION
I2C1->CR1 &= ~(1 << 0); // Disable I2C peripheral before config
I2C1->TIMINGR = 0x10420F13; // Standard 100kHz timing setup
I2C1->CR1 |= (1 << 0); // Enable I2C peripheral
// Initialize Display and print static template
LCD_Init();
LCD_SetCursor(0, 0);
LCD_String("Target Dosage:");
// 4. TIM1 (PWM) CONFIGURATION (50Hz exactly for Servo valve control)
TIM1->PSC = 48 - 1; // Prescaler: 48 MHz to 1 MHz (1 us tick)
TIM1->ARR = 20000 - 1; // Auto-Reload: 20000 us = 20 ms = 50 Hz
TIM1->CCMR1 |= (6 << 4); // PWM Mode 1
TIM1->CCMR1 |= (1 << 3); // Output Compare Preload Enable
TIM1->CCER |= (1 << 0); // Enable Capture/Compare 1 output
TIM1->BDTR |= (1 << 15); // Main Output Enable (MOE)
TIM1->CR1 |= (1 << 0); // Start Timer 1
// 5. ADC CONFIGURATION
ADC1->CR |= (1 << 28); // Enable ADC Voltage Regulator
my_delay(1000); // Wait for regulator to stabilize
ADC1->CR |= (1 << 31); // Start ADC Calibration
while(ADC1->CR & (1 << 31)); // Wait until calibration is complete
ADC1->CR |= (1 << 0); // Enable ADC
while(!(ADC1->ISR & (1 << 0))); // Wait until ADC is ready
ADC1->CHSELR |= (1 << 0); // Select Channel 0 (PA0)
char display_buffer[16];
// 6. MAIN CONTROL LOOP
// 6. MAIN CONTROL LOOP
while (1) {
ADC1->CR |= (1 << 2); // Start Conversion
while(!(ADC1->ISR & (1 << 2))); // Wait for EOC (End of Conversion) flag
uint32_t adc_val = ADC1->DR; // Read raw ADC data (0-4095)
// Map 12-bit ADC value to Servo Duty Cycle (1000 us to 2000 us)
TIM1->CCR1 = 1000 + ((adc_val * 1000) / 4095);
// Map 12-bit ADC value to a visual dosage representation (0.00 mL to 50.00 mL)
int dosage = (adc_val * 5000) / 4095; //
int whole_ml = dosage / 100; //
int fractional_ml = dosage % 100; //
// Format string and update LCD
sprintf(display_buffer, " %2d.%02d mL ", whole_ml, fractional_ml);
LCD_SetCursor(1, 4);
LCD_String(display_buffer);
my_delay(50000); // Loop delay to prevent visual flickering on the LCD
}
}