-
Notifications
You must be signed in to change notification settings - Fork 134
Add esp_dac, for now only with oneshot mode #1998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
51a5477
8c85f1f
ca2d520
8eaf1bf
f9e669f
a649e1d
913e65c
4825580
39f916d
6fcebae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ set(ERLANG_MODULES | |
| emscripten | ||
| epmd | ||
| esp | ||
| esp_dac | ||
| esp_adc | ||
| gpio | ||
| i2c | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| -module(esp_dac). | ||
|
|
||
| -export([ | ||
| new_channel/2, | ||
| oneshot_output_voltage/2, | ||
| oneshot_del_channel/1 | ||
| ]). | ||
|
|
||
| -type dac_rsrc() :: {'$dac', Resource :: binary(), Ref :: reference()}. | ||
|
|
||
| -type oneshot_channel_opts() :: [{chan_id, 0 | 1}]. | ||
|
|
||
| -spec new_channel(oneshot, Opts :: oneshot_channel_opts()) -> {ok, Channel :: dac_rsrc()} | {error, Reason :: term()}. | ||
| new_channel(oneshot, Opts) -> | ||
| case Opts of | ||
| [{chan_id, 0}] -> | ||
| ?MODULE:oneshot_new_channel_p(0); | ||
| [{chan_id, 1}] -> | ||
| ?MODULE:oneshot_new_channel_p(1); | ||
| _Else -> | ||
| {error, badarg} | ||
| end. | ||
|
|
||
| -spec oneshot_output_voltage(Channel :: dac_rsrc(), Level :: 0..255) -> ok | {error, Reason :: term()}. | ||
| oneshot_output_voltage(_res, _level) -> | ||
| erlang:nif_error(undefined). | ||
|
|
||
| -spec oneshot_del_channel(Channel :: dac_rsrc()) -> ok | {error, Reason :: term()}. | ||
| oneshot_del_channel(_res) -> | ||
| erlang:nif_error(undefined). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| /* | ||
| * This file is part of AtomVM. | ||
| * | ||
| * Copyright 2020-2023 dushin.net | ||
| * Copyright 2024 Ricardo Lanziano <arpunk@fatelectron.net> | ||
| * Copyright 2022-2024 Winford <winford@object.stream> | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later | ||
| */ | ||
|
|
||
| // References | ||
| // https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/dac.html | ||
|
|
||
schnittchen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #include <context.h> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on top? just to follow all/most other nifs pattern?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment probably was for an old version? Currently, the top of the file (after the comments) starts like this and reordering does not work, the NIF functions are no longer available (I just checked before and after the change).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, all other similar nifs in the folder starts with: So just for similarity, and peace of mind I suggest following that, eg. so a reviewer doesn't have to investigate if a new pattern is proper, and under what compilers circumstances, future changes etc etc. Also let's squash to one commit.. per https://doc.atomvm.org/latest/CONTRIBUTING.html#git-recommended-practices |
||
| #include <defaultatoms.h> | ||
| #include <erl_nif_priv.h> | ||
| // removing this gives a strange error... | ||
| #include <esp32_sys.h> | ||
| #include <globalcontext.h> | ||
| #include <nifs.h> | ||
| #include <term.h> | ||
|
|
||
| // #define ENABLE_TRACE | ||
| #include <trace.h> | ||
|
|
||
| #include <driver/dac_oneshot.h> | ||
| #include <esp_log.h> | ||
|
|
||
| #define TAG "atomvm_dac" | ||
|
|
||
| static ErlNifResourceType *oneshot_channel_resource; | ||
|
|
||
| // All channel resource structs start with a `handle` field: | ||
| struct AnyChannelResource | ||
| { | ||
| void *handle; | ||
| }; | ||
|
|
||
| struct OneshotChannelResource | ||
| { | ||
| dac_oneshot_handle_t handle; | ||
| }; | ||
|
|
||
| // | ||
| // internal functions | ||
| // | ||
|
|
||
| static term create_pair(Context *ctx, term term1, term term2) | ||
| { | ||
| term ret = term_alloc_tuple(2, &ctx->heap); | ||
| term_put_tuple_element(ret, 0, term1); | ||
| term_put_tuple_element(ret, 1, term2); | ||
|
|
||
| return ret; | ||
| } | ||
|
|
||
| static term error_return_tuple(Context *ctx, term term) | ||
| { | ||
| if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { | ||
| RAISE_ERROR(OUT_OF_MEMORY_ATOM); | ||
| } | ||
| return create_pair(ctx, ERROR_ATOM, term); | ||
| } | ||
|
|
||
| static term get_channel_resource(Context *ctx, term t, ErlNifResourceType *res_type, struct AnyChannelResource **res) | ||
| { | ||
| bool likely_valid = (term_is_tuple(t) && term_get_tuple_arity(t) == 3 && globalcontext_is_term_equal_to_atom_string(ctx->global, term_get_tuple_element(t, 0), ATOM_STR("\x4", "$dac")) && term_is_binary(term_get_tuple_element(t, 1)) && term_is_reference(term_get_tuple_element(t, 2))); | ||
|
|
||
| if (likely_valid) { | ||
| if (UNLIKELY(!enif_get_resource(erl_nif_env_from_context(ctx), term_get_tuple_element(t, 1), res_type, (void **) res))) { | ||
| ESP_LOGE(TAG, "resource is not a valid adc channel resource"); | ||
|
|
||
| return error_return_tuple(ctx, BADARG_ATOM); | ||
| } | ||
|
|
||
| if (LIKELY((*res)->handle)) { | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| ESP_LOGE(TAG, "resource is not a valid adc channel resource"); | ||
| return error_return_tuple(ctx, BADARG_ATOM); | ||
| } | ||
|
|
||
| // | ||
| // Nif functions | ||
| // | ||
|
|
||
| static term nif_oneshot_new_channel_p(Context *ctx, int argc, term argv[]) | ||
| { | ||
| UNUSED(argc); | ||
|
|
||
| struct OneshotChannelResource *chan_rsrc = enif_alloc_resource(oneshot_channel_resource, sizeof(struct OneshotChannelResource)); | ||
| if (IS_NULL_PTR(chan_rsrc)) { | ||
| ESP_LOGE(TAG, "failed to allocate resource: %s:%i.", __FILE__, __LINE__); | ||
| RAISE_ERROR(OUT_OF_MEMORY_ATOM); | ||
| } | ||
|
|
||
| chan_rsrc->handle = 0; | ||
|
|
||
| if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) { | ||
| ESP_LOGE(TAG, "failed to allocate memory for resource: %s:%i.", __FILE__, __LINE__); | ||
| enif_release_resource(chan_rsrc); | ||
| RAISE_ERROR(OUT_OF_MEMORY_ATOM); | ||
| } | ||
| ERL_NIF_TERM chan_obj = enif_make_resource(erl_nif_env_from_context(ctx), chan_rsrc); | ||
|
|
||
| const dac_oneshot_config_t config = { | ||
| .chan_id = term_to_uint8(argv[0]) | ||
| }; | ||
|
|
||
| const esp_err_t err = dac_oneshot_new_channel(&config, &chan_rsrc->handle); | ||
|
|
||
| enif_release_resource(chan_rsrc); | ||
|
|
||
| if (!err) { | ||
| term chan_tup = term_alloc_tuple(3, &ctx->heap); | ||
| term_put_tuple_element(chan_tup, 0, globalcontext_make_atom(ctx->global, ATOM_STR("\x4", "$dac"))); | ||
| term_put_tuple_element(chan_tup, 1, chan_obj); | ||
| uint64_t ref_ticks = globalcontext_get_ref_ticks(ctx->global); | ||
| term ref = term_from_ref_ticks(ref_ticks, &ctx->heap); | ||
| term_put_tuple_element(chan_tup, 2, ref); | ||
|
|
||
| term ret = term_alloc_tuple(2, &ctx->heap); | ||
| term_put_tuple_element(ret, 0, OK_ATOM); | ||
| term_put_tuple_element(ret, 1, chan_tup); | ||
|
|
||
| return ret; | ||
| } | ||
|
|
||
| term reason = term_invalid_term(); | ||
| switch (err) { | ||
| case ESP_ERR_INVALID_ARG: | ||
| reason = BADARG_ATOM; | ||
| break; | ||
| case ESP_ERR_INVALID_STATE: | ||
| reason = globalcontext_make_atom(ctx->global, ATOM_STR("\xD", "invalid_state")); | ||
| break; | ||
| case ESP_ERR_NO_MEM: | ||
| reason = OUT_OF_MEMORY_ATOM; | ||
| break; | ||
| default: | ||
| reason = BADARG_ATOM; | ||
| } | ||
|
|
||
| return error_return_tuple(ctx, reason); | ||
| }; | ||
|
|
||
| static term nif_oneshot_output_voltage(Context *ctx, int argc, term argv[]) | ||
| { | ||
| VALIDATE_VALUE(argv[1], term_is_uint8); | ||
|
|
||
| struct OneshotChannelResource *chan_rsrc; | ||
|
|
||
| term error_term = get_channel_resource(ctx, argv[0], oneshot_channel_resource, (struct AnyChannelResource **) &chan_rsrc); | ||
| if (UNLIKELY(error_term)) { | ||
| return error_term; | ||
| } | ||
|
|
||
| esp_err_t err = dac_oneshot_output_voltage(chan_rsrc->handle, term_to_uint8(argv[1])); | ||
| if (UNLIKELY(err != ESP_OK)) { | ||
| ESP_LOGE(TAG, "dac_oneshot_output_voltage failed"); | ||
| return error_return_tuple(ctx, BADARG_ATOM); | ||
| } | ||
|
|
||
| return OK_ATOM; | ||
| }; | ||
|
|
||
| static term nif_oneshot_del_channel(Context *ctx, int argc, term argv[]) | ||
| { | ||
| struct OneshotChannelResource *chan_rsrc; | ||
|
|
||
| term error_term = get_channel_resource(ctx, argv[0], oneshot_channel_resource, (struct AnyChannelResource **) &chan_rsrc); | ||
| if (UNLIKELY(error_term)) { | ||
| return error_term; | ||
| } | ||
|
|
||
| esp_err_t err = dac_oneshot_del_channel(chan_rsrc->handle); | ||
|
|
||
| if (UNLIKELY(err != ESP_OK)) { | ||
| return ERROR_ATOM; | ||
| } | ||
|
|
||
| chan_rsrc->handle = NULL; | ||
|
|
||
| return OK_ATOM; | ||
| }; | ||
|
|
||
| // | ||
| // Nif Entry/Exit | ||
| // | ||
|
|
||
| static void nif_dac_oneshot_chan_res_dtor(ErlNifEnv *caller_env, void *obj) | ||
| { | ||
| UNUSED(caller_env); | ||
|
|
||
| struct OneshotChannelResource *chan_rsrc = (struct OneshotChannelResource *) obj; | ||
|
|
||
| if (chan_rsrc->handle) { | ||
| dac_oneshot_del_channel(chan_rsrc->handle); | ||
| } | ||
| } | ||
|
|
||
| static const ErlNifResourceTypeInit OneshotChannelResourceTypeInit = { | ||
| .members = 1, | ||
| .dtor = nif_dac_oneshot_chan_res_dtor, | ||
| }; | ||
|
|
||
| static const struct Nif oneshot_new_channel_p_nif = { | ||
| .base.type = NIFFunctionType, | ||
| .nif_ptr = nif_oneshot_new_channel_p | ||
| }; | ||
|
|
||
| static const struct Nif oneshot_output_voltage_nif = { | ||
| .base.type = NIFFunctionType, | ||
| .nif_ptr = nif_oneshot_output_voltage | ||
| }; | ||
|
|
||
| static const struct Nif oneshot_del_channel_nif = { | ||
| .base.type = NIFFunctionType, | ||
| .nif_ptr = nif_oneshot_del_channel | ||
| }; | ||
|
|
||
| void atomvm_dac_init(GlobalContext *global) | ||
| { | ||
| ErlNifEnv env; | ||
| erl_nif_env_partial_init_from_globalcontext(&env, global); | ||
|
|
||
| oneshot_channel_resource = enif_init_resource_type(&env, "dac_oneshot_channel_resource", &OneshotChannelResourceTypeInit, ERL_NIF_RT_CREATE, NULL); | ||
| }; | ||
|
|
||
| const struct Nif *atomvm_dac_get_nif(const char *nifname) | ||
| { | ||
| TRACE("Locating nif %s ...", nifname); | ||
| if (strcmp("esp_dac:oneshot_new_channel_p/1", nifname) == 0) { | ||
| TRACE("Resolved platform nif %s ...", nifname); | ||
| return &oneshot_new_channel_p_nif; | ||
| } | ||
| if (strcmp("esp_dac:oneshot_output_voltage/2", nifname) == 0) { | ||
| TRACE("Resolved platform nif %s ...", nifname); | ||
| return &oneshot_output_voltage_nif; | ||
| } | ||
|
|
||
| if (strcmp("esp_dac:oneshot_del_channel/1", nifname) == 0) { | ||
| TRACE("Resolved platform nif %s ...", nifname); | ||
| return &oneshot_del_channel_nif; | ||
| } | ||
| return NULL; | ||
| }; | ||
|
|
||
| REGISTER_NIF_COLLECTION(atomvm_dac, atomvm_dac_init, NULL, atomvm_dac_get_nif) | ||
schnittchen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.