!!! AI GENERATED MESSAGE !!! This commit introduces support for the SC7A20 accelerometer. The accelerometer readings can now be displayed on the screen, and the display mode can be toggled using the encoder. Additionally, the EMA filter constants for voltage and current readings have been updated from K2 to K4 for potentially smoother readings.
458 lines
14 KiB
C
458 lines
14 KiB
C
#include <ch32fun.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <fsusb.h>
|
|
|
|
#define USBPD_IMPLEMENTATION
|
|
#include "usbpd.h"
|
|
|
|
#include "lib_i2c.h"
|
|
#include "display.h"
|
|
#include "filter.h"
|
|
#include "sc7a20.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 41 // roughly 24 fps
|
|
|
|
// constants
|
|
// LUT for converting NTC readings to degrees kelvin
|
|
// Nominal: 1kOhm, Beta: 3380, Step: 64
|
|
const uint8_t ntc_step_size = 64;
|
|
const int16_t ntc_lut[] = {
|
|
1316, 197, 155, 133, 119, 108, 100, 93, 87, 82, 77, 73, 69, 66, 63, 60,
|
|
57, 54, 52, 50, 47, 45, 43, 41, 39, 37, 35, 34, 32, 30, 28, 27,
|
|
25, 23, 22, 20, 19, 17, 15, 14, 12, 11, 9, 7, 6, 4, 2, 0,
|
|
-1, -3, -5, -7, -9, -11, -14, -16, -19, -22, -25, -28, -32, -38, -44, -55,
|
|
-55 // extra value to not have an extra if statement
|
|
};
|
|
|
|
|
|
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
|
|
|
|
|
|
// Convert the raw adc reading to a temperature in kelvin with the ntc lut,
|
|
// linearly interpolating between positions
|
|
static inline int16_t get_temp_k(uint16_t adc_reading)
|
|
{
|
|
if (adc_reading > 4095) return 0;
|
|
uint8_t index = adc_reading / ntc_step_size;
|
|
uint8_t remainder = adc_reading % ntc_step_size;
|
|
int16_t temp_base = index < 64 ? ntc_lut[index] : 0;
|
|
int16_t temp_next = ntc_lut[index + 1];
|
|
return temp_base + ((temp_next - temp_base) * remainder)/ntc_step_size;
|
|
}
|
|
|
|
|
|
// convert the raw TPA191 adc reading to a current in milliamps
|
|
static inline int16_t get_current_ma(uint16_t adc_reading)
|
|
{
|
|
// Rshunt = 4 milliOhm
|
|
// Gain = 100
|
|
u32 mv = ((u32)adc_reading * VCC_MV) / 4096;
|
|
return (mv * 10) / 4;
|
|
}
|
|
|
|
|
|
void print_i2c_device(uint8_t addr)
|
|
{
|
|
printf("Device found at 0x%02X\n", addr);
|
|
}
|
|
|
|
|
|
// this callback is mandatory when FUNCONF_USE_USBPRINTF is defined,
|
|
// can be empty though
|
|
void handle_usbfs_input(int numbytes, uint8_t *data)
|
|
{
|
|
// handle single character commands
|
|
// TODO:
|
|
// - 'c' to calibrate the tip temperature
|
|
// - 't' to test tip presence
|
|
if(numbytes == 1) {
|
|
switch(data[0]) {
|
|
case 'r': // toggle the 12V regulator
|
|
if (funDigitalRead(PIN_12V)) {
|
|
funDigitalWrite(PIN_12V, 0);
|
|
printf("Disabled 12V Regulator\n");
|
|
} else {
|
|
funDigitalWrite(PIN_12V, 1);
|
|
printf("Enabled 12V Regulator\n");
|
|
}
|
|
break;
|
|
case 'h':
|
|
printf(
|
|
"Available commands:\n"
|
|
"\tr : toggle the 12V regulator\n"
|
|
"\ts : scan I2C bus\n"
|
|
);
|
|
break;
|
|
case 's':
|
|
printf("Scanning I2C bus...\n");
|
|
i2c_scan(I2C_TARGET, print_i2c_device);
|
|
break;
|
|
default:
|
|
printf("Unknown command '%c'\n", data[0]);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// echo
|
|
// _write(0, (const char*)data, numbytes);
|
|
}
|
|
}
|
|
|
|
|
|
// triggered on the falling edge of the rotary encoder PIN_A
|
|
void EXTI15_8_IRQHandler(void) __attribute__((interrupt));
|
|
void EXTI15_8_IRQHandler(void)
|
|
{
|
|
uint32_t now = funSysTick32();
|
|
if (now - last_interrupt > ENCODER_DEBOUNCE) {
|
|
last_interrupt = now;
|
|
if (funDigitalRead(PIN_ENC_A)) {
|
|
encoder++;
|
|
} else {
|
|
encoder--;
|
|
}
|
|
}
|
|
EXTI->INTFR = EXTI_Line11;
|
|
}
|
|
|
|
|
|
// Procedure to get hardware I2C working on the CH32X035F8U6
|
|
static inline void setup_i2c(void)
|
|
{
|
|
// Order here matters, first initialize the AFIO and I2C subsystems then
|
|
// change register values, do that the other way around and the configuration
|
|
// wont take effect
|
|
// Enable AFIO (Alternate Function IO)
|
|
RCC->APB2PCENR |= RCC_AFIOEN;
|
|
// Init I2C
|
|
i2c_init(I2C_TARGET, FUNCONF_SYSTEM_CORE_CLOCK, 400000);
|
|
|
|
// To utilize the I2C bus we need to disable SWD first, since the pins overlap
|
|
AFIO->PCFR1 &= ~(0b0111 << 24);
|
|
AFIO->PCFR1 |= 0b0100 << 24;
|
|
|
|
// Map SCL to PC18 and SDA to PC19
|
|
AFIO->PCFR1 |= 0b0101 << 2;
|
|
// Manually set the I2C pins to Alternate Function IO, CNF=10b, MODE=10b
|
|
GPIOC->CFGXR &= ~((0xF << 8) | (0xF << 12)); // first clear the bits
|
|
// then set them
|
|
GPIOC->CFGXR |= 0b1010 << 8; // PC18
|
|
GPIOC->CFGXR |= 0b1010 << 12; // PC19
|
|
}
|
|
|
|
|
|
// FIXME: this holds the max number of ADC channels, ideally this would be lower
|
|
volatile uint16_t adc_buffer[16];
|
|
// Set-up the adc to perform continous conversion of enabled channels, enable
|
|
// channel injection and setup DMA to automatically transfer conversion results
|
|
// to a buffer
|
|
static inline void setup_adc_and_dma(uint8_t *channels, uint8_t ch_size, uint8_t *injected, uint8_t in_size)
|
|
{
|
|
if (channels == NULL || ch_size == 0) return;
|
|
// FIXME: for now only support a single configuration register, so at max 6 channels
|
|
// I don't want to implement logic to switch registers right now
|
|
// TODO: report an error
|
|
if (ch_size > 6) return;
|
|
|
|
// General ADC initialization, I use this only for enabling clocks and such
|
|
funAnalogInit(); // general initialization
|
|
|
|
// Set the SCAN and CONT bits to enter continous conversion mode
|
|
// Do not set the IAUTO bit to ensure injected channels are converted only
|
|
// when specified by software
|
|
// Set the regular channel trigger mode to software (EXTSEL = 0b111) to ensure
|
|
// conversion starts in sync with DMA (this should already be done by funAnalogInit())
|
|
ADC1->CTLR1 |= ADC_SCAN;
|
|
ADC1->CTLR2 |= ADC_CONT | ADC_EXTSEL;
|
|
|
|
// Setup regular channels (scanned continously)
|
|
// TODO: setup sampling time
|
|
for (uint8_t i = 0; i < ch_size; i++) {
|
|
// Increase sampling time to have less cross talk between channels
|
|
// 0b111 means 11 ADC clock cycles (max)
|
|
if (channels[i] < 10) {
|
|
ADC1->SAMPTR2 |= 0b111 << (channels[i]*3);
|
|
} else {
|
|
ADC1->SAMPTR1 |= 0b111 << ((channels[i]-10)*3);
|
|
}
|
|
ADC1->RSQR3 |= channels[i] << (i*5);
|
|
}
|
|
// Set the number of normal conversion channels
|
|
ADC1->RSQR1 |= (ch_size-1) << 20;
|
|
|
|
// Set-up injection channels
|
|
// The injection channel register allows for up to 4 channels, if the buffer is larger
|
|
// skip this step
|
|
// TODO: report an error
|
|
if (injected != NULL && in_size != 0 && in_size <= 4) {
|
|
// enable injection group
|
|
// JEXTSEL = 0b111 (software trigger)
|
|
ADC1->CTLR2 |= ADC_JEXTSEL;
|
|
for (uint8_t i = 0; i < in_size; i++) {
|
|
ADC1->ISQR |= injected[i] << ((4-in_size+i)*5);
|
|
}
|
|
// Set the number of injection channels
|
|
ADC1->ISQR |= (in_size-1) << 20;
|
|
}
|
|
|
|
// Set-up DMA, ADC is connected only to channel 1
|
|
// Turn on DMA
|
|
RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;
|
|
// 1. Set the address of the source peripheral, in this case the output
|
|
// register of the ADC
|
|
DMA1_Channel1->PADDR = (uint32_t)&(ADC1->RDATAR);
|
|
// 2. Set the destination (base) address of the data
|
|
DMA1_Channel1->MADDR = (uint32_t)adc_buffer;
|
|
// 3. Set the number of transfers to be done each cycle, in this case it's
|
|
// the same as the number of regular conversion cycles
|
|
DMA1_Channel1->CNTR = ch_size;
|
|
// 4. Set mode: Peripheral to Memory, MEM2MEM=0 DIR=0
|
|
// Circular mode CIRC=1, restart after CNTR transfers
|
|
// Max priority to not lose conversions and offset values in the buffer
|
|
DMA1_Channel1->CFGR =
|
|
DMA_M2M_Disable |
|
|
DMA_Priority_VeryHigh |
|
|
DMA_MemoryDataSize_HalfWord |
|
|
DMA_PeripheralDataSize_HalfWord |
|
|
DMA_MemoryInc_Enable |
|
|
DMA_Mode_Circular |
|
|
DMA_DIR_PeripheralSRC;
|
|
// Turn on DMA channel 1
|
|
DMA1_Channel1->CFGR |= DMA_CFGR1_EN;
|
|
|
|
// Enable DMA requests from the ADC
|
|
ADC1->CTLR2 |= ADC_DMA;
|
|
// Power up ADC again
|
|
ADC1->CTLR2 |= ADC_ADON;
|
|
// start conversion
|
|
ADC1->CTLR2 |= ADC_SWSTART;
|
|
}
|
|
|
|
|
|
volatile uint16_t injection_results[4];
|
|
bool adc_injection_conversion() {
|
|
// Clear any pending flags
|
|
ADC1->STATR &= ~(ADC_JEOC);
|
|
|
|
// Start injection conversion using external trigger method
|
|
ADC1->CTLR2 |= ADC_JSWSTART;
|
|
|
|
// Wait for all conversions to complete
|
|
s32 timeout = 1000000;
|
|
while(!(ADC1->STATR & ADC_JEOC) && timeout--) {}
|
|
|
|
if (timeout <= 0) return false;
|
|
|
|
// Read all results
|
|
injection_results[0] = ADC1->IDATAR1 & 0x0FFF;
|
|
injection_results[1] = ADC1->IDATAR2 & 0x0FFF;
|
|
injection_results[2] = ADC1->IDATAR3 & 0x0FFF;
|
|
injection_results[3] = ADC1->IDATAR4 & 0x0FFF;
|
|
|
|
// Clear JEOC flag
|
|
ADC1->STATR &= ~ADC_JEOC;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
__attribute__((noreturn)) int main(void)
|
|
{
|
|
SystemInit();
|
|
funGpioInitAll();
|
|
USBFSSetup();
|
|
|
|
funPinMode(PIN_VBUS, GPIO_CFGLR_IN_ANALOG);
|
|
funPinMode(PIN_CURRENT, GPIO_CFGLR_IN_ANALOG);
|
|
funPinMode(PIN_NTC, GPIO_CFGLR_IN_ANALOG);
|
|
funPinMode(PIN_TEMP, GPIO_CFGLR_IN_ANALOG);
|
|
|
|
uint8_t adc_channels[3] = {
|
|
VBUS_ADC_CHANNEL,
|
|
CURRENT_ADC_CHANNEL,
|
|
NTC_ADC_CHANNEL
|
|
};
|
|
uint8_t adc_injected[1] = {
|
|
TEMP_ADC_CHANNEL
|
|
};
|
|
setup_adc_and_dma(adc_channels, 3, adc_injected, 1);
|
|
|
|
funPinMode(PIN_12V, GPIO_CFGLR_OUT_10Mhz_PP);
|
|
funDigitalWrite(PIN_12V, 0);
|
|
funPinMode(PIN_HEATER, GPIO_CFGLR_OUT_10Mhz_PP);
|
|
funDigitalWrite(PIN_HEATER, 0);
|
|
funPinMode(PIN_DISP_RST, GPIO_CFGLR_OUT_10Mhz_PP);
|
|
funDigitalWrite(PIN_DISP_RST, 1); // start with display disabled
|
|
|
|
funPinMode(PIN_ENC_A, GPIO_CFGLR_IN_PUPD); // enable pull-up/down
|
|
funDigitalWrite(PIN_ENC_A, 1); // specify pull-up
|
|
funPinMode(PIN_ENC_B, GPIO_CFGLR_IN_PUPD); // enable pull-up/down
|
|
funDigitalWrite(PIN_ENC_B, 1); // specify pull-up
|
|
funPinMode(PIN_BTN, GPIO_CFGLR_IN_FLOAT);
|
|
|
|
setup_i2c();
|
|
|
|
// Configure the IO as an interrupt.
|
|
// PIN_ENC_B is on port B, channel 11
|
|
AFIO->EXTICR1 = AFIO_EXTICR1_EXTI11_PB; // Port B channel (pin) 11
|
|
EXTI->INTENR = EXTI_INTENR_MR11; // Enable EXT11
|
|
EXTI->FTENR = EXTI_FTENR_TR11; // Falling edge trigger
|
|
// enable interrupt
|
|
NVIC_EnableIRQ(EXTI15_8_IRQn);
|
|
|
|
Delay_Ms(500);
|
|
|
|
u8g2 = display_init();
|
|
sc7a20_init();
|
|
|
|
// Init USBPD
|
|
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);
|
|
}
|
|
|
|
// USBPD_Reset();
|
|
bool has_pd = false;
|
|
USBPD_SPR_CapabilitiesMessage_t *capabilities = NULL;
|
|
uint32_t cap_count = 0;
|
|
int max_v = 5;
|
|
int idx_9v = -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;
|
|
}
|
|
|
|
u8g2_ClearBuffer(u8g2);
|
|
u8g2_SetBitmapMode(u8g2, 1);
|
|
u8g2_SetFontMode(u8g2, 1);
|
|
u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
|
|
u8g2_DrawStr(u8g2, 0, 18, "waiting...");
|
|
u8g2_SendBuffer(u8g2);
|
|
}
|
|
if (result != eUSBPD_OK) {
|
|
printf("USBPD_SinkNegotiate failed: %s, state: %s\n",
|
|
USBPD_ResultToStr(result),
|
|
USBPD_StateToStr(USBPD_GetState())
|
|
);
|
|
} 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;
|
|
}
|
|
if (pdo->FixedSupply.VoltageIn50mV/20 == 9) {
|
|
idx_9v = i;
|
|
}
|
|
break;
|
|
case eUSBPD_PDO_BATTERY:
|
|
if (pdo->BatterySupply.MaxVoltageIn50mV/20 > max_v) {
|
|
max_v = pdo->BatterySupply.MaxVoltageIn50mV/20;
|
|
}
|
|
break;
|
|
case eUSBPD_PDO_VARIABLE:
|
|
if (pdo->VariableSupply.MaxVoltageIn50mV/20 > max_v) {
|
|
max_v = pdo->VariableSupply.MaxVoltageIn50mV/20;
|
|
}
|
|
break;
|
|
case eUSBPD_PDO_AUGMENTED:
|
|
// TODO
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (;;) {
|
|
static uint16_t tip_mv, vbus_mv, current_ma;
|
|
static int16_t temp_k;
|
|
u32 start = funSysTick32();
|
|
|
|
poll_input(); // usb
|
|
|
|
vbus_mv = U16_FP_EMA_K4(vbus_mv, ((u32)adc_buffer[0]*VCC_MV*11)/4096);
|
|
current_ma = U16_FP_EMA_K4(current_ma, get_current_ma(adc_buffer[1]));
|
|
temp_k = I16_FP_EMA_K4(temp_k, get_temp_k(adc_buffer[2]));
|
|
if (!adc_injection_conversion()) {
|
|
printf("injection conversion failed");
|
|
} else {
|
|
tip_mv = U16_FP_EMA_K4(tip_mv, (u32)(injection_results[0]*VCC_MV)/4096);
|
|
}
|
|
|
|
u8g2_ClearBuffer(u8g2);
|
|
u8g2_SetBitmapMode(u8g2, 1);
|
|
u8g2_SetFontMode(u8g2, 1);
|
|
u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
|
|
#define x_off 0
|
|
#define y_off 8
|
|
static bool mode = true;
|
|
if (mode) {
|
|
u8g2_DrawStr(u8g2, x_off+0, y_off+7, "TIP:");
|
|
u8g2_DrawStr(u8g2, x_off+20, y_off+7, u8x8_u16toa(tip_mv, 4));
|
|
u8g2_DrawStr(u8g2, x_off+0, y_off+15, "VBUS:");
|
|
u8g2_DrawStr(u8g2, x_off+25, y_off+15, u8x8_u16toa(vbus_mv, 4));
|
|
u8g2_DrawStr(u8g2, x_off+51, y_off+7, "TEMP:");
|
|
u8g2_DrawStr(u8g2, x_off+75, y_off+7, u8x8_u16toa(temp_k, 2));
|
|
u8g2_DrawStr(u8g2, x_off+51, y_off+15, "V:");
|
|
u8g2_DrawStr(u8g2, x_off+60, y_off+15, u8x8_u16toa(max_v, 2));
|
|
} else {
|
|
static int16_t ax, ay, az;
|
|
sc7a20_get_readings(&ax, &ay, &az);
|
|
u8g2_DrawStr(u8g2, x_off+0, y_off+7, ax > 0 ? "AX:+" : "AX:-");
|
|
u8g2_DrawStr(u8g2, x_off+20, y_off+7, u8x8_u16toa(ax > 0 ? ax : -ax, 5));
|
|
u8g2_DrawStr(u8g2, x_off+0, y_off+15, ay > 0 ? "AY:+" : "AY:-");
|
|
u8g2_DrawStr(u8g2, x_off+20, y_off+15, u8x8_u16toa(ay > 0 ? ay : -ay, 5));
|
|
u8g2_DrawStr(u8g2, x_off+50, y_off+7, az > 0 ? "AZ:+" : "AZ:-");
|
|
u8g2_DrawStr(u8g2, x_off+70, y_off+7, u8x8_u16toa(az > 0 ? az : -az, 5));
|
|
}
|
|
|
|
u8g2_SendBuffer(u8g2);
|
|
|
|
if (idx_9v != -1 && funDigitalRead(PIN_BTN) == 0) {
|
|
USBPD_SelectPDO(idx_9v, 0);
|
|
Delay_Ms(200);
|
|
}
|
|
if (encoder != 0) {
|
|
mode = !mode;
|
|
encoder = 0;
|
|
}
|
|
|
|
// printf("VBUS=%d, CURRENT=%d, TEMP=%d, TIP=%d, COUNTER=%d\n", vbus_mv, current_ma, temp_k, tip_mv, encoder);
|
|
|
|
u32 elapsed = funSysTick32() - start;
|
|
if (elapsed < Ticks_from_Ms(FRAME_TIME_MS)) {
|
|
DelaySysTick(Ticks_from_Ms(FRAME_TIME_MS) - elapsed);
|
|
} else {
|
|
printf("Frame took too long: %ld ms\n", elapsed/DELAY_MS_TIME);
|
|
}
|
|
}
|
|
}
|