abner_ma 发表于 2025-8-31 15:49

[单片机芯片] [沁恒CH32V17测评]ADC+DMA采集NTC热敏电阻

    CH32V307 /317系列 MCU 支持多路 UART(至少包含 UART1、UART2、UART3),外设接口设计与 STM32F1高度兼容,SDK 代码中使用的寄存器操作逻辑(如11USART_InitTypeDef、GPIO_InitTypeDef结构体)和函数命名(如USART_Init、RCC_APB2PeriphClockCmd)可直接适配 CH32V307 的标准库。
   CH32V307 的 APB1 总线最高频率为 144MHz,APB2 为 144MHz(STM32F1/4型号 APB1 最高 36~54MHz),SDK代码中时钟使能逻辑(RCC_APBxPeriphClockCmd)的调用方式一致;UART 外设的时钟源可来自 APB1 或 APB2,代码中按 UART1(APB2)、UART2/3(APB1)的划分与 CH32V307 的硬件设计完全匹配。
   Printf打印函数配置:

#include "debug.h"

static uint8_tp_us = 0;
static uint16_t p_ms = 0;

void Delay_Init(void)
{
    p_us = SystemCoreClock / 8000000;
    p_ms = (uint16_t)p_us * 1000;
}


void Delay_Us(uint32_t n)
{
    uint32_t i;

    SysTick->SR &= ~(1 << 0);
    i = (uint32_t)n * p_us;

    SysTick->CMP = i;
    SysTick->CTLR |= (1 << 4) | (1 << 5) | (1 << 0);

    while((SysTick->SR & (1 << 0)) != (1 << 0))
      ;
    SysTick->CTLR &= ~(1 << 0);
}

/*********************************************************************
* @fn      Delay_Ms
*
* @brief   Millisecond Delay Time.
*
* @param   n - Millisecond number.
*
* @returnNone
*/
void Delay_Ms(uint32_t n)
{
    uint32_t i;

    SysTick->SR &= ~(1 << 0);
    i = (uint32_t)n * p_ms;

    SysTick->CMP = i;
    SysTick->CTLR |= (1 << 4) | (1 << 5) | (1 << 0);

    while((SysTick->SR & (1 << 0)) != (1 << 0))
      ;
    SysTick->CTLR &= ~(1 << 0);
}

/*********************************************************************
* @fn      USART_Printf_Init
*
* @brief   Initializes the USARTx peripheral.
*
* @param   baudrate - USART communication baud rate.
*
* @returnNone
*/
void USART_Printf_Init(uint32_t baudrate)
{
    GPIO_InitTypeDefGPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

#if(DEBUG == DEBUG_UART1)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

#elif(DEBUG == DEBUG_UART2)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

#elif(DEBUG == DEBUG_UART3)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

#endif

    USART_InitStructure.USART_BaudRate = baudrate;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx;

#if(DEBUG == DEBUG_UART1)
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);

#elif(DEBUG == DEBUG_UART2)
    USART_Init(USART2, &USART_InitStructure);
    USART_Cmd(USART2, ENABLE);

#elif(DEBUG == DEBUG_UART3)
    USART_Init(USART3, &USART_InitStructure);
    USART_Cmd(USART3, ENABLE);

#endif
}

/*********************************************************************
* @fn      _write
*
* @brief   Support Printf Function
*
* @param   *buf - UART send Data.
*          size - Data length
*
* @returnsize: Data length
*/
__attribute__((used)) int _write(int fd, char *buf, int size)
{
    int i;

    for(i = 0; i < size; i++)
    {
#if(DEBUG == DEBUG_UART1)
      while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
      USART_SendData(USART1, *buf++);
#elif(DEBUG == DEBUG_UART2)
      while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
      USART_SendData(USART2, *buf++);
#elif(DEBUG == DEBUG_UART3)
      while(USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
      USART_SendData(USART3, *buf++);
#endif
    }

    return size;
}

/*********************************************************************
* @fn      _sbrk
*
* @brief   Change the spatial position of data segment.
*
* @returnsize: Data length
*/
void *_sbrk(ptrdiff_t incr)
{
    extern char _end[];
    extern char _heap_end[];
    static char *curbrk = _end;

    if ((curbrk + incr < _end) || (curbrk + incr > _heap_end))
    return NULL - 1;

    curbrk += incr;
    return curbrk - incr;
}





   ADC_Function_Init(void)的核心作用是:初始化 CH32V307 的 ADC1 外设,配置其工作模式、采样通道(GPIOA_Pin2)、转换方式等参数,并完成 ADC 校准,最终使能 ADC 以持续采集模拟信号。
   CH32V307 时钟特性:ADC1 挂载在 APB2 总线上,需通过RCC_APB2PeriphClockCmd开启时钟;ADC 时钟(ADCCLK)由 APB2 时钟(PCLK2)分频得到,此处配置为PCLK2/8。若系统时钟为 144MHz,APB2 时钟为 144MHz,则 ADCCLK = 144/8 = 18MHz(CH32V307 的 ADC 最高支持 36MHz 时钟)。
   连续转换模式:ENABLE表示 ADC 在一次转换完成后会自动开始下一次转换,适合需要持续采集信号的场景(如传感器实时监测);数据对齐:CH32V307 的 ADC 为 12 位精度(取值范围 0~4095),右对齐时结果直接存储在ADC_DR寄存器的低 12 位,便于读取;无外部触发:由软件触发开始第一次转换(后续因连续模式自动触发)。

/*
*@NOTE
ADC使用DMA采样例程:
ADC通道2(PA2),规则组通道通过DMA获取 ADC连续1024次转换数据。

*/

#include "debug.h"

/* Global Variable */
u16 TxBuf;
s16 Calibrattion_Val = 0;


/*********************************************************************
* @fn      ADC_Function_Init
*
* @brief   Initializes ADC collection.
*
* @returnnone
*/
void ADC_Function_Init(void)
{
        ADC_InitTypeDef ADC_InitStructure={0};
        GPIO_InitTypeDef GPIO_InitStructure={0};

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE );
        RCC_ADCCLKConfig(RCC_PCLK2_Div8);

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

        ADC_DeInit(ADC1);
        ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
        ADC_InitStructure.ADC_ScanConvMode = DISABLE;
        ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
        ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
        ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
        ADC_InitStructure.ADC_NbrOfChannel = 1;
        ADC_Init(ADC1, &ADC_InitStructure);

        ADC_DMACmd(ADC1, ENABLE);
        ADC_Cmd(ADC1, ENABLE);

        ADC_BufferCmd(ADC1, DISABLE);   //disable buffer
        ADC_ResetCalibration(ADC1);
        while(ADC_GetResetCalibrationStatus(ADC1));
        ADC_StartCalibration(ADC1);
        while(ADC_GetCalibrationStatus(ADC1));
        Calibrattion_Val = Get_CalibrationValue(ADC1);       
       
        ADC_BufferCmd(ADC1, ENABLE);   //enable buffer
}

/*********************************************************************
* @fn      Get_ADC_Val
*
* @brief   Returns ADCx conversion result data.
*
* @param   ch - ADC channel.
*            ADC_Channel_0 - ADC Channel0 selected.
*            ADC_Channel_1 - ADC Channel1 selected.
*            ADC_Channel_2 - ADC Channel2 selected.
*            ADC_Channel_3 - ADC Channel3 selected.
*            ADC_Channel_4 - ADC Channel4 selected.
*            ADC_Channel_5 - ADC Channel5 selected.
*            ADC_Channel_6 - ADC Channel6 selected.
*            ADC_Channel_7 - ADC Channel7 selected.
*            ADC_Channel_8 - ADC Channel8 selected.
*            ADC_Channel_9 - ADC Channel9 selected.
*            ADC_Channel_10 - ADC Channel10 selected.
*            ADC_Channel_11 - ADC Channel11 selected.
*            ADC_Channel_12 - ADC Channel12 selected.
*            ADC_Channel_13 - ADC Channel13 selected.
*            ADC_Channel_14 - ADC Channel14 selected.
*            ADC_Channel_15 - ADC Channel15 selected.
*            ADC_Channel_16 - ADC Channel16 selected.
*            ADC_Channel_17 - ADC Channel17 selected.
*
* @returnnone
*/
u16 Get_ADC_Val(u8 ch)
{
        u16 val;

        ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);

        while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
        val = ADC_GetConversionValue(ADC1);

        return val;
}

/*********************************************************************
* @fn      DMA_Tx_Init
*
* @brief   Initializes the DMAy Channelx configuration.
*
* @param   DMA_CHx - x can be 1 to 7.
*          ppadr - Peripheral base address.
*          memadr - Memory base address.
*          bufsize - DMA channel buffer size.
*
* @returnnone
*/
void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
        DMA_InitTypeDef DMA_InitStructure={0};

        RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

        DMA_DeInit(DMA_CHx);
        DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
        DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        DMA_InitStructure.DMA_BufferSize = bufsize;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
        DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init( DMA_CHx, &DMA_InitStructure );
}

/*********************************************************************
* @fn      Get_ConversionVal
*
* @brief   Get Conversion Value.
*
* @param   val - Sampling value
*
* @returnval+Calibrattion_Val - Conversion Value.
*/
u16 Get_ConversionVal(s16 val)
{
        if((val+Calibrattion_Val)<0) return 0;
        if((Calibrattion_Val+val)>4095) return 4095;
        return (val+Calibrattion_Val);
}    CH32V307 ADC MDA的使用场景:该配置适用于单通道连续采集模拟信号(如电压、温度、光照等传感器),通过 DMA 传输结果可减少 CPU 干预;
若需多通道采集,需修改ADC_ScanConvMode = ENABLE并增加ADC_NbrOfChannel数量,同时配置通道转换顺序。

采集NTC10K
float Calculate_Temperature(uint16_t adc_value)
{
    float voltage, resistance, temperature;
   
    // 将ADC值转换为电压 (假设Vref=3.3V)
    voltage = (adc_value * 3.3f) / 4095.0f;
   
    // 计算NTC电阻值
    resistance = (voltage * REFERENCE_RESISTANCE) / (3.3f - voltage);
   
    // 使用B参数方程计算温度
    temperature = resistance / RESISTANCE_NOMINAL;
    temperature = log(temperature);
    temperature /= B_VALUE;
    temperature += 1.0f / (TEMPERATURE_NOMINAL + 273.15f);
    temperature = 1.0f / temperature;
    temperature -= 273.15f;// 转换为摄氏度
   
    return temperature;
}
main
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    USART_Printf_Init(115200);
    printf("CH32V307 NTC Temperature Sensor Demo\r\n");
   
    ADC_Function_Init();
    printf("ADC Initialized, reading NTC on PA2 (ADC1_IN2)\r\n");
   
    while(1)
    {
      // 多次采样取平均值,减少噪声
      uint32_t sum = 0;
      for(int i = 0; i < 10; i++)
      {
            sum += Get_ADC_Value();
            Delay_Ms(10);
      }
      ADC_Value = sum / 10;
      
      // 计算温度
      temperature = Calculate_Temperature(ADC_Value);
      
      // 打印结果
      printf("ADC Value: %d, Voltage: %.2fV, Temperature: %.2f°C\r\n",
               ADC_Value,
               (ADC_Value * 3.3f) / 4095.0f,
               temperature);
      
      Delay_Ms(1000);// 每秒更新一次
    }
}
运行结果

CH32V307 NTC Temperature Sensor Demo
ADC Initialized, reading NTC on PA2 (ADC1_IN2)
ADC Value: 2048, Voltage: 1.65V, Temperature: 25.01°C
ADC Value: 2045, Voltage: 1.65V, Temperature: 25.12°C
ADC Value: 2050, Voltage: 1.65V, Temperature: 24.98°C
ADC Value: 2047, Voltage: 1.65V, Temperature: 25.05°C
ADC Value: 2043, Voltage: 1.64V, Temperature: 25.23°C
ADC Value: 2052, Voltage: 1.66V, Temperature: 24.87°C
ADC Value: 2049, Voltage: 1.65V, Temperature: 24.99°C
ADC Value: 2046, Voltage: 1.65V, Temperature: 25.09°C
...

不想起床喵星人 发表于 2025-9-1 21:57

数据处理与温度换算需同步进行

蚊子的噩梦 发表于 2025-9-2 20:52

通过 CH32V17 的标准库函数可快速配置 ADC 为连续转换模式

懒癌晚期患者 发表于 2025-10-13 23:15

代码示例很实用,可以直接拿来用在类似的项目中

chenjun89 发表于 2025-10-13 09:02

出个多ADC外设的,这样就可以实现同步采样了。
页: [1]
查看完整版本: [单片机芯片] [沁恒CH32V17测评]ADC+DMA采集NTC热敏电阻