8.1 实验内容
通过本实验主要学习以下内容:
8.2 实验原理
8.2.1 ADC原理
我们知道,自然界中有非常多的模拟信号,比如光照强度,还有其他的例如温度、声音等等,那么人们是怎么来衡量一个模拟信号的呢?
我们通常会说今天光照度达到了3万Lux(照度单位),现在测量到的体温是36.5℃,我们所处的环境是40分贝,没错,人们就是通过将这些模拟信号数字化,从而达到衡量这些模拟信号的目的。那对于MCU来说,如果要测量一个模拟量,可以通过自带的ADC(Analog-to-Digital converters)模块,即模-数转换器将模拟量转化为可以被MCU读取到的数字量。
8.2.2 GD32F470 ADC工作原理
GD32F470有3个12位逐次逼近型ADC(SAR ADC),这三个ADC可以独立工作,也可以工作在同步模式下。有最多24个外部ADC引脚可用于将连接到这些引脚的电压值转换为数字量,这些引脚号可以通过Datasheet获得。
表中ADC012_INx的意思是:该IO口可以作为通道x用于ADC0、ADC1和ADC2。如ADC012_IN0,表示PA0可以用于ADC0的通道0使用,也可以作为ADC1和ADC2的通道0使用。但要注意:不能在同一个时刻让不同的ADC去转换同一个通道,否则会有无法预料的结果 |
以下总结了GD32F470 ADC的特性:
– ADC采样分辨率: 12位、 10位、 8位、或者6位分辨率;
– ADC采样率: 12位分辨率为2.6 MSPs, 10位分辨率为3.0 MSPs。分辨率越低,转换越快;
– 自校准时间: 131个ADC时钟周期;
– 可编程采样时间;
– 数据存储模式:最高有效位对齐和最低有效位对齐;
– DMA请求。
– 16个外部模拟输入通道;
– 1个内部温度传感通道(VSENSE);
– 1个内部参考电压输入通道(VREFINT);
– 1个外部监测电池VBAT供电引脚输入通道。
– 软件触发;
– 硬件触发。
– 转换单个通道,或者扫描一序列的通道;
– 单次运行模式,每次触发转换一次选择的输入通道;
– 连续运行模式,连续转换所选择的输入通道;
– 间断运行模式;
– 同步模式(适用于具有两个或多个ADC的设备)。
– 常规转换结束;
– 模拟看门狗事件;
– 溢出事件。
– 16位的数据寄存器;
– 可调整的过采样率,从2x到256x;
– 高达8位的可编程数据移位。
下面介绍下GD32F470的ADC框图:
标注1:输入电压和参考电压
输入电压引脚定义如下表:
大于等于100pin的GD32F470,ADC参考电压等于VREFP,100pin以下的GD32F470,ADC参考电压等于VDDA |
GD32F470的ADC是12bit有效位的,满量程对应的转换值是4095,即当采样引脚上的电压等于ADC参考电压时,得到的转换值即为4095。故理论采样是指可通过以下公式得到:
标注2:输入通道
前面提到,ADC有最多16个外部模拟通道和3个内部通道,外部通道号从IN0~IN15,由IO口号来决定,两个内部通道是IN16(温度传感器)和IN17(内部Vrefint,典型值1.2V),下表给出了IO口号对应的ADC通道:
标注3:规则组
GD32F470的ADC转换组称为规则组,也叫常规序列。
规则组有两个重要的参数,其一为转换的个数,其二为转换的序列,规定好这两个参数后,一旦开始规则组的转换,则ADC就按照转换序列一个一个的进行模-数转换,直到达到要求的转换个数。
规则组的转换个数由ADC_RSQ0寄存器的RL[3:0]位规定,转换的总数目为RL[3:0]+1,转换总数目最大为16个;转换序列由ADC_RSQ0~ADC_RSQ2共同决定,我们来看下这几个寄存器。
ADC_RSQ0寄存器:
ADC_RSQ1寄存器:
ADC_RSQ2寄存器:
举个例子,现需要按照CH3->CH2->CH1的顺序进行规则组转换,则设定RL[3:0] = 2,然后设定RSQ0为CH3,RSQ1为CH2,RSQ2为CH1,则当开始规则组转换时,ADC首先进行RSQ0规定的通道即CH3的转换,再进行RSQ1规定的通道即CH2的转换,最后进行RSQ2规定的通道即CH1转换,当这三个通道转换完后,规则组转换结束。
需要注意的是,每转换一个规则组通道,转换结果都会放在寄存器ADC_RDATA中,所以CPU一定要在下一个通道转换完成前将上一个通道转换结果读走,否则会导致上一个通道数据被新的数据覆盖。所以在多通道规则组转换时,为了保证能读到所有通道的数据,一定要使用DMA(直接存储器访问控制器),每个通道转换结束后,都会给DMA发送请求,DMA就会将最新的ADC_RDATA中的数据搬走。关于ADC配合DMA的使用,后面会详细介绍。
标注4:触发源
ADC的规则组需要选特定的触发源用于触发ADC转换,注意,ADC的Enable(即ADC_CTL1寄存器的ADC_ON位置“1”)不会触发ADC转换,而是当选定的触发源来临后ADC才开始转换。
触发源分为内部触发和外部触发,内部触发是指软件触发;外部触发源是除了内部触发源以外的触发源,外部触发源可以通过ADC_CTL1寄存器查看:
ADC_CTL1寄存器:
标注5:规则组数据寄存器
如标注3规则组的表述,每个ADC的规则组只有一个数据寄存器ADC_RDATA,每转换一个通道,转换结果放在这个寄存器中,在下一通道转换结束前必须要将上一个通道的转换结果取走。
标注6:ADC中断及标志位
ADC的中断总共有两种:规则组转换结束中断以及模拟看门狗,可以通过将ADC_CTL0中的EOCIE和WDEIE置“1”来开启相应中断。
ADC_STAT寄存器中的EOC和WDE表示相应事件发生,EOC置“1”表示规则组的转换已经结束。
8.2.3 DMA原理
本实验中ADC通道有两个,分别为PF7和PF10,所以我们用规则组多通道采样实现双电压读取,从上一节内容中可以知道,ADC规则组实现多通道转换时,必须要用到DMA。下面我们介绍下DMA原理。
DMA(直接存储器访问控制器)是一个非常好用的外设,它提供了一种硬件的方式在外设和存储器之间或者存储器和存储器之间传输数据,而无需 CPU 的介入,从而使 CPU 可以专注在处理其他系统功能上。GD32F470有两个DMA,其中DMA0有8个通道,DMA1也有8个通道。
GD32F470支持DMA的单一传输和多数据传输模式。这里只讲解单一传输。DMA实现很简单,只要配置好以下几要素即可。
上面描述了DMA配置的一些要素,那么DMA是如何被触发的呢,我们来看下DMA请求映射表:
DMA0各通道请求表:
DMA1各通道请求表:
本实验中是ADC配合DMA来使用,如果使用DMA去搬运ADC0的数据,从上表查询得知需要使用DMA1的通道0,如果是搬运ADC2的数据,也可以用到DMA1的通道0。如现在设置DMA1的通道0去搬运ADC2的数据,当ADC2每转换一个通道,ADC2_RDATA会更新一次数据,此时ADC2会自动向DMA1的通道0发出一次搬运请求,DMA收到请求后会进行一次数据搬运。DMA的请求和应答方式见下图:
8.3 硬件设计
本实验是使用PF7和PF10来进行电压采集,读者可以采用飞线方式外接电压到这两个引脚进行测试。
8.4 代码解析
本实验用到两个ADC2通道,使用ADC2规则组搭配DMA1通道0进行数据转换和搬运,ADC2规则组和DMA1通道0都开启循环模式,一旦开始ADC2规则组转换,会持续PF7和PF10上的电压进行转换和数据搬运。
8.4.1 DMA和ADC初始化
在driver_adc.c中定义driver_adc_regular_ch_dma_config函数,该函数实现DMA和ADC的初始化。
C void driver_adc_regular_ch_dma_config(typdef_adc_ch_general *ADC, typdef_adc_ch_parameter *ADC_CH,void *buffer) { dma_single_data_parameter_struct dma_single_data_parameter; rcu_periph_clock_enable(ADC->dma_parameter.rcu_dma); /*DMA时钟开启*/ dma_deinit(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); /*DMA通道参数复位*/ /*DMA源地址、目标地址、增量方式、传输位宽、传输方向、传输个数、优先级设置*/ dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC->adc_port)); dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_single_data_parameter.memory0_addr = (uint32_t)(buffer); dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; if(ADC->adc_mode == ADC_DAUL_ROUTINE_PARALLEL) { dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_32BIT; } else { dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT; } dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY; dma_single_data_parameter.number = ADC->dma_parameter.dma_number; dma_single_data_parameter.priority = ADC->dma_parameter.dma_priority; dma_single_data_mode_init(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel, &dma_single_data_parameter); dma_channel_subperipheral_select(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel,ADC->dma_parameter.dma_subperipheral_num); /*DMA循环模式设置*/ if(ADC->dma_parameter.dma_circulation_mode == ENABLE) { dma_circulation_enable(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); } else { dma_circulation_disable(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); } dma_channel_enable(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); /* 使能DMA */ driver_adc_config(ADC,ADC_CH); /* ADC初始化 */ } |
在driver_adc.h中声明了ADC DMA的结构体:
C typedef struct __typdef_adc_dma_parameter { rcu_periph_enum rcu_dma; //DMA时钟 uint32_t dma_periph; //DMA号 dma_channel_enum dma_channel;//DMA通道号 dma_subperipheral_enum dma_subperipheral_num;//外设请求号 uint32_t dma_number; //DMA传输个数 uint32_t dma_priority; //DMA通道优先级 EventStatus dma_circulation_mode;//循环模式 }typdef_adc_dma_parameter; |
这段代码比较简单,请读者按照前面介绍的DMA原理进行解析。
8.4.2 BSP_ADC设置所需要的参数及IO口结构体定义
在bsp_adc.c中,对BSP_ADC设置所需要的参数及IO扩结构体数组进行了定义:
C typdef_adc_ch_general BSP_ADC= { .rcu_adc = RCU_ADC2, /* ADC2的时钟 */ .adc_psc = ADC_ADCCK_PCLK2_DIV8, /* ADC2设置为APB2 8分频 */ .adc_port = ADC2, /* ADC口为ADC2 */ .adc_mode = ADC_SYNC_MODE_INDEPENDENT, /* ADC模式为独立模式 */ .adc_channel_group = ADC_ROUTINE_CHANNEL, /* 使用规则组 */ .adc_scan_function = ENABLE, /* 开启扫描模式 */ .adc_continuous_function = ENABLE, /* 开启循环模式 */ .ch_count = 2, /* 转换长度为2 */ .adc_external_trigger_mode = EXTERNAL_TRIGGER_DISABLE, .dma_parameter = { .rcu_dma = RCU_DMA1, /* DMA1的时钟 */ .dma_periph = DMA1, /* 使用DMA1 */ .dma_channel = DMA_CH0, /* 使用通道4 */ .dma_number = 2, /* DMA传输长度为2 */ .dma_subperipheral_num = DMA_SUBPERI2, .dma_priority = DMA_PRIORITY_HIGH, /* DMA通道优先级 */ .dma_circulation_mode = ENABLE /* DMA循环模式打开 */ }, .trigger_source = ADC_EXTTRIG_ROUTINE_T0_CH0, /* ADC触发源选择为软件触发 */ .DMA_mode = ENABLE /* 使用DMA */ }; typdef_adc_ch_parameter BSP_ADC_ch[2] = { { .rcu_port = RCU_GPIOF, /* GPIOF时钟 */ .port = GPIOF, /* GPIO port */ .pin = GPIO_PIN_7, /* PF7 */ .gpio_speed = GPIO_OSPEED_2MHZ, /* PF7速度设置为10MHz */ .adc_channel = ADC_CHANNEL_5, /* PF7是ADC2的通道5 */ .sample_time = ADC_SAMPLETIME_144 /* 设置采样周期为55.5 */ } , { .rcu_port = RCU_GPIOF, /* GPIOF时钟 */ .port = GPIOF, /* GPIO port */ .pin = GPIO_PIN_10, /* PF8 */ .gpio_speed = GPIO_OSPEED_2MHZ, /* PF8速度设置为10MHz */ .adc_channel = ADC_CHANNEL_8, /* PF8是ADC2的通道10 */ .sample_time = ADC_SAMPLETIME_144 /* 设置采样周期为55.5 */ } };/* ADC通道参数配置,包括IO口,和对应通道以及采样周期 */ |
8.4.3 BSP_ ADC初始化和触发ADC转换的具体实现函数
在bsp_adc.c中定义了 DMA和ADC初始化和触发ADC转换的函数:
C uint16_t BSP_ADC_data[2] ; void bsp_ADC_config() { driver_adc_regular_ch_dma_config(&BSP_ADC,BSP_ADC_ch,(uint16_t*)BSP_ADC_data); driver_adc_software_trigger_enable(&BSP_ADC); } |
8.4.4 main函数实现
C int main(void) { driver_init();//延时函数初始化 bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化 bsp_ADC_config();//ADC配置 while (1) { delay_ms(100);//延时100ms printf_log(" the BSP_ADC data is %d,%d \r\n", BSP_ADC_data[0],BSP_ADC_data[1]);//打印ADC数据 } } |
本例程main函数首先进行了延时函数初始化,为了演示实验结果,这里初始化了BOARD_UART串口,关于串口的使用,请读者参考串口章节,然后是BSP_ADC配置。在主循环中,每100ms打印一次PF7和PF10的ADC转换数据。
8.5 实验结果
如上main函数实现说明。
本教程由GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网
全部0条评论
快来发表一下你的评论吧 !