From 669d6756a0f82ffa5085ce3438fddbb55c8e9509 Mon Sep 17 00:00:00 2001 From: Winford Date: Mon, 2 Jun 2025 15:45:30 -0700 Subject: [PATCH 1/2] Remove old nifs Removes the nifs since they are now all in upstream AtomVM. Signed-off-by: Winford --- CMakeLists.txt | 34 ------ Kconfig | 15 --- component.mk | 4 - markdown/components.md | 40 ------- nifs/atomvm_lib.c | 212 -------------------------------------- nifs/include/atomvm_lib.h | 27 ----- 6 files changed, 332 deletions(-) delete mode 100644 CMakeLists.txt delete mode 100644 Kconfig delete mode 100644 component.mk delete mode 100644 markdown/components.md delete mode 100644 nifs/atomvm_lib.c delete mode 100644 nifs/include/atomvm_lib.h diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 02053c1..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is part of AtomVM. -# -# Copyright 2022 Fred Dushin -# -# 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 -# - -set(ATOMVM_LIB_COMPONENT_SRCS - "nifs/atomvm_lib.c" -) - -idf_component_register( - SRCS ${ATOMVM_LIB_COMPONENT_SRCS} - INCLUDE_DIRS "nifs/include" - PRIV_REQUIRES "libatomvm" "avm_sys" "mbedtls" -) - -idf_build_set_property( - LINK_OPTIONS "-Wl,--whole-archive ${CMAKE_CURRENT_BINARY_DIR}/lib${COMPONENT_NAME}.a -Wl,--no-whole-archive" - APPEND -) diff --git a/Kconfig b/Kconfig deleted file mode 100644 index c0c61ad..0000000 --- a/Kconfig +++ /dev/null @@ -1,15 +0,0 @@ -menu "ATOMVM_LIB Configuration" - -config AVM_LIB_ENABLE - bool "Enable AtomVM LIB driver" - default y - help - Use this parameter to enable or disable the AtomVM LIB driver. - -config RTC_MEMORY_SIZE - int "RTC Memory Size (in bytes)" - default 0 - help - Use this parameter to set the size of the RTC memory buffer. - -endmenu diff --git a/component.mk b/component.mk deleted file mode 100644 index 36b6511..0000000 --- a/component.mk +++ /dev/null @@ -1,4 +0,0 @@ -COMPONENT_ADD_INCLUDEDIRS := nifs/include -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive -COMPONENT_SRCDIRS := nifs -CXXFLAGS += -fno-rtti diff --git a/markdown/components.md b/markdown/components.md deleted file mode 100644 index dee2c00..0000000 --- a/markdown/components.md +++ /dev/null @@ -1,40 +0,0 @@ -# `atomvm_lib` Components - -The `atomvm_lib` repository includes mostly Erlang APIs that provide added functionality to the AtomVM virtual machine. Many of these APIs can be used on a standard AtomVM image. - -However, some of these libraries make use of native-C extensions to the AtomVM virtual machine on the ESP32. These extensions are implemented as ESP IDF SDK "components", which are collections of native C code that can be compiled and linked into the AtomVM ESP32 image. Because the ESP IDF SDK and tool-chain does not make use of dynamic linkage, these components must be known and configured at _build_ time. - -This page provides instructions for building and linking `atomvm_lib` components into an AtomVM image, so that they can be used in your applications. - -## Prerequisites - -* The `git` source control tool -* GNU Make -* [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/release-v3.3/index.html) and tool chain, and its pre-requisite software. -* [AtomVM](https://github.com/bettio/AtomVM) source tree, and its pre-requisite software. - -> Note. These instructions assume you have downloaded the AtomVM virtual machine and have the required software needed to build the VM, including the IDF SDK and tool chain. Instructions for building AtomVM are outside of the scope of this document. For information about how to build AtomVM targeted for the ESP32, see the [AtomVM Implementors Guide](http://doc.atomvm.net). - -## `atomvm_lib` Build Instructions - -This section describes how to build `atomvm_lib` components into the AtomVM virtual machine. In this section, we use `` to refer to the top level of the AtomVM source tree. - -Start by cloning the `atomvm_lib` repository into the `src/platforms/esp32/components` directory of the AtomVM source tree: - - shell$ cd /src/platforms/esp32/components - shell$ git clone git@github.com:atomvm/atomvm_lib.git - ... - -If you have not already built AtomVM, you can issue the `make` command from the `src/platforms/esp32` directory of the AtomVM source tree: - - shell$ cd /src/platforms/esp32 - shell$ make - ... - -This step will compile the AtomVM sources, as well as the `atomvm_lib` sources. However, it will not link any of the `atomvm_lib` components into the AtomVM virtual machine image. - -This step should create a new AtomVM image, with the `atomvm_lib` components linked into it. - -Now that you have built an AtomVM VM image containing `atomvm_lib` components, you can now flash the AtomVM image to your device. - -> Note. Flashing the AtomVM image, containing the AtomVM VM and core Erlang libraries is outside of the scope of this document. For information about how to create an AtomVM image and flash it to an ESP32, see the [AtomVM Implementors Guide](http://doc.atomvm.net). diff --git a/nifs/atomvm_lib.c b/nifs/atomvm_lib.c deleted file mode 100644 index 27f1e58..0000000 --- a/nifs/atomvm_lib.c +++ /dev/null @@ -1,212 +0,0 @@ -// -// Copyright (c) 2020 dushin.net -// All rights reserved. -// -// 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. -// - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// #define ENABLE_TRACE -#include "trace.h" - -#define TAG "atomvm_lib" - -RTC_DATA_ATTR size_t data_len = 0; -RTC_DATA_ATTR char *data[CONFIG_RTC_MEMORY_SIZE]; - -static term nif_set_rtc_memory(Context *ctx, int argc, term argv[]) -{ - UNUSED(argc); - - term binary = argv[0]; - VALIDATE_VALUE(binary, term_is_binary); - - size_t binary_len = term_binary_size(binary); - if (CONFIG_RTC_MEMORY_SIZE < binary_len) { - RAISE_ERROR(BADARG_ATOM); - } - data_len = binary_len; - memcpy(data, term_binary_data(binary), binary_len); - - return OK_ATOM; -} - -static term nif_get_rtc_memory(Context *ctx, int argc, term argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(data_len)) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - - return term_from_literal_binary(data, data_len, &ctx->heap, ctx->global); -} - -#define MAC_LENGTH 6 - -static term nif_get_mac(Context *ctx, int argc, term argv[]) -{ - UNUSED(argc); - UNUSED(argv); - - uint8_t mac[MAC_LENGTH]; - esp_efuse_mac_get_default(mac); - - if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(2 * MAC_LENGTH)) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - char buf[2 * MAC_LENGTH + 1]; - snprintf(buf, 2 * MAC_LENGTH + 1, - "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - - return term_from_literal_binary(buf, 2 * MAC_LENGTH, &ctx->heap, ctx->global); -} - -#define SHA1_LEN 20 - -static term nif_sha1(Context *ctx, int argc, term argv[]) -{ - UNUSED(argc); - term binary = argv[0]; - VALIDATE_VALUE(binary, term_is_binary); - - if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(SHA1_LEN)) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - term ret = term_create_uninitialized_binary(SHA1_LEN, &ctx->heap, ctx->global); - binary = argv[0]; - - int res = mbedtls_sha1_ret((const unsigned char *) term_binary_data(binary), term_binary_size(binary), (unsigned char *) term_binary_data(ret)); - if (res != 0) { - RAISE_ERROR(BADARG_ATOM); - } - - return ret; -} - -static term nif_set_time_of_day(Context *ctx, int argc, term argv[]) -{ - UNUSED(argc); - - VALIDATE_VALUE(argv[0], term_is_any_integer); - - avm_int64_t ms_since_unix_epoch = term_maybe_unbox_int64(argv[0]); - - TRACE("ms_since_unix_epoch: %lli\n", ms_since_unix_epoch); - - struct timeval tp = { - .tv_sec = ms_since_unix_epoch / 1000, - .tv_usec = (ms_since_unix_epoch % 1000) * 1000 - }; - struct timezone tz = { - .tz_minuteswest = 0, - .tz_dsttime = 0 - }; - int res = settimeofday(&tp, &tz); - if (res != 0) { - if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } - term error = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error, 0, ERROR_ATOM); - term_put_tuple_element(error, 1, term_from_int(errno)); - return RAISE_ERROR(error); - } else { - return OK_ATOM; - } -} - -static const struct Nif set_rtc_memory_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_set_rtc_memory -}; -static const struct Nif get_rtc_memory_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_get_rtc_memory -}; -static const struct Nif get_mac_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_get_mac -}; -static const struct Nif sha1_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_sha1 -}; -static const struct Nif set_time_of_day_nif = -{ - .base.type = NIFFunctionType, - .nif_ptr = nif_set_time_of_day -}; - - -// -// Component Nif Entrypoints -// - -void atomvm_lib_init(GlobalContext *global) -{ - // no-op -} - -const struct Nif *atomvm_lib_get_nif(const char *nifname) -{ - TRACE("Locating nif %s ...", nifname); - if (strcmp("atomvm_lib:set_rtc_memory/1", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &set_rtc_memory_nif; - } - if (strcmp("atomvm_lib:get_rtc_memory/0", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &get_rtc_memory_nif; - } - if (strcmp("atomvm_lib:get_mac/0", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &get_mac_nif; - } - if (strcmp("atomvm_lib:sha1/1", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &sha1_nif; - } - if (strcmp("atomvm_lib:set_time_of_day/1", nifname) == 0) { - TRACE("Resolved platform nif %s ...\n", nifname); - return &set_time_of_day_nif; - } - return NULL; -} - -#include -#ifdef CONFIG_AVM_LIB_ENABLE -REGISTER_NIF_COLLECTION(atomvm_lib, atomvm_lib_init, NULL, atomvm_lib_get_nif) -#endif diff --git a/nifs/include/atomvm_lib.h b/nifs/include/atomvm_lib.h deleted file mode 100644 index f571793..0000000 --- a/nifs/include/atomvm_lib.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2020 dushin.net -// All rights reserved. -// -// 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. -// - -#ifndef __ATOMVM_LIB_H -#define __ATOMVM_LIB_H - -#include -#include - -void atomvm_lib_init(GlobalContext *global); -const struct Nif *atomvm_lib_get_nif(const char *nifname); - -#endif From f304afab9a312aa3f2e4e425f74a9a91d386e182 Mon Sep 17 00:00:00 2001 From: Winford Date: Mon, 2 Jun 2025 15:56:58 -0700 Subject: [PATCH 2/2] Remove lora modules Remove the lora modules since they are now in the standalon atomvm_lora repo. Signed-off-by: Winford --- src/lora.erl | 154 --------- src/lora_node.erl | 299 ----------------- src/lora_sx126x.erl | 429 ------------------------- src/lora_sx127x.erl | 714 ----------------------------------------- src/sx126x_cmd.erl | 766 -------------------------------------------- 5 files changed, 2362 deletions(-) delete mode 100644 src/lora.erl delete mode 100644 src/lora_node.erl delete mode 100644 src/lora_sx126x.erl delete mode 100644 src/lora_sx127x.erl delete mode 100644 src/sx126x_cmd.erl diff --git a/src/lora.erl b/src/lora.erl deleted file mode 100644 index 78af55f..0000000 --- a/src/lora.erl +++ /dev/null @@ -1,154 +0,0 @@ -%% -%% Copyright (c) 2021 dushin.net -%% All rights reserved. -%% -%% 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. -%% --module(lora). - -%%% -%%% @doc -%%% An SPI driver for the LoRa (SX127X) chipset. -%%% -%%% This module can be used to send and receive messages using LoRa modulation. -%%% Currently, this module only supports point-to-point communications. This -%%% module does not support LoRaWAN. -%%% -%%% References -%%% SemTech SX127x data sheet: https://semtech.my.salesforce.com/sfc/p/#E0000000JelG/a/2R0000001Rbr/6EfVZUorrpoKFfvaF_Fkpgp5kzjiNyiAbqcpqh9qSjE -%%% Python implementation: https://github.com/lemariva/uPyLoRaWAN -%%% -%%% @end - --export([start/1, stop/1, broadcast/2, sleep/1]). -%% debugging --export([dump_registers/1]). - -% -define(TRACE_ENABLED, true). --include_lib("atomvm_lib/include/trace.hrl"). - --type lora() :: any(). --type message() :: iodata(). - --type frequency() :: freq_169mhz | freq_433mhz | freq_868mhz | freq_915mhz | non_neg_integer(). --type bandwidth() :: bw_7_8khz | bw_10_4khz | bw_15_6khz | bw_20_8khz | bw_31_25khz | bw_41_7khz | bw_62_5khz - | bw_125khz | bw_250khz | bw_500khz. --type tx_power() :: 2..17. --type spreading_factor() :: sf_5 | sf_6 | sf_7 | sf_8 | sf_9 | sf_10 | sf_11 | sf_12 | 5..12. --type ldro() :: on | off. --type preamble_length() :: 6..65535. --type lna_gain() :: lna_1 | lna_2 | lna_3 | lna_4 | lna_5 | lna_6 | auto. - --type coding_rate() :: cr_4_5 | cr_4_6 | cr_4_7 | cr_4_8. --type header_mode() :: implicit | explicit. --type device() :: sx127x | sx126x. - --type config() :: #{ - device => device(), - device_name => atom(), - frequency => frequency(), - bandwidth => bandwidth(), - tx_power => tx_power(), - spreading_factor => spreading_factor(), - preamble_length => preamble_length(), - lna_gain => lna_gain(), - coding_rate => coding_rate(), - ldro => ldro(), - header_mode => header_mode(), - sync_word => non_neg_integer(), - enable_crc => boolean(), - invert_iq => boolean(), - binary => boolean() -}. - --define(DEFAULT_CONFIG, #{ - device => sx127x, - frequency => freq_915mhz, - bandwidth => bw_125khz, - tx_power => 2, - spreading_factor => 7, - preamble_length => 8, - lna_gain => auto, - coding_rate => cr_4_8, - header_mode => explicit, - sync_word => 16#12, - enable_crc => true, - invert_iq => false, - binary => true -}). - - -%%% -%%% Public API -%%% - --spec start(config()) -> {ok, lora()} | {error, Reason::term()}. -start(Config) -> - ?TRACE("Config: ~p", [Config]), - SPI = get_or_load_spi(maps:get(spi, Config)), - NewConfig = verify_config(maps:merge(?DEFAULT_CONFIG, Config#{spi => SPI})), - ?TRACE("NewConfig: ~p", [NewConfig]), - Module = get_module(NewConfig), - case Module:start(NewConfig) of - {ok, Impl} -> - {ok, {Module, Impl}}; - E -> E - end. - --spec stop(Lora::lora()) -> ok. -stop({Module, Impl}) -> - Module:stop(Impl). - --spec broadcast(Lora::lora(), Message::message()) -> {ok, Length::non_neg_integer()} | {error, Reason::term()}. -broadcast({Module, Impl}, Message) -> - Data = erlang:iolist_to_binary(Message), - Module:broadcast(Impl, Data). - --spec sleep(Lora::lora()) -> ok. -sleep({Module, Impl}) -> - Module:sleep(Impl). - -%% @hidden -dump_registers({_Module, Impl}) -> - ?TRACE("Calling dump_registers", []), - gen_server:call(Impl, dump_registers). - -%%% -%%% internal functions -%%% - -%% @private -verify_config(Config) -> - %% TODO - Config. - -%% @private -get_module(Config) -> - %% Note. In order to get r - Device = maps:get(device, Config), - case Device of - sx127x -> - lora_sx127x; - sx126x -> - lora_sx126x; - Unknown -> - throw({unsupported_device, Unknown}) - end. - -%% @private -get_or_load_spi(SPI) when is_pid(SPI) -> - SPI; -get_or_load_spi(SPIConfig) when is_map(SPIConfig) -> - get_or_load_spi(maps:to_list(SPIConfig)); -get_or_load_spi(SPIConfig) when is_list(SPIConfig) -> - spi:open(SPIConfig). diff --git a/src/lora_node.erl b/src/lora_node.erl deleted file mode 100644 index cf1d575..0000000 --- a/src/lora_node.erl +++ /dev/null @@ -1,299 +0,0 @@ -%% -%% Copyright (c) 2021 dushin.net -%% All rights reserved. -%% -%% 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. -%% --module(lora_node). - --export([start/2, call/3, call/4, cast/3, multicast/2, get_lora/1]). - -%% gen_server --export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). - --behavior(gen_server). - -% -define(TRACE_ENABLED, true). --include_lib("atomvm_lib/include/trace.hrl"). - --define(DEFAULT_TIMEOUT_MS, 15000). - -start(Name, Config) -> - gen_server:start(?MODULE, {Name, Config}, []). - -cast(LauraNode, ToNodeName, Term) -> - ?TRACE("Casting to ~p: ~p ...", [ToNodeName, Term]), - gen_server:cast(LauraNode, {cast, ToNodeName, Term}). - -multicast(LauraNode, Term) -> - gen_server:cast(LauraNode, {multicast, Term}). - -call(LoraNode, ToNodeName, Term) -> - call(LoraNode, ToNodeName, Term, undefined). - -call(LoraNode, ToNodeName, Term, undefined) -> - gen_server:call(LoraNode, {call, ToNodeName, Term, undefined}, ?DEFAULT_TIMEOUT_MS); -call(LoraNode, ToNodeName, Term, TimeoutMs) -> - gen_server:call(LoraNode, {call, ToNodeName, Term, TimeoutMs}, TimeoutMs). - -get_lora(LoraNode) -> - gen_server:call(LoraNode, get_lora). - -%%% -%%% gen_server implementation -%%% - --record(state, { - lora, - config, - name, - outstanding_requests = #{}, - n = 0 -}). - -%% @hidden -init({Name, Config}) -> - ?TRACE("init({~p ~p})", [Name, Config]), - LoraConfig = maps:get(lora, Config), - NewLoraConfig = LoraConfig#{ - receive_handler => self() - }, - ?TRACE("Starting lora with ~p", [NewLoraConfig]), - case lora:start(NewLoraConfig) of - {ok, Lora} -> - State = #state{lora=Lora, config=Config#{lora => NewLoraConfig}, name=Name}, - ?TRACE("Initialized LoraNode. State=~p", [State]), - {ok, State}; - Error -> - Error - end. - -%% @hidden -handle_cast({cast, ToNodeName, Term}, State) -> - ?TRACE("handle_cast: {cast, ~p, ~p}", [ToNodeName, Term]), - NewState = do_cast(State, ToNodeName, Term), - {noreply, NewState}; -handle_cast(Message, State) -> - io:format("Unhandled cast. Message: ~p~n", [Message]), - {noreply, State}. - -%% @hidden -handle_call({call, ToNodeName, Term, TimeoutMs}, From, State) -> - ?TRACE("handle_call: {call, ~p, ~p, ~p}", [ToNodeName, Term, TimeoutMs]), - NewState = do_call(State, From, ToNodeName, Term, TimeoutMs), - {noreply, NewState}; -% handle_call({multicast, Term}, From, State) -> -% ?TRACE("handle_multicast: {multicast, ~p}", [Term]), -% NewState = do_multicast(State, From, Term), -% {noreply, NewState}; -handle_call(get_lora, _From, State) -> - {reply, State#state.lora, State}; -handle_call(Request, _From, State) -> - io:format("lora_node Unhandled call. Request: ~p~n", [Request]), - {reply, error, State}. - -%% @hidden -handle_info({lora_receive, _Lora, Message, QoS}, State) -> - ?TRACE("handle_info: {lora_receive, _Lora, ~p, _QoS}", [Message]), - % diag:print_proc_infos(), - try - do_handle_message(State, Message, QoS) - catch - _:E -> - io:format("Error handling received message: ~p~n", [E]) - end, - {noreply, State}; -handle_info({request_completed, RequestId}, State) -> - ?TRACE("handle_info: {request_completed, ~p}", [RequestId]), - OutstandingRequests = State#state.outstanding_requests, - {noreply, State#state{outstanding_requests=maps:remove(RequestId, OutstandingRequests)}}; -handle_info(Message, State) -> - io:format("Unhandled info. Message: ~p~n", [Message]), - {noreply, State}. - -%% @hidden -terminate(_Reason, _State) -> - ok. - -%%% -%%% private -%%% - --define(ERLANG_ENCODING, 16#01). --define(MSG_TYPE_NET, 16#00). --define(MSG_TYPE_APP, 16#F0). - - -%%% -%%% Magic byte -%%% -%%% +-+-+-+-+-+-+-+-+ -%%% |7|6|5|4|3|2|1|0| -%%% +-+-+-+-+-+-+-+-+ -%%% |<----->|<----->| -%%% msg encoding -%%% type - -do_call(State, From, ToNodeName, Term, TimeoutMs) -> - N = State#state.n, - RequestId = create_request_id(State#state.name, ToNodeName), - Payload = create_lora_call_message(RequestId, Term), - Self = self(), - ActualTimeoutMs = case TimeoutMs of undefined -> maps:get(timeout_ms, State#state.config, ?DEFAULT_TIMEOUT_MS); _ -> TimeoutMs end, - Pid = spawn(fun() -> do_call_async(Self, ActualTimeoutMs, State#state.lora, RequestId, Payload, From) end), - OutstandingRequests = State#state.outstanding_requests, - State#state{outstanding_requests=OutstandingRequests#{RequestId => Pid}, n=N+1}. - -do_cast(State, ToNodeName, Term) -> - N = State#state.n, - RequestId = create_request_id(State#state.name, ToNodeName), - Payload = create_lora_cast_message(RequestId, Term), - ?TRACE("spawning do_cast_async with payload ~p", [Payload]), - spawn(fun() -> do_cast_async(State#state.lora, Payload) end), - State#state{n=N+1}. - -create_request_id(FromNodeName, ToNodeName) -> - {FromNodeName, ToNodeName, 0}. %%atomvm:random()}. - -create_lora_call_message(RequestId, Term) -> - create_lora_app_message({call, {RequestId, Term}}). - -create_lora_cast_message(RequestId, Term) -> - create_lora_app_message({cast, {RequestId, Term}}). - -create_lora_app_message(Msg) -> - Magic = ?MSG_TYPE_APP bor ?ERLANG_ENCODING, - Message = term_to_binary(Msg), - << - Magic:8, - Message/binary - >>. - -do_call_async(LoraNode, TimeoutMs, Lora, RequestId, Payload, From) -> - lora:broadcast(Lora, Payload), - receive - {RequestId, Reply, _QoS} -> - ?TRACE("I got a reply from ~p: ~p", [From, Reply]), - gen_server:reply(From, Reply) - after TimeoutMs -> - ?TRACE("oh well, timed out... ~p", [self()]), - gen_server:reply(From, {error, timeout}) - end, - LoraNode ! {request_completed, RequestId}. - -do_cast_async(Lora, Payload) -> - ?TRACE("Broadcasting payload to Lora ~p", [Lora]), - lora:broadcast(Lora, Payload). - - - -%%% -%%% Message handling (receive side) -%%% - -do_handle_message(_State, <<"">>, _QoS) -> - ?TRACE("Empty payload", []), - empty_payload; -do_handle_message(State, Payload, QoS) -> - <> = Payload, - MsgType = Magic band 16#F0, - case MsgType of - ?MSG_TYPE_NET -> - ?TRACE("NET msg type", []), - handle_net_message(State, Magic band 16#0F, Msg, QoS); - ?MSG_TYPE_APP -> - ?TRACE("APP msg type", []), - handle_app_message(State, Magic band 16#0F, Msg, QoS); - _ -> - ?TRACE("Unknown msg type", []), - unknown_msg_type - end. - -handle_net_message(_State, _Encoding, _Msg, _QoS) -> - unimplemented. - -handle_app_message(State, Encoding, Msg, QoS) -> - case Encoding of - ?ERLANG_ENCODING -> - ?TRACE("Erlang encoding", []), - {MsgType, Message} = binary_to_term(Msg), - handle_application_message_type(MsgType, State, Message, QoS); - _ -> - ?TRACE("Unknown encoding", []), - unknown_encoding - end. - -handle_application_message_type(cast, State, {RequestId, Term}, QoS) -> - ?TRACE("handle_message_type: {call, ~p}", [{RequestId, Term}]), - {FromNodeName, ToNodeName, _Ref} = RequestId, - MyName = State#state.name, - case ToNodeName of - MyName -> - ?TRACE("Cast message was intended for me ~p", [MyName]), - case maps:get(cast_handler, State#state.config, undefined) of - undefined -> - ?TRACE("No cast handler for ~p", [MyName]), - no_cast_handler; - CastHandler -> - ?TRACE("found cast handler", []), - CastHandler(Term, #{from => FromNodeName, qos => QoS}) - end; - _SomeoneElse -> - ?TRACE("Cast message was intended for someone else", []), - intended_for_someone_else - end; -handle_application_message_type(call, State, {RequestId, Term}, QoS) -> - ?TRACE("handle_message_type: {call, ~p}", [{RequestId, Term}]), - {FromNodeName, ToNodeName, _Ref} = RequestId, - MyName = State#state.name, - case ToNodeName of - MyName -> - ?TRACE("Call message was intended for me ~p", [MyName]), - case maps:get(call_handler, State#state.config, undefined) of - undefined -> - ?TRACE("No call handler for ~p", [MyName]), - no_call_handler; - CallHandler -> - ?TRACE("found call handler", []), - Reply = CallHandler(Term, #{from => FromNodeName, qos => QoS}), - ?TRACE("reply: ~p", [Reply]), - ReplyMessage = create_lora_reply_message(RequestId, Reply), - ?TRACE("broadcasting reply message: ~p", [ReplyMessage]), - lora:broadcast(State#state.lora, ReplyMessage) - end; - _SomeoneElse -> - ?TRACE("Call message was intended for someone else", []), - intended_for_someone_else - end; -handle_application_message_type(reply, State, {RequestId, Reply}, QoS) -> - ?TRACE("handling reply ~p", [{RequestId, Reply}]), - {FromNodeName, _ToNodeName, _Ref} = RequestId, - MyName = State#state.name, - case FromNodeName of - MyName -> - ?TRACE("Reply message was intended for me ~p", [MyName]), - case maps:get(RequestId, State#state.outstanding_requests, undefined) of - undefined -> - maybe_request_timed_out; - Pid -> - ?TRACE("found pid waiting for reply: ~p", [Pid]), - Pid ! {RequestId, Reply, QoS} - end; - _SomeoneElse -> - ?TRACE("Reply message was intended for someone else", []), - ignore - end. - - -create_lora_reply_message(RequestId, Term) -> - create_lora_app_message({reply, {RequestId, Term}}). diff --git a/src/lora_sx126x.erl b/src/lora_sx126x.erl deleted file mode 100644 index ae5795d..0000000 --- a/src/lora_sx126x.erl +++ /dev/null @@ -1,429 +0,0 @@ -%% -%% Copyright (c) 2022 dushin.net -%% All rights reserved. -%% -%% 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. -%% --module(lora_sx126x). - -%%% -%%% @doc -%%% An SPI driver for the LoRa (SX126X) chipset. -%%% -%%% This module can be used to send and receive messages using LoRa modulation. -%%% Currently, this module only supports point-to-point communications. This -%%% module does not support LoRaWAN. -%%% -%%% References -%%% SemTech SX126x data sheet: https://semtech.my.salesforce.com/sfc/p/#E0000000JelG/a/2R0000001Rbr/6EfVZUorrpoKFfvaF_Fkpgp5kzjiNyiAbqcpqh9qSjE -%%% SemTech reference implementation: https://github.com/Lora-net/sx126x_driver -%%% Python implementation (for interoperability testing): https://github.com/ehong-tl/micropySX126X -%%% -%%% @end - -%% Internal Lora provider API --export([start/1, start_link/1, stop/1, broadcast/2, sleep/1]). - -%% gen_statem --export([init/1, waiting_to_receive/3, waiting_tx_done/3, terminate/3]). - -% -behavior(gen_statem). - -% -define(TRACE_ENABLED, true). --include_lib("atomvm_lib/include/trace.hrl"). - - -%% -%% Lora Provider API -%% - -%% @hidden -start(Config) -> - gen_statem:start(?MODULE, Config, []). - -%% @hidden -start_link(Config) -> - gen_statem:start_link(?MODULE, Config, []). - -%% @hidden -stop(Lora) -> - gen_statem:stop(Lora). - -%% @hidden -broadcast(Lora, Message) -> - gen_statem:call(Lora, {broadcast, Message}). - -%% @hidden -sleep(Lora) -> - gen_statem:call(Lora, sleep). - -%%% -%%% gen_statem implementation -%%% - --record(state, { - spi, - config, - irq, - busy_pin, - pending -}). - -%% @hidden -init(Config) -> - ?TRACE("init(~p)", [Config]), - SPI = {maps:get(spi, Config), maps:get(device_name, Config)}, - - ok = maybe_reset(maps:get(reset, Config, undefined)), - - BusyPin = maps:get(busy, Config, undefined), - gpio:set_pin_mode(BusyPin, input), - case maybe_wait_until_not_busy(BusyPin, 1000) of - ok -> - case init_lora(SPI, Config) of - ok -> - - GPIO = gpio:start(), - ok = maybe_set_interrupt(GPIO, rising, maps:get(irq, Config, undefined)), - - set_recv_mode(SPI, Config), - - State = #state{ - spi = SPI, - config = Config, - irq = maps:get(irq, Config, undefined), - busy_pin = BusyPin - }, - {ok, waiting_to_receive, State}; - LoraError -> - {stop, LoraError} - end; - Error -> - {stop, Error} - end. - -%% -%% gen_statem state machine functions -%% - -%% -%% waiting_to_receive state -%% We are waiting to receive messages -%% * If we get a request to broadcast a message, we broadcast the message -%% and go into the waiting_tx_done state, to wait for the TX_DONE IRQ -%% * Any unknown calls are rejected with an error -%% * Any unknown messages are silently discarded -%% - -%% @hidden -waiting_to_receive(info, {gpio_interrupt, Pin}, #state{irq = Pin} = State) -> - ?TRACE("gpio_interrupt IRQ Pin=~p Level=~p in waiting_to_receive state", [Pin, gpio:digital_read(Pin)]), - do_receive(State), - {next_state, waiting_to_receive, State}; -waiting_to_receive({call, From}, {broadcast, Message}, State) -> - case maybe_wait_until_not_busy(State#state.busy_pin, 10) of - ok -> - ok = do_broadcast(State#state.spi, State#state.config, Message), - {next_state, waiting_tx_done, State#state{pending=From}, [{state_timeout, 9000, {error, tx_timeout}}]}; - Error -> - {next_state, waiting_to_receive, State#state{pending=From}, [{reply, From, Error}]} - end; -waiting_to_receive({call, From}, sleep, State) -> - do_sleep(State#state.spi), - {next_state, sleep, State, [{reply, From, ok}]}; -waiting_to_receive({call, From}, _Request, State) -> - ?TRACE("Unhandled call in waiting_to_receive state. Request: ~p", [Request]), - {next_state, waiting_tx_done, State, [{reply, From, {error, unknown_request}}]}; -waiting_to_receive(_EventType, _Request, State) -> - ?TRACE("Unhandled message in waiting_to_receive state. EventType: ~p Request: ~p", [EventType, Request]), - {next_state, waiting_to_receive, State}. - -%% -%% waiting_tx_done state -%% We are waiting for a signal that transmission of a message has completed -%% * If we get the expected interrupt on the IRQ pin, then reply ok to caller -%% and go back into the waiting_to_receive state -%% * If we timeout waiting, then reply to caller with a timeout error -%% * Any calls are rejected with an error -%% * Any unknown messages are silently discarded -%% - -waiting_tx_done(info, {gpio_interrupt, Pin}, #state{irq = Pin} = State) -> - ?TRACE("gpio_interrupt IRQ Pin=~p Level=~p in waiting_tx_done state", [Pin, gpio:digital_read(Pin)]), - case maybe_wait_until_not_busy(State#state.busy_pin, 1000) of - ok -> - % ok; - set_recv_mode(State#state.spi, State#state.config); - _E -> - io:format("Error! Unable to get into receive mode!~n") - end, - NewState = State#state{pending=undefined}, - {next_state, waiting_to_receive, NewState, [{reply, State#state.pending, ok}]}; -waiting_tx_done(state_timeout, ErrorMessage, State) -> - ?TRACE("Timed out waiting for tx_done IRQ. Error message: ~p", [ErrorMessage]), - % init_lora(State#state.spi, State#state.config), - set_recv_mode(State#state.spi, State#state.config), - NewState = State#state{pending=undefined}, - ?TRACE("going back into receive state. Will reply with ErrorMessage=~p", [ErrorMessage]), - {next_state, waiting_to_receive, NewState, [{reply, State#state.pending, ErrorMessage}]}; -waiting_tx_done({call, From}, _Request, State) -> - ?TRACE("Illegal call in waiting_tx_done state. Request: ~p", [Request]), - {next_state, waiting_tx_done, State, [{reply, From, {error, busy_waiting_tx_done}}]}; -waiting_tx_done(_EventType, _Request, State) -> - ?TRACE("Unhandled message in waiting_tx_done state. EventType: ~p Request: ~p", [EventType, Request]), - {next_state, waiting_tx_done, State}. - -%% @hidden -terminate(_Reason, _CurrentState, _State) -> - ok. - -%%% -%%% internal functions -%%% - -%% @private -init_lora(SPI, Config) -> - - ok = sx126x_cmd:set_standby_xosc(SPI), - ok = sx126x_cmd:set_buffer_base_address(SPI, - maps:get(tx_base_address, Config, 16#00), - maps:get(rx_base_address, Config, 16#00) - ), - - sx126x_cmd:clear_device_errors(SPI), - ok = sx126x_cmd:set_lora_packet_type(SPI), - ?TRACE("packet type: ~p", [sx126x_cmd:get_packet_type(SPI)]), - - ok = sx126x_cmd:set_rx_tx_fallback_mode(SPI, rc), - % ok = sx126x_cmd:set_cad_params(SPI), - ok = sx126x_cmd:clear_irq_status(SPI), - ok = sx126x_cmd:clear_irq_params(SPI), - ok = sx126x_cmd:calibration_all(SPI), - - %% For some reason this appears to be needed - %% on the NiceRF SX126x in order to send messages - ok = sx126x_cmd:set_dio3_as_tcxoc_ctl(SPI), - - ok = sx126x_cmd:set_modulation_params(SPI, - maps:get(spreading_factor, Config, sf_7), - maps:get(bandwidth, Config, bw_125khz), - maps:get(coding_rate, Config, cr_4_8), - maps:get(ldro, Config, off) - ), - - ok = sx126x_cmd:set_sync_word(SPI, - maps:get(sync_word, Config, 16#12) - ), - - % ok = sx126x_cmd:set_dio2_as_rf_switch_ctl(SPI, enable), - % ok = sx126x_cmd:set_regulator_mode(SPI), - - ok = sx126x_cmd:set_packet_params(SPI, - maps:get(preamble_length, Config, 8), - maps:get(header_mode, Config, explicit), - maps:get(max_payload_length, Config, 16#FF), - maps:get(enable_crc, Config, true), - maps:get(invert_iq, Config, false) - ), - - ok = sx126x_cmd:set_frequency(SPI, maps:get(frequency, Config, freq_915mhz)), - - ok = sx126x_cmd:set_pa_config(SPI, sx1262), - ok = sx126x_cmd:set_tx_params(SPI, maps:get(tx_power, Config, 2)), - - ?TRACE("device_errors: ~p", [sx126x_cmd:get_device_errors(SPI)]), - - ok. - -%% @private -set_recv_mode(SPI, Config) -> - ?TRACE("Setting mode to recv", []), - sx126x_cmd:set_standby_xosc(SPI), - - %% This seems to be required in order to receive - %% using the NiceRF SX1262 module (?) - ok = sx126x_cmd:calibrate_image(SPI), - - ok = sx126x_cmd:set_rx_irq(SPI), - - ok = sx126x_cmd:set_buffer_base_address(SPI, - maps:get(tx_base_address, Config, 16#00), - maps:get(rx_base_address, Config, 16#00) - ), - - ok = sx126x_cmd:clear_irq_status(SPI), - - ok = sx126x_cmd:set_packet_params(SPI, - maps:get(preamble_length, Config, 8), - maps:get(header_mode, Config, explicit), - maps:get(max_payload_length, Config, 16#FF), - maps:get(enable_crc, Config, true), - maps:get(invert_iq, Config, false) - ), - - ok = sx126x_cmd:set_rx(SPI), - ok. - -%% @private -maybe_set_interrupt(_GPIO, _Trigger, undefined) -> - ok; -maybe_set_interrupt(GPIO, Trigger, Pin) -> - ?TRACE("maybe_set_interrupt on pin ~p for trigger ~p", [Pin, Trigger]), - gpio:set_pin_pull(Pin, down), - gpio:set_int(GPIO, Pin, Trigger). - -%% @private -maybe_reset(undefined) -> - ?TRACE("Reset pin not set. Skipping...", []), - ok; -maybe_reset(ResetPin) -> - ?TRACE("Resetting on pin ~p ...", [ResetPin]), - ok = gpio:set_pin_mode(ResetPin, output), - ok = gpio:digital_write(ResetPin, high), - timer:sleep(1), - ok = gpio:digital_write(ResetPin, low), - timer:sleep(1), - ok = gpio:digital_write(ResetPin, high), - ok. - -%% @private -maybe_wait_until_not_busy(_BusyPin, 0) -> {error, timeout_busy}; -maybe_wait_until_not_busy(undefined, _I) -> ok; -maybe_wait_until_not_busy(BusyPin, I) -> - case gpio:digital_read(BusyPin) of - low -> - ?TRACE("Pin ~p is not busy.", [BusyPin]), - ok; - _ -> - ?TRACE("Pin ~p still busy. Waiting some more.", [BusyPin]), - % timer:sleep(10), - maybe_wait_until_not_busy(BusyPin, I - 1) - end. - -%%% -%%% send -%%% - --define(MAX_PACKET_LEN, 255). - -do_broadcast(SPI, Config, Data) -> - Len = erlang:byte_size(Data), - case Len > ?MAX_PACKET_LEN of - true -> - {error, payload_too_large}; - _ -> - % init_lora(SPI, Config), - %% - %% prepare - %% - ?TRACE("preparing transmit...", []), - - ok = sx126x_cmd:set_standby_xosc(SPI), - - ok = sx126x_cmd:set_packet_params(SPI, - maps:get(preamble_length, Config, 8), - maps:get(header_mode, Config, explicit), - Len, - maps:get(enable_crc, Config, true), - maps:get(invert_iq, Config, false) - ), - - ok = sx126x_cmd:clear_irq_status(SPI), - ok = sx126x_cmd:set_tx_irq(SPI), - - ok = sx126x_cmd:set_buffer_base_address(SPI, 0, 0), - - ?TRACE("writing data to FIFO (len=~p): ~p", [byte_size(Data), Data]), - _Response = sx126x_cmd:write_buffer(SPI, Data), - - %% fix sensitivity - % write_command(SPI, 16#1D, <<16#8,16#89>>), - % write_command(SPI, 16#0D, <<16#8,16#89,16#4>>), - - ?TRACE("Populated buffer and setting TX mode to transmit...", []), - ok = sx126x_cmd:set_tx(SPI), - - ok - end. - -%%% -%%% receive -%%% - -%% @private -do_receive(State) -> - SPI = State#state.spi, - Config = State#state.config, - - ?TRACE("Receiving message", []), - - ok = sx126x_cmd:set_standby_xosc(SPI), - - IRQStatus = sx126x_cmd:get_irq_status(SPI), - ?TRACE("IRQStatus: ~p", [IRQStatus]), - - try - case lists:member(crc_err, IRQStatus) of - true -> - io:format("CRC error on receive! Ignoring message.~n"), - {error, crc_err}; - false -> - {PayloadLengthRx, RxStartBufferPointer} = sx126x_cmd:get_rx_buffer_status(SPI), - ?TRACE("PayloadLengthRx: ~p RxStartBufferPointer: ~p", [PayloadLengthRx, RxStartBufferPointer]), - - Payload = sx126x_cmd:read_buffer(SPI, RxStartBufferPointer, PayloadLengthRx), - ?TRACE("Payload: ~p", [Payload]), - - %% - %% Notify handler - %% - Lora = {?MODULE, self()}, - case maps:get(receive_handler, Config, undefined) of - undefined -> - ?TRACE("No receive handler configured for received message. Ignoring message.", []), - ok; - Handler -> - ReplyData = case maps:get(binary, Config, true) of - true -> - Payload; - _ -> - binary_to_list(Payload) - end, - QoS = get_qos(SPI), - if - is_pid(Handler) -> - Handler ! {lora_receive, Lora, ReplyData, QoS}; - is_function(Handler) -> - spawn(fun() -> Handler(Lora, ReplyData, QoS) end); - true -> - {error, unsupported_receive_handler} - end - end - end - after - set_recv_mode(SPI, State#state.config) - end. - -%% @private -get_qos(SPI) -> - {Rssi, Snr, _SignalRssi} = sx126x_cmd:get_packet_status(SPI), - #{ - rssi => Rssi, - snr => Snr - }. - - -%% @private -do_sleep(SPI) -> - ?TRACE("do_sleep", []), - sx126x_cmd:set_sleep(SPI). diff --git a/src/lora_sx127x.erl b/src/lora_sx127x.erl deleted file mode 100644 index 047116a..0000000 --- a/src/lora_sx127x.erl +++ /dev/null @@ -1,714 +0,0 @@ -%% -%% Copyright (c) 2022 dushin.net -%% All rights reserved. -%% -%% 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. -%% --module(lora_sx127x). - -%%% -%%% @doc -%%% An SPI driver for the LoRa (SX127X) chipset. -%%% -%%% This module can be used to send and receive messages using LoRa modulation. -%%% Currently, this module only supports point-to-point communications. This -%%% module does not support LoRaWAN. -%%% -%%% References -%%% SemTech SX127x data sheet: https://semtech.my.salesforce.com/sfc/p/#E0000000JelG/a/2R0000001Rbr/6EfVZUorrpoKFfvaF_Fkpgp5kzjiNyiAbqcpqh9qSjE -%%% Python implementation: https://github.com/lemariva/uPyLoRaWAN -%%% AtomVM example program: https://github.com/bettio/AtomVM/blob/master/examples/erlang/esp32/sx127x.erl -%%% -%%% @end - -%% Internal Lora provider API --export([start/1, start_link/1, stop/1, broadcast/2, sleep/1]). - -%% gen_server --export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). - --behavior(gen_server). - -% -define(TRACE_ENABLED, true). --include_lib("atomvm_lib/include/trace.hrl"). - --define (REG_FIFO, 16#00). --define (REG_OP_MODE, 16#01). --define (REG_FRF_MSB, 16#06). --define (REG_FRF_MID, 16#07). --define (REG_FRF_LSB, 16#08). --define (REG_PA_CONFIG, 16#09). --define (REG_LR_OCP, 16#0B). --define (REG_LNA, 16#0C). --define (REG_FIFO_ADDR_PTR, 16#0D). --define (REG_FIFO_TX_BASE_ADDR, 16#0E). --define (REG_FIFO_RX_BASE_ADDR, 16#0F). --define (REG_FIFO_RX_CURRENT_ADDR, 16#10). --define (REG_IRQ_FLAGS, 16#12). --define (REG_RX_NB_BYTES, 16#13). --define (REG_PKT_RSSI_VALUE, 16#1A). --define (REG_PKT_SNR_VALUE, 16#1B). --define (REG_MODEM_CONFIG_1, 16#1D). --define (REG_MODEM_CONFIG_2, 16#1E). --define (REG_PREAMBLE_MSB, 16#20). --define (REG_PREAMBLE_LSB, 16#21). --define (REG_PAYLOAD_LENGTH, 16#22). --define (REG_MODEM_CONFIG_3, 16#26). --define (REG_RSSI_WIDEBAND, 16#2C). --define (REG_DETECTION_OPTIMIZE, 16#31). --define (REG_DETECTION_THRESHOLD, 16#37). --define (REG_SYNC_WORD, 16#39). --define (REG_DIO_MAPPING_1, 16#40). --define (REG_VERSION, 16#42). --define (REG_PADAC, 16#4D). - --define (REG_INVERTIQ, 16#33). --define (RFLR_INVERTIQ_RX_MASK, 16#BF). --define (RFLR_INVERTIQ_RX_OFF, 16#00). --define (RFLR_INVERTIQ_RX_ON, 16#40). --define (RFLR_INVERTIQ_TX_MASK, 16#FE). --define (RFLR_INVERTIQ_TX_OFF, 16#01). --define (RFLR_INVERTIQ_TX_ON, 16#00). - --define (REG_INVERTIQ2, 16#3B). --define (RFLR_INVERTIQ2_ON, 16#19). --define (RFLR_INVERTIQ2_OFF, 16#1D). - --define (MODE_LONG_RANGE_MODE, 16#80). --define (MODE_SLEEP, 16#00). --define (MODE_STDBY, 16#01). --define (MODE_TX, 16#03). --define (MODE_RX_CONTINUOUS, 16#05). --define (MODE_RX_SINGLE, 16#06). - --define (AUTO_AGC_FLAG, 16#04). - --define (IRQ_TX_DONE_MASK, 16#08). --define (IRQ_PAYLOAD_CRC_ERROR_MASK, 16#20). --define (IRQ_RX_DONE_MASK, 16#40). - - - -%%% -%%% gen_server implementation -%%% - --record(state, { - spi, - config, - irq -}). - -%% @hidden -start(Config) -> - gen_server:start(?MODULE, Config, []). - -%% @hidden -start_link(Config) -> - gen_server:start_link(?MODULE, Config, []). - -%% @hidden -stop(Lora) -> - gen_server:stop(Lora). - -%% @hidden -broadcast(Lora, Message) -> - gen_server:call(Lora, {broadcast, Message}). - -%% @hidden -sleep(Lora) -> - gen_server:call(Lora, sleep). - -%% @hidden -init(Config) -> - ?TRACE("init(~p)", [Config]), - timer:sleep(424), - SPI = {maps:get(spi, Config), maps:get(device_name, Config)}, - case verify_version(SPI) of - ok -> - case init_lora(SPI, Config) of - ok -> - % io:format("Registers: ~p~n", [do_dump_registers(SPI)]), - set_mode(SPI, recv), - State = #state{ - spi = SPI, - config = Config, - irq = get_irq(Config) - }, - {ok, State}; - LoraError -> - {stop, LoraError} - end; - VersionError -> - {stop, VersionError} - end. - -%% @hidden -handle_cast(Message, State) -> - io:format("Unhandled cast. Message: ~p~n", [Message]), - {noreply, State}. - -%% @hidden -handle_call({broadcast, Message}, _From, State) -> - Reply = do_broadcast(State#state.spi, Message), - set_mode(State#state.spi, recv), - {reply, Reply, State}; -handle_call(dump_registers, _From, State) -> - {reply, do_dump_registers(State#state.spi), State}; -handle_call(sleep, _From, State) -> - do_sleep(State#state.spi), - {reply, ok, State}; -handle_call(Request, _From, State) -> - io:format("lora Unhandled call. Request: ~p~n", [Request]), - {reply, error, State}. - -%% @hidden -handle_info({gpio_interrupt, Pin}, #state{irq = Pin} = State) -> - do_receive(State), - {noreply, State}; -handle_info(Message, State) -> - io:format("lora Unhandled info. Message: ~p~n", [Message]), - {noreply, State}. - -%% @hidden -terminate(_Reason, _State) -> - ok. - -%%% -%%% internal functions -%%% - -%% @private -verify_version(SPI) -> - ?TRACE("verify_version", []), - case read_register(SPI, ?REG_VERSION) of - {ok, 16#12} -> - ok; - {ok, UnexpectedVersion} -> - {error, {unexpected_version, UnexpectedVersion}}; - Error -> - Error - end. - -%% @private -init_lora(SPI, Config) -> - ok = set_mode(SPI, sleep), - - ok = set_frequency(SPI, maps:get(frequency, Config)), - ok = set_signal_bandwidth(SPI, maps:get(bandwidth, Config)), - ok = set_lna_boost(SPI), - ok = set_automatic_gain_control(SPI), - ok = set_tx_power(SPI, maps:get(tx_power, Config)), - ok = set_header_mode(SPI, maps:get(header_mode, Config)), - ok = set_spreading_factor(SPI, maps:get(spreading_factor, Config)), - ok = set_coding_rate(SPI, maps:get(coding_rate, Config)), - ok = set_preamble_length(SPI, maps:get(preamble_length, Config)), - ok = set_sync_word(SPI, maps:get(sync_word, Config)), - ok = set_enable_crc(SPI, maps:get(enable_crc, Config)), - ok = set_invert_iq(SPI, maps:get(invert_iq, Config)), - ok = set_base_addr(SPI), - - ok = set_mode(SPI, standby), - - GPIO = gpio:open(), - ok = maybe_set_irq(SPI, GPIO, get_irq(Config)), - ok = maybe_reset(GPIO, maps:get(reset, Config, undefined)). - -%% @private -get_irq(Config) -> - case {maps:get(dio_0, Config, undefined), maps:get(irq, Config, undefined)} of - {undefined, undefined} -> - undefined; - {undefined, IRQ} -> - IRQ; - {DPIO_0, undefined} -> - io:format("WARNING: dpio_0 deprecated. Use irq instead.~n"), - DPIO_0; - {_DPIO_0, IRQ} -> - io:format("WARNING: dpio_0 deprecated and IRQ defined. Using IRQ value.~n"), - IRQ - end. - -%% @private -set_mode(SPI, sleep) -> - set_mode(SPI, ?MODE_SLEEP); -set_mode(SPI, standby) -> - set_mode(SPI, ?MODE_STDBY); -set_mode(SPI, recv) -> - set_mode(SPI, ?MODE_RX_CONTINUOUS); -set_mode(SPI, Mode) -> - ?TRACE("set_mode ~p", [Mode]), - ok = write_register(SPI, ?REG_OP_MODE, ?MODE_LONG_RANGE_MODE bor Mode), - ok. - -%% @private -get_mode(SPI) -> - ?TRACE("get_mode", []), - {ok, Mode} = read_register(SPI, ?REG_OP_MODE), - Mode. - -%% @private -set_frequency(SPI, freq_169mhz) -> - set_frequency_internal(SPI, 2768896); -set_frequency(SPI, freq_433mhz) -> - set_frequency_internal(SPI, 7094272); -set_frequency(SPI, freq_868mhz) -> - set_frequency_internal(SPI, 14221312); -set_frequency(SPI, freq_915mhz) -> - set_frequency_internal(SPI, 14991360); -set_frequency(SPI, Freq) when is_integer(Freq) -> - %% caution: requires AtomVM fix for parsing external terms > 0x0FFFFFFF - {F, _} = rational:simplify( - rational:reduce( - rational:multiply( - Freq, - {256,15625} %% 32Mhz/2^19 or rational:reduce(rational:divide(1 bsl 19, 32000000)) - ) - ) - ), - set_frequency_internal(SPI, F). - -%% @private -set_frequency_internal(SPI, F) when is_integer(F) -> - ?TRACE("set_frequency_internal ~p", [F]), - ok = write_register(SPI, ?REG_FRF_MSB, ((F bsr 16) band 16#FF)), - ok = write_register(SPI, ?REG_FRF_MID, ((F bsr 8) band 16#FF)), - ok = write_register(SPI, ?REG_FRF_LSB, F band 16#FF), - ok. - -%% @private -set_signal_bandwidth(SPI, bw_7_8khz) -> - set_signal_bandwidth(SPI, 0); -set_signal_bandwidth(SPI, bw_10_4khz) -> - set_signal_bandwidth(SPI, 1); -set_signal_bandwidth(SPI, bw_15_6khz) -> - set_signal_bandwidth(SPI, 2); -set_signal_bandwidth(SPI, bw_20_8khz) -> - set_signal_bandwidth(SPI, 3); -set_signal_bandwidth(SPI, bw_31_25khz) -> - set_signal_bandwidth(SPI, 4); -set_signal_bandwidth(SPI, bw_41_7khz) -> - set_signal_bandwidth(SPI, 5); -set_signal_bandwidth(SPI, bw_62_5khz) -> - set_signal_bandwidth(SPI, 6); -set_signal_bandwidth(SPI, bw_125khz) -> - set_signal_bandwidth(SPI, 7); -set_signal_bandwidth(SPI, bw_250khz) -> - set_signal_bandwidth(SPI, 8); -set_signal_bandwidth(SPI, bw_500khz) -> - set_signal_bandwidth(SPI, 9); -set_signal_bandwidth(SPI, I) -> - ?TRACE("set_signal_bandwidth ~p", [I]), - {ok, ModemConfig1} = read_register(SPI, ?REG_MODEM_CONFIG_1), - ok = write_register(SPI, ?REG_MODEM_CONFIG_1, (ModemConfig1 band 16#0F) bor (I bsl 4)), - ok. - -%% @private -set_lna_boost(SPI) -> - ?TRACE("set_lna_boost", []), - {ok, LNA} = read_register(SPI, ?REG_LNA), - ok = write_register(SPI, ?REG_LNA, LNA bor 16#03), - ok. - -%% @private -set_automatic_gain_control(SPI) -> - ?TRACE("set_automatic_gain_control", []), - ok = write_register(SPI, ?REG_MODEM_CONFIG_3, ?AUTO_AGC_FLAG), - ok. - -%% @private -set_tx_power(SPI, Level) when 2 =< Level andalso Level =< 17 -> - ?TRACE("set_tx_power ~p", [Level]), - ok = write_register(SPI, ?REG_PADAC, 16#87), - ok = write_register(SPI, ?REG_PA_CONFIG, 16#80 bor (Level - 2)), - ok; -set_tx_power(_SPI, Level) -> - {error, {tx_lower, Level}}. - -%% @private -set_header_mode(SPI, implicit) -> - ?TRACE("set_header_mode implicit", []), - {ok, ModemConfig1} = read_register(SPI, ?REG_MODEM_CONFIG_1), - ok = write_register(SPI, ?REG_MODEM_CONFIG_1, ModemConfig1 bor 16#01), - ok; -set_header_mode(SPI, explicit) -> - ?TRACE("set_header_mode explicit", []), - {ok, ModemConfig1} = read_register(SPI, ?REG_MODEM_CONFIG_1), - ok = write_register(SPI, ?REG_MODEM_CONFIG_1, ModemConfig1 band 16#FE), - ok. - -%% @private -set_spreading_factor(SPI, SF) -> - ?TRACE("set_spreading_factor ~p", [SF]), - ok = write_register(SPI, ?REG_DETECTION_OPTIMIZE, 16#c3), - ok = write_register(SPI, ?REG_DETECTION_THRESHOLD, 16#0a), - {ok, ModemConfig2} = read_register(SPI, ?REG_MODEM_CONFIG_2), - ok = write_register(SPI, ?REG_MODEM_CONFIG_2, (ModemConfig2 band 16#0f) bor ((SF bsl 4) band 16#f0)), - ok. - -%% @private -set_coding_rate(SPI, cr_4_5) -> - set_coding_rate(SPI, 5); -set_coding_rate(SPI, cr_4_6) -> - set_coding_rate(SPI, 6); -set_coding_rate(SPI, cr_4_7) -> - set_coding_rate(SPI, 7); -set_coding_rate(SPI, cr_4_8) -> - set_coding_rate(SPI, 8); -set_coding_rate(SPI, Denominator) -> - ?TRACE("set_coding_rate ~p", [Denominator]), - Cr = Denominator - 4, - {ok, ModemConfig1} = read_register(SPI, ?REG_MODEM_CONFIG_1), - ok = write_register( - SPI, - ?REG_MODEM_CONFIG_1, - (ModemConfig1 band 16#F1) bor (Cr bsl 1) - ), - ok. - -%% @private -set_preamble_length(SPI, Length) -> - ?TRACE("set_preamble_length ~p", [Length]), - ok = write_register(SPI, ?REG_PREAMBLE_MSB, (Length bsr 8) band 16#FF), - ok = write_register(SPI, ?REG_PREAMBLE_LSB, (Length bsr 0) band 16#FF), - ok. - -%% @private -set_sync_word(SPI, Word) -> - ?TRACE("set_sync_word", []), - ok = write_register(SPI, ?REG_SYNC_WORD, Word), - ok. - -%% @private -set_enable_crc(SPI, true) -> - ?TRACE("set_enable_crc ~p", [true]), - {ok, ModemConfig2} = read_register(SPI, ?REG_MODEM_CONFIG_2), - ok = write_register(SPI, ?REG_MODEM_CONFIG_2, ModemConfig2 bor 16#04), - ok; -set_enable_crc(SPI, false) -> - ?TRACE("set_enable_crc ~p", [false]), - {ok, ModemConfig2} = read_register(SPI, ?REG_MODEM_CONFIG_2), - ok = write_register(SPI, ?REG_MODEM_CONFIG_2, ModemConfig2 band 16#FB), - ok. - -%% @private -set_invert_iq(SPI, true) -> - ?TRACE("set_invert_iq ~p", [true]), - {ok, InvertIQ} = read_register(SPI, ?REG_INVERTIQ), - Value = (InvertIQ band ?RFLR_INVERTIQ_TX_MASK band ?RFLR_INVERTIQ_RX_MASK) - bor ?RFLR_INVERTIQ_RX_ON bor ?RFLR_INVERTIQ_TX_ON, - ok = write_register(SPI, ?REG_INVERTIQ, Value), - ok = write_register(SPI, ?REG_INVERTIQ2, ?RFLR_INVERTIQ2_ON), - ok; -set_invert_iq(SPI, false) -> - ?TRACE("set_invert_iq ~p", [false]), - {ok, InvertIQ} = read_register(SPI, ?REG_INVERTIQ), - Value = (InvertIQ band ?RFLR_INVERTIQ_TX_MASK band ?RFLR_INVERTIQ_RX_MASK) - bor ?RFLR_INVERTIQ_RX_OFF bor ?RFLR_INVERTIQ_TX_OFF, - ok = write_register(SPI, ?REG_INVERTIQ, Value), - ok = write_register(SPI, ?REG_INVERTIQ2, ?RFLR_INVERTIQ2_OFF), - ok. - -%% @private -set_base_addr(SPI) -> - ?TRACE("set_base_addr", []), - ok = write_register(SPI, ?REG_FIFO_TX_BASE_ADDR, 0), - ok = write_register(SPI, ?REG_FIFO_RX_BASE_ADDR, 0), - ok. - -%% @private -maybe_set_irq(_SPI, _GPIO, undefined) -> - ok; -maybe_set_irq(SPI, GPIO, Pin) -> - ?TRACE("maybe_set_irq ~p", [Pin]), - ok = write_register(SPI, ?REG_DIO_MAPPING_1, 16#00), - gpio:set_int(GPIO, Pin, rising), - ok. - -%% @private -maybe_reset(_GPIO, undefined) -> - ok; -maybe_reset(GPIO, Pin) -> - ?TRACE("maybe_reset ~p", [Pin]), - gpio:set_direction(GPIO, Pin, output), - gpio:set_level(GPIO, Pin, 0), - timer:sleep(20), - gpio:set_level(GPIO, Pin, 1), - timer:sleep(50), - ok. - -%% @private -get_rssi(SPI, Frequency) -> - {ok, RSSI} = read_register(SPI, ?REG_PKT_RSSI_VALUE), - Sub = case Frequency of - freq_868mhz -> 157; - freq_915mhz -> 157; - _ -> 164 - end, - RSSI - Sub. - -%% @private -get_snr(SPI) -> - {ok, SNR} = read_register(SPI, ?REG_PKT_SNR_VALUE), - SNR bsr 2. - - -%%% -%%% send -%%% - -do_broadcast(SPI, Data) -> - %% - %% prepare - %% - ?TRACE("preparing transmit...", []), - set_mode(SPI, standby), - set_header_mode(SPI, explicit), - ok = write_register(SPI, ?REG_FIFO_ADDR_PTR, 0), - ok = write_register(SPI, ?REG_PAYLOAD_LENGTH, 0), - %% - %% write data to FIFO in Lora chip - %% - ?TRACE("writing data to FIFO: ~p", [Data]), - {ok, CurrentLength} = read_register(SPI, 16#22), - Len = write_packet_data(SPI, Data), - ok = write_register(SPI, ?REG_PAYLOAD_LENGTH, CurrentLength + Len), - %% - %% transmit and wait for signal - %% - ?TRACE("transmitting", []), - ok = write_register(SPI, ?REG_OP_MODE, ?MODE_LONG_RANGE_MODE bor ?MODE_TX), - try - case wait_flags(SPI, ?REG_IRQ_FLAGS, ?IRQ_TX_DONE_MASK, 10, 157) of - ok -> - ok = write_register(SPI, ?REG_IRQ_FLAGS, ?IRQ_TX_DONE_MASK), - ok; - Error -> - Error - end - after - %% - %% drop back into receive mode - %% - ?TRACE("set mode to recv", []), - set_mode(SPI, recv), - ?TRACE("done", []) - end. - -%% @private -write_packet_data(SPI, L) -> - write_packet_data(SPI, L, 0). - -%% @private -write_packet_data(_SPI, [], Len) -> - Len; -write_packet_data(_SPI, <<"">>, Len) -> - Len; -write_packet_data(SPI, L, Len) -> - %% Workaround for AtomVM bug: Can't pattern match on Lists/Binaries without - %% a badarg exception being thrown. Use if/then/else instead - if is_list(L) -> - [H|T] = L, - if is_integer(H) -> - write_register(SPI, ?REG_FIFO, H), - write_packet_data(SPI, T, Len + 1); - true -> - K = write_packet_data(SPI, H), - K + write_packet_data(SPI, T) - end; - is_binary(L) -> - <> = L, - ok = write_register(SPI, ?REG_FIFO, H), - write_packet_data(SPI, T, Len + 1); - true -> - throw({unsupported_payload, L}) - end. - -%% @private -wait_flags(SPI, Register, Mask, NumTries, SleepMs) -> - wait_flags(SPI, Register, Mask, NumTries, SleepMs, 0). - -%% @private -wait_flags(_SPI, _Register, _Mask, 0, _SleepMs, 0) -> - ?TRACE("Timed out waiting", []), - {error, timeout}; -wait_flags(SPI, Register, Mask, NumTries, SleepMs, 0) -> - ?TRACE("waiting...", []), - {ok, Flags} = read_register(SPI, Register), - case Flags band Mask of - 0 -> - timer:sleep(SleepMs); - _ -> - ok - end, - wait_flags(SPI, Register, Mask, NumTries - 1, SleepMs, Flags band Mask); -wait_flags(_SPI, _Register, _Mask, _NumTries, _SleepMs, _NotZero) -> - ok. - -%%% -%%% receive -%%% - -%% @private -do_receive(State) -> - ?TRACE("do_receive: State=~p", [State]), - SPI = State#state.spi, - {ok, IRQFlags} = read_register(SPI, ?REG_IRQ_FLAGS), - ok = write_register(SPI, ?REG_IRQ_FLAGS, IRQFlags), - - RxDone = (IRQFlags band ?IRQ_RX_DONE_MASK) =/= 0, - CrcError = (IRQFlags band ?IRQ_PAYLOAD_CRC_ERROR_MASK) =/= 0, - case {RxDone, CrcError} of - {true, false} -> - {ok, PacketLength} = read_register(SPI, ?REG_RX_NB_BYTES), - {ok, CurrentAddr} = read_register(SPI, ?REG_FIFO_RX_CURRENT_ADDR), - - ok = write_register(SPI, ?REG_FIFO_ADDR_PTR, CurrentAddr), - Data = read_packet_data(SPI, PacketLength), - Frequency = maps:get(frequency, State#state.config), - QoS = #{ - rssi => get_rssi(SPI, Frequency), - snr => get_snr(SPI) - }, - ?TRACE("Received data (len=~p); Qos: ~p", [length(Data), QoS]), - - ok = write_register(SPI, ?REG_FIFO_ADDR_PTR, 0), - %% - %% Notify handler - %% - Lora = {?MODULE, self()}, - case maps:get(receive_handler, State#state.config, undefined) of - undefined -> - ok; - Handler -> - ReplyData = case maps:get(binary, State#state.config, true) of - true -> - list_to_binary(Data); - _ -> - Data - end, - % erlang:garbage_collect(), - if - is_pid(Handler) -> - Handler ! {lora_receive, Lora, ReplyData, QoS}; - is_function(Handler) -> - spawn(fun() -> Handler(Lora, ReplyData, QoS) end); - true -> - ok - end - end; - {_, true} -> - ?TRACE("CRC error", []); - {_, _} -> - ?TRACE("Unexpected IRQFlags: ~p", [IRQFlags]) - end. - -%% @private -read_packet_data(_SPI, 0) -> - []; -read_packet_data(SPI, Len) -> - {ok, Datum} = read_register(SPI, ?REG_FIFO), - [Datum | read_packet_data(SPI, Len - 1)]. - -%% @private -read_register({SPI, DeviceName}, Address) -> - ?TRACE("Reading from SPI=~p DeviceName=~p Address=~p", [SPI, DeviceName, Address]), - spi:read_at(SPI, DeviceName, Address bor 16#80, 8). - -%% @private -write_register({SPI, DeviceName}, Address, Data) -> - ?TRACE("Writing register SPI=~p DeviceName=~p Address=~p Data=~p", [SPI, DeviceName, Address, Data]), - {ok, _} = spi:write_at(SPI, DeviceName, Address bor 16#80, 8, Data), - ok. - - -%%% -%%% debugging -%%% - -get_registers() -> - ToHex = fun to_hex/1, - [ - {reg_op_mode, 16#01, ToHex}, - {reg_fr_msb, 16#06, ToHex}, - {reg_fr_mid, 16#07, ToHex}, - {reg_fr_lsb, 16#08, ToHex}, - {reg_pa_config, 16#09, ToHex}, - {reg_pa_ramp, 16#0A, ToHex}, - {reg_pa_ocp, 16#0B, ToHex}, - {reg_lna, 16#0C, ToHex}, - {reg_fifo_addr_ptr, 16#0D, ToHex}, - {reg_fifo_tx_base_addr, 16#0E, ToHex}, - {reg_fifo_rx_base_addr, 16#0F, ToHex}, - {reg_fifo_rx_current_addr, 16#10, ToHex}, - {reg_irq_flags_mask, 16#11, ToHex}, - {reg_irq_flags, 16#12, ToHex}, - {reg_rx_nb_bytes, 16#13, ToHex}, - {reg_header_cnt_value_msb, 16#14, ToHex}, - {reg_header_cnt_value_lsb, 16#15, ToHex}, - {reg_packet_cnt_value_msb, 16#16, ToHex}, - {reg_packet_cnt_value_lsb, 16#17, ToHex}, - {reg_modem_stat, 16#18, ToHex}, - {reg_pkt_snr_value, 16#19, ToHex}, - {reg_pkr_rssi_value, 16#1A, ToHex}, - {reg_rssi_value, 16#1B, ToHex}, - {reg_hop_channel, 16#1C, ToHex}, - {reg_modem_config_1, 16#1D, ToHex}, - {reg_modem_config_2, 16#1E, ToHex}, - {reg_symb_timeout_lsb, 16#1F, ToHex}, - {reg_preamble_msb, 16#20, ToHex}, - {reg_preamble_lsb, 16#21, ToHex}, - {reg_payload_length, 16#22, ToHex}, - {reg_max_payload_length, 16#23, ToHex}, - {reg_hop_period, 16#24, ToHex}, - {reg_fifo_rx_byte_addr, 16#25, ToHex}, - {reg_modem_config_3, 16#26, ToHex}, - {reg_fei_msb, 16#28, ToHex}, - {reg_fei_mid, 16#29, ToHex}, - {reg_fei_lsb, 16#2A, ToHex}, - {reg_rssi_wideband, 16#2C, ToHex}, - {reg_detect_optimize, 16#31, ToHex}, - {reg_invert_iq, 16#33, ToHex}, - {reg_detection_threshold, 16#37, ToHex}, - {reg_sync_word, 16#39, ToHex} - ]. - -do_dump_registers(SPI) -> - ?TRACE("do_dump_registers", []), - Mode = get_mode(SPI), - Registers = get_registers(), - try - set_mode(SPI, sleep), - [ - begin - {ok, Value} = read_register(SPI, Address), - {Name, Parse(Value)} - end - || {Name, Address, Parse} <- Registers - ] - after - set_mode(SPI, Mode) - end. - -do_sleep(SPI) -> - ?TRACE("do_sleep", []), - set_mode(SPI, sleep). - -to_hex(Value) -> - "0x" ++ codec:encode(Value, hex). - - -% parse_op_mode(Value) -> -% #{ -% long_range_mode => (Value band 16#80) bsr 7 -% }. diff --git a/src/sx126x_cmd.erl b/src/sx126x_cmd.erl deleted file mode 100644 index c537612..0000000 --- a/src/sx126x_cmd.erl +++ /dev/null @@ -1,766 +0,0 @@ -%% -%% Copyright (c) 2022 dushin.net -%% All rights reserved. -%% -%% 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. -%% --module(sx126x_cmd). - -%%% -%%% @doc -%%% An SPI driver for the LoRa (SX126X) chipset. -%%% -%%% This module can be used to send and receive messages using LoRa modulation. -%%% Currently, this module only supports point-to-point communications. This -%%% module does not support LoRaWAN. -%%% -%%% References -%%% SemTech SX126x data sheet: https://semtech.my.salesforce.com/sfc/p/#E0000000JelG/a/2R0000001Rbr/6EfVZUorrpoKFfvaF_Fkpgp5kzjiNyiAbqcpqh9qSjE -%%% SemTech reference implementation: https://github.com/Lora-net/sx126x_driver -%%% Python implementation (for interoperability testing): https://github.com/ehong-tl/micropySX126X -%%% -%%% @end - --compile(export_all). - -% -define(TRACE_ENABLED, true). --include_lib("atomvm_lib/include/trace.hrl"). - - - -%% -%% SX126x command set -%% -%% The following SPI "commands" (opcodes and arguments) are used to configure -%% the SemTech SX126x modem. -%% -%% See section 13 (commands interface) for the meanings of these SPI commands. -%% - --define(EMPTY_BINARY, <<"">>). --define(NOP, <<16#00:8>>). - - --define(LORA_SYNC_WORD_ADDRESS, 16#0740). - -%% @private -set_sync_word(SPI, SyncWord) -> - ?TRACE("set_sync_word(~p)", [SyncWord]), - MSB = (SyncWord band 16#F0) bor 16#04, - LSB = ((SyncWord band 16#0F) bsl 4) bor 16#04, - Data = <>, - _Response = write_register(SPI, ?LORA_SYNC_WORD_ADDRESS, Data), - ok. - - --define(OCP_CURRENT_LIMIT_ADDRESS, 16#08E7). - -%% @private -set_current_limit(SPI, CurrentLimit) -> - ?TRACE("set_current_limit(~p)", [CurrentLimit]), - RawLimit = rational:divide(CurrentLimit * 2, 5), - %% TODO broken - Data = <>, - _Response = write_register(SPI, ?OCP_CURRENT_LIMIT_ADDRESS, Data), - ok. - - -%% 13.1.1 SetSleep --define(SET_SLEEP_OPCODE, 16#84). --define(SLEEP_START_COLD, 2#000). --define(SLEEP_START_WARM, 2#100). --define(SLEEP_RTC_DISABLE, 2#000). --define(SLEEP_RTC_ENABLE, 2#001). - -%% @private -set_sleep(SPI) -> - set_sleep(SPI, ?SLEEP_START_COLD, ?SLEEP_RTC_ENABLE). - -%% @private -set_sleep(SPI, Start, RTC) -> - ?TRACE("SetSleep(~p, ~p)", [Start, RTC]), - Data = <<(Start bor RTC):8>>, - write_command(SPI, ?SET_SLEEP_OPCODE, Data). - - -%% 13.1.2 SetStandby --define(SET_STANDBY_OPCODE, 16#80). --define(STDBY_RC, 16#00). --define(STDBY_XOSC, 16#01). - -%% @private -set_standby(SPI) -> - set_standby(SPI, ?STDBY_RC). - -set_standby_xosc(SPI) -> - set_standby(SPI, ?STDBY_XOSC). - -%% @private -set_standby(SPI, StandbyConfig) -> - ?TRACE("SetStandby(~p)", [StandbyConfig]), - Data = <>, - write_command(SPI, ?SET_STANDBY_OPCODE, Data). - - -% %% 13.1.3 SetFS -% -define(SET_FS_OPCODE, 16#C1). - -% %% @private -% set_fs(SPI) -> -% ?TRACE("SetFS()", []), -% write_command(SPI, ?SET_FS_OPCODE, ?EMPTY_BINARY). - - -%% 13.1.4 SetTx --define(SET_TX_OPCODE, 16#83). --define(TX_TIMEOUT_DISABLE, 16#000000). - -%% @private -set_tx(SPI) -> set_tx(SPI, ?TX_TIMEOUT_DISABLE). - -%% @private -set_tx(SPI, Timeout) -> - ?TRACE("SetTx(~p)", [Timeout]), - Data = <>, - write_command(SPI, ?SET_TX_OPCODE, Data). - - -%% 13.1.5 SetRx --define(SET_RX_OPCODE, 16#82). --define(RX_SINGLE_MODE, 16#000000). --define(RX_CONTINUOUS_MODE, 16#FFFFFF). - -%% @private -set_rx(SPI) -> - set_rx(SPI, ?RX_CONTINUOUS_MODE). - -%% @private -set_rx(SPI, Timeout) -> - ?TRACE("SetRx(~p)", [Timeout]), - Data = <>, - write_command(SPI, ?SET_RX_OPCODE, Data). - -% %% 13.1.6 StopTimerOnPreamble -% -define(STOP_TIMER_ON_PREAMBLE_OPCODE, 16#9F). -% -define(STOP_TIMER_ON_PREAMBLE_DISABLE, 16#00). -% -define(STOP_TIMER_ON_PREAMBLE_ENABLE, 16#01). - -% %% @private -% stop_timer_on_preamble(SPI, Value) -> -% ?TRACE("StopTimerOnPreamble(~p)", [Value]), -% Data = <>, -% write_command(SPI, ?STOP_TIMER_ON_PREAMBLE_OPCODE, Data). - - -% %% 13.1.7 SetRxDutyCycle -% -define(SET_RX_DUTY_CYCLE_OPCODE, 16#94). - -% %% @private -% set_rx_duty_cycle(SPI, RxPeriod, SleepPeriod) -> -% ?TRACE("SetRxDutyCycle(~p, ~p)", [RxPeriod, SleepPeriod]), -% Data = <>, -% write_command(SPI, ?SET_RX_DUTY_CYCLE_OPCODE, Data). - - -% %% 13.1.8 SetCad -% -define(SET_CAD_OPCODE, 16#C5). - -% %% @private -% set_cad(SPI) -> -% ?TRACE("SetCad()", []), -% write_command(SPI, ?SET_CAD_OPCODE, ?EMPTY_BINARY). - - -% %% 13.1.9 SetTxContinuousWave -% -define(SET_TX_CONTINUOUS_WAVE_OPCODE, 16#D1). - -% %% @private -% set_tx_continuous_wave(SPI) -> -% ?TRACE("SetTxContinuousWave()", []), -% write_command(SPI, ?SET_TX_CONTINUOUS_WAVE_OPCODE, ?EMPTY_BINARY). - - -% %% 13.1.10 SetTxInfinitePreamble -% -define(SET_TX_INFINITE_PREAMBLE_OPCODE, 16#D2). - -% %% @private -% set_tx_infinite_preamble(SPI) -> -% ?TRACE("SetTxInfinitePreamble()", []), -% write_command(SPI, ?SET_TX_INFINITE_PREAMBLE_OPCODE, ?EMPTY_BINARY). - - -%% 13.1.11 SetRegulatorMode --define(SET_REGULATOR_MODE_OPCODE, 16#96). --define(REGULATOR_MODE_ONLY_LDO, 16#00). --define(REGULATOR_MODE_DC_DC_LRO, 16#01). - - -set_regulator_mode(SPI) -> - set_regulator_mode(SPI, ?REGULATOR_MODE_DC_DC_LRO). - -%% @private -set_regulator_mode(SPI, Mode) -> - ?TRACE("SetRegulatorMode()", []), - Data = <>, - write_command(SPI, ?SET_REGULATOR_MODE_OPCODE, Data). - - -%% 13.1.12 CalibrateFunction --define(CALIBRATE_FUNCTION_OPCODE, 16#89). --define(RC64K_CALIBRATION_ENABLED, 16#01). --define(RC13M_CALIBRATION_ENABLED, 16#02). --define(PLL_CALIBRATION_ENABLED, 16#04). --define(ADC_PULSE_CALIBRATION_ENABLED, 16#08). --define(ADC_BULK_N_CALIBRATION_ENABLED, 16#10). --define(ADC_BULK_P_CALIBRATION_ENABLED, 16#20). --define(IMAGE_CALIBRATION_ENABLED, 16#40). - -%% @private -calibration_all(SPI) -> - calibration_function(SPI, 16#7F). - -%% @private -calibration_function(SPI, CalibParam) -> - ?TRACE("CalibrateFunction(~p)", [CalibParam]), - Data = <>, - write_command(SPI, ?CALIBRATE_FUNCTION_OPCODE, Data). - - -%% 13.1.13 CalibrateImage --define(CALIBRATE_IMAGE_OPCODE, 16#98). --define(FREQ_BAND_430_440, <<16#6B:8, 16#6F:8>>). --define(FREQ_BAND_470_510, <<16#75:8, 16#81:8>>). --define(FREQ_BAND_779_787, <<16#C1:8, 16#C5:8>>). --define(FREQ_BAND_863_870, <<16#D7:8, 16#DB:8>>). --define(FREQ_BAND_902_928, <<16#E1:8, 16#E9:8>>). - -calibrate_image(SPI) -> - %% TODO for now use defaults - calibrate_image(SPI, ?FREQ_BAND_902_928). - -%% @private -calibrate_image(SPI, FreqBand) -> - ?TRACE("CalibrateImage(~p)", [FreqBand]), - write_command(SPI, ?CALIBRATE_IMAGE_OPCODE, FreqBand). - - -%% 13.1.14 SetPaConfig --define(SET_PA_CONFIG_OPCODE, 16#95). --define(PA_DUTY_CYCLE, 16#04). %% TODO parameterize -- see datasheet for optimal combinations --define(HP_MAX, 16#07). %% TODO parameterize --define(SX1262_SEL, 16#00). --define(PA_LUT, 16#01). - -%% @private -set_pa_config(SPI, sx1262) -> - set_pa_config(SPI, ?PA_DUTY_CYCLE, ?HP_MAX, ?SX1262_SEL, ?PA_LUT). - -%% @private -set_pa_config(SPI, PaDutyCycle, HpMax, DevSel, PaLut) -> - ?TRACE("SetPaConfig(~p, ~p, ~p, ~p)", [PaDutyCycle, HpMax, DevSel, PaLut]), - Data = <>, - write_command(SPI, ?SET_PA_CONFIG_OPCODE, Data). - - -%% 13.1.15 SetRxTxFallbackMode --define(SET_RX_TX_FALLBACK_MODE_OPCODE, 16#93). --define(FALLBACK_MODE_FS, 16#40). --define(FALLBACK_MODE_XOSC, 16#30). --define(FALLBACK_MODE_RC, 16#20). - - -%% @private -set_rx_tx_fallback_mode(SPI, rc) -> - set_rx_tx_fallback_mode(SPI, ?FALLBACK_MODE_RC); -set_rx_tx_fallback_mode(SPI, FallbackMode) -> - ?TRACE("SetRxTxFallbackMode(~p)", [FallbackMode]), - Data = <>, - write_command(SPI, ?SET_RX_TX_FALLBACK_MODE_OPCODE, Data). - - -%% 13.2 Registers and Buffer Access - -%% 13.2.1 WriteRegister Function --define(WRITE_REGISTER_OPCODE, 16#0D). - -%% @private -write_register(SPI, Address, Data) -> - ?TRACE("WriteRegister(~p, ~p)", [Address, Data]), - InputData = <>, - write_read_command(SPI, ?WRITE_REGISTER_OPCODE, InputData). - - -%% 13.2.2 ReadRegister Function --define(READ_REGISTER_OPCODE, 16#1D). - -%% @private -read_register(SPI, Address, Len) -> - ?TRACE("ReadRegister(~p, ~p, ~p)", [SPI, Address, Len]), - NopPayload = create_nop_payload(Len + 1, []), - InputData = <>, - Response = write_read_command(SPI, ?READ_REGISTER_OPCODE, InputData), - <<_AddressStatus:2/binary, _FirstNopStatus:8, OutputData/binary>> = Response, - OutputData. - -%% 13.2.3 WriteBuffer Function --define(WRITE_BUFFER_OPCODE, 16#0E). - -%% @private -write_buffer(SPI, Data) -> - write_buffer(SPI, 0, Data). - -%% @private -write_buffer(SPI, Offset, Data) -> - ?TRACE("WriteBuffer(~p, ~p)", [Offset, Data]), - InputData = <>, - Response = write_read_command(SPI, ?WRITE_BUFFER_OPCODE, InputData), - Response. - -%% 13.2.4 ReadBuffer Function --define(READ_BUFFER_OPCODE, 16#1E). - -%% @private -read_buffer(SPI, Offset, Len) -> - ?TRACE("ReadBuffer(~p, ~p)", [Offset, Len]), - NopPayload = create_nop_payload(Len + 1, []), - InputData = <>, - Response = write_read_command(SPI, ?READ_BUFFER_OPCODE, InputData), - <<_RFU:8, _OffsetStatus:8, _FirstNopStatus:8, OutputData/binary>> = Response, - OutputData. - -%% @private -create_nop_payload(0, Accum) -> - erlang:iolist_to_binary(Accum); -create_nop_payload(I, Accum) -> - create_nop_payload(I - 1, [?NOP|Accum]). - - -%% 13.3 DIO and IRQ Control Functions - -%% 13.3.1 SetDioIrqParams --define(SET_DIO_IRQ_PARAMS_OPCODE, 16#08). --define(IRQ_MASK_NONE, 2#0000000000). --define(IRQ_MASK_TX_DONE, 2#0000000001). --define(IRQ_MASK_RX_DONE, 2#0000000010). --define(IRQ_MASK_PREABLE_DETECTED, 2#0000000100). --define(IRQ_MASK_SYNC_WORD_VALID, 2#0000001000). --define(IRQ_MASK_HEADER_VALID, 2#0000010000). --define(IRQ_MASK_HEADER_ERR, 2#0000100000). --define(IRQ_MASK_CRC_ERR, 2#0001000000). --define(IRQ_MASK_CAD_DONE, 2#0010000000). --define(IRQ_MASK_CAD_DETECTED, 2#0100000000). --define(IRQ_MASK_TIMEOUT, 2#1000000000). - --define(IRQ_MASK_LIST, [ - {?IRQ_MASK_TX_DONE, tx_done}, - {?IRQ_MASK_RX_DONE, rx_done}, - {?IRQ_MASK_PREABLE_DETECTED, preamble_detected}, - {?IRQ_MASK_SYNC_WORD_VALID, sync_word_valid}, - {?IRQ_MASK_HEADER_VALID, header_valid}, - {?IRQ_MASK_HEADER_ERR, header_err}, - {?IRQ_MASK_CRC_ERR, crc_err}, - {?IRQ_MASK_CAD_DONE, cad_done}, - {?IRQ_MASK_CAD_DETECTED, cad_detected}, - {?IRQ_MASK_TIMEOUT, timeout} -]). - -%% @private -clear_irq_params(SPI) -> - set_dio_irq_params(SPI, ?IRQ_MASK_NONE, ?IRQ_MASK_NONE, ?IRQ_MASK_NONE, ?IRQ_MASK_NONE). - -%% @private -set_tx_irq(SPI) -> - set_dio_irq_params(SPI, ?IRQ_MASK_TX_DONE, ?IRQ_MASK_TX_DONE, ?IRQ_MASK_NONE, ?IRQ_MASK_NONE). - -%% @private -set_rx_irq(SPI) -> - set_dio_irq_params(SPI, ?IRQ_MASK_RX_DONE, ?IRQ_MASK_RX_DONE, ?IRQ_MASK_NONE, ?IRQ_MASK_NONE). - -%% @private -set_dio_irq_params(SPI, IRQMask, DIO1Mask, DIO2Mask, DIO3Mask) -> - ?TRACE("SetDioIrqParams(~p, ~p, ~p, ~p)", [IRQMask, DIO1Mask, DIO2Mask, DIO3Mask]), - Data = <>, - write_command(SPI, ?SET_DIO_IRQ_PARAMS_OPCODE, Data). - - -%% 13.3.3 GetIrqStatus --define(GET_IRQ_STATUS_OPCODE, 16#12). - -get_irq_status(SPI) -> - ?TRACE("GetIrqStatus()", []), - Response = write_read_command(SPI, ?GET_IRQ_STATUS_OPCODE, <>), - % ?TRACE("Response: ~p", [Response]), - <<_RFU:8, _Status:8, IrqStatus:16>> = Response, - [Mnemonic || {Mask, Mnemonic} <- ?IRQ_MASK_LIST, Mask band IrqStatus =/= 0]. - -%% 13.3.4 ClearIrqStatus --define(CLEAR_IRQ_STATUS_OPCODE, 16#02). - -%% @private -clear_irq_status(SPI) -> - clear_irq_status(SPI, 16#03FF). - -clear_irq_status(SPI, Mask) -> - ?TRACE("ClearIrqStatus(~p)", [Mask]), - Data = <>, - write_command(SPI, ?CLEAR_IRQ_STATUS_OPCODE, Data). - - -%% 13.3.5 SetDIO2AsRfSwitchCtrl --define(SET_DIO2_AS_RF_SWITCH_CTL_OPCODE, 16#9D). --define(DIO2_AS_RF_SWITCH_DISABLE, 16#00). --define(DIO2_AS_RF_SWITCH_ENABLE, 16#01). - -%% @private -set_dio2_as_rf_switch_ctl(SPI, enable) -> - set_dio2_as_rf_switch_ctl(SPI, ?DIO2_AS_RF_SWITCH_ENABLE); -set_dio2_as_rf_switch_ctl(SPI, disable) -> - set_dio2_as_rf_switch_ctl(SPI, ?DIO2_AS_RF_SWITCH_DISABLE); -set_dio2_as_rf_switch_ctl(SPI, Enable) -> - ?TRACE("SetDIO2AsRfSwitchCtrl(~p)", [Enable]), - Data = <>, - write_command(SPI, ?SET_DIO2_AS_RF_SWITCH_CTL_OPCODE, Data). - -%% 13.3.6 SetDIO3AsTCXOCtrl --define(SET_DIO3_AS_TCXOC_CTL_OPCODE, 16#97). --define(TCXOC_VOLTAGE_16, 16#00). --define(TCXOC_VOLTAGE_17, 16#01). --define(TCXOC_VOLTAGE_18, 16#02). --define(TCXOC_VOLTAGE_22, 16#03). --define(TCXOC_VOLTAGE_24, 16#04). --define(TCXOC_VOLTAGE_27, 16#05). --define(TCXOC_VOLTAGE_30, 16#06). --define(TCXOC_VOLTAGE_33, 16#07). - -%% @private -set_dio3_as_tcxoc_ctl(SPI) -> - set_dio3_as_tcxoc_ctl(SPI, v_17, 320). - -%% @private -set_dio3_as_tcxoc_ctl(SPI, Voltage, Delay) -> - ?TRACE("SetDIO3AsTCXOCtrl(~p, ~p)", [Voltage, Delay]), - V = get_voltage(Voltage), - Data = <>, - write_command(SPI, ?SET_DIO3_AS_TCXOC_CTL_OPCODE, Data). - -%% @private -get_voltage(v_16) -> ?TCXOC_VOLTAGE_16; -get_voltage(v_17) -> ?TCXOC_VOLTAGE_17; -get_voltage(v_18) -> ?TCXOC_VOLTAGE_18; -get_voltage(v_22) -> ?TCXOC_VOLTAGE_22; -get_voltage(v_24) -> ?TCXOC_VOLTAGE_24; -get_voltage(v_27) -> ?TCXOC_VOLTAGE_27; -get_voltage(v_30) -> ?TCXOC_VOLTAGE_30; -get_voltage(v_33) -> ?TCXOC_VOLTAGE_33. - -%% 13.4 Modulation and Packet-Related Functions - -%% 13.4.1 SetRfFrequency --define(SET_RF_FREQUENCY_OPCODE, 16#86). - -%% @private -set_frequency(SPI, freq_169mhz) -> - % rational:reduce(rational:multiply(169000000, {16384,15625})). - % {177209344,1} - set_rf_frequency(SPI, 177209344); -set_frequency(SPI, freq_433mhz) -> - % rational:reduce(rational:multiply(433000000, {16384,15625})). - % {454033408,1} - set_rf_frequency(SPI, 454033408); -set_frequency(SPI, freq_868mhz) -> - % rational:reduce(rational:multiply(868000000, {16384,15625})). - % {910163968,1} - set_rf_frequency(SPI, 910163968); -set_frequency(SPI, freq_915mhz) -> - % rational:reduce(rational:multiply(915000000, {16384,15625})). - % {959447040,1} - set_rf_frequency(SPI, 959447040); -set_frequency(SPI, Freq) when is_integer(Freq) -> - %% Caution: requires AtomVM fix for parsing external terms > 0x0FFFFFFF - %% from datasheet - %% - %% RF * F - %% Freq XTAL - %% RF = -------------------- - %% frequency 25 - %% 2 - %% - %% Where F_{XTAL} = 32Mhz - %% - {F, _} = rational:simplify( - rational:reduce( - rational:multiply( - Freq, - {16384,15625} %% 2^25/32Mhz or rational:reduce(rational:divide(1 bsl 25, 32000000)) - ) - ) - ), - set_rf_frequency(SPI, F). - -%% @private -set_rf_frequency(SPI, F) when is_integer(F) -> - ?TRACE("SetRfFrequency(~p)", [F]), - % Data = <>, - Data = << - ((F bsr 24) band 16#FF):8, - ((F bsr 16) band 16#FF):8, - ((F bsr 8) band 16#FF):8, - (F band 16#FF):8 - >>, - write_command(SPI, ?SET_RF_FREQUENCY_OPCODE, Data). - - -%% 13.4.2 SetPacketType --define(SET_PACKET_TYPE_OPCODE, 16#8A). --define(PACKET_TYPE_GFSK, 16#00). --define(PACKET_TYPE_LORA, 16#01). - -%% @private -set_lora_packet_type(SPI) -> set_packet_type(SPI, ?PACKET_TYPE_LORA). - -%% @private -set_packet_type(SPI, PacketType) -> - ?TRACE("SetPacketType(~p)", [PacketType]), - Data = <>, - write_command(SPI, ?SET_PACKET_TYPE_OPCODE, Data). - -%% 13.4.3 GetPacketType --define(GET_PACKET_TYPE_OPCODE, 16#11). - -get_packet_type(SPI) -> - ?TRACE("GetPacketType()", []), - Data = create_nop_payload(2, []), - Response = write_read_command(SPI, ?GET_PACKET_TYPE_OPCODE, Data), - <<_RFU:8, _Status:8, PacketType:8>> = Response, - PacketType. - -% 13.4.4 SetTxParams --define(SET_TX_PARAMS_OPCODE, 16#8E). --define(TX_PARAMS_RAMP_10U, 16#00). --define(TX_PARAMS_RAMP_20U, 16#01). --define(TX_PARAMS_RAMP_40U, 16#02). --define(TX_PARAMS_RAMP_80U, 16#03). --define(TX_PARAMS_RAMP_200U, 16#04). --define(TX_PARAMS_RAMP_800U, 16#05). --define(TX_PARAMS_RAMP_1700U, 16#06). --define(TX_PARAMS_RAMP_3400U, 16#07). - -%% @private -set_tx_params(SPI, Power) -> - set_tx_params(SPI, Power, ?TX_PARAMS_RAMP_200U). - -%% @private -set_tx_params(SPI, Power, RampTime) when -9 =< Power andalso Power =< 22 andalso 16#00 =< RampTime andalso RampTime =< 16#07 -> - ?TRACE("SetTxParams(~p, ~p)", [Power, RampTime]), - Data = <>, - write_command(SPI, ?SET_TX_PARAMS_OPCODE, Data). - - -%% 13.4.5 SetModulationParams --define(SET_MODULATION_PARAMS_OPCODE, 16#8B). - -%% @private -set_modulation_params(SPI, SpreadingFactor, BandWidth, CodingRate, LowDataRateOptimize) -> - SF = sf_value(SpreadingFactor), - BW = bw_value(BandWidth), - CR = cr_value(CodingRate), - LDRO = ldro_value(LowDataRateOptimize), - ?TRACE("SetModulationParams(~p, ~p, ~p, ~p)", [SF, BW, CR, LDRO]), - Data = <>, - write_command(SPI, ?SET_MODULATION_PARAMS_OPCODE, Data). - -%% @private -sf_value(sf_5) -> 16#05; -sf_value(sf_6) -> 16#06; -sf_value(sf_7) -> 16#07; -sf_value(sf_8) -> 16#08; -sf_value(sf_9) -> 16#09; -sf_value(sf_10) -> 16#0A; -sf_value(sf_11) -> 16#0B; -sf_value(sf_12) -> 16#0C; -sf_value(X) when is_integer(X) -> - io:format("WARNING: Using deprecated spreading factor integer value (~p) -- Use atomic mnemonics, instead.~n", [X]), - X. - -%% @private -bw_value(bw_7_8khz) -> 16#00; -bw_value(bw_10_4khz) -> 16#08; -bw_value(bw_15_6khz) -> 16#01; -bw_value(bw_20_8khz) -> 16#09; -bw_value(bw_31_25khz) -> 16#02; -bw_value(bw_41_7khz) -> 16#0A; -bw_value(bw_62_5khz) -> 16#03; -bw_value(bw_125khz) -> 16#04; -bw_value(bw_250khz) -> 16#05; -bw_value(bw_500khz) -> 16#06. - -%% @private -cr_value(cr_4_5) -> 16#01; -cr_value(cr_4_6) -> 16#02; -cr_value(cr_4_7) -> 16#03; -cr_value(cr_4_8) -> 16#04. - -%% @private -ldro_value(off) -> 16#00; -ldro_value(on) -> 16#01. - - -%% 13.4.6 SetPacketParams --define(SET_PACKET_PARAMS_OPCODE, 16#8C). - -%% @private -set_packet_params(SPI, PreambleLength, HeaderType, PayloadLength, CRCType, InvertIQ) -> - HT = ht_value(HeaderType), - CRC = crc_value(CRCType), - IIRQ = iirq_value(InvertIQ), - ?TRACE("SetPacketParams(~p, ~p, ~p, ~p, ~p)", [PreambleLength, HT, PayloadLength, CRC, IIRQ]), - Data = <>, - write_command(SPI, ?SET_PACKET_PARAMS_OPCODE, Data). - -%% @private -ht_value(explicit) -> 16#00; -ht_value(implicit) -> 16#01. - -%% @private -crc_value(false) -> 16#00; -crc_value(true) -> 16#01. - -%% @private -iirq_value(false) -> 16#00; -iirq_value(true) -> 16#01. - -%% 13.4.7 SetCadParams --define(SET_CAD_PARAMS_OPCODE, 16#88). --define(CAD_ON_1_SYMB, 16#00). --define(CAD_ON_2_SYMB, 16#01). --define(CAD_ON_4_SYMB, 16#02). --define(CAD_ON_8_SYMB, 16#03). --define(CAD_ON_16_SYMB, 16#04). - --define(CAD_ONLY, 16#00). --define(CAD_RX, 16#01). - -%% @private -set_cad_params(SPI) -> - % data[0] = SX126X_CAD_ON_8_SYMB - % data[1] = self._sf + 13 - % data[2] = 10 - % data[3] = SX126X_CAD_GOTO_STDBY - % data[4] = 0x00 - % data[5] = 0x00 - % data[6] = 0x00 - set_cad_params(SPI, ?CAD_ON_8_SYMB, 16#19, 10, ?CAD_ONLY, 0). - -%% @private -set_cad_params(SPI, CadSymbolNum, CadDetPeak, CadDetMin, CadExitMode, CadTimeout) -> - ?TRACE("SetCadParams(~p, ~p, ~p, ~p, ~p)", [CadSymbolNum, CadDetPeak, CadDetMin, CadExitMode, CadTimeout]), - Data = <>, - write_command(SPI, ?SET_CAD_PARAMS_OPCODE, Data). - -%% 13.4.8 SetBufferBaseAddress --define(SET_BUFFER_ADDRESS_OPCODE, 16#8F). - -set_buffer_base_address(SPI, TXBaseAddress, RXBaseAddres) -> - ?TRACE("SetBufferBaseAddress(~p, ~p)", [TXBaseAddress, RXBaseAddres]), - Data = <>, - write_command(SPI, ?SET_BUFFER_ADDRESS_OPCODE, Data). - -%% 13.4.9 SetLoRaSymbNumTimeout --define(SET_LORA_SYMB_NUM_TIMEOUT_OPCODE, 16#A0). - - -%% 13.5 Communication Status Information - -%% 13.5.1 GetStatus --define(GET_STATUS_OPCODE, 16#C0). - -get_status(SPI) -> - ?TRACE("GetStatus()", []), - write_read_command(SPI, ?GET_STATUS_OPCODE, ?NOP). - - -%% 13.5.2 GetRxBufferStatus --define(GET_RX_BUFFER_STATUS_OPCODE, 16#13). - -get_rx_buffer_status(SPI) -> - ?TRACE("GetRxBufferStatus()", []), - Data = create_nop_payload(3, []), - Response = write_read_command(SPI, ?GET_RX_BUFFER_STATUS_OPCODE, Data), - <<_RFU:8, _Status:8, PayloadLengthRx:8, RxStartBufferPointer:8>> = Response, - {PayloadLengthRx, RxStartBufferPointer}. - - -%% 13.5.3 GetPacketStatus --define(GET_PACKET_STATUS_OPCODE, 16#14). - -get_packet_status(SPI) -> - ?TRACE("GetPacketStatus()", []), - Data = create_nop_payload(4, []), - Response = write_read_command(SPI, ?GET_PACKET_STATUS_OPCODE, Data), - <<_RFU:8, _Status:8, RssiPkt:8, SnrPkt:8, SignalRssiPkt>> = Response, - % {RssiPkt, SnrPkt, SignalRssiPkt}. - {-1 * RssiPkt div 2, SnrPkt div 4, -1 * SignalRssiPkt div 2}. - - -%% 13.5.4 GetRssiInst --define(GET_RSSI_INST_OPCODE, 16#15). -%% 13.5.5 GetStats --define(GET_STATS_OPCODE, 16#10). -%% 13.5.6 ResetStats --define(RESET_STATS_OPCODE, 16#00). - -%% 13.6 Miscellaneous - -%% 13.6.1 GetDeviceErrors --define(GET_DEVICE_ERRORS_OPCODE, 16#17). - -get_device_errors(SPI) -> - ?TRACE("GetDeviceErrors()", []), - Data = create_nop_payload(3, []), - Response = write_read_command(SPI, ?GET_DEVICE_ERRORS_OPCODE, Data), - <<_RFU:8, _Status:8, OpError:16>> = Response, - OpError. - -%% 13.6.2 ClearDeviceErrors --define(CLEAR_DEVICE_ERRORS_OPCODE, 16#07). - -clear_device_errors(SPI) -> - ?TRACE("ClearDeviceErrors()", []), - Data = create_nop_payload(2, []), - Response = write_read_command(SPI, ?CLEAR_DEVICE_ERRORS_OPCODE, Data), - <<_RFU:8, Status:16>> = Response, - Status. - -%% -%% internal functions -%% - -% %% @private -% read_command({SPI, DeviceName}, OpCode) -> -% {ok, Bin} = spi:read_at(SPI, DeviceName, OpCode, 8), -% <> = Bin, -% {ok, Data}. - -%% @private -write_command({SPI, DeviceName}, OpCode, Data) -> - Payload = <>, - % ?TRACE("[erl] write [~s]", [atomvm_lib:to_hex(Payload)]), - Result = spi:write(SPI, DeviceName, #{write_data => Payload}), - Result. - -%% @private -write_read_command({SPI, DeviceName}, OpCode, Data) -> - Payload = <>, - {ok, Response} = spi:write_read(SPI, DeviceName, #{write_data => Payload}), - % ?TRACE("[erl] write-read [~s] -> [~s]", [atomvm_lib:to_hex(Payload), atomvm_lib:to_hex(Response)]), - Response.