timdac/src/timdac_hw_ch32v103.c

283 lines
7.5 KiB
C

// intellectual property is bullshit bgdc
// ============================================================================
// TIMDAC HARDWARE DRIVER -- WCH CH32V103
// ----------------------------------------------------------------------------
// This driver implements TIMDAC for the WCH CH32V103 microcontroller. It
// requires the following:
//
// - Core clock = 72 MHz
// - One general-purpose timer module
// - The standard TIMDAC GPIOs:
// - 1 output compare pin
// - 1 mux inhibit pin
// - 1 discharge pin
// - 0 or 1 polarity pins
// - From 0 to 8 channel select pins
//
// To use it, include this source file in your build, and define the following
// in timdac_config.h:
//
// #define TIMDAC_HW_TIMER
// Name of the timer module (e.g. TIM3)
//
// #define TIMDAC_HW_TIMER_CHANNEL
// Number of the output compare channel, from 1 to 4
//
// #define TIMDAC_HW_GPIO_TIMER
// GPIO and pin corresponding to the output compare channel
// (e.g. GPIOC, GPIO_Pin_6)
//
// #define TIMDAC_HW_GPIO_MUXINHIBIT
// GPIO and pin corresponding to the mux inhibit signal
//
// #define TIMDAC_HW_GPIO_DISCHARGE
// GPIO and pin corresponding to the discharge signal
//
// #define TIMDAC_HW_GPIO_POLARITY
// OPTIONAL, GPIO and pin corresponding to the polarity signal, where High
// means Negative. If not using polarity, do not define this.
//
// #define TIMDAC_HW_GPIO_TUNE
// GPIO and pin corresponding to the tuning comparator output signal.
//
// #define TIMDAC_HW_GPIO_TUNE_POL
// Polarity of the tuning comparator; false = inverting with respect to the
// positive-going ramp. Note that the reference design uses inverting
// polarity, so this should generally be false.
//
// #define TIMDAC_HW_GPIO_CHAN_PORT
// GPIO port where the channel select pins live. If there is only one
// channel, this may be left undefined. If this is undefined,
// TIMDAC_HW_GPIO_CHAN_PINS must also be undefined.
// (e.g. GPIOC)
//
// #define TIMDAC_HW_GPIO_CHAN_PINS
// List of channel select pins, least significant bit first, up to eight.
// If there is only one channel, this may be left undefined. If this is
// undefined, TIMDAC_HW_GPIO_CHAN must also be undefined. Note that if
// a channel number is requested that exceeds pow2(CHAN_PINS)-1, the
// channel number will be truncated.
// (e.g. GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_2)
// ============================================================================
#include "timdac_ll.h"
#include "timdac_config.h"
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <ch32v10x_tim.h>
#include <ch32v10x_gpio.h>
#if defined(TIMDAC_HW_GPIO_CHAN_PORT) && !defined(TIMDAC_HW_GPIO_CHAN_PINS)
# error "timdac_hw: channel select port defined, but no pins"
#elif defined(TIMDAC_HW_GPIO_CHAN_PINS) && !defined(TIMDAC_HW_GPIO_CHAN_PORT)
# error "timdac_hw: channel select pins defined, but no port"
#elif defined(TIMDAC_HW_GPIO_CHAN_PORT)
static const uint16_t _chansel[8] = {TIMDAC_HW_GPIO_CHAN_PINS};
#define N_CHANSEL (sizeof(_chansel)/sizeof(_chansel[0]))
#endif
static uint16_t _ntim_to_channel(uint16_t ntim);
static void _init_gpio(
GPIO_TypeDef * gpio,
uint16_t pin,
GPIOMode_TypeDef mode,
bool fast,
bool value
);
void timdac_hw_init(void)
{
_init_gpio(TIMDAC_HW_GPIO_MUXINHIBIT, GPIO_Mode_Out_PP, false, true);
_init_gpio(TIMDAC_HW_GPIO_DISCHARGE, GPIO_Mode_Out_PP, false, true);
#ifdef TIMDAC_HW_GPIO_POLARITY
_init_gpio(TIMDAC_HW_GPIO_POLARITY, GPIO_Mode_Out_PP, false, false);
#endif
#ifdef TIMDAC_HW_GPIO_CHAN_PORT
for (size_t i = 0; i < N_CHANSEL; i++)
{
_init_gpio(
TIMDAC_HW_GPIO_CHAN_PORT,
_chansel[i],
GPIO_Mode_Out_PP,
false,
false
);
}
#endif
_init_gpio(TIMDAC_HW_GPIO_TUNE, GPIO_Mode_IPU, false, false);
_init_gpio(TIMDAC_HW_GPIO_TIMER, GPIO_Mode_AF_PP, true, false);
TIM_TimeBaseInitTypeDef tbi = {
.TIM_Prescaler = 0,
.TIM_CounterMode = TIM_CounterMode_Up,
.TIM_Period = TIMDAC_DISCHARGE_TIME,
.TIM_ClockDivision = TIM_CKD_DIV1,
.TIM_RepetitionCounter = 0x0000,
};
TIM_TimeBaseInit(TIMDAC_HW_TIMER, &tbi);
TIM_OCInitTypeDef oci = {
.TIM_OCMode = TIM_OCMode_PWM2,
.TIM_OutputState = TIM_OutputState_Enable,
.TIM_OutputNState = TIM_OutputNState_Disable,
.TIM_Pulse = 0,
.TIM_OCPolarity = TIM_OCPolarity_High,
.TIM_OCNPolarity = TIM_OCPolarity_High,
.TIM_OCIdleState = TIM_OCIdleState_Reset, // TIM1/8 only
.TIM_OCNIdleState = TIM_OCIdleState_Reset, // TIM1/8 only
};
switch (TIMDAC_HW_TIMER_CHANNEL) {
case 1:
TIM_OC1Init(TIMDAC_HW_TIMER, &oci);
TIM_OC1PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
case 2:
TIM_OC2Init(TIMDAC_HW_TIMER, &oci);
TIM_OC2PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
case 3:
TIM_OC3Init(TIMDAC_HW_TIMER, &oci);
TIM_OC3PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
case 4:
TIM_OC4Init(TIMDAC_HW_TIMER, &oci);
TIM_OC4PreloadConfig(TIMDAC_HW_TIMER, TIM_OCPreload_Disable);
break;
}
TIM_SelectOnePulseMode(TIMDAC_HW_TIMER, TIM_OPMode_Single);
TIM_CtrlPWMOutputs(TIMDAC_HW_TIMER, ENABLE);
#ifdef TIMDAC_HW_TIMER_IRQn
NVIC_InitTypeDef tim_it_init = {
.NVIC_IRQChannel = TIMDAC_HW_TIMER_IRQn,
.NVIC_IRQChannelPreemptionPriority = TIMDAC_HW_TIMER_IRQPRIO,
.NVIC_IRQChannelSubPriority = TIMDAC_HW_TIMER_IRQSUBPRIO,
.NVIC_IRQChannelCmd = ENABLE,
};
NVIC_Init(&tim_it_init);
TIM_ITConfig(TIMDAC_HW_TIMER, TIM_IT_Update, ENABLE);
#endif
}
void timdac_hw_timer_set_period(uint16_t counts)
{
TIM_SetAutoreload(TIMDAC_HW_TIMER, counts);
}
void timdac_hw_timer_set_compare(uint16_t counts)
{
// Our timer mode results in a pulse that is LOW for `counts`, so
// invert the duration:
uint16_t value = UINT16_MAX - counts;
switch (TIMDAC_HW_TIMER_CHANNEL) {
case 1:
TIM_SetCompare1(TIMDAC_HW_TIMER, value);
break;
case 2:
TIM_SetCompare2(TIMDAC_HW_TIMER, value);
break;
case 3:
TIM_SetCompare3(TIMDAC_HW_TIMER, value);
break;
case 4:
TIM_SetCompare4(TIMDAC_HW_TIMER, value);
break;
}
}
void timdac_hw_timer_set_oc_enabled(bool en)
{
TIM_CCxCmd(TIMDAC_HW_TIMER, _ntim_to_channel(TIMDAC_HW_TIMER_CHANNEL),
en ? TIM_CCx_Enable : TIM_CCx_Disable);
}
void timdac_hw_timer_start(void)
{
TIM_Cmd(TIMDAC_HW_TIMER, ENABLE);
}
bool timdac_hw_timer_running(void)
{
return TIMDAC_HW_TIMER->CTLR1 & TIM_CEN;
}
void timdac_hw_gpio_muxinhibit(bool en)
{
GPIO_WriteBit(TIMDAC_HW_GPIO_MUXINHIBIT, en ? Bit_SET : Bit_RESET);
}
void timdac_hw_gpio_discharge(bool en)
{
GPIO_WriteBit(TIMDAC_HW_GPIO_DISCHARGE, en ? Bit_SET : Bit_RESET);
}
void timdac_hw_gpio_polarity(bool pos)
{
#ifdef TIMDAC_HW_GPIO_POLARITY
GPIO_WriteBit(TIMDAC_HW_GPIO_POLARITY, !pos ? Bit_SET : Bit_RESET);
#endif
}
void timdac_hw_gpio_select_channel(uint8_t chan)
{
#ifdef TIMDAC_HW_GPIO_CHAN_PORT
uint16_t chan_mask = 0, chan_bits = 0;
for (uint8_t i = 0; i < N_CHANSEL; i++, chan >>= 1)
{
if (!_chansel[i]) break;
uint16_t bit = _chansel[i];
chan_mask |= bit;
if (chan & 1) chan_bits |= bit;
}
GPIO_ResetBits(TIMDAC_HW_GPIO_CHAN_PORT, chan_mask);
GPIO_SetBits(TIMDAC_HW_GPIO_CHAN_PORT, chan_bits);
#else
(void) chan;
#endif
}
bool timdac_hw_gpio_tune_is_high(void)
{
return GPIO_ReadInputDataBit(TIMDAC_HW_GPIO_TUNE)
^ !TIMDAC_HW_GPIO_TUNE_POL;
}
static uint16_t _ntim_to_channel(uint16_t ntim)
{
return (ntim - 1) << 2;
}
static void _init_gpio(
GPIO_TypeDef * gpio,
uint16_t pin,
GPIOMode_TypeDef mode,
bool fast,
bool value
)
{
if (mode == GPIO_Mode_Out_OD || mode == GPIO_Mode_Out_PP)
GPIO_WriteBit(gpio, pin, value);
GPIO_InitTypeDef init = {
.GPIO_Pin = pin,
.GPIO_Speed = fast ? GPIO_Speed_50MHz : GPIO_Speed_2MHz,
.GPIO_Mode = mode,
};
GPIO_Init(gpio, &init);
}