[APM32F0]

APM32F003外设ADC单通道和多通道应用

[复制链接]
621|2
手机看帖
扫描二维码
随时随地手机跟帖
susutata|  楼主 | 2022-4-28 15:16 | 显示全部楼层 |阅读模式
本帖最后由 susutata 于 2022-4-28 15:23 编辑

APM32F003外设ADC单通道和多通道应用


01 ADC简介

APM32F003的ADC是一个12位,ADC时钟最高可选择Fmaster时钟的一半。带有9个通道,可测量8个外部和1个内部信号源。各通道的A/D转换可选择单次、连续和扫描模式执行。而ADC的转换结果也可以以左对齐或右对齐的方式存储在16位数据寄存器中。

02 ADC特点

APM32F003的ADC相比于F0x,F10x,F4x系列有所简化,但也保留了相对核心的功能,能够满足普通场合下的应用需求。
ADC.png

03 ADC应用
## 单通道转换
### 定义公共信息
/** ADC Channel Definition*/
#define ADC_CH0_CHANNEL                     ADC_CHANNEL_0

/** ADC GPIO Definition*/
#define ADC_CH0_GPIO_PIN                    GPIO_PIN_5
#define ADC_CH_GPIO_PORTC                   GPIOC

/** ADC Info*/
#define VDD_VOLTAGE                         ((uint32_t)3300)
#define ADC_RANGE                           ((uint32_t)4095)

### 设置ADC通道和方式
/** ADC Single Convert Init*/
void ADC_SingleConvInit(void)
{
    ADC_Config_T adcConfig;

    ADC_GPIOConfig();

    ADC_SetMode(ADC_MODE_SINGLE_END);

    /** ADC Configuration*/
    ADC_ConfigStructInit(&adcConfig);
    adcConfig.channel = ADC_CH0_CHANNEL;
    adcConfig.convMode = ADC_CONV_MODE_CONTINUOUS;
    adcConfig.interrupt = ADC_INT_CC;
    ADC_Config(&adcConfig);

    /** Calibration*/
    ADC_Calibration();

    ADC_Enable();
    ADC_StartConversion();
}

### 轮询转换值
/** ADC Poll for Single Conversion*/
void ADC_PollSingleConv(void)
{
    uint16_t adcData;
    uint16_t voltage;

    if(ADC_ReadStatusFlag(ADC_FLAG_CC) == SET)
    {
        ADC_ClearStatusFlag(ADC_FLAG_CC);

        adcData = ADC_ReadData();

        voltage = (adcData * VDD_VOLTAGE) / ADC_RANGE;

        printf("ADC Channel 0 Voltage: %d\r\n",adcData);
    }
}

## 多通道扫描
### 定义公共信息
/** ADC Channel Definition*/
#define ADC_CH0_CHANNEL                     ADC_CHANNEL_0
#define ADC_CH1_CHANNEL                     ADC_CHANNEL_1
#define ADC_CH2_CHANNEL                     ADC_CHANNEL_2

/** ADC GPIO Definition*/
#define ADC_CH0_GPIO_PIN                    GPIO_PIN_5
#define ADC_CH1_GPIO_PIN                    GPIO_PIN_6
#define ADC_CH2_GPIO_PIN                    GPIO_PIN_4
#define ADC_CH_GPIO_PORTC                   GPIOC

/** ADC Info*/
#define VDD_VOLTAGE                         ((uint32_t)3300)
#define ADC_RANGE                           ((uint32_t)4095)

### 设置ADC通道和方式
> 注意扫描的通道数量和所设置的通道一致,比如设置了通道CH2,则ADC会从ADC CH0扫描到ADC CH2,共三个通道。

/** ADC GPIO Config*/
void ADC_GPIOConfig(void)
{
    GPIO_Config_T gpioConfig;

    gpioConfig.intEn = GPIO_EINT_DISABLE;
    gpioConfig.mode = GPIO_MODE_IN_FLOATING;
    gpioConfig.pin = ADC_CH0_GPIO_PIN | ADC_CH1_GPIO_PIN | ADC_CH2_GPIO_PIN;
    GPIO_Config(ADC_CH_GPIO_PORTC, &gpioConfig);
}

/** ADC Continuous Scan Init*/
void ADC_ContinuousScanInit(void)
{
    ADC_Config_T adcConfig;

    ADC_GPIOConfig();

    ADC_SetMode(ADC_MODE_SINGLE_END);

    /** ADC Configuration*/
    ADC_ConfigStructInit(&adcConfig);
    adcConfig.div = ADC_DIV_4;
    adcConfig.channel = ADC_CH2_CHANNEL;
    adcConfig.convMode = ADC_CONV_MODE_CONTINUOUS;
    adcConfig.scanMode = ADC_SCAN_MODE_ENABLE;
    adcConfig.interrupt = ADC_INT_CC;
    ADC_Config(&adcConfig);

    /** Calibration*/
    ADC_Calibration();

    ADC_Enable();
    ADC_StartConversion();
}

### 轮询转换值
/** ADC Poll for Conversion*/
void ADC_PollForConv(void)
{
    uint16_t adcArray[10] = {0};
    uint16_t voltage;
    uint8_t bufferIndex;

    if(ADC_ReadStatusFlag(ADC_FLAG_CC) == SET)
    {
        ADC_ClearStatusFlag(ADC_FLAG_CC);

        for(bufferIndex = 0; bufferIndex < 10; bufferIndex)
        {
            adcArray[bufferIndex] = ADC_ReadBufferData((ADC_BUFFER_IDX_T)(bufferIndex));
        }

        for(bufferIndex = 0; bufferIndex < 3; bufferIndex++)
        {
            voltage = (adcArray[bufferIndex] * VDD_VOLTAGE) / ADC_RANGE;

            printf("ADC Channel %d Voltage: %d\r\n",bufferIndex, voltage);
        }
    }
}

## 软件滤波
在硬件抗干扰能力不足的情况下,我们可以牺牲采用速率,采用软件方法进行滤波,从而得到更加稳定的采样值。一般应用的情况下,“抗脉冲干扰均值滤波算法就能够满足需求。
/** Anti-interference Average Filtering*/
uint16_t Anti_AverageFilter(uint16_t *buffer, uint16_t length)
{
    uint16_t temp;
    uint16_t i,j;

    /**Bubbling Sort*/
    for(j = 0; j < length - 1; j++)
    {
        for(i = 0; i < length - j - 1; i++)
        {
            if(buffer > buffer[i + 1])
            {
                temp = buffer;
                buffer = buffer[i + 1];
                buffer[i + 1] = temp;
            }
        }
    }

    temp = 0;

    for(i = 1; i < length - 1; i++)
    {
        temp += buffer;
    }
    temp /= (length - 2);

    return temp;
}
## 软件校准
如果采样值较为固定,但离目标值相差大,还可以利用线性拟合(线性校准曲线)的方式让采样值更接近目标值。

### 采样
首先基于标准源表采样足够多的点(点数越多,拟合越准确)。
Snipaste_2022-04-28_15-18-37.png

### 拟合
在数学工具(Matlab或Excel等)中做线性拟合(线性、多项式、指数皆可),得到校准公式和相关系数R值。
Snipaste_2022-04-28_15-19-18.png

### 校准
用拟合步骤中得到的校准公式对采样值进行校准。
Snipaste_2022-04-28_15-19-01.png






使用特权

评论回复
skyred| | 2022-5-5 13:37 | 显示全部楼层
这个代码整理的真好,太用心了

使用特权

评论回复
zhanxiao| | 2023-12-12 14:34 | 显示全部楼层
两个通道channel0 和channel2采样,channel0会影响到channel2,反过来一样,测试是channel0通过电阻上拉到3.3v,channel2也上拉到3.3v,如果channel2接地,channel0采样过来的值会缩减一半,还能用吗?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

15

主题

23

帖子

3

粉丝