added firmware section
This commit is contained in:
15
firmware/.gitignore
vendored
Normal file
15
firmware/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Compiled binaries
|
||||
*.elf
|
||||
*.bin
|
||||
*.hex
|
||||
|
||||
# Build artifacts and memory maps
|
||||
*.map
|
||||
*.o
|
||||
*.d
|
||||
*.lst
|
||||
|
||||
# Specifically ignore the output files for our target
|
||||
main.elf
|
||||
main.bin
|
||||
main.map
|
||||
12
firmware/Makefile
Normal file
12
firmware/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
all : flash
|
||||
|
||||
TARGET:=main
|
||||
TARGET_MCU:=CH32V003
|
||||
|
||||
# Path based on your error output
|
||||
CH32FUN:=/home/martin/ch32fun/ch32fun
|
||||
|
||||
include $(CH32FUN)/ch32fun.mk
|
||||
|
||||
flash : cv_flash
|
||||
clean : cv_clean
|
||||
80
firmware/Readme.md
Normal file
80
firmware/Readme.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# CH32V003 Firmware & Linux Development Setup
|
||||
|
||||
This folder contains the firmware source code for the CNC Water Flow Controller, built using the [ch32fun](https://github.com/cnlohr/ch32fun) framework.
|
||||
|
||||
Below is the guide to set up the build environment, compile, and flash the CH32V003 series on Linux using CLI tools.
|
||||
|
||||
*Tested on: Ubuntu Linux, CH32V003J4M6 (8-pin)*
|
||||
|
||||
---
|
||||
|
||||
## 1. Install toolchain
|
||||
|
||||
Download xPack RISC-V GCC:
|
||||
[https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases](https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases)
|
||||
|
||||
Extract it:
|
||||
```bash
|
||||
mkdir -p ~/toolchains
|
||||
tar -xzf xpack-riscv-none-elf-gcc-*-linux-x64.tar.gz -C ~/toolchains
|
||||
```
|
||||
|
||||
Activate it (add this to your ~/.bashrc for permanence):
|
||||
```Bash
|
||||
export TOOLCHAIN=$HOME/toolchains/xpack-riscv-none-elf-gcc-* # Update e.g.: xpack-riscv-none-elf-gcc-15.2.0-1
|
||||
export PATH=$TOOLCHAIN/bin:$PATH
|
||||
```
|
||||
|
||||
Verify the installation:
|
||||
```Bash
|
||||
riscv-none-elf-gcc --version
|
||||
```
|
||||
|
||||
## 2. Install Flashing Tool (minichlink)
|
||||
Clone the ch32fun repository and build minichlink:
|
||||
```Bash
|
||||
git clone https://github.com/cnlohr/ch32fun ~/ch32fun
|
||||
cd ~/ch32fun/minichlink
|
||||
make
|
||||
sudo mv minichlink /usr/local/bin/
|
||||
```
|
||||
|
||||
## 3. Build the Firmware
|
||||
Navigate to this firmware directory where the Makefile and main.c are located. Make sure the CH32FUN path in the Makefile points to where you cloned ch32fun.
|
||||
```Bash
|
||||
cd Flow-Controller/firmware
|
||||
make clean
|
||||
make
|
||||
```
|
||||
This will generate main.bin and main.elf.
|
||||
|
||||
## 4. Flash Firmware
|
||||
Connect your WCH-LinkE programmer to the board's J1 header:
|
||||
| Signal | Programmer | MCU Header (J1) |
|
||||
| :--- | :--- | :--- |
|
||||
| SWIO | SWDIO | Pin 3 (SWIO) |
|
||||
| GND | GND | Pin 2 (GND) |
|
||||
| 5V | 3.3V | Pin 1 (+3.3V) |
|
||||
Flash the compiled binary:
|
||||
```Bash
|
||||
make flash
|
||||
```
|
||||
or manually:
|
||||
```bash
|
||||
minichlink -w main.bin flash
|
||||
```
|
||||
|
||||
## 5. Logs
|
||||
See logs on the SWIO pin:
|
||||
```Bash
|
||||
minichlink -T
|
||||
```
|
||||
|
||||
## 6. Unbricking
|
||||
If you accidentally misconfigure the SWIO pin and lock yourself out, you can use the CH32V003 Unbrick CLI tool:
|
||||
```Bash
|
||||
git clone https://github.com/shakir-abdo/ch32v003-unbrick.git
|
||||
cd ch32v003-unbrick
|
||||
npm install
|
||||
./unbrick.js
|
||||
```
|
||||
13
firmware/funconfig.h
Normal file
13
firmware/funconfig.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef _FUNCONFIG_H
|
||||
#define _FUNCONFIG_H
|
||||
|
||||
#define CH32V003 1
|
||||
|
||||
// Default system clock to 48MHz
|
||||
#define FUNCONF_SYSTEM_CORE_CLOCK 48000000
|
||||
|
||||
// support "printf-over-single-wire" at about 400kBaud on the SWIO pin (PD1)
|
||||
#define FUNCONF_USE_DEBUGPRINTF 1
|
||||
#define FUNCONF_USE_UARTPRINTF 0
|
||||
|
||||
#endif // _FUNCONFIG_H
|
||||
169
firmware/main.c
Normal file
169
firmware/main.c
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "ch32fun.h"
|
||||
#include <stdio.h>
|
||||
|
||||
// ==========================================
|
||||
// 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 ADC_UP_BTN_MAX 200 // Button 1 range: 50..200 (center ~93)
|
||||
#define ADC_UP_BTN_MIN 20
|
||||
#define ADC_DOWN_BTN_MAX 15 // Button 2 range: 0..15 (should read ~0)
|
||||
|
||||
#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)
|
||||
|
||||
// ==========================================
|
||||
// INTERRUPT HANDLER (Flow Sensor Pulses)
|
||||
// ==========================================
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// HARDWARE INITIALIZATION
|
||||
// ==========================================
|
||||
void Init_Hardware(void) {
|
||||
// 1. Enable Clocks for GPIOA, GPIOC, GPIOD, ADC, AFIO
|
||||
RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_ADC1 | RCC_APB2Periph_AFIO;
|
||||
|
||||
// 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)); // Default to ALARM STATE (Low = MOSFET OFF)
|
||||
|
||||
// 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); // Enable pull-up
|
||||
|
||||
// Setup EXTI on PD6 (CH32V003 has single EXTICR, 2 bits per pin, Port D = 0b11)
|
||||
AFIO->EXTICR |= (0b11 << (FLOW_PIN * 2)); // PD6: Port D=0b11, bits [13:12]
|
||||
EXTI->INTENR |= (1 << FLOW_PIN); // Enable EXTI interrupt on line 6
|
||||
EXTI->FTENR |= (1 << FLOW_PIN); // Falling edge trigger
|
||||
NVIC_EnableIRQ(EXTI7_0_IRQn); // Enable interrupt in NVIC
|
||||
|
||||
// 4. Configure BUTTON_ADC Input (PC4 / AIN2) - Analog mode (no pull)
|
||||
GPIOC->CFGLR &= ~(0xF << (4 * BUTTON_PIN));
|
||||
|
||||
// Setup ADC
|
||||
ADC1->CTLR2 |= ADC_ADON; // Turn on ADC
|
||||
ADC1->RSQR3 = 2; // PC4 is ADC Channel 2 (AIN2)
|
||||
ADC1->SAMPTR2 = 7 << (3 * 2); // Max sampling time for CH2
|
||||
|
||||
ADC1->CTLR2 |= ADC_RSTCAL; // Reset & calibrate
|
||||
while(ADC1->CTLR2 & ADC_RSTCAL);
|
||||
ADC1->CTLR2 |= ADC_CAL;
|
||||
while(ADC1->CTLR2 & ADC_CAL);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// HELPER FUNCTIONS
|
||||
// ==========================================
|
||||
uint16_t Read_ADC(void) {
|
||||
ADC1->CTLR2 |= ADC_SWSTART;
|
||||
while(!(ADC1->STATR & ADC_EOC));
|
||||
return ADC1->RDATAR;
|
||||
}
|
||||
|
||||
void Update_Display(uint32_t current_flow, uint32_t threshold) {
|
||||
// TODO: Implement OLED I2C drawing here using ch32fun's I2C bitbang
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// MAIN LOOP
|
||||
// ==========================================
|
||||
int main() {
|
||||
SystemInit();
|
||||
Init_Hardware();
|
||||
|
||||
printf("\r\n==================================\r\n");
|
||||
printf(" CNC Flow Controller Initialized\r\n");
|
||||
printf("==================================\r\n");
|
||||
printf("[INFO] Active Threshold: %lu mL/min\r\n", threshold_ml_min);
|
||||
|
||||
uint32_t last_time = SysTick->CNT;
|
||||
uint32_t last_button_time = 0;
|
||||
|
||||
while(1) {
|
||||
uint32_t current_time = SysTick->CNT;
|
||||
uint32_t ticks_per_sec = DELAY_US_TIME * 1000000; // = FUNCONF_SYSTEM_CORE_CLOCK
|
||||
|
||||
// --- 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 ---
|
||||
if (flow_rate_ml_min >= threshold_ml_min) {
|
||||
// Flow OK -> Drive ALARM_PIN High (MOSFET ON, CNC OK)
|
||||
GPIOA->BSHR = (1 << ALARM_PIN);
|
||||
|
||||
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));
|
||||
|
||||
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) ---
|
||||
// Thresholds match actual voltages: no-press ~1023, BTN1 ~93, BTN2 ~0.
|
||||
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 1: voltage divider 10k+1k -> ~0.30V -> ADC ~93
|
||||
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_MAX) {
|
||||
// Button 2: direct to GND -> ~0V -> ADC ~0
|
||||
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);
|
||||
Update_Display(flow_rate_ml_min, threshold_ml_min);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user