From f7949e42fded24c4d4a55882173801830699681c Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Wed, 18 Jan 2023 18:46:47 +0100 Subject: [PATCH] cpu/gd32v: add periph_adc support --- cpu/gd32v/include/periph_cpu.h | 31 +++ cpu/gd32v/include/vendor/gd32vf103_periph.h | 4 +- cpu/gd32v/periph/adc.c | 212 ++++++++++++++++++++ 3 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 cpu/gd32v/periph/adc.c diff --git a/cpu/gd32v/include/periph_cpu.h b/cpu/gd32v/include/periph_cpu.h index e0b3438e5e..fd353c8476 100644 --- a/cpu/gd32v/include/periph_cpu.h +++ b/cpu/gd32v/include/periph_cpu.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Koen Zandberg + * 2023 Gunar Schorcht * * This file is subject to the terms and conditions of the GNU Lesser General * Public License v2.1. See the file LICENSE in the top level directory for more @@ -14,6 +15,7 @@ * @brief CPU specific definitions for internal peripheral handling * * @author Koen Zandberg + * @author Gunar Schorcht */ #ifndef PERIPH_CPU_H @@ -166,6 +168,35 @@ typedef enum { GPIO_AF_OUT_OD = 0xf, /**< alternate function output - open-drain */ } gpio_af_t; +/** + * @brief Configure the alternate function for the given pin + * + * @param[in] pin pin to configure + * @param[in] af alternate function to use + */ +void gpio_init_af(gpio_t pin, gpio_af_t af); + +/** + * @brief Configure the given pin to be used as ADC input + * + * @param[in] pin pin to configure + */ +void gpio_init_analog(gpio_t pin); + +/** + * @brief Available number of ADC devices + */ +#define ADC_DEVS (2U) + +/** + * @brief ADC channel configuration data + */ +typedef struct { + gpio_t pin; /**< pin connected to the channel */ + uint8_t dev; /**< ADCx - 1 device used for the channel */ + uint8_t chan; /**< CPU ADC channel connected to the pin */ +} adc_conf_t; + /** * @brief GD32V timers have 4 capture-compare channels */ diff --git a/cpu/gd32v/include/vendor/gd32vf103_periph.h b/cpu/gd32v/include/vendor/gd32vf103_periph.h index 52d0d437ea..4187cfaa7a 100644 --- a/cpu/gd32v/include/vendor/gd32vf103_periph.h +++ b/cpu/gd32v/include/vendor/gd32vf103_periph.h @@ -11529,8 +11529,8 @@ typedef struct { /*!< (@ 0x40002C00) WWDGT Struct * @{ */ -//#define ADC0_BASE 0x40012400UL -//#define ADC1_BASE 0x40012800UL +#define ADC0_BASE 0x40012400UL +#define ADC1_BASE 0x40012800UL //#define AFIO_BASE 0x40010000UL //#define BKP_BASE 0x40006C00UL //#define CAN0_BASE 0x40006400UL diff --git a/cpu/gd32v/periph/adc.c b/cpu/gd32v/periph/adc.c new file mode 100644 index 0000000000..4dfdc98a5c --- /dev/null +++ b/cpu/gd32v/periph/adc.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2016 Engineering-Spirit + * 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_gd32v + * @ingroup drivers_periph_adc + * @{ + * + * @file + * @brief Low-level ADC driver implementation + * + * @author Hauke Petersen + * @author Nick van IJzendoorn + * @author Gunar Schorcht + * + * @} + */ + +#include "cpu.h" +#include "macros/units.h" +#include "mutex.h" +#include "periph/adc.h" +#include "periph_conf.h" + +/** + * @brief Maximum allowed ADC clock speed + */ +#define MAX_ADC_SPEED MHZ(14) + +/** + * @brief Allocate locks for all three available ADC devices + */ +static mutex_t locks[] = { +#if ADC_DEVS > 1 + MUTEX_INIT, +#endif + MUTEX_INIT +}; + +static inline ADC0_Type *dev(adc_t line) +{ + switch (adc_config[line].dev) { + case 0: + return (ADC0_Type *)ADC0_BASE; +#if ADC_DEVS > 1 + case 1: + return (ADC0_Type *)ADC1_BASE; +#endif + default: + assert(0); + return NULL; + } +} + +static inline void prep(adc_t line, adc_res_t res) +{ + mutex_lock(&locks[adc_config[line].dev]); + periph_clk_en(APB2, (RCU_APB2EN_ADC0EN_Msk << adc_config[line].dev)); + + /* enable the ADC module */ + dev(line)->CTL1 |= ADC0_CTL1_ADCON_Msk; + + /* configure the resolution */ + dev(line)->OVSAMPCTL &= ~ADC0_OVSAMPCTL_DRES_Msk; + switch (res) { + case ADC_RES_12BIT: + dev(line)->OVSAMPCTL |= 0 << ADC0_OVSAMPCTL_DRES_Pos; + break; + case ADC_RES_10BIT: + dev(line)->OVSAMPCTL |= 1 << ADC0_OVSAMPCTL_DRES_Pos; + break; + case ADC_RES_8BIT: + dev(line)->OVSAMPCTL |= 2 << ADC0_OVSAMPCTL_DRES_Pos; + break; + case ADC_RES_6BIT: + dev(line)->OVSAMPCTL |= 3 << ADC0_OVSAMPCTL_DRES_Pos; + break; + default: + break; + } + + /* check if this channel is an internal ADC channel, if so + * enable the internal temperature and Vref */ + if (adc_config[line].chan == 16 || adc_config[line].chan == 17) { + dev(line)->CTL1 |= ADC0_CTL1_TSVREN_Msk; + } +} + +static inline void done(adc_t line) +{ + /* disable the internal temperature and Vref */ + dev(line)->CTL1 &= ~ADC0_CTL1_TSVREN_Msk; + + /* disable the ADC module */ + dev(line)->CTL1 &= ~ADC0_CTL1_ADCON_Msk; + + periph_clk_dis(APB2, (RCU_APB2EN_ADC0EN_Msk << adc_config[line].dev)); + mutex_unlock(&locks[adc_config[line].dev]); +} + +int adc_init(adc_t line) +{ + uint32_t clk_div = 2; + + /* check if the line is valid */ + if (line >= ADC_NUMOF) { + return -1; + } + + /* lock and power-on the device */ + prep(line, ADC_RES_12BIT); + + /* configure the pin */ + if (adc_config[line].pin != GPIO_UNDEF) { + gpio_init_analog(adc_config[line].pin); + } + /* set clock prescaler to get the maximal possible ADC clock value */ + for (clk_div = 2; clk_div < 8; clk_div += 2) { + if ((CLOCK_CORECLOCK / clk_div) <= MAX_ADC_SPEED) { + break; + } + } + RCU->CFG0 &= ~(RCU_CFG0_ADCPSC_2_Msk); + RCU->CFG0 |= ((clk_div / 2) - 1) << RCU_CFG0_ADCPSC_2_Pos; + + /* resets the selected ADC calibration registers */ + dev(line)->CTL1 |= ADC0_CTL1_RSTCLB_Msk; + /* check the status of RSTCAL bit */ + while (dev(line)->CTL1 & ADC0_CTL1_RSTCLB_Msk) {} + + /* enable the selected ADC calibration process */ + dev(line)->CTL1 |= ADC0_CTL1_CLB_Msk; + /* wait for the calibration to have finished */ + while (dev(line)->CTL1 & ADC0_CTL1_CLB_Msk) {} + + /* set all channels to maximum (239.5) cycles for best accuracy */ + dev(line)->SAMPT0 |= 0x00ffffff; + dev(line)->SAMPT1 |= 0x3fffffff; + /* we want to sample one channel */ + dev(line)->RSQ0 = 1 << ADC0_RSQ0_RL_Pos; + /* start sampling from software */ + dev(line)->CTL1 |= ADC0_CTL1_ETERC_Msk | ADC0_CTL1_ETSRC_Msk; + + /* check if the internal channels are configured to use ADC0 */ + if (adc_config[line].chan == 16 || adc_config[line].chan == 17) { + assert (dev(line) == ADC0); + } + + /* free the device again */ + done(line); + return 0; +} + +int32_t adc_sample(adc_t line, adc_res_t res) +{ + int sample; + + /* check if the linenel is valid */ + if (line >= ADC_NUMOF) { + return -1; + } + + /* check valid resolution */ + dev(line)->OVSAMPCTL &= ~ADC0_OVSAMPCTL_DRES_Msk; + switch (res) { + case ADC_RES_12BIT: + case ADC_RES_10BIT: + case ADC_RES_8BIT: + case ADC_RES_6BIT: + break; + default: + return -1; + } + + /* lock and power on the ADC device */ + prep(line, res); + + /* set conversion channel */ + dev(line)->RSQ2 = adc_config[line].chan; + /* start conversion and wait for results */ + dev(line)->CTL1 |= ADC0_CTL1_SWRCST_Msk; + while (!(dev(line)->STAT & ADC0_STAT_EOC_Msk)) {} + /* finally read sample and reset the STRT bit in the status register */ + sample = (int)dev(line)->RDATA; + + /* the sample is 12 bit even if the resolution is less than 12 bit, + * scale down the 12 bit value to the requested resolution */ + switch (res) { + case ADC_RES_10BIT: + sample = sample >> 2; + break; + case ADC_RES_8BIT: + sample = sample >> 4; + break; + case ADC_RES_6BIT: + sample = sample >> 6; + break; + default: + break; + } + + /* power off and unlock device again */ + done(line); + + return sample; +}