​ESP32-S3 ADC外设单次触发模式

描述

IDF写的还挺好,我觉得,这么多芯片可以缝缝补补的用一个SDK,牛的咧。

ESP

这个是最常见的宏定义

ESP

单次触发的单元体

ESP

这个绝对是里面最常见的宏了

该宏定义接受以下参数:

a:要检查的条件表达式。

err_code:在条件为假时返回的错误代码。

goto_tag:条件为假时要跳转到的标签位置。

log_tag:用于记录日志的标签位置(可选)。

format:格式化字符串(可选)。

...:可变参数列表(可选)。

该宏的作用是执行以下操作:

如果提供了 log_tag,则将其值打印到日志中。

检查条件 a 是否为假。如果为假,则执行以下操作:

将错误代码赋值给变量 ret。

使用 goto_tag 跳转到指定标签位置。

这个宏通常用于在条件为假时进行错误处理或跳转到特定的代码块。

ESP

在这里是错误的代码的处理处

ESP

连着就2个

这是一个条件判断的宏定义,用于检查 init_config 和 ret_unit 是否为空指针。如果其中任何一个为空指针,则跳转到指定的标签位置 err,并打印错误信息 "invalid argument: null pointer"。

ESP

具体解释如下:

init_config && ret_unit:这是一个逻辑与操作,用于检查 init_config 和 ret_unit 是否都非空。如果两者都非空,则表达式的结果为真;否则为假。

ESP_ERR_INVALID_ARG:这是错误代码常量,表示参数无效的错误。

err:这是要跳转到的标签位置。

TAG:这是要打印日志的标签位置。

"invalid argument: null pointer":这是要打印的错误信息字符串。

当 init_config 或 ret_unit 为空指针时,执行以下操作:

如果提供了 TAG,则将其值打印到日志中。

将错误代码赋值给变量 ret。

使用 err 跳转到指定标签位置。

这个宏的作用是在参数无效时进行错误处理,并将错误信息打印到日志中。

ESP

这是一个C语言的函数,名为heap_caps_calloc。这个函数用于在指定的内存堆上分配一块连续的、指定大小的内存空间,并初始化为0。

参数解析:

n:需要分配的元素数量。

size:每个元素的大小(以字节为单位)。

caps:内存分配器的容量属性。这通常用于指定内存分配器可以处理的最大对象大小。

函数执行流程:

调用heap_caps_calloc_base(n, size, caps)函数尝试在指定的内存堆上分配n * size字节的内存空间,并返回一个指向这块内存的指针。

如果分配失败(即ptr为NULL),并且size大于0,那么调用heap_caps_alloc_failed(size, caps, __func__)函数报告内存分配失败。

最后,返回分配的内存块的指针。

ESP

这是一个C语言的函数,名为heap_caps_calloc_base。这个函数用于在指定的内存堆上分配一块连续的、指定大小的内存空间,并初始化为0。

参数解析:

n:需要分配的元素数量。

size:每个元素的大小(以字节为单位)。

caps:内存分配器的容量属性。这通常用于指定内存分配器可以处理的最大对象大小。

函数执行流程:

首先,函数检查n * size是否会导致乘法溢出。如果会导致溢出,那么函数返回NULL,表示内存分配失败。

这是通过调用__builtin_mul_overflow(n, size, &size_bytes)来实现的,这个内建函数会检查乘法操作是否会导致溢出,如果会导致溢出,那么它会把溢出的大小存储在size_bytes中,并返回非零值。

如果n * size不会导致溢出,那么函数就使用calloc函数来分配内存。calloc函数接受两个参数:需要分配的元素数量和每个元素的大小,然后返回一个指向分配的内存的指针。

最后,函数返回calloc函数返回的指针。

ESP

让我们来看看这个函数的样子

这是一个C语言的函数,名为_calloc_r。这个函数是C标准库函数calloc的一个包装器,它用于在指定的内存堆上分配一块连续的、指定大小的内存空间,并初始化为0。

参数解析:

struct _reent *r:一个指向_reent结构体的指针,这个结构体通常用于封装某些与环境相关的信息。在这个函数中,我们并没有使用到这个参数。

size_t nmemb:需要分配的元素数量。

size_t size:每个元素的大小(以字节为单位)。

函数执行流程:

首先,函数检查nmemb * size是否会导致乘法溢出。如果会导致溢出,那么函数返回NULL,表示内存分配失败。这是通过调用__builtin_mul_overflow(nmemb, size, &size_bytes)来实现的,这个内建函数会检查乘法操作是否会导致溢出,如果会导致溢出,那么它会把溢出的大小存储在size_bytes中,并返回非零值。

然后,函数调用heap_caps_malloc_default(size_bytes)来分配一块内存。这个函数接受一个参数,表示需要分配的字节数,然后返回一个指向分配的内存的指针。

如果内存分配成功(即result != NULL),函数使用bzero(result, size_bytes)来将这块内存的所有字节都设置为0。

最后,函数返回指向分配的内存的指针。

ESP

这是一个C语言的语句,它调用了之前定义的heap_caps_calloc函数来分配一块内存,并将这块内存的地址赋值给变量unit。

参数解析:

1:需要分配的元素数量。在这里,我们只分配一个元素,所以这个值是1。

sizeof(adc_oneshot_unit_ctx_t):每个元素的大小。这里,我们使用sizeof操作符来获取adc_oneshot_unit_ctx_t类型数据的大小(以字节为单位),然后把它作为calloc函数的第二个参数。这意味着我们想要分配一块能够存储一个adc_oneshot_unit_ctx_t类型数据的内存。

ADC_MEM_ALLOC_CAPS:内存分配器的容量属性。这通常用于指定内存分配器可以处理的最大对象大小。

在这里,我们使用ADC_MEM_ALLOC_CAPS作为这个参数,表示我们希望使用能够处理最大对象大小为ADC_MEM_ALLOC_CAPS的内存分配器来分配这块内存。

函数执行流程:

调用heap_caps_calloc函数,传入参数1、sizeof(adc_oneshot_unit_ctx_t)和ADC_MEM_ALLOC_CAPS。

如果分配成功,heap_caps_calloc函数会返回一个指向分配的内存的指针,我们将这个指针赋值给变量unit。

如果分配失败,heap_caps_calloc函数会返回NULL,我们将不会得到任何结果。

ESP

看出来没有,每一个语句都要进行一次校验。

ESP

解析:

_lock_acquire(&s_ctx.mutex);:这行代码获取名为s_ctx.mutex的互斥锁,以确保在初始化过程中只有一个线程可以访问该代码块。

s_ctx.units[init_config->unit_id] = unit;:这行代码将unit指针存储在s_ctx.units数组中,数组索引为init_config->unit_id。这意味着根据配置中的单元ID,将特定的单元与上下文关联起来。

_lock_release(&s_ctx.mutex);:这行代码释放之前获取的互斥锁,允许其他线程访问被保护的代码块。

unit->unit_id = init_config->unit_id;:这行代码将单元的ID设置为配置中指定的单元ID,确保单元的唯一性。

unit->ulp_mode = init_config->ulp_mode;:这行代码将单元的ULP(单位长度脉冲)模式设置为配置中指定的ULP模式,用于控制ADC的采样率。

总结:这段代码片段展示了一个使用互斥锁保护的ADC单触发模式的初始化过程,其中通过将单元ID和ULP模式与相应的配置关联起来来初始化单元。

ESP

这些都是内联函数

ESP

实现在C

ESP

这是一个C语言的代码片段,它定义了一个名为_lock_acquire的函数,该函数用于获取一个互斥锁。这个函数是兼容旧版newlib锁函数的。

解析:

typedef int _lock_t;:这行代码定义了一个新的类型别名_lock_t,它是一个整数类型。

void IRAM_ATTR _lock_acquire(_lock_t *lock):这行代码定义了一个函数_lock_acquire,它接受一个指向_lock_t类型的指针lock作为参数。IRAM_ATTR是一个编译器指令,表示这个函数是"in-ram"属性的,即它在编译时会被嵌入到程序的ROM中,而不是在运行时从外部存储器加载。

{ lock_acquire_generic(lock, portMAX_DELAY, queueQUEUE_TYPE_MUTEX); }:这行代码调用了lock_acquire_generic函数,尝试获取由lock指向的互斥锁。portMAX_DELAY和queueQUEUE_TYPE_MUTEX是传递给lock_acquire_generic函数的参数,它们可能是指定等待时间(以微秒为单位)和锁的类型(在这个例子中是互斥锁)。

ESP

这个是属于我一样看不懂的东西,但是大概率是一个去取值复制

ESP

这是我上次写的单次触发结构体,真尼玛老母猪带胸罩,一套一套的

ESP

那么这个结构体就是给了一个新变量吧

ESP

这个ADC单次触发的内容?但是是这个是S_CTX的发源地

ESP

也就是最上面的这个结构体

这段代码定义了一个名为adc_oneshot_ctx_t的结构体,用于存储ADC(模数转换器)单触发模式的上下文信息。

解析:

typedef struct adc_oneshot_ctx_t:这是一个类型定义,将结构体adc_oneshot_ctx_t定义为一个新的类型名,以便在后续的代码中引用。

{ _lock_t mutex; ... }:这是结构体的成员列表,列出了该结构体包含的所有成员变量。

adc_oneshot_unit_ctx_t *units[SOC_ADC_PERIPH_NUM];:这是一个指向adc_oneshot_unit_ctx_t类型的指针数组,用于存储ADC单元的上下文信息。SOC_ADC_PERIPH_NUM是一个宏定义,表示ADC外设的数量。

int apb_periph_ref_cnts;:这是一个整型变量,用于记录使用APB_SARADC外设的ADC单触发模式芯片的引用计数。

ESP

看这个,其实就是线程安全的写法

ESP

获取这个结构体里面的mutex

ESP

这段代码是将一个单元(unit)对象存储到s_ctx.units数组中,数组的索引由init_config->unit_id指定。

根据代码片段提供的信息,我们可以推断出以下内容:

s_ctx是一个全局变量或结构体的成员变量,它包含了与ADC单触发模式相关的上下文信息。

units是s_ctx中的一个成员变量,它是一个指针数组,用于存储ADC单元的上下文信息。

init_config是一个指向初始化配置结构体的指针,其中包含了要存储的单元的ID和相关配置信息。

unit_id是init_config中的一个成员变量,它指定了要存储的单元的ID。

unit是要存储的单元对象。

根据上述分析,该代码的作用是将单元对象存储到s_ctx.units数组中,以便在后续的操作中使用。

ESP

unit->unit_id = init_config->unit_id;:这行代码将init_config中的unit_id值赋给unit对象的unit_id成员变量。

unit->ulp_mode = init_config->ulp_mode;:这行代码将init_config中的ulp_mode值赋给unit对象的ulp_mode成员变量。

ESP

这段代码定义了一个名为clk_src的变量,类型为adc_oneshot_clk_src_t,并将其初始化为ADC_DIGI_CLK_SRC_DEFAULT。

然后,代码检查init_config结构体中的clk_src成员变量是否存在有效的值。如果存在有效值,则将clk_src变量的值更新为init_config->clk_src。

ESP

接着就是设置时钟以及看看没有设置成功

ESP

里面的这个代码呢是在每个芯片里面都有的

ESP

来看参数

ESP

第一个是soc的clk模块

ESP

这段代码定义了一个枚举类型soc_module_clk_t,用于表示不同的时钟源。以下是每个时钟源的含义:

SOC_MOD_CLK_CPU:CPU时钟可以从XTAL、PLL、RC_FAST或APLL中获取,通过配置soc_cpu_clk_src_t来指定。

SOC_MOD_CLK_RTC_FAST:RTC快速时钟可以从XTAL_D4或RC_FAST中获取,通过配置soc_rtc_fast_clk_src_t来指定。

SOC_MOD_CLK_RTC_SLOW:RTC慢速时钟可以从RC_SLOW、XTAL32K或RC_FAST_D256中获取,通过配置soc_rtc_slow_clk_src_t来指定。

SOC_MOD_CLK_APB:APB时钟高度依赖于CPU时钟源。

SOC_MOD_CLK_PLL_D2:PLL_D2时钟从PLL派生,具有固定的分频器为2。

SOC_MOD_CLK_PLL_F160M:PLL_F160M时钟从PLL派生,具有固定的频率为160MHz。

SOC_MOD_CLK_XTAL32K:XTAL32K时钟来自外部32kHz晶体,将时钟门控传递给外设。

SOC_MOD_CLK_RC_FAST:RC_FAST时钟来自内部8MHz rc振荡器,将时钟门控传递给外设。

SOC_MOD_CLK_RC_FAST_D256:RC_FAST_D256时钟来自内部8MHz rc振荡器,分频器为256,将时钟门控传递给外设。

SOC_MOD_CLK_XTAL:XTAL时钟来自外部晶体(2~40MHz)。

SOC_MOD_CLK_REF_TICK:REF_TICK从APB派生,即使APB频率改变,其频率也固定为1MHz。

SOC_MOD_CLK_APLL:APLL从PLL获取,其频率可以通过APLL配置寄存器进行配置。

SOC_MOD_CLK_INVALID:表示可用模块时钟源的结束。

这个枚举类型用于在系统中确定和配置各个模块的时钟源。

ESP

这段代码定义了一个枚举类型esp_clk_tree_src_freq_precision_t,用于表示不同的时钟源精度。以下是每个精度的含义:

ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED:从驱动程序缓存的数据中获取值;如果数据为0,则执行校准操作。

ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX:获取其近似频率值。

ESP_CLK_TREE_SRC_FREQ_PRECISION_EXACT:始终执行校准操作。

ESP_CLK_TREE_SRC_FREQ_PRECISION_INVALID:无效的精度级别。

这个枚举类型用于在系统中确定和配置各个模块的时钟源精度。

ESP

其实着才开始配置最后一个,ULP

ESP

随意看吧

ESP

大概就是所有结构体都要抛头露面

ESP

终于来了初始化了

ESP

这段代码定义了一个名为adc_oneshot_hal_ctx_t的结构体,用于表示ADC(模数转换器)的单次触发模式的硬件抽象层(HAL)上下文。

以下是每个成员的解释:

dev:ADC SoC(系统级芯片)层的句柄,用于与ADC SoC进行交互。

unit:表示ADC单元的变量。

work_mode:表示ADC的工作模式,可能是连续模式、单次触发模式等。

chan_configs:表示每个ADC通道的配置数组。

clk_src:表示时钟源的选择。

clk_src_freq_hz:表示时钟源的频率,以Hz为单位

ESP

ADC的初始化

ESPESPESP

这段代码片段展示了一个名为adc1_handle的变量,类型为adc_oneshot_unit_handle_t。它被用于存储ADC(模数转换器)单元的句柄。

接下来,代码定义了一个名为init_config1的结构体变量,类型为adc_oneshot_unit_init_cfg_t。这个结构体包含了ADC单元的配置信息。在这个例子中,unit_id成员被设置为ADC_UNIT_1,表示当前配置的是第一个ADC单元。

这段代码的作用是初始化ADC单元1,并将其句柄存储在adc1_handle变量中。

ESP

设置 ADC 的初始配置后,使用adc_oneshot_new_unit()准备好的adc_oneshot_unit_init_cfg_t. 如果分配成功,该函数将返回 ADC 单元句柄。

ESP

通道的设置

ESP

扩展通道的时候衰减系数

ESP

位宽

ESP

狠狠的注入

ESP

折腾这么久就是为了这个read,我可真想死

ESP

写完就跑

ESP

一个好的函数从参数开始

ESP

搞对象都没有这么费劲

进入现场,保护一下

ESP

下面就是要转换了

函数接受三个参数:

handle:指向adc_oneshot_unit_handle_t类型的指针,表示ADC单元的句柄。

chan:表示要读取的ADC通道。

out_raw:指向整数类型的指针,用于存储转换结果的原始值。

函数返回一个esp_err_t类型的错误码,表示操作的结果。如果操作成功,返回ESP_OK;否则,返回相应的错误码。

函数的具体实现如下:

首先,检查输入参数的有效性。如果handle或out_raw为空指针,或者chan超出了有效范围,将返回相应的错误码。

然后,尝试获取ADC单元的锁。如果获取失败,返回ESP_ERR_TIMEOUT。

进入临界区,使用rtc_spinlock进行保护。

调用adc_oneshot_hal_setup函数,设置ADC单元的相关参数。

如果支持ADC校准功能(版本1),则获取当前通道的衰减值,并初始化ADC硬件校准。

调用adc_oneshot_hal_convert函数,执行实际的ADC转换操作,并将结果存储在out_raw中。

退出临界区,释放ADC单元的锁。

根据转换结果的有效性,返回相应的错误码。

差不多就是这样,获取参数以后来判断,在操作系统的监督下进行一个安全的操作,然后就释放资源。

ESP

这个就是转换函数,感觉又是一篇文章,死了

这段代码是一个函数定义,函数名为adc_oneshot_hal_convert。它的作用是执行ADC(模数转换器)的单次转换操作,并返回转换结果的有效性。

函数接受两个参数:

hal:指向adc_oneshot_hal_ctx_t类型的指针,表示ADC单元的上下文信息。

out_raw:指向整数类型的指针,用于存储转换结果的原始值。

函数返回一个布尔值,表示操作的结果。如果操作成功,返回true;否则,返回false。

函数的具体实现如下:

首先,根据hal->unit的值确定要使用的ADC通道。如果hal->unit等于ADC_UNIT_1,则使用通道1;否则,使用通道2。

清除指定通道的事件标志位。

禁用所有ADC单元。

启用指定的ADC通道。

启动ADC单元的单次转换操作,设置时钟源频率为hal->clk_src_freq_hz。

等待直到指定通道的事件被触发。

读取转换结果的原始值,并将其存储在out_raw中。

如果ADC单元的数量为2(即SOC_ADC_PERIPH_NUM == 2),则对转换结果进行进一步的校验。如果校验失败,将out_raw设置为-1。

再次禁用所有ADC单元。

返回转换结果的有效性(valid)

ESP

看着简单,但是实现确实复杂

ESP

最终的数据在这里获取

ESP

@brief宏强制在外设寄存器上进行32位读取

@注:这个宏只能在xxx结构体的注册域上调用。当前实现读入uint32_t类型。

ESP

最后居然是怎么个东西

ESP

在寄存器上干活了

ESP

也可以开启这个功能

ESP

说实话我没有看懂

ESP

不用你操心的原因是因为别人已经操心过了

ESP

这些地方是ADC的封装处

ESP

ADC 数字控制器模式配置

ESP

ADC数字控制器(DMA模式)输出数据格式。用于分析采集到的ADC(DMA)数据。

ESP

上面我说不明白的结构体在文档里面都有

ESP

ESP

ESP

ESP

ESP

 

写程序试试呢?





审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分