目录
GD32F450的ADC最多有3个,每个ADC有19个通道(16个外部通道),ADC0的通道16,通道17和通道18分别连接到温度传感器,内部参考电压和VBAT/4模拟输入。 ADC1的通道16,通道17和通道18内部都连接到VSSA。 ADC2的通道16,通道17和通道18内部都连接到VSSA。
注意:Datasheet中似乎通道数与用户手册的不一样,但是应该是19个通道。
最高12位分辨率,可配置为12、10、8、6位分辨率。
采用率最高2.6MSPs(12位分辨率)、3.0MSPs(10位分辨率)。
通道可以按照特定的序列组织为2种方式的序列:规则通道和注入通道
规则通道:最多16 个转换的序列,这种方式类似顺序执行的代码,ADC硬件按照定义好的顺序(由寄存器RSQ0、RSQ1、RSQ2决定个数和顺序)转换ADC。规则通道得到的ADC值都是更新到寄存器ADC_SYNCDATA中,因此多规则通道如果需要每个通道正确的读到ADC值,必须采用DMA的方式。
注入通道:最多4 个转换的序列,这种方式类似中断程序,它会中断规则通道(由寄存器ISQ决定个数和顺序)。注入通道得到的ADC值每个通道有对应的寄存器(ADC_IDATA0 - ADC_IDATA3)。
ADC的功能组合特别多,为了方便使用和理解,先选择最简单的一种方式:只用注入通道、软件启动,无DMA,数据格式右对齐。
1. 初始化ADC
1.1 使能RCU
switch(port)
{
case HW_ADC0:
rcu_periph_clock_enable(RCU_ADC0);
break;
case HW_ADC1:
rcu_periph_clock_enable(RCU_ADC1);
break;
case HW_ADC2:
rcu_periph_clock_enable(RCU_ADC2);
break;
case HW_ADC_MAX:
return;
}
1.2 设置频率
ADC的频率设置是设置寄存器同步控制寄存器 ADC_SYNCCT的16-18位。
PCLK2最大100MHz,HCLK最大200M,对应ADC的时钟为:50M,25M,16.67M,12.5M,40M,33.33M, 20M,10M。为了兼容其他芯片的设定,取前面4种设定。
switch(clkDiv)
{
case ADC_CLK_DIV2:
default:
adc_clock_config(ADC_ADCCK_PCLK2_DIV2);
break;
case ADC_CLK_DIV4:
adc_clock_config(ADC_ADCCK_PCLK2_DIV4);
break;
case ADC_CLK_DIV6:
adc_clock_config(ADC_ADCCK_PCLK2_DIV6);
break;
case ADC_CLK_DIV8:
adc_clock_config(ADC_ADCCK_PCLK2_DIV8);
break;
}
1.3 设置分辨率
switch(resolutin)
{
case ADC_RESOLUTION_8BIT:
adc_resolution_config(adcGroup[port], ADC_RESOLUTION_12B);
break;
case ADC_RESOLUTION_10BIT:
adc_resolution_config(adcGroup[port], ADC_RESOLUTION_10B);
break;
case ADC_RESOLUTION_6BIT:
adc_resolution_config(adcGroup[port], ADC_RESOLUTION_6B);
break;
case ADC_RESOLUTION_12BIT:
default:
adc_resolution_config(adcGroup[port], ADC_RESOLUTION_12B);
break;
}
1.4 设置数据对齐方式
adc_data_alignment_config(adcGroup[port], ADC_DATAALIGN_RIGHT)
默认选择右对齐(最常用的方式)
1.5 使能扫描模式
adc_special_function_config(adcGroup[port], ADC_SCAN_MODE, ENABLE);
1.6 设置触发模式
adc_external_trigger_config(adcGroup[port], ADC_INSERTED_CHANNEL, DISABLE);
adc_external_trigger_config(adcGroup[port], ADC_REGULAR_CHANNEL, DISABLE);
默认将2种规则通道的触发都Disable,由软件启动或另外的API函数设置对应的触发方式。
1.6 使能ADC
adc_enable(adcGroup[port]);
adc_calibration_enable(adcGroup[port]);
使能ADC并使能校准功能。
2. 初始化通道规则
void adcCHInit(uint8_t port, uint8_t *pRegualer, uint8_t regualerLen, uint8_t *pInserted, uint8_t insertedLen)
{
uint8_t i;
if(port > HW_ADC_MAX)
return;
adc_channel_length_config(adcGroup[port], ADC_REGULAR_CHANNEL, regualerLen);
for (i = 0; i < regualerLen; i++)
{
adc_regular_channel_config(adcGroup[port], i, pRegualer[i], ADC_SAMPLETIME_480);
}
adc_channel_length_config(adcGroup[port], ADC_INSERTED_CHANNEL, insertedLen);
for (i = 0; i < insertedLen; i++)
{
adc_inserted_channel_config(adcGroup[port], i, pInserted[i], ADC_SAMPLETIME_480);
}
}
定义哪些通道属于规则通道还是注入通道,这里默认采样率为480个时钟。
3. 通道使能
if(ch > 15)
{
if(port == HW_ADC0)
{
if(ch == 18)
adc_channel_16_to_18(ADC_VBAT_CHANNEL_SWITCH, ENABLE);
else
adc_channel_16_to_18(ADC_TEMP_VREF_CHANNEL_SWITCH, ENABLE);
}
else
return;
}
ADC0的通道16,17,18是特殊的,需要特殊使能。
4. 软件触发使能
void adcSoftwareTrigger(uint8_t port, uint8_t ch)
{
uint8_t i;
adc_flag_clear(adcGroup[port], ADC_FLAG_EOC);
adc_flag_clear(adcGroup[port], ADC_FLAG_EOIC);
for (i = 0; i < 4; i++)
{
if(ch == ((ADC_ISQ(adcGroup[port]) >> (5 * i)) & 0x1F))
{
adc_software_trigger_enable(adcGroup[port], ADC_INSERTED_CHANNEL);
return;
}
}
for (i = 0; i < 6; i++)
{
if(ch == ((ADC_RSQ2(adcGroup[port]) >> (5 * i)) & 0x1F))
{
adc_software_trigger_enable(adcGroup[port], ADC_REGULAR_CHANNEL);
return;
}
}
for (i = 0; i < 6; i++)
{
if(ch == ((ADC_RSQ1(adcGroup[port]) >> (5 * i)) & 0x1F))
{
adc_software_trigger_enable(adcGroup[port], ADC_REGULAR_CHANNEL);
return;
}
}
for (i = 0; i < 4; i++)
{
if(ch == ((ADC_RSQ0(adcGroup[port]) >> (5 * i)) & 0x1F))
{
adc_software_trigger_enable(adcGroup[port], ADC_REGULAR_CHANNEL);
return;
}
}
}
根据初始化通道规则配置判断通道属于哪种规则,然后使能对应的触发。
每次触发前需要先把EOC(规则通道转换完成)和EOIC(注入通道转换完成)清0。
5. 获取ADC的值
uint16_t adcGetValue(uint8_t port, uint8_t ch)
{
uint16_t adcValue = 0;
uint8_t i;
uint16_t timeout = 0;
uint8_t isqIndex = 0;
for (i = 0; i < 4; i++)
{
if(ch == ((ADC_ISQ(adcGroup[port]) >> (5 * i)) & 0x1F))
{
while(adc_flag_get(adcGroup[port], ADC_FLAG_EOIC) == 0)
{
delayms(1);
timeout++;
if(timeout > 2000)
return 0;
}
adcValue = adc_inserted_data_read(adcGroup[port], isqIndex);
return adcValue;
}
if(((ADC_ISQ(adcGroup[port]) >> (5 * i)) & 0x1F) > 0)
isqIndex++;
}
while(adc_flag_get(adcGroup[port], ADC_FLAG_EOC) == 0)
{
delayms(1);
timeout++;
if(timeout > 2000)
return 0;
}
adcValue = adc_regular_data_read(adcGroup[port]);
return adcValue;
}
这里有个奇怪的设定,寄存器ADC_ISQ中通道序列排列比较特别,比如这里ADC_ISQ的值为0x294600, 从高位到低位的含义以此为:0x2表示3组注入通道, 通道18,通道17,通道16,0b00000表示没使用。
另外,官方的用户手册EOC的描述也有问题。
5.1 等待转换结束
通过读取寄存器ADC_STAT的EOC是否为1判断ADC转换是否结束。等待EOC为1后需要软件清0。
5.2 读入数值
先判断当前通道是否是注入通道,如果是,则读入对应注入通道的ADC值(寄存器ADC_IDATA0 - ADC_IDATA3)。
多规则通道如果需要每个通道正确的读到ADC值,必须采用DMA的方式。这里只读入ADC_RDATA的值,只会是最后一个通道的ADC值。
6. 实例
GD32450i-EVAL的外部输入是ADC通道13,GPIOC3.
6.1 GPIO初始化
#define ADC_VR1 ADC0
#define ADC_CH_VR1 GPIOC
#define PIN_ADC_CH_VR1 3
#define ADCCHSetANI() gpio_mode_set(IO_ADC_CH_VR1, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ((uint32_t)1 << PIN_ADC_CH_VR1))
6.2 初始化ADC
这里把外部输入设为规则通道,其他3个内部的ADC通道16,17,18设置为注入通道。
#define ADC_CH_TEMP 16
#define ADC_CH_VREF 17
#define ADC_CH_VBAT 18
uint8_t Inserted[3];
uint8_t Regualer[1];
Regualer[0] = ADC_CH_VR1;
Inserted[0] = ADC_CH_TEMP;
Inserted[1] = ADC_CH_VREF;
Inserted[2] = ADC_CH_VBAT;
adcCHInit(ADC_VR1, Regualer, 1, Inserted, 3);
adcCHEnable(ADC_VR1, ADC_CH_VR1, ADC_SAMPLETIME_144);
adcCHEnable(HW_ADC0, ADC_CH_TEMP, ADC_SAMPLETIME_144);
adcCHEnable(HW_ADC0, ADC_CH_VREF, ADC_SAMPLETIME_144);
adcCHEnable(HW_ADC0, ADC_CH_VBAT, ADC_SAMPLETIME_144);
adcInit(ADC_VR1, ADC_CLK_DIV2, ADC_RESOLUTION_12BIT);
6.3 软件触发
adcSoftwareTrigger(ADC_VR1, ADC_CH_VR1);
adcSoftwareTrigger(HW_ADC0, ADC_CH_TEMP);
adcSoftwareTrigger(HW_ADC0, ADC_CH_VREF);
adcSoftwareTrigger(HW_ADC0, ADC_CH_VBAT);
6.4 打印ADC结果
Printf("VR1 Value:%d mV\r\n", (uint32_t)(((float)adcGetValue(ADC_VR1, ADC_CH_VR1) / 4095) * 3.3f * 1000));
Printf("Temperature Value:%d mC\r\n", (uint32_t)((1.42f - (float)adcGetValue(HW_ADC0, ADC_CH_TEMP) * 3.3f / 4096) * 1000 / 4.35f + 25) * 1000);
Printf("Reference Voltage:%d mV\r\n", (uint32_t)(((float)adcGetValue(HW_ADC0, ADC_CH_VREF) / 4095) * 3.3f * 1000));
Printf("VBAT Voltage:%d mV\r\n", (uint32_t)(((float)adcGetValue(HW_ADC0, ADC_CH_VBAT) / 4095) * 3.3f * 4 * 1000));
得到的打印结果如下:
************ ADC Result *************
VR1 Value:1481 mV
Temperature Value:35000 mC
Reference Voltage:1200 mV
VBAT Voltage:3284 mV
扭动VR1可以看到VR1 Value的值会变化。