IDF写的还挺好,我觉得,这么多芯片可以缝缝补补的用一个SDK,牛的咧。
这个是最常见的宏定义
单次触发的单元体
这个绝对是里面最常见的宏了
该宏定义接受以下参数:
a:要检查的条件表达式。
err_code:在条件为假时返回的错误代码。
goto_tag:条件为假时要跳转到的标签位置。
log_tag:用于记录日志的标签位置(可选)。
format:格式化字符串(可选)。
...:可变参数列表(可选)。
该宏的作用是执行以下操作:
如果提供了log_tag,则将其值打印到日志中。
检查条件a是否为假。如果为假,则执行以下操作:
将错误代码赋值给变量ret。
使用goto_tag跳转到指定标签位置。
这个宏通常用于在条件为假时进行错误处理或跳转到特定的代码块。
在这里是错误的代码的处理处
连着就2个
这是一个条件判断的宏定义,用于检查 init_config 和 ret_unit 是否为空指针。如果其中任何一个为空指针,则跳转到指定的标签位置 err,并打印错误信息 "invalid argument: null pointer"。
具体解释如下:
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跳转到指定标签位置。
这个宏的作用是在参数无效时进行错误处理,并将错误信息打印到日志中。
这是一个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__)函数报告内存分配失败。
最后,返回分配的内存块的指针。
这是一个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函数返回的指针。
让我们来看看这个函数的样子
这是一个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。
最后,函数返回指向分配的内存的指针。
这是一个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,我们将不会得到任何结果。
看出来没有,每一个语句都要进行一次校验。
解析:
_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模式与相应的配置关联起来来初始化单元。
这些都是内联函数
实现在C
这是一个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函数的参数,它们可能是指定等待时间(以微秒为单位)和锁的类型(在这个例子中是互斥锁)。
这个是属于我一样看不懂的东西,但是大概率是一个去取值复制
这是我上次写的单次触发结构体,真尼玛老母猪带胸罩,一套一套的
那么这个结构体就是给了一个新变量吧
这个ADC单次触发的内容?但是是这个是S_CTX的发源地
也就是最上面的这个结构体
这段代码定义了一个名为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单触发模式芯片的引用计数。
看这个,其实就是线程安全的写法
获取这个结构体里面的mutex
这段代码是将一个单元(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数组中,以便在后续的操作中使用。
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成员变量。
这段代码定义了一个名为clk_src的变量,类型为adc_oneshot_clk_src_t,并将其初始化为ADC_DIGI_CLK_SRC_DEFAULT。
然后,代码检查init_config结构体中的clk_src成员变量是否存在有效的值。如果存在有效值,则将clk_src变量的值更新为init_config->clk_src。
接着就是设置时钟以及看看没有设置成功
里面的这个代码呢是在每个芯片里面都有的
来看参数
第一个是soc的clk模块
这段代码定义了一个枚举类型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_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:无效的精度级别。
这个枚举类型用于在系统中确定和配置各个模块的时钟源精度。
其实着才开始配置最后一个,ULP
随意看吧
大概就是所有结构体都要抛头露面
终于来了初始化了
这段代码定义了一个名为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为单位
ADC的初始化
这段代码片段展示了一个名为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变量中。
设置 ADC 的初始配置后,使用adc_oneshot_new_unit()准备好的adc_oneshot_unit_init_cfg_t.如果分配成功,该函数将返回 ADC 单元句柄。
通道的设置
扩展通道的时候衰减系数
位宽
狠狠的注入
折腾这么久就是为了这个read,我可真想死
写完就跑
一个好的函数从参数开始
搞对象都没有这么费劲
进入现场,保护一下
下面就是要转换了
函数接受三个参数:
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单元的锁。
根据转换结果的有效性,返回相应的错误码。
差不多就是这样,获取参数以后来判断,在操作系统的监督下进行一个安全的操作,然后就释放资源。
这个就是转换函数,感觉又是一篇文章,死了
这段代码是一个函数定义,函数名为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)
看着简单,但是实现确实复杂
最终的数据在这里获取
@brief宏强制在外设寄存器上进行32位读取
@注:这个宏只能在xxx结构体的注册域上调用。当前实现读入uint32_t类型。
最后居然是怎么个东西
在寄存器上干活了
也可以开启这个功能
说实话我没有看懂
不用你操心的原因是因为别人已经操心过了
这些地方是ADC的封装处
ADC 数字控制器模式配置
ADC数字控制器(DMA模式)输出数据格式。用于分析采集到的ADC(DMA)数据。
上面我说不明白的结构体在文档里面都有
写程序试试呢?
审核编辑:刘清
-
adc
+关注
关注
98文章
6496浏览量
544475 -
C语言
+关注
关注
180文章
7604浏览量
136704 -
分配器
+关注
关注
0文章
193浏览量
25747 -
ESP
+关注
关注
0文章
183浏览量
33928 -
触发器
+关注
关注
14文章
2000浏览量
61132
原文标题:ESP32-S3 ADC外设.2-单次触发模式
文章出处:【微信号:TT1827652464,微信公众号:云深之无迹】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论