/*
* =========================================================
* SMART PLANT WATERING SYSTEM
* STM32C031C6 - REGISTER LEVEL CODE
* WOKWI COMPATIBLE
* =========================================================
*
* SOIL SENSOR : PA0
* RELAY LED : PB10
* STATUS LED : PA5
*
* MENU BUTTON : PC13
* UP BUTTON : PB4
* DOWN BUTTON : PB5
*
* LCD I2C:
* SCL -> PB8
* SDA -> PB9
*
* =========================================================
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
/* =========================================================
GPIO STRUCTURES
========================================================= */
typedef struct {
volatile uint32_t MODER;
volatile uint32_t OTYPER;
volatile uint32_t OSPEEDR;
volatile uint32_t PUPDR;
volatile uint32_t IDR;
volatile uint32_t ODR;
volatile uint32_t BSRR;
volatile uint32_t LCKR;
volatile uint32_t AFR[2];
volatile uint32_t BRR;
} GPIO_t;
typedef struct {
volatile uint32_t CR;
volatile uint32_t ICSCR;
volatile uint32_t CFGR1;
volatile uint32_t CFGR2;
volatile uint32_t CFGR3;
volatile uint32_t RESERVED0[3];
volatile uint32_t CIFR;
volatile uint32_t CICR;
volatile uint32_t IOPRSTR;
volatile uint32_t AHBRSTR;
volatile uint32_t APBRSTR1;
volatile uint32_t APBRSTR2;
volatile uint32_t IOPENR;
volatile uint32_t AHBENR;
volatile uint32_t APBENR1;
volatile uint32_t APBENR2;
} RCC_t;
typedef struct {
volatile uint32_t ISR;
volatile uint32_t IER;
volatile uint32_t CR;
volatile uint32_t CFGR1;
volatile uint32_t CFGR2;
volatile uint32_t SMPR;
volatile uint32_t RESERVED0[2];
volatile uint32_t TR1;
volatile uint32_t TR2;
volatile uint32_t TR3;
volatile uint32_t RESERVED1;
volatile uint32_t RESERVED2[4];
volatile uint32_t DR;
} ADC_t;
typedef struct {
volatile uint32_t CR1;
volatile uint32_t CR2;
volatile uint32_t OAR1;
volatile uint32_t OAR2;
volatile uint32_t TIMINGR;
volatile uint32_t TIMEOUTR;
volatile uint32_t ISR;
volatile uint32_t ICR;
volatile uint32_t PECR;
volatile uint32_t RXDR;
volatile uint32_t TXDR;
} I2C_t;
/* =========================================================
BASE ADDRESSES
========================================================= */
#define GPIOA ((GPIO_t*)0x50000000UL)
#define GPIOB ((GPIO_t*)0x50000400UL)
#define GPIOC ((GPIO_t*)0x50000800UL)
#define RCC ((RCC_t*) 0x40021000UL)
#define ADC1 ((ADC_t*) 0x40012400UL)
#define I2C1 ((I2C_t*) 0x40005400UL)
/* =========================================================
LCD
========================================================= */
#define LCD_ADDR 0x4E
/* =========================================================
GLOBAL VARIABLES
========================================================= */
static int lowerThreshold = 1500;
static int upperThreshold = 3000;
static bool pumpState = false;
static bool ledState = false;
static int menuMode = 0;
static uint32_t appTick = 0;
static uint32_t lastPress = 0;
static uint32_t lastBlink = 0;
/* =========================================================
DELAY
========================================================= */
static void delay_ms(uint32_t ms)
{
for(uint32_t i=0;i<ms;i++)
{
for(volatile uint32_t j=0;j<1200;j++);
}
}
/* =========================================================
CLOCK INIT
========================================================= */
static void clock_init(void)
{
RCC->IOPENR |= (1<<0);
RCC->IOPENR |= (1<<1);
RCC->IOPENR |= (1<<2);
RCC->APBENR2 |= (1<<20);
RCC->APBENR1 |= (1<<21);
}
/* =========================================================
GPIO INIT
========================================================= */
static void gpio_init(void)
{
/* PA0 ANALOG */
GPIOA->MODER |= (3<<(0*2));
/* PA5 OUTPUT */
GPIOA->MODER &= ~(3<<(5*2));
GPIOA->MODER |= (1<<(5*2));
/* PB10 OUTPUT */
GPIOB->MODER &= ~(3<<(10*2));
GPIOB->MODER |= (1<<(10*2));
/* PB8 PB9 AF */
GPIOB->MODER &= ~((3<<(8*2)) | (3<<(9*2)));
GPIOB->MODER |= ((2<<(8*2)) | (2<<(9*2)));
GPIOB->OTYPER |= (1<<8) | (1<<9);
GPIOB->PUPDR &= ~((3<<(8*2)) | (3<<(9*2)));
GPIOB->PUPDR |= ((1<<(8*2)) | (1<<(9*2)));
GPIOB->AFR[1] &= ~((0xF<<0) | (0xF<<4));
GPIOB->AFR[1] |= ((6<<0) | (6<<4));
/* PC13 BUTTON */
GPIOC->MODER &= ~(3<<(13*2));
GPIOC->PUPDR &= ~(3<<(13*2));
GPIOC->PUPDR |= (1<<(13*2));
/* PB4 PB5 BUTTONS */
GPIOB->MODER &= ~((3<<(4*2)) | (3<<(5*2)));
GPIOB->PUPDR &= ~((3<<(4*2)) | (3<<(5*2)));
GPIOB->PUPDR |= ((1<<(4*2)) | (1<<(5*2)));
}
/* =========================================================
ADC INIT
========================================================= */
static void adc_init(void)
{
ADC1->CR |= (1<<28);
delay_ms(1);
ADC1->CR |= (1<<31);
while(ADC1->CR & (1<<31));
ADC1->CFGR1 = 0;
ADC1->SMPR = 7;
*((volatile uint32_t*)(0x40012400UL + 0x28)) = (1<<0);
ADC1->CR |= (1<<0);
while(!(ADC1->ISR & (1<<0)));
}
/* =========================================================
ADC READ
========================================================= */
static uint32_t adc_read(void)
{
ADC1->CR |= (1<<2);
while(!(ADC1->ISR & (1<<2)));
ADC1->ISR &= ~(1<<2);
return ADC1->DR;
}
/* =========================================================
I2C INIT
========================================================= */
static void i2c_init(void)
{
I2C1->CR1 = 0;
I2C1->TIMINGR = 0x00100413;
I2C1->CR1 = (1<<0);
}
/* =========================================================
I2C WRITE
========================================================= */
static void i2c_write(uint8_t addr,uint8_t data)
{
while(I2C1->ISR & (1<<15));
I2C1->CR2 =
(1<<25) |
(1<<13) |
(1<<16) |
(addr & 0xFE);
while(!(I2C1->ISR & (1<<1)));
I2C1->TXDR = data;
while(!(I2C1->ISR & (1<<5)));
I2C1->ICR = (1<<5);
}
/* =========================================================
LCD
========================================================= */
static void lcd_nibble(uint8_t nib,uint8_t rs)
{
uint8_t d =
(nib & 0xF0) |
0x08 |
(rs ? 1 : 0);
i2c_write(LCD_ADDR,d | 0x04);
delay_ms(1);
i2c_write(LCD_ADDR,d & ~0x04);
}
static void lcd_byte(uint8_t b,uint8_t rs)
{
lcd_nibble(b & 0xF0,rs);
lcd_nibble((b<<4)&0xF0,rs);
delay_ms(2);
}
static void lcd_cmd(uint8_t c)
{
lcd_byte(c,0);
}
static void lcd_char(char c)
{
lcd_byte(c,1);
}
static void lcd_clear(void)
{
lcd_cmd(0x01);
delay_ms(3);
}
static void lcd_cursor(uint8_t col,uint8_t row)
{
uint8_t off[] = {0x00,0x40};
lcd_cmd(0x80 | (col + off[row]));
}
static void lcd_print(const char *s)
{
while(*s)
{
lcd_char(*s++);
}
}
static void lcd_init(void)
{
delay_ms(50);
lcd_nibble(0x30,0);
delay_ms(5);
lcd_nibble(0x30,0);
delay_ms(2);
lcd_nibble(0x30,0);
delay_ms(2);
lcd_nibble(0x20,0);
lcd_cmd(0x28);
lcd_cmd(0x0C);
lcd_cmd(0x06);
lcd_cmd(0x01);
delay_ms(3);
}
/* =========================================================
GPIO HELPERS
========================================================= */
#define LED_ON() (GPIOA->BSRR = (1<<5))
#define LED_OFF() (GPIOA->BRR = (1<<5))
#define RELAY_ON() (GPIOB->BSRR = (1<<10))
#define RELAY_OFF() (GPIOB->BRR = (1<<10))
#define READ_MENU() (!(GPIOC->IDR & (1<<13)))
#define READ_UP() (!(GPIOB->IDR & (1<<4)))
#define READ_DOWN() (!(GPIOB->IDR & (1<<5)))
/* =========================================================
BUTTONS
========================================================= */
static void handle_buttons(void)
{
if(appTick - lastPress < 300)
return;
bool menu = READ_MENU();
bool up = READ_UP();
bool down = READ_DOWN();
if(menu)
{
menuMode = (menuMode + 1) % 3;
lcd_clear();
lastPress = appTick;
return;
}
if(menuMode == 1)
{
if(up && lowerThreshold < 3500)
{
lowerThreshold += 100;
lastPress = appTick;
}
if(down && lowerThreshold > 100)
{
lowerThreshold -= 100;
lastPress = appTick;
}
}
else if(menuMode == 2)
{
if(up && upperThreshold < 4095)
{
upperThreshold += 100;
lastPress = appTick;
}
if(down && upperThreshold > 500)
{
upperThreshold -= 100;
lastPress = appTick;
}
}
}
/* =========================================================
PUMP + LED
========================================================= */
static void handle_pump_led(uint32_t raw)
{
if(menuMode != 0)
{
RELAY_OFF();
LED_OFF();
pumpState = false;
return;
}
/* DRY */
if(raw < (uint32_t)lowerThreshold)
{
pumpState = true;
RELAY_ON();
if(appTick - lastBlink >= 300)
{
lastBlink = appTick;
ledState = !ledState;
ledState ? LED_ON() : LED_OFF();
}
}
/* WET */
else if(raw > (uint32_t)upperThreshold)
{
pumpState = false;
RELAY_OFF();
LED_OFF();
}
/* MID */
else
{
pumpState = false;
RELAY_OFF();
if(appTick - lastBlink >= 1000)
{
lastBlink = appTick;
ledState = !ledState;
ledState ? LED_ON() : LED_OFF();
}
}
}
/* =========================================================
LCD UPDATE
========================================================= */
static void update_lcd(int pct,uint32_t raw)
{
char buf[17];
if(menuMode == 0)
{
lcd_cursor(0,0);
snprintf(buf,sizeof(buf),
"M:%3d%% R:%4lu",
pct,
raw);
lcd_print(buf);
lcd_cursor(0,1);
snprintf(buf,sizeof(buf),
"L:%4d H:%4d",
lowerThreshold,
upperThreshold);
lcd_print(buf);
}
else if(menuMode == 1)
{
lcd_cursor(0,0);
lcd_print("SET LOW THRESH");
lcd_cursor(0,1);
snprintf(buf,sizeof(buf),
"LOW = %4d",
lowerThreshold);
lcd_print(buf);
}
else
{
lcd_cursor(0,0);
lcd_print("SET HIGH THR");
lcd_cursor(0,1);
snprintf(buf,sizeof(buf),
"HIGH= %4d",
upperThreshold);
lcd_print(buf);
}
}
/* =========================================================
MAIN
========================================================= */
int main(void)
{
clock_init();
gpio_init();
adc_init();
i2c_init();
lcd_init();
lcd_cursor(0,0);
lcd_print("Plant Watering");
lcd_cursor(0,1);
lcd_print("STM32C031C6");
delay_ms(1000);
lcd_clear();
while(1)
{
uint32_t raw = adc_read();
int pct =
(int)((raw * 100) / 4095);
handle_buttons();
handle_pump_led(raw);
update_lcd(pct,raw);
delay_ms(50);
appTick += 50;
}
}