From 45f80bc6b1b4a7a5e15e31cbf188477c96439e9c Mon Sep 17 00:00:00 2001 From: cpu Date: Tue, 19 May 2026 01:49:23 +0200 Subject: [PATCH] implemented display --- firmware/main.c | 229 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 168 insertions(+), 61 deletions(-) diff --git a/firmware/main.c b/firmware/main.c index 57ad143..0c708d9 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -4,33 +4,121 @@ // ========================================== // PIN CONFIGURATION (CH32V003J4M6 - SOP8) // ========================================== -// Package Pin 1: PD6 (FLOW_PULSE_MCU) -> EXTI Input -// Package Pin 3: PA2 (ALARM_TRIGGER) -> GPIO Output -// Package Pin 5: PC1 (I2C_SDA) -> Bitbang or HW I2C -// Package Pin 6: PC2 (I2C_SCL) -> Bitbang or HW I2C -// Package Pin 7: PC4 (BUTTON_ADC) -> ADC Channel 2 Input -// Package Pin 8: PD1 (SWIO) -> Programming & Logging - #define FLOW_PIN 6 // PD6 = bit 6 of GPIOD #define ALARM_PIN 2 // PA2 = bit 2 of GPIOA #define BUTTON_PIN 4 // PC4 = bit 4 of GPIOC // Constants -#define FLOW_CONVERSION_FACTOR 5.5 // L/min = Hz / 5.5 (Adjust for ZJ-S401) +#define FLOW_CONVERSION_FACTOR 5.5 +#define DEBOUNCE_DELAY_MS 200 -// Button ladder -#define ADC_UP_BTN_MAX 15 // UP connects direct to GND -> ~0V -> ADC ~0 +// Fixed ADC limits for the resistor ladder +#define ADC_UP_BTN_MAX 15 // UP connects direct to GND (~0V -> ADC ~0) #define ADC_UP_BTN_MIN 0 -#define ADC_DOWN_BTN_MAX 200 // DOWN connects via 1k -> ~0.45V -> ADC ~93 +#define ADC_DOWN_BTN_MAX 200 // DOWN connects via 1k divider (~0.45V -> ADC ~93) #define ADC_DOWN_BTN_MIN 50 -#define DEBOUNCE_DELAY_MS 200 - // Globals volatile uint32_t pulse_count = 0; -uint32_t flow_rate_ml_min = 0; // Stored in mL/min for integer precision -uint32_t threshold_ml_min = 2000; // Default: 2.0 L/min -uint8_t alarm_active = 0; // Track state to prevent log spam (0 = OK, 1 = ALARM) +uint32_t flow_rate_ml_min = 0; +uint32_t threshold_ml_min = 2000; +uint8_t alarm_active = 0; + +// ========================================== +// COMPACT 5x8 FONT (ASCII 32 to 126) +// ========================================== +const uint8_t font5x8[] = { + 0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x4f,0x00,0x00, 0x00,0x07,0x00,0x07,0x00, 0x14,0x7f,0x14,0x7f,0x14, + 0x24,0x2a,0x7f,0x2a,0x12, 0x23,0x13,0x08,0x64,0x62, 0x36,0x49,0x55,0x22,0x50, 0x00,0x05,0x03,0x00,0x00, + 0x00,0x1c,0x22,0x41,0x00, 0x00,0x41,0x22,0x1c,0x00, 0x08,0x2a,0x1c,0x2a,0x08, 0x08,0x08,0x3e,0x08,0x08, + 0x00,0x50,0x30,0x00,0x00, 0x08,0x08,0x08,0x08,0x08, 0x00,0x60,0x60,0x00,0x00, 0x20,0x10,0x08,0x04,0x02, + 0x3e,0x51,0x49,0x45,0x3e, 0x00,0x42,0x7f,0x40,0x00, 0x42,0x61,0x51,0x49,0x46, 0x21,0x41,0x45,0x4b,0x31, + 0x18,0x14,0x12,0x7f,0x10, 0x27,0x45,0x45,0x45,0x39, 0x3c,0x4a,0x49,0x49,0x30, 0x01,0x71,0x09,0x05,0x03, + 0x36,0x49,0x49,0x49,0x36, 0x06,0x49,0x49,0x29,0x1e, 0x00,0x36,0x36,0x00,0x00, 0x00,0x56,0x36,0x00,0x00, + 0x00,0x08,0x14,0x22,0x41, 0x14,0x14,0x14,0x14,0x14, 0x41,0x22,0x14,0x08,0x00, 0x02,0x01,0x51,0x09,0x06, + 0x32,0x49,0x79,0x41,0x3e, 0x7e,0x11,0x11,0x11,0x7e, 0x7f,0x49,0x49,0x49,0x36, 0x3e,0x41,0x41,0x41,0x22, + 0x7f,0x41,0x41,0x22,0x1c, 0x7f,0x49,0x49,0x49,0x41, 0x7f,0x09,0x09,0x01,0x01, 0x3e,0x41,0x41,0x51,0x32, + 0x7f,0x08,0x08,0x08,0x7f, 0x00,0x41,0x7f,0x41,0x00, 0x20,0x40,0x41,0x3f,0x01, 0x7f,0x08,0x14,0x22,0x41, + 0x7f,0x40,0x40,0x40,0x40, 0x7f,0x02,0x04,0x02,0x7f, 0x7f,0x04,0x08,0x10,0x7f, 0x3e,0x41,0x41,0x41,0x3e, + 0x7f,0x09,0x09,0x09,0x06, 0x3e,0x41,0x51,0x21,0x5e, 0x7f,0x09,0x19,0x29,0x46, 0x46,0x49,0x49,0x49,0x31, + 0x01,0x01,0x7f,0x01,0x01, 0x3f,0x40,0x40,0x40,0x3f, 0x1f,0x20,0x40,0x20,0x1f, 0x3f,0x40,0x38,0x40,0x3f, + 0x63,0x14,0x08,0x14,0x63, 0x03,0x04,0x78,0x04,0x03, 0x61,0x51,0x49,0x45,0x43, 0x00,0x7f,0x41,0x41,0x00, + 0x02,0x04,0x08,0x10,0x20, 0x00,0x41,0x41,0x7f,0x00, 0x04,0x02,0x01,0x02,0x04, 0x40,0x40,0x40,0x40,0x40, + 0x00,0x01,0x02,0x04,0x00, 0x20,0x54,0x54,0x54,0x78, 0x7f,0x48,0x44,0x44,0x38, 0x38,0x44,0x44,0x44,0x20, + 0x38,0x44,0x44,0x48,0x7f, 0x38,0x54,0x54,0x54,0x18, 0x08,0x7e,0x09,0x01,0x02, 0x08,0x14,0x54,0x54,0x3c, + 0x7f,0x08,0x04,0x04,0x78, 0x00,0x44,0x7d,0x40,0x00, 0x20,0x40,0x44,0x3d,0x00, 0x7f,0x10,0x28,0x44,0x00, + 0x00,0x41,0x7f,0x40,0x00, 0x7c,0x04,0x18,0x04,0x78, 0x7c,0x08,0x04,0x04,0x78, 0x38,0x44,0x44,0x44,0x38, + 0x7c,0x14,0x14,0x14,0x08, 0x08,0x14,0x14,0x18,0x7c, 0x7c,0x08,0x04,0x04,0x08, 0x48,0x54,0x54,0x54,0x20, + 0x04,0x3f,0x44,0x40,0x20, 0x3c,0x40,0x40,0x20,0x7c, 0x1c,0x20,0x40,0x20,0x1c, 0x3c,0x40,0x30,0x40,0x3c, + 0x44,0x28,0x10,0x28,0x44, 0x0c,0x50,0x50,0x50,0x3c, 0x44,0x64,0x54,0x4c,0x44, 0x00,0x08,0x36,0x41,0x00, + 0x00,0x00,0x7f,0x00,0x00, 0x00,0x41,0x36,0x08,0x00, 0x08,0x08,0x2a,0x1c,0x08, 0x08,0x1c,0x2a,0x08,0x08 +}; + +// ========================================== +// I2C & SSD1306 DRIVER +// ========================================== +#define OLED_ADDR 0x3C + +// Resilient I2C Write with Timeouts (prevents MCU freezing if OLED is disconnected) +void I2C_Write(uint8_t addr, uint8_t reg, uint8_t data) { + uint16_t timeout; + + timeout = 10000; while((I2C1->STAR2 & I2C_STAR2_BUSY) && --timeout); + if (!timeout) return; + + I2C1->CTLR1 |= I2C_CTLR1_START; + timeout = 10000; while(!(I2C1->STAR1 & I2C_STAR1_SB) && --timeout); + + I2C1->DATAR = addr << 1; + timeout = 10000; while(!(I2C1->STAR1 & I2C_STAR1_ADDR) && --timeout); + (void)I2C1->STAR2; // Read STAR2 to clear ADDR flag + + I2C1->DATAR = reg; + timeout = 10000; while(!(I2C1->STAR1 & I2C_STAR1_TXE) && --timeout); + + I2C1->DATAR = data; + timeout = 10000; while(!(I2C1->STAR1 & I2C_STAR1_BTF) && --timeout); + + I2C1->CTLR1 |= I2C_CTLR1_STOP; +} + +void OLED_Cmd(uint8_t cmd) { I2C_Write(OLED_ADDR, 0x00, cmd); } +void OLED_Data(uint8_t data) { I2C_Write(OLED_ADDR, 0x40, data); } + +void OLED_Init(void) { + const uint8_t init_sequence[] = { + 0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40, + 0x8D, 0x14, 0x20, 0x02, 0xA1, 0xC8, 0xDA, 0x12, + 0x81, 0xCF, 0xD9, 0xF1, 0xDB, 0x40, 0xA4, 0xA6, 0xAF + }; + for(int i=0; i> 4) | 0x10); + OLED_Cmd(x & 0x0F); +} + +void OLED_Clear(void) { + for(uint8_t y=0; y<8; y++) { + OLED_SetPos(0, y); + for(uint8_t x=0; x<128; x++) OLED_Data(0x00); + } +} + +// Print string using 5x8 font (scaled to 6x8 with spacing) +void OLED_Print(uint8_t x, uint8_t y, const char* str) { + OLED_SetPos(x, y); + while(*str) { + if (*str >= 32 && *str <= 126) { + uint16_t idx = (*str - 32) * 5; + for(uint8_t i=0; i<5; i++) OLED_Data(font5x8[idx + i]); + OLED_Data(0x00); // 1px spacing + } + str++; + } +} // ========================================== // INTERRUPT HANDLER (Flow Sensor Pulses) @@ -39,7 +127,7 @@ void EXTI7_0_IRQHandler(void) __attribute__((interrupt)); void EXTI7_0_IRQHandler(void) { if (EXTI->INTFR & (1 << FLOW_PIN)) { pulse_count++; - EXTI->INTFR = (1 << FLOW_PIN); // Clear interrupt flag + EXTI->INTFR = (1 << FLOW_PIN); } } @@ -47,44 +135,52 @@ void EXTI7_0_IRQHandler(void) { // HARDWARE INITIALIZATION // ========================================== void Init_Hardware(void) { - // 1. Enable Clocks for GPIOA, GPIOC, GPIOD, ADC, AFIO + // 1. Enable Clocks RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_ADC1 | RCC_APB2Periph_AFIO; + RCC->APB1PCENR |= RCC_APB1Periph_I2C1; - // [NEW FIX] Set ADC Clock Prescaler to DIV8 (48MHz / 8 = 6MHz). Must be <= 14MHz! - RCC->CFGR0 &= ~RCC_ADCPRE; // Clear prescaler bits - RCC->CFGR0 |= RCC_ADCPRE_DIV8; // Set to divide by 8 + // 2. Configure I2C Pins (PC1 SDA, PC2 SCL) -> AF Open Drain, 10MHz + GPIOC->CFGLR &= ~(0xFF << 4); + GPIOC->CFGLR |= (0xDD << 4); // 0xD = Alternate Function Open Drain (11) + 10Mhz (01) - // 2. Configure ALARM_TRIGGER Output (PA2) - Push-Pull - GPIOA->CFGLR &= ~(0xF << (4 * ALARM_PIN)); - GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * ALARM_PIN); - GPIOA->BSHR = (1 << (16 + ALARM_PIN)); + // 3. Init Hardware I2C (100kHz @ 48MHz SysClk) + I2C1->CTLR1 |= I2C_CTLR1_SWRST; + I2C1->CTLR1 &= ~I2C_CTLR1_SWRST; + I2C1->CTLR2 = 48; + I2C1->CKCFGR = 240; + I2C1->CTLR1 |= I2C_CTLR1_PE; - // 3. Configure FLOW_PULSE_MCU Input (PD6) - Pull-up - GPIOD->CFGLR &= ~(0xF << (4 * FLOW_PIN)); - GPIOD->CFGLR |= (GPIO_CNF_IN_PUPD) << (4 * FLOW_PIN); - GPIOD->BSHR = (1 << FLOW_PIN); + // 4. ADC Setup (Fixed Clocks & SW Trigger Setup) + RCC->CFGR0 &= ~RCC_ADCPRE; + RCC->CFGR0 |= RCC_ADCPRE_DIV8; // CH32V003 ADC max is 14MHz, 48/8=6MHz is safe - AFIO->EXTICR |= (0b11 << (FLOW_PIN * 2)); - EXTI->INTENR |= (1 << FLOW_PIN); - EXTI->FTENR |= (1 << FLOW_PIN); - NVIC_EnableIRQ(EXTI7_0_IRQn); - - // 4. Configure BUTTON_ADC Input (PC4 / AIN2) - Analog mode - GPIOC->CFGLR &= ~(0xF << (4 * BUTTON_PIN)); - - // Setup ADC + GPIOC->CFGLR &= ~(0xF << (4 * BUTTON_PIN)); // Analog mode (no pull) ADC1->CTLR2 |= ADC_ADON; - ADC1->RSQR3 = 2; + ADC1->RSQR3 = 2; // AIN2 ADC1->SAMPTR2 = 7 << (3 * 2); - // [NEW FIX] Must configure EXTTRIG and EXTSEL for SWSTART to actually work - ADC1->CTLR2 |= ADC_EXTSEL; // Select SWSTART trigger (111) - ADC1->CTLR2 |= ADC_EXTTRIG; // Enable external trigger + ADC1->CTLR2 |= ADC_EXTSEL; // [FIX] Select SWSTART trigger (111) + ADC1->CTLR2 |= ADC_EXTTRIG; // [FIX] Enable external trigger for SW to work ADC1->CTLR2 |= ADC_RSTCAL; while(ADC1->CTLR2 & ADC_RSTCAL); ADC1->CTLR2 |= ADC_CAL; while(ADC1->CTLR2 & ADC_CAL); + + // 5. Output / Input Pins + GPIOA->CFGLR &= ~(0xF << (4 * ALARM_PIN)); + GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * ALARM_PIN); + GPIOA->BSHR = (1 << (16 + ALARM_PIN)); + + GPIOD->CFGLR &= ~(0xF << (4 * FLOW_PIN)); + GPIOD->CFGLR |= (GPIO_CNF_IN_PUPD) << (4 * FLOW_PIN); + GPIOD->BSHR = (1 << FLOW_PIN); + + // 6. EXTI Interrupt + AFIO->EXTICR |= (0b11 << (FLOW_PIN * 2)); + EXTI->INTENR |= (1 << FLOW_PIN); + EXTI->FTENR |= (1 << FLOW_PIN); + NVIC_EnableIRQ(EXTI7_0_IRQn); } // ========================================== @@ -97,7 +193,25 @@ uint16_t Read_ADC(void) { } void Update_Display(uint32_t current_flow, uint32_t threshold) { - // TODO: Implement OLED I2C drawing here using ch32fun's I2C bitbang + char buf[32]; + + // Page 0 (Top line) + OLED_Print(16, 0, "CNC WATER FLOW"); + + // Page 2 (Current Flow) + snprintf(buf, sizeof(buf), "Flow: %4lu mL/m", current_flow); + OLED_Print(0, 3, buf); + + // Page 4 (Threshold) + snprintf(buf, sizeof(buf), "Lim : %4lu mL/m", threshold); + OLED_Print(0, 5, buf); + + // Page 6 (Status) + if (current_flow >= threshold) { + OLED_Print(0, 7, "Status: SYSTEM OK "); + } else { + OLED_Print(0, 7, "Status: ALARM !!! "); + } } // ========================================== @@ -106,6 +220,10 @@ void Update_Display(uint32_t current_flow, uint32_t threshold) { int main() { SystemInit(); Init_Hardware(); + + OLED_Init(); + OLED_Clear(); + Update_Display(flow_rate_ml_min, threshold_ml_min); printf("\r\n==================================\r\n"); printf(" CNC Flow Controller Initialized\r\n"); @@ -117,57 +235,46 @@ int main() { while(1) { uint32_t current_time = SysTick->CNT; - uint32_t ticks_per_sec = DELAY_US_TIME * 1000000; // = FUNCONF_SYSTEM_CORE_CLOCK + uint32_t ticks_per_sec = DELAY_US_TIME * 1000000; // --- 1. EVALUATE FLOW EVERY 1 SECOND --- if ((current_time - last_time) >= ticks_per_sec) { last_time = current_time; - // Copy and reset pulse count safely NVIC_DisableIRQ(EXTI7_0_IRQn); uint32_t hz = pulse_count; pulse_count = 0; NVIC_EnableIRQ(EXTI7_0_IRQn); - // Calculate Flow: L/min = Hz / 5.5 => mL/min = (Hz * 10000) / 55 flow_rate_ml_min = (hz * 10000) / 55; - // --- 2. FAILSAFE LOGIC & LOGGING --- + // Failsafe Logic if (flow_rate_ml_min >= threshold_ml_min) { - // Flow OK -> Drive ALARM_PIN High (MOSFET ON, CNC OK) - GPIOA->BSHR = (1 << ALARM_PIN); - + GPIOA->BSHR = (1 << ALARM_PIN); // Alarm OK (MOSFET ON) if (alarm_active == 1) { alarm_active = 0; printf("[INFO] Flow restored: %lu mL/min. Alarm CLEARED.\r\n", flow_rate_ml_min); - } - } else { - // Flow Low -> Drive ALARM_PIN Low (MOSFET OFF, CNC Alarm) - GPIOA->BSHR = (1 << (16 + ALARM_PIN)); - + } } else { + GPIOA->BSHR = (1 << (16 + ALARM_PIN)); // Alarm Triggered (MOSFET OFF) if (alarm_active == 0) { alarm_active = 1; printf("[WARN] Low flow detected: %lu mL/min (Thr: %lu). Alarm TRIGGERED!\r\n", flow_rate_ml_min, threshold_ml_min); - } - } + } } Update_Display(flow_rate_ml_min, threshold_ml_min); } - // --- 3. BUTTON HANDLING (ADC Resistor Ladder) --- + // --- 2. BUTTON HANDLING --- uint16_t adc_val = Read_ADC(); if ((current_time - last_button_time) > (DEBOUNCE_DELAY_MS * DELAY_US_TIME * 1000)) { - if (adc_val >= ADC_UP_BTN_MIN && adc_val <= ADC_UP_BTN_MAX) { - // Button UP pressed threshold_ml_min += 100; last_button_time = current_time; printf("[SET] Threshold increased to: %lu mL/min\r\n", threshold_ml_min); Update_Display(flow_rate_ml_min, threshold_ml_min); } else if (adc_val >= ADC_DOWN_BTN_MIN && adc_val <= ADC_DOWN_BTN_MAX) { - // Button DOWN pressed if (threshold_ml_min >= 100) threshold_ml_min -= 100; last_button_time = current_time; printf("[SET] Threshold decreased to: %lu mL/min\r\n", threshold_ml_min);