Compare commits

..

3 Commits

Author SHA1 Message Date
f0dfc95910 reverted pwm frequency to 100khz
Also recompile on header change
2026-05-01 19:08:18 +02:00
7aa3e0c3e5 moved pd negotiation to owm module 2026-05-01 18:57:25 +02:00
14a33963b0 moved defines to funconfig.h 2026-04-30 22:27:48 +02:00
5 changed files with 210 additions and 163 deletions

View File

@ -12,7 +12,8 @@ U8G2_SRC:=$(U8G2_DIR)/u8x8_d_$(DISPLAY).c $(filter-out $(U8G2_DIR)/u8x8_d_%.c, \
$(filter-out $(U8G2_DIR)/mui%.c, $(wildcard $(U8G2_DIR)/*.c)))
EXTRA_CFLAGS += -I$(U8G2_DIR)
ADDITIONAL_C_FILES += lib_i2c.c display.c sc7a20.c
ADDITIONAL_C_FILES += lib_i2c.c display.c sc7a20.c pd.c
HEADER_FILES := $(wildcard *.h)
include ch32fun/ch32fun/ch32fun.mk
@ -29,7 +30,7 @@ $(BUILD_DIR)/%.o : $(U8G2_DIR)/%.c | $(BUILD_DIR)
# build target
TARGET_OBJS := $(addprefix $(BUILD_DIR)/, $(filter-out ch32fun.o, $(notdir $(FILES_TO_COMPILE:.c=.o))))
$(BUILD_DIR)/%.o : %.c | $(BUILD_DIR)
$(BUILD_DIR)/%.o : %.c $(HEADER_FILES) | $(BUILD_DIR)
$(PREFIX)-gcc $(CFLAGS) -c $< -o $@
# link target, the rest of the build is specified in ch32fun.mk

View File

@ -1,12 +1,44 @@
#ifndef _FUNCONFIG_H
#define _FUNCONFIG_H
#define FUNCONF_USE_DEBUGPRINTF 0 // can only have one printf
#define FUNCONF_USE_USBPRINTF 1
#define FUNCONF_DEBUG_HARDFAULT 0
#define CH32X035 1
#define I2C_TARGET I2C1
#define VCC_MV 3480
#define FUNCONF_USE_DEBUGPRINTF 0 // can only have one printf
#define FUNCONF_USE_USBPRINTF 1
#define FUNCONF_DEBUG_HARDFAULT 0
#define CH32X035 1
#define I2C_TARGET I2C1
#define VCC_MV 3480
#define FRAME_TIME_MS 20 // 50Hz
#define PWM_FREQ_HZ 100000 // TIM3 PWM frequency
// Pin definitions
#define PIN_VBUS PA0 // vbus voltage feedback
#define PIN_CURRENT PA1 // current feedback
#define PIN_NTC PA2 // ntc temperature sensor
#define PIN_TEMP PA3 // thermocouple amplifier
#define PIN_12V PA5 // 12V regulator enable
#define PIN_HEATER PA6 // power mosfet gate control
#define PIN_ENC_A PB3 // rotary encoder A
#define PIN_ENC_B PB11 // rotary encoder B
#define PIN_BTN PB1 // rotary encoder button
// Analog channel definitions
#define VBUS_ADC_CHANNEL ANALOG_0 // PA0
#define CURRENT_ADC_CHANNEL ANALOG_1 // PA1
#define NTC_ADC_CHANNEL ANALOG_2 // PA2
#define TEMP_ADC_CHANNEL ANALOG_3 // PA3
#define ENCODER_DEBOUNCE 6000
// TODO: these need to be calibrated
// Tip mV to deg C conversion factor numerator
#define TC_CONV_NOM 151
// Tip mV to deg C conversion factor denumerator
#define TC_CONV_DEN 1000
#define MAX_BOARD_TEMP 50
#define MAX_TIP_TEMP 500
#define TURN_OFF_DELAY 2
#define CYCLES_PER_MEASURE 2
#endif // _FUNCONFIG_H

201
fw/main.c
View File

@ -3,36 +3,14 @@
#include <stdio.h>
#include <fsusb.h>
#define USBPD_IMPLEMENTATION
#include "usbpd.h"
#include "funconfig.h"
#include "lib_i2c.h"
#include "display.h"
#include "filter.h"
#include "sc7a20.h"
#include "pd.h"
// Pin definitions
#define PIN_VBUS PA0 // vbus voltage feedback
#define PIN_CURRENT PA1 // current feedback
#define PIN_NTC PA2 // ntc temperature sensor
#define PIN_TEMP PA3 // thermocouple amplifier
#define PIN_12V PA5 // 12V regulator enable
#define PIN_HEATER PA6 // power mosfet gate control
#define PIN_ENC_A PB3 // rotary encoder A
#define PIN_ENC_B PB11 // rotary encoder B
#define PIN_BTN PB1 // rotary encoder button
// Analog channel definitions
#define VBUS_ADC_CHANNEL ANALOG_0 // PA0
#define CURRENT_ADC_CHANNEL ANALOG_1 // PA1
#define NTC_ADC_CHANNEL ANALOG_2 // PA2
#define TEMP_ADC_CHANNEL ANALOG_3 // PA3
#define FRAME_TIME_MS 20 // 50Hz
#define PWM_FREQ_HZ 150000
// constants
// LUT for converting NTC readings to degrees celsius
// Nominal: 1kOhm, Beta: 3380, Step: 64
@ -49,29 +27,9 @@ const int16_t ntc_lut[] = {
u8g2_t *u8g2;
int16_t encoder = 0; // rotary encoder counter
uint32_t last_interrupt = 0; // last time the encoder interrupt was triggered
#define ENCODER_DEBOUNCE 6000
// TODO: these need to be calibrated
// Tip mV to deg C conversion factor numerator
#define TC_CONV_NOM 151
// Tip mV to deg C conversion factor denumerator
#define TC_CONV_DEN 1000
#define MAX_BOARD_TEMP 50
#define MAX_TIP_TEMP 500
#define TURN_OFF_DELAY 2
#define CYCLES_PER_MEASURE 2
// Current profile
struct profile_t {
uint16_t voltage; // Vbus Voltage in millivolts
uint16_t max_current; // Maximum current in milliamps
uint16_t power_avail; // Available power (from supply) in watts
uint16_t set_power; // Maximum power in watts, set by the user
uint16_t set_temp; // Set temperature in celsius, set by the user
uint16_t tip_r; // Tip resistance in milliOhms
uint8_t max_duty; // Maximum duty cycle (0-100) to stay within the power limit
} pd_profile;
struct pd_profile_t pd_profile;
static const int8_t x_off = 0;
static const int8_t y_off = 8;
// Convert the raw adc reading to a temperature in celsius with the ntc lut,
// linearly interpolating between positions
@ -407,120 +365,55 @@ __attribute__((noreturn)) int main(void)
u8g2 = display_init();
sc7a20_init();
// Init USBPD
bool has_pd = false;
USBPD_VCC_e vcc = eUSBPD_VCC_3V3;
USBPD_Result_e result = USBPD_Init(vcc);
if (result != eUSBPD_OK) {
printf("USBPD_Init failed: %d\n", result);
}
u8g2_ClearBuffer(u8g2);
u8g2_SetBitmapMode(u8g2, 1);
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
u8g2_DrawStr(u8g2, 0, 18, "negotiating...");
u8g2_SendBuffer(u8g2);
USBPD_SPR_CapabilitiesMessage_t *capabilities = NULL;
uint32_t cap_count = 0;
u16 max_v = 5;
u16 max_idx = -1;
u32 start = funSysTick32();
while (eUSBPD_BUSY == (result = USBPD_SinkNegotiate())) {
u32 now = funSysTick32();
if (now - start > Ticks_from_Ms(5000)) {
printf("USBPD_SinkNegotiate timed out\n");
break;
}
// Init USBPD
bool has_pd = pd_negotiate(eUSBPD_VCC_3V3);
if (has_pd == false) {
u8g2_ClearBuffer(u8g2);
u8g2_SetBitmapMode(u8g2, 1);
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
u8g2_DrawStr(u8g2, x_off+0, y_off+7, "Negotiation FAILED");
u8g2_DrawStr(u8g2, x_off+0, y_off+14, USBPD_ResultToStr(pd_get_result()));
u8g2_SendBuffer(u8g2);
Delay_Ms(5000);
} else {
pd_get_profile(&pd_profile, 100);
// TODO: let the user decide the power profile
pd_profile.set_temp = 360;
pd_profile.set_power = 95; // Slightly below max power to avoid overloading
pd_profile.tip_r = 2500; // TODO: tip check and resistance calculator
pd_profile.max_duty = MIN(
(100*(u32)isqrt((
MIN(pd_profile.set_power, pd_profile.power_avail)*pd_profile.tip_r)/1000) * 1000) / pd_profile.voltage
, 100);
u8g2_ClearBuffer(u8g2);
u8g2_SetBitmapMode(u8g2, 1);
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
u8g2_DrawStr(u8g2, 0, 18, "waiting...");
// Display tip temperature
u8g2_DrawStr(u8g2, x_off+0, y_off+7, "A:");
u8g2_DrawStr(u8g2, x_off+10, y_off+7, u8g2_u16toa(pd_profile.max_current, 4));
// Display bus voltage
u8g2_DrawStr(u8g2, x_off+45, y_off+7, "V:");
u8g2_DrawStr(u8g2, x_off+55, y_off+7, u8g2_u16toa(pd_profile.voltage, 5));
// Display power
u8g2_DrawStr(u8g2, x_off+0, y_off+15, "W:");
u8g2_DrawStr(u8g2, x_off+10, y_off+15, u8g2_u16toa(pd_profile.power_avail, 3));
// Display current
u8g2_DrawStr(u8g2, x_off+45, y_off+15, "D:");
u8g2_DrawStr(u8g2, x_off+55, y_off+15, u8g2_u16toa(pd_profile.max_duty, 3));
u8g2_SendBuffer(u8g2);
Delay_Ms(5000);
}
if (result != eUSBPD_OK) {
printf("USBPD_SinkNegotiate failed: %s, state: %s\n",
USBPD_ResultToStr(result),
USBPD_StateToStr(USBPD_GetState())
);
has_pd = false;;
} else {
has_pd = true;
cap_count = USBPD_GetCapabilities(&capabilities);
for (u32 i = 0; i < cap_count; i++) {
USBPD_SinkPDO_t *pdo = &capabilities->Sink[i];
switch (pdo->Header.PDOType) {
case eUSBPD_PDO_FIXED:
if (pdo->FixedSupply.VoltageIn50mV/20 > max_v) {
max_v = pdo->FixedSupply.VoltageIn50mV/20;
max_idx = i;
}
break;
case eUSBPD_PDO_BATTERY:
if (pdo->BatterySupply.MaxVoltageIn50mV/20 > max_v) {
max_v = pdo->BatterySupply.MaxVoltageIn50mV/20;
max_idx = i;
}
break;
case eUSBPD_PDO_VARIABLE:
// TODO: PPS
// if (pdo->VariableSupply.MaxVoltageIn50mV/20 > max_v) {
// max_v = pdo->VariableSupply.MaxVoltageIn50mV/20;
// }
break;
case eUSBPD_PDO_AUGMENTED:
// TODO: EPR
break;
}
}
}
if (has_pd && max_idx >= 0) {
USBPD_SelectPDO(max_idx, 0);
Delay_Ms(200);
USBPD_SinkPDO_t *pdo = &capabilities->Sink[max_idx];
switch (pdo->Header.PDOType) {
case eUSBPD_PDO_FIXED:
pd_profile.voltage = pdo->FixedSupply.VoltageIn50mV * 50;
pd_profile.max_current = pdo->FixedSupply.CurrentIn10mA * 10;
pd_profile.power_avail = ((u32)pd_profile.voltage * pd_profile.max_current) / (u32)1000000;
break;
case eUSBPD_PDO_BATTERY:
pd_profile.voltage = pdo->BatterySupply.MaxVoltageIn50mV * 50;
pd_profile.power_avail = pdo->BatterySupply.MaxPowerIn250mW / 4;
pd_profile.max_current = pd_profile.power_avail * 1000 / pd_profile.voltage;
break;
case eUSBPD_PDO_VARIABLE:
// TODO: PPS
break;
case eUSBPD_PDO_AUGMENTED:
// TODO: EPR
break;
}
}
// TODO: let the user decide the power profile
pd_profile.set_temp = 360;
pd_profile.set_power = 30;
pd_profile.tip_r = 2500; // TODO: tip check and resistance calculator
pd_profile.max_duty = (100*(u32)isqrt((MIN(pd_profile.set_power, pd_profile.power_avail)*pd_profile.tip_r)/1000) * 1000) / pd_profile.voltage;
u8g2_ClearBuffer(u8g2);
u8g2_SetBitmapMode(u8g2, 1);
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
static const int8_t x_off = 0;
static const int8_t y_off = 8;
// Display tip temperature
u8g2_DrawStr(u8g2, x_off+0, y_off+7, "A:");
u8g2_DrawStr(u8g2, x_off+10, y_off+7, u8g2_u16toa(pd_profile.max_current, 4));
// Display bus voltage
u8g2_DrawStr(u8g2, x_off+45, y_off+7, "V:");
u8g2_DrawStr(u8g2, x_off+55, y_off+7, u8g2_u16toa(pd_profile.voltage, 5));
// Display power
u8g2_DrawStr(u8g2, x_off+0, y_off+15, "W:");
u8g2_DrawStr(u8g2, x_off+10, y_off+15, u8g2_u16toa(pd_profile.power_avail, 3));
// Display current
u8g2_DrawStr(u8g2, x_off+45, y_off+15, "D:");
u8g2_DrawStr(u8g2, x_off+55, y_off+15, u8g2_u16toa(pd_profile.max_duty, 3));
u8g2_SendBuffer(u8g2);
Delay_Ms(5000);
for (;;) {
@ -531,8 +424,6 @@ __attribute__((noreturn)) int main(void)
u8g2_SetBitmapMode(u8g2, 1);
u8g2_SetFontMode(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
static const int8_t x_off = 0;
static const int8_t y_off = 8;
static bool pwm = false; // PWM status
static bool enabled = false; // Power electronics enabled

98
fw/pd.c Normal file
View File

@ -0,0 +1,98 @@
#include <ch32fun.h>
#define USBPD_IMPLEMENTATION
#include "usbpd.h"
#include "pd.h"
static size_t cap_count = 0;
static USBPD_SPR_CapabilitiesMessage_t *capabilities = NULL;
static USBPD_Result_e result;
USBPD_Result_e pd_get_result()
{
return result;
}
// Initializes the Power Delivery peripheral and starts the Power Delivery
// negotiation, returns true if successful, indicating the board is connected to
// a Power Delivery source.
// As a side effect, fills the capabilities buffer with the source's capabilities.
bool pd_negotiate(USBPD_VCC_e vcc)
{
result = USBPD_Init(vcc);
if (result != eUSBPD_OK) {
return false;
}
Delay_Ms(100);
u32 start = funSysTick32();
while (eUSBPD_BUSY == (result = USBPD_SinkNegotiate())) {
u32 now = funSysTick32();
if (now - start > Ticks_from_Ms(5000)) {
break;
}
Delay_Ms(100);
}
if (result != eUSBPD_OK) {
return false;
}
cap_count = USBPD_GetCapabilities(&capabilities);
if (capabilities == NULL || cap_count == 0) {
return false;
}
return true;
}
bool pd_get_profile(struct pd_profile_t *profile, uint16_t min_power)
{
if (profile == NULL || min_power == 0 || min_power > 140) {
return false;
}
if (capabilities == NULL || cap_count == 0) {
return false;
}
u16 voltage = 0, current = 0, power = 0;
for (u32 i = 0; i < cap_count; i++) {
USBPD_SinkPDO_t *pdo = &capabilities->Sink[i];
switch (pdo->Header.PDOType) {
case eUSBPD_PDO_FIXED:
voltage = pdo->FixedSupply.VoltageIn50mV * 50;
current = pdo->FixedSupply.CurrentIn10mA * 10;
power = ((u32)voltage * current) / (u32)1000000;
break;
case eUSBPD_PDO_BATTERY:
voltage = pdo->BatterySupply.MaxVoltageIn50mV * 50;
power = pdo->BatterySupply.MaxPowerIn250mW / 4;
current = ((u32)power * 1000) / voltage;
break;
case eUSBPD_PDO_VARIABLE:
// TODO: PPS
// if (pdo->VariableSupply.MaxVoltageIn50mV/20 > max_v) {
// max_v = pdo->VariableSupply.MaxVoltageIn50mV/20;
// }
break;
case eUSBPD_PDO_AUGMENTED:
// TODO: EPR
break;
}
// Selects the first PDO that meets the minimum power requirement
if (power >= min_power) {
if (USBPD_SelectPDO(i, 0) != eUSBPD_OK) {
return false;
}
profile->voltage = voltage;
profile->max_current = current;
profile->power_avail = power;
return true;
}
}
return false;
}

25
fw/pd.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef _PD_H
#define _PD_H
#include <stdint.h>
#include "usbpd.h"
struct pd_profile_t {
uint16_t voltage; // Vbus Voltage in millivolts
uint16_t max_current; // Maximum current in milliamps
uint16_t power_avail; // Available power (from supply) in watts
uint16_t set_power; // Maximum power in watts, set by the user
uint16_t set_temp; // Set temperature in celsius, set by the user
uint16_t tip_r; // Tip resistance in milliOhms
uint8_t max_duty; // Maximum duty cycle (0-100) to stay within the power limit
};
USBPD_Result_e pd_get_result();
bool pd_negotiate(USBPD_VCC_e vcc);
bool pd_get_profile(struct pd_profile_t *profile, uint16_t min_power);
#endif // _PD_H