本帖最后由 Ai-Thinker小泽 于 2023-10-24 19:48 编辑
零基础开发小安派-Eyes-S1【外设篇】——ADC
ADC 指的是模数转换器(Analog-to-Digital Converter),它是一种用于将模拟信号转换为数字信号的电子设备或电路。
模拟信号是连续变化的信号,可以取无限个可能的值,而数字信号则是离散的,只能表示有限个数值。ADC 的作用就是将模拟信号转换为离散的数字信号,以便数字电子系统进行处理、存储和传输。
ADC 的基本工作原理是通过一系列的采样和量化过程来实现模拟到数字的转换:
1.采样(Sampling):ADC 根据一定的时间间隔,从模拟信号中获取一系列离散的采样点。采样率决定了采样点的密度,较高的采样率可以更精确地表示原始模拟信号。
2.量化(Quantization):采样得到的模拟信号样本通常是连续的,量化则将每个采样点映射为一个特定的数字值。量化过程将连续的模拟信号离散化,并分配给每个样本一个数字值。
3.编码(Encoding):编码将量化后的数字值表示为二进制形式,以便于数字系统处理。常见的编码方式包括无符号二进制、补码和格雷码等。
总的来说,生活中会有许多“模拟量”,如一段从低音到高音的音频,在数字信号中,我们不能简单的把低音表示0,高音表示1,这样歌曲中间的音频变化是空的,没有小数点这些概念来显示他们从0到0.5到1的过程。所以需要有个模数转换的过程,称为ADC。比如用12位精度的ADC来表示这段音频,低音就0,高音就是2^12次也就是4096,那么在这段音频中我们就可以通过0-4096的过程来判断音频的高和低,具体到高到有多高,低到有多低,而ADC的精度越高,如16位,就可达到0-65535的范围。
一、了解小安派-Eyes-S1的ADC
芯片内置一个 12bits 的逐次逼近式模拟数字转换器 (ADC),支持 12 路外部模拟输入和若干内部模拟信号选择。ADC可以工作在 4 种模式下,转换结果为 12/14/16bits左对齐模式。ADC 拥有深度为 32 字节的 FIFO,支持多种中断,支持 DMA 操作。ADC 除了用于普通模拟信号测量外,还可以用于测量供电电压,此外 ADC 还可以通过测量内/外部二极管电压用于温度检测。
具有12路外部模拟通道,对应的GPIO如下:
通道 |
GPIO |
Channel_0 |
GPIO_20 |
Channel_1 |
GPIO_19 |
Channel_2 |
GPIO_2 |
Channel_3 |
GPIO_3 |
Channel_4 |
GPIO_14 |
Channel_5 |
GPIO_13 |
Channel_6 |
GPIO_12 |
Channel_7 |
GPIO_10 |
Channel_8 |
GPIO_1 |
Channel_9 |
GPIO_0 |
Channel_10 |
GPIO_27 |
Channel_11 |
GPIO_28 |
此外,还具有2路DAC内部通道,1路VBAT/2通道,1路TSEN通道
adc clock div
对 adc 时钟再一次进行分频。分频后的时钟必须小于等于 500K。ADC CLK = CLK_SOURCE/CLK_DIV/adc_clk_div
#define ADC_CLK_DIV_4 1
#define ADC_CLK_DIV_8 2
#define ADC_CLK_DIV_12 3
#define ADC_CLK_DIV_16 4
#define ADC_CLK_DIV_20 5
#define ADC_CLK_DIV_24 6
#define ADC_CLK_DIV_32 7
adc resolution
adc 位数,可以选择 12B、14B、16B。其中 14B 和 16B 自带过采样处理
#define ADC_RESOLUTION_12B 0
#define ADC_RESOLUTION_14B 2
#define ADC_RESOLUTION_16B 4
adc vref
adc 内置参考电压选择,可以选择 2.0 V 或者 3.2V
#define ADC_VREF_3P2V 0
#define ADC_VREF_2P0V 1
二、结构体与API
struct bflb_adc_config_s
说明:adc 初始化配置结构体。
struct bflb_adc_config_s {
uint8_t clk_div;
uint8_t scan_conv_mode;
uint8_t continuous_conv_mode;
uint8_t differential_mode;
uint8_t resolution;
uint8_t vref;
};
parameter |
description |
clk_div |
分频值 |
scan_conv_mode |
是否开启扫描模式 |
continuous_conv_mode |
是否开启连续转换模式 |
differential_mode |
是否开启差分模式 |
resolution |
采样位数 |
vref |
参考电压选择 |
扫描模式:可按照用户指定通道个数和顺序逐个转换,结果推入ADC的FIFO。
连续转换模式:允许ADC连续不断地执行模数转换,只需要调用一次ADC启动
差分模式:ADC支持单端输入模式和差分模式,选择单端时负极输入通道选择GND
struct bflb_adc_channel_s
说明:配置 adc 通道时使用。
struct bflb_adc_channel_s {
uint8_t pos_chan;
uint8_t neg_chan;
};
parameter |
description |
pos_chan |
正向通道 |
neg_chan |
反向通道(单端模式下无用) |
struct bflb_adc_result_s
说明:adc 标准转换结果
struct bflb_adc_result_s {
int8_t pos_chan;
int8_t neg_chan;
int32_t value;
int32_t millivolt;
};
parameter |
description |
pos_chan |
正向通道 |
neg_chan |
反向通道(单端模式下无用) |
value |
adc 转换结果 |
millivolt |
转换结果转 mv |
bflb_adc_init
说明: 初始化 adc。adc 使用之前需要开启 adc ip 时钟、设置 adc 时钟源和分频值、选择使用的 gpio 为 analog 模式。
void bflb_adc_init(struct bflb_device_s *dev, const struct bflb_adc_config_s *config);
parameter |
description |
dev |
设备句柄 |
config |
配置项 |
bflb_adc_deinit
说明: 反初始化 adc。
void bflb_adc_deinit(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
bflb_adc_link_rxdma
说明: adc dma 功能开关。
void bflb_adc_link_rxdma(struct bflb_device_s *dev, bool enable);
parameter |
description |
dev |
设备句柄 |
enable |
是否开启 dma 功能 |
bflb_adc_channel_config
说明: 配置 adc 通道。
int bflb_adc_channel_config(struct bflb_device_s *dev, struct bflb_adc_channel_s *chan, uint8_t channels);
parameter |
description |
dev |
设备句柄 |
chan |
通道(一对) |
channels |
通道对数(单次扫描模式下只能为 1) |
bflb_adc_start_conversion
说明: 启动 adc 转换。连续转换模式下只需要调用一次。
void bflb_adc_start_conversion(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
bflb_adc_stop_conversion
说明: 停止 adc 转换。
void bflb_adc_stop_conversion(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
bflb_adc_get_count
说明: 获取 adc 转换个数。
uint8_t bflb_adc_get_count(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
return |
转换个数(最大为32) |
bflb_adc_read_raw
说明: 读取一次 adc 转换值。
uint32_t bflb_adc_read_raw(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
return |
转换值(注意不是最终采样值) |
bflb_adc_rxint_mask
说明: adc 转换完成中断开关。
void bflb_adc_rxint_mask(struct bflb_device_s *dev, bool mask);
parameter |
description |
dev |
设备句柄 |
mask |
是否屏蔽中断 |
bflb_adc_errint_mask
说明: adc 错误中断开关。
void bflb_adc_errint_mask(struct bflb_device_s *dev, bool mask);
parameter |
description |
dev |
设备句柄 |
mask |
是否屏蔽中断 |
bflb_adc_get_intstatus
说明: adc 中断标志。
uint32_t bflb_adc_get_intstatus(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
return |
中断标志 |
返回值如下:
ADC_INTSTS_NEG_SATURATION
ADC_INTSTS_NEG_SATURATION
ADC_INTSTS_FIFO_UNDERRUN
ADC_INTSTS_FIFO_OVERRUN
ADC_INTSTS_ADC_READY
bflb_adc_int_clear
说明: 清除 adc 中断标志。
void bflb_adc_int_clear(struct bflb_device_s *dev, uint32_t int_clear);
parameter |
description |
dev |
设备句柄 |
int_clear |
清除值 |
int_clear 可以填入以下参数:
ADC_INTCLR_NEG_SATURATION
ADC_INTCLR_POS_SATURATION
ADC_INTCLR_FIFO_UNDERRUN
ADC_INTCLR_FIFO_OVERRUN
ADC_INTCLR_ADC_READY
bflb_adc_parse_result
说明: 对 adc 转换结果进行解析。
void bflb_adc_parse_result(struct bflb_device_s *dev, uint32_t *buffer, struct bflb_adc_result_s *result, uint16_t count);
parameter |
description |
dev |
设备句柄 |
buffer |
转换值 |
result |
输出结果 |
count |
转换个数 |
bflb_adc_tsen_init
说明: 初始化 adc tsen 模块。
void bflb_adc_tsen_init(struct bflb_device_s *dev, uint8_t tsen_mod);
parameter |
description |
dev |
设备句柄 |
tsen_mod |
模式选择 |
bflb_adc_tsen_get_temp
说明: 获取 adc tsen 模块采集的温度。
float bflb_adc_tsen_get_temp(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
return |
温度 |
bflb_adc_vbat_enable
说明: 开启 vbat 。
void bflb_adc_vbat_enable(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
bflb_adc_vbat_disable
说明: 关闭 vbat。
void bflb_adc_vbat_disable(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
三、示例——连续读取IO的ADC值,ADC转换完成中断
Main
#include "bflb_adc.h"
#include "bflb_mtimer.h"
#include "board.h"
#include "bflb_gpio.h"
//头文件
struct bflb_device_s *adc;
//外设结构体设置
void My_adc_gpio_init()
{
struct bflb_device_s *gpio;
gpio = bflb_device_get_by_name("gpio");
bflb_gpio_init(gpio, GPIO_PIN_0, GPIO_ANALOG | GPIO_SMT_EN | GPIO_DRV_0);
}
//设置需要AD采集的IO脚,对应的通道和IO要匹配
volatile uint32_t raw_data;
//接收AD值变量
void adc_isr(int irq, void *arg)
{
uint32_t intstatus = bflb_adc_get_intstatus(adc);
if (intstatus & ADC_INTSTS_ADC_READY) {
bflb_adc_int_clear(adc, ADC_INTCLR_ADC_READY);
raw_data= bflb_adc_read_raw(adc);
}
}
//中断函数,清除中断标志位,将读取的AD变量赋给raw_data
int main(void)
{
board_init();
My_adc_gpio_init();
adc = bflb_device_get_by_name("adc");
/* adc clock = XCLK / 2 / 32 */
struct bflb_adc_config_s cfg;
cfg.clk_div = ADC_CLK_DIV_32;
cfg.scan_conv_mode = false;
cfg.continuous_conv_mode = false;
cfg.differential_mode = false;
cfg.resolution = ADC_RESOLUTION_16B;
cfg.vref = ADC_VREF_3P2V;
//adc结构体配置
struct bflb_adc_channel_s chan;
chan.pos_chan = ADC_CHANNEL_9;
chan.neg_chan = ADC_CHANNEL_GND;
//通道配置,单端模式下neg选择GND,pos注意对应IO口的通道
bflb_adc_init(adc, &cfg);
bflb_adc_channel_config(adc, &chan, 1);
bflb_adc_rxint_mask(adc, false);
bflb_irq_attach(adc->irq_num, adc_isr, NULL);
bflb_irq_enable(adc->irq_num);
//中断使能配置
while (1) {
struct bflb_adc_result_s result;
bflb_adc_start_conversion(adc);
bflb_adc_parse_result(adc, (uint32_t *)&raw_data, &result, 1);
printf("\r\npos chan %d\r\nADC Value = %d\r\nCurrent Voltage = %d mv\r\n", result.pos_chan, result.value, result.millivolt);
bflb_adc_stop_conversion(adc);
bflb_mtimer_delay_ms(1000);
//主函数读取AD值并转化为电压
}
}
效果