diff --git a/Documentation/components/drivers/special/wireless.rst b/Documentation/components/drivers/special/wireless.rst index 9997ed887ff28..bdf0366c9857d 100644 --- a/Documentation/components/drivers/special/wireless.rst +++ b/Documentation/components/drivers/special/wireless.rst @@ -1,3 +1,11 @@ ================ Wireless Drivers ================ + +.. toctree:: + :glob: + :maxdepth: 1 + :titlesonly: + :caption: Contents + + ./wireless/* diff --git a/Documentation/components/drivers/special/wireless/rn2xx3.rst b/Documentation/components/drivers/special/wireless/rn2xx3.rst new file mode 100644 index 0000000000000..72569a28f2225 --- /dev/null +++ b/Documentation/components/drivers/special/wireless/rn2xx3.rst @@ -0,0 +1,245 @@ +====== +RN2XX3 +====== + +This driver provides support for the RN2XX3 family of LoRa radio transceivers by +Microchip. This includes both the RN2903 and RN2483 modules. + +.. warning:: + This driver only contains preliminary support for a few 'radio set' commands + and raw radio transmit/receive. There is no support for the LoRaWAN stack + yet. + +Application Programming Interface +================================= + +To register the device for use, you will need to enable the standard upper half +serial drivers (``CONFIG_STANDARD_SERIAL``), since the RN2XX3 driver requires +the path to the UART interface the module is connected to. + +You will also need to ensure that the baud rate of the UART interface is set to +57600, which is the baud rate of the RN2XX3. + +At registration time, the driver will automatically determine if the device is +the RN2903 or RN2483. + +.. code-block:: c + + #include + + #ifdef CONFIG_LPWAN_RN2XX3 + + /* Register the RN2XX3 device driver */ + + ret = rn2xx3_register("/dev/rn2903", "/dev/ttyS1"); + if (ret < 0) { + syslog(LOG_ERR, "Failed to register RN2XX3 device driver: %d\n", ret); + } + #endif + +This driver uses the standard POSIX character device interface, implementing +``read()``, ``write()`` and ``ioctl()``. + +To transmit, the ``write()`` function can be used. Bytes in the provided buffer +will be transmitted as a packet. This has the following behaviour: + +* If the radio is in FSK modulation mode, packets will only contain up to 64 + bytes. A buffer of more than 64 bytes will only have 64 bytes transmitted. +* If the radio is in LoRa modulation mode, packets will only contain up to 255 + bytes. +* If the buffer contains less than the current packet size limit (64 or 255 + bytes), its contents will be transmitted as a single packet. + +.. code-block:: c + + int radio = open("/dev/rn2903", O_RDWR); + if (radio < 0) + { + fprintf(stderr, "Couldn't open radio: %d\n", errno); + return -1; + } + + char message[] = "Hello, world!"; + ssize_t b_sent = write(radio, message, sizeof(message)); + if (b_sent < 0) + { + fprintf(stderr, "Couldn't transmit: %d\n", errno); + return -1; + } + +To receive, the ``read()`` function can be used. As much of the received packet +as possible will be stored in the user buffer. This has the following behaviour: + +* If the buffer is too small to contain the full received packet, as much of the + packet as possible will be stored in the buffer. +* When the packet is fully read, ``read()`` will return ``0``. +* If only part of the packet has been read and a call to ``write()`` or + ``ioctl()`` is made, the remainder of the packet is discarded. + +.. code-block:: c + + int radio = open("/dev/rn2903", O_RDWR); + if (radio < 0) + { + fprintf(stderr, "Couldn't open radio: %d\n", errno); + return -1; + } + + char buffer[16]; + ssize_t b_read; + + do { + b_read = read(radio, buffer, sizeof(buffer)); + if (b_read < 0) + { + fprintf(stderr, "Couldn't receive: %d\n", errno); + return -1; + } + write(0, buffer, b_read); /* Print received bytes to stdout */ + } while (b_read != 0); + +Finally, the ``ioctl()`` interface provides access to some underlying module +commands. + +``WLIOC_GETSNR`` +---------------- + +Gets the signal to noise ration of the last received packet. If no packets have +been received, it will default to -128. Argument is a pointer to an ``int8_t``. + +.. code-block:: c + + int8_t snr; + err = ioctl(radio, WLIOC_GETSNR, &snr); + +``WLIOC_SETRADIOFREQ`` +---------------------- + +Sets the operating frequency of the radio module. The argument is the desired +frequency in Hz (``uint32_t``). + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETRADIOFREQ, 902400000); + +``WLIOC_GETRADIOFREQ`` +---------------------- + +Gets the current operating frequency of the radio module in Hz. The argument is +a pointer to a ``uint32_t``. + +.. code-block:: c + + uint32_t freq; + err = ioctl(radio, WLIOC_GETRADIOFREQ, &freq); + +``WLIOC_SETTXPOWER`` +-------------------- + +Sets the transmission power of the radio. Argument is a pointer to a ``float`` +containing the desired transmission power in dBm. After setting the transmission +power successfully, this pointer will contain the new transmission power. This +value may be different from the desired value, but will be the closest available +setting that is greater than or equal to the desired value. + +.. code-block:: c + + float txpower = 12.0; + err = ioctl(radio, WLIOC_SETTXPOWER, &txpower); + printf("Actual TX power: %.2f dBm\n", txpower); + +``WLIOC_GETTXPOWER`` +-------------------- + +Gets the current transmission power level, as the integer level that the radio +uses. The argument is a pointer to an ``int8_t``. + +.. code-block:: c + + int8_t txpwr; + err = ioctl(radio, WLIOC_GETTXPOWER, &txpwr); + +``WLIOC_SETBANDWIDTH`` +---------------------- + +Sets the operating bandwidth of the radio module. The argument is the desired +bandwidth in kHz (``uint32_t``). The radio only supports exact values of 125, +250 and 500. + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETBANDWIDTH, 250); + +``WLIOC_GETBANDWIDTH`` +---------------------- + +Gets the current operating bandwidth of the radio module in kHz. The argument is +a pointer to a ``uint32_t``. + +.. code-block:: c + + uint32_t bandwidth; + err = ioctl(radio, WLIOC_GETBANDWIDTH, &bandwidth); + +``WLIOC_SETSPREAD`` +---------------------- + +Sets the operating spread factor of the radio module. The argument is a +``uint8_t`` containing the desired spread factor between 7 and 12 (inclusive). + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETSPREAD, 8); + +``WLIOC_GETSPREAD`` +---------------------- + +Gets the current operating spread factor of the radio module. The argument is a +pointer to a ``uint8_t``. + +.. code-block:: c + + uint8_t spread; + err = ioctl(radio, WLIOC_GETSPREAD, &spread); + +``WLIOC_SETPRLEN`` +---------------------- + +Sets the operating preamble length of the radio module. The argument is a +``uint16_t`` containing the desired preamble length. + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETPRLEN, 8); + +``WLIOC_GETPRLEN`` +---------------------- + +Gets the current operating preamble length of the radio module. The argument is +a pointer to a ``uint16_t``. + +.. code-block:: c + + uint16_t prlen; + err = ioctl(radio, WLIOC_GETPRLEN, &prlen); + +``WLIOC_SETMOD`` +---------------------- + +Sets the operating modulation of the radio module. The argument is one of the +values in ``enum rn2xx3_mod_e``. + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETMOD, RN2XX3_MOD_FSK); + +``WLIOC_GETMOD`` +---------------------- + +Gets the current operating modulation of the radio module. The argument is a +pointer to a string which can store at least 10 characters. + +.. code-block:: c + + char modulation[10]; + err = ioctl(radio, WLIOC_GETMOD, modulation); diff --git a/drivers/wireless/lpwan/Kconfig b/drivers/wireless/lpwan/Kconfig index eb8dae6258d9a..8c2c56c50f9e7 100644 --- a/drivers/wireless/lpwan/Kconfig +++ b/drivers/wireless/lpwan/Kconfig @@ -5,6 +5,17 @@ if DRIVERS_LPWAN +config LPWAN_RN2XX3 + bool "Microchip RN2xx3 driver support" + default n + select UART # TODO what to select? + ---help--- + Enable driver support for the RN2xx3 LoRa radio transceiver. + +if LPWAN_RN2XX3 + +endif # LPWAN_RN2XX3 + config LPWAN_SX127X bool "SX127X Low Power Long Range transceiver support" default n diff --git a/drivers/wireless/lpwan/Make.defs b/drivers/wireless/lpwan/Make.defs index 023bff806f2c5..8a4f1aabe2904 100644 --- a/drivers/wireless/lpwan/Make.defs +++ b/drivers/wireless/lpwan/Make.defs @@ -25,5 +25,6 @@ ifeq ($(CONFIG_DRIVERS_LPWAN),y) include wireless/lpwan/sx127x/Make.defs +include wireless/lpwan/rn2xx3/Make.defs endif # CONFIG_DRIVERS_LPWAN diff --git a/drivers/wireless/lpwan/rn2xx3/CMakeLists.txt b/drivers/wireless/lpwan/rn2xx3/CMakeLists.txt new file mode 100644 index 0000000000000..008dd7069fdfe --- /dev/null +++ b/drivers/wireless/lpwan/rn2xx3/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# drivers/wireless/lpwan/rn2xx3/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you 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. +# +# ############################################################################## + +if(CONFIG_LPWAN_RN2903) + target_sources(drivers PRIVATE rn2xx3.c) +endif() diff --git a/drivers/wireless/lpwan/rn2xx3/Make.defs b/drivers/wireless/lpwan/rn2xx3/Make.defs new file mode 100644 index 0000000000000..d1adf6b5af73f --- /dev/null +++ b/drivers/wireless/lpwan/rn2xx3/Make.defs @@ -0,0 +1,31 @@ +############################################################################ +# drivers/wireless/lpwan/rn2xx3/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +ifeq ($(CONFIG_LPWAN_RN2XX3),y) + +CSRCS += rn2xx3.c + +DEPPATH += --dep-path wireless$(DELIM)lpwan$(DELIM)rn2xx3 +VPATH += :wireless$(DELIM)lpwan$(DELIM)rn2xx3 +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)drivers$(DELIM)wireless$(DELIM)lpwan$(DELIM)rn2xx3 + +endif # CONFIG_LPWAN_RN2903 diff --git a/drivers/wireless/lpwan/rn2xx3/rn2xx3.c b/drivers/wireless/lpwan/rn2xx3/rn2xx3.c new file mode 100644 index 0000000000000..2f660acdf31f0 --- /dev/null +++ b/drivers/wireless/lpwan/rn2xx3/rn2xx3.c @@ -0,0 +1,1508 @@ +/**************************************************************************** + * drivers/wireless/lpwan/rn2xx3/rn2xx3.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Duration of maximum MAC layer pause in milliseconds */ + +#define MAC_PAUSE_DUR "4294967245" + +/* TODO: implement the following commands + * coding rate + * iqi + * crc + * sync word + * bitrate + */ + +/**************************************************************************** + * Private Data Types + ****************************************************************************/ + +/* Radio transceiver modules */ + +enum rn2xx3_type_e +{ + RN2903 = 0, /* RN2903 transceiver */ + RN2483 = 1, /* RN2483 transceiver */ +}; + +struct rn2xx3_dev_s +{ + FAR struct file uart; /* UART interface */ + bool receiving; /* Currently receiving */ + mutex_t devlock; /* Exclusive access */ + enum rn2xx3_type_e model; /* Transceiver model */ + enum rn2xx3_mod_e mod; /* Modulation mode */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static ssize_t rn2xx3_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static ssize_t rn2xx3_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static int rn2xx3_ioctl(FAR struct file *filep, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_rn2xx3fops = +{ + .open = NULL, + .close = NULL, + .read = rn2xx3_read, + .write = rn2xx3_write, + .ioctl = rn2xx3_ioctl, +}; + +/* Modulation types */ + +static const char *MODULATIONS[] = +{ + "lora", /* RN2XX3_MOD_LORA */ + "fsk", /* RN2XX3_MOD_FSK */ +}; + +/* Max packet sizes for each modulation type. */ + +static const uint8_t MAX_PKTSIZE[] = +{ + 255, /* RN2XX3_MOD_LORA */ + 64, /* RN2XX3_MOD_FSK */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: radio_flush + * + * Description: + * Flushes all characters in the receive buffer from the RN2xx3 radio. + * Returns the number of bytes read. + * + ****************************************************************************/ + +static int radio_flush(FAR struct rn2xx3_dev_s *priv) +{ + int err; + int to_read; + char buf[10]; + + err = file_ioctl(&priv->uart, FIONREAD, &to_read); + if (err < 0) + { + return err; + } + + while (to_read > 0) + { + err = file_read(&priv->uart, buf, sizeof(buf)); + if (err < 0) + { + return err; + } + + to_read -= err; + } + + return 0; +} + +/**************************************************************************** + * Name: read_line + * + * Description: + * Reads a whole line of response from the RN2xx3 radio. Returns the number + * of bytes read. + * + ****************************************************************************/ + +static ssize_t read_line(FAR struct rn2xx3_dev_s *priv, FAR char *buf, + size_t nbytes) +{ + ssize_t length = 0; + ssize_t i = 0; + + for (; i < nbytes; ) + { + length = file_read(&priv->uart, &buf[i], nbytes - i); + + if (length < 0) + { + return length; + } + + i += length; + + if (buf[i] == '\n' || (buf[i - 1] == '\n' && i > 0)) + { + break; + } + } + + return i + 1; /* Number of bytes read */ +} + +/**************************************************************************** + * Name: mac_pause + * + * Description: + * Pauses the MAC layer. Required before every transmission/receive. + * + ****************************************************************************/ + +static int mac_pause(FAR struct rn2xx3_dev_s *priv) +{ + ssize_t length = 0; + char response[30]; + + /* Issue pause command */ + + length = + file_write(&priv->uart, "mac pause\r\n", sizeof("mac pause\r\n") - 1); + if (length < 0) + { + return length; + } + + /* Wait for response of watchdog timer timeout */ + + length = read_line(priv, response, sizeof(response)); + if (length < 0) + { + return length; + } + + response[length] = '\0'; /* Avoid strstr overrun */ + + /* Check for pause duration */ + + if (strstr(response, MAC_PAUSE_DUR) == NULL) + { + return -EIO; + } + + return 0; +}; + +/**************************************************************************** + * Name: get_command_err + * + * Description: + * Parses the command error response from the radio module and converts it + * into an error code. 0 for 'ok', -EINVAL for 'invalid_param' and -EBUSY + * for 'busy'. If an unknown error occurs, -EIO is returned. + * + ****************************************************************************/ + +static int get_command_err(FAR struct rn2xx3_dev_s *priv) +{ + char response[18]; + ssize_t length; + + length = read_line(priv, response, sizeof(response)); + if (length < 0) + { + return length; + } + + response[length] = '\0'; + + if (strstr(response, "ok")) + { + /* Do nothing, this is good */ + + return 0; + } + else if (strstr(response, "invalid_param")) + { + wlerr("RN2xx3 invalid_param\n"); + return -EINVAL; + } + else if (strstr(response, "busy")) + { + wlerr("RN2xx3 busy"); + return -EBUSY; + } + + wlerr("Unknown error"); + return -EIO; +} + +/**************************************************************************** + * Name: rn2903_txpwrlevel + * + * Description: + * Get the transmission power level that is greater than or equal to the + * requested transmission power in dBm. These levels are for the RN2903. + * The true dBm level that was set is returned in the `dbm` pointer. + * + ****************************************************************************/ + +static int8_t rn2903_txpwrlevel(FAR float *dbm) +{ + if (*dbm <= 3.0f) + { + *dbm = 3.0f; + return 2; + } + else if (3.0f < *dbm && *dbm <= 4.0f) + { + *dbm = 4.0f; + return 3; + } + else if (4.0f < *dbm && *dbm <= 5.0f) + { + *dbm = 5.0f; + return 4; + } + else if (5.0f < *dbm && *dbm <= 6.0f) + { + *dbm = 6.0f; + return 5; + } + else if (6.0f < *dbm && *dbm <= 7.0f) + { + *dbm = 7.0f; + return 6; + } + else if (7.0f < *dbm && *dbm <= 8.0f) + { + *dbm = 8.0f; + return 7; + } + else if (8.0f < *dbm && *dbm <= 9.0f) + { + *dbm = 9.0f; + return 8; + } + else if (9.0f < *dbm && *dbm <= 10.0f) + { + *dbm = 10.0f; + return 9; + } + else if (10.0f < *dbm && *dbm <= 11.0f) + { + *dbm = 11.0f; + return 10; + } + else if (11.0f < *dbm && *dbm <= 12.0f) + { + *dbm = 12.0f; + return 11; + } + else if (12.0f < *dbm && *dbm <= 13.0f) + { + *dbm = 13.0f; + return 12; + } + else if (13.0f < *dbm && *dbm <= 14.7) + { + *dbm = 14.7; + return 14; + } + else if (14.7 < *dbm && *dbm <= 15.5) + { + *dbm = 15.5; + return 15; + } + else if (15.5 < *dbm && *dbm <= 16.3) + { + *dbm = 16.3; + return 16; + } + else if (16.3 < *dbm && *dbm <= 17.0f) + { + *dbm = 17.0f; + return 16; + } + + *dbm = 18.5f; + return 20; +} + +/**************************************************************************** + * Name: rn2483_txpwrlevel + * + * Description: + * Get the transmission power level that is greater than or equal to the + * requested transmission power in dBm. These levels are for the RN2483. + * The true dBm level that was set is returned in the `dbm` pointer. + * + ****************************************************************************/ + +static int8_t rn2483_txpwrlevel(FAR float *dbm) +{ + if (*dbm <= -4.0f) + { + *dbm = -4.0f; + return -3; + } + else if (-4.0f < *dbm && *dbm <= -2.9f) + { + *dbm = -2.9f; + return -2; + } + else if (-2.9f < *dbm && *dbm <= -1.9f) + { + *dbm = -1.9f; + return -1; + } + else if (-1.9f < *dbm && *dbm <= -1.7f) + { + *dbm = -1.7f; + return 0; + } + else if (-1.7f < *dbm && *dbm <= -0.6f) + { + *dbm = -0.6f; + return 1; + } + else if (-0.6f < *dbm && *dbm <= 0.4f) + { + *dbm = 0.4f; + return 2; + } + else if (0.4f < *dbm && *dbm <= 1.4f) + { + *dbm = 1.4f; + return 3; + } + else if (1.4f < *dbm && *dbm <= 2.5f) + { + *dbm = 2.5f; + return 4; + } + else if (2.5f < *dbm && *dbm <= 3.6f) + { + *dbm = 3.6f; + return 5; + } + else if (3.6f < *dbm && *dbm <= 4.7f) + { + *dbm = 4.7f; + return 6; + } + else if (4.7f < *dbm && *dbm <= 5.8f) + { + *dbm = 5.8f; + return 7; + } + else if (5.8f < *dbm && *dbm <= 6.9f) + { + *dbm = 6.9f; + return 8; + } + else if (6.9f < *dbm && *dbm <= 8.1f) + { + *dbm = 8.1f; + return 9; + } + else if (8.1f < *dbm && *dbm <= 9.3f) + { + *dbm = 9.3f; + return 10; + } + else if (9.3f < *dbm && *dbm <= 10.4f) + { + *dbm = 10.4f; + return 11; + } + else if (10.4f < *dbm && *dbm <= 11.6f) + { + *dbm = 11.6f; + return 12; + } + else if (11.6f < *dbm && *dbm <= 12.5f) + { + *dbm = 12.5f; + return 13; + } + else if (12.5f < *dbm && *dbm <= 13.5f) + { + *dbm = 13.5f; + return 14; + } + + *dbm = 14.1f; + return 15; +} + +/**************************************************************************** + * Name: rn2xx3_getmodel + * + * Description: + * Get the transceiver model type. + * + ****************************************************************************/ + +static int rn2xx3_getmodel(FAR struct rn2xx3_dev_s *priv, + FAR enum rn2xx3_type_e *model) +{ + int err; + char response[40]; + + err = radio_flush(priv); + if (err < 0) + { + return err; + } + + err = file_write(&priv->uart, "sys get ver\r\n", + sizeof("sys get ver\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + response[err] = '\0'; + + if (strstr(response, "RN2903") != NULL) + { + *model = RN2903; + } + else if (strstr(response, "RN2483") != NULL) + { + *model = RN2483; + } + else + { + /* Something went wrong, model number should have been returned */ + + return -EIO; + } + + /* Flush any leftover radio data */ + + err = radio_flush(priv); + if (err < 0) + { + return err; + } + + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_getsnr + * + * Description: + * Get the signal to noise ratio of the last received packet. + * + ****************************************************************************/ + +static int rn2xx3_getsnr(FAR struct rn2xx3_dev_s *priv, FAR int8_t *snr) +{ + int err; + char response[20]; + + err = file_write(&priv->uart, "radio get snr\r\n", + sizeof("radio get snr\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + response[err] = '\0'; + *snr = atoi(response); + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_setfreq + * + * Description: + * Set the operating frequency of the RN2xx3 in Hz. + * + ****************************************************************************/ + +static int rn2xx3_setfreq(FAR struct rn2xx3_dev_s *priv, uint32_t freq) +{ + char freqstr[18]; + ssize_t length; + + length = file_write(&priv->uart, "radio set freq ", + sizeof("radio set freq ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(freqstr, sizeof(freqstr), "%lu\r\n", freq); + length = file_write(&priv->uart, freqstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getfreq + * + * Description: + * Get the operating frequency of the RN2xx3 in Hz. + * + ****************************************************************************/ + +static int rn2xx3_getfreq(FAR struct rn2xx3_dev_s *priv, FAR uint32_t *freq) +{ + int err; + char response[20]; + + err = file_write(&priv->uart, "radio get freq\r\n", + sizeof("radio get freq\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + response[err] = '\0'; + *freq = strtoul(response, NULL, 10); + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_settxpwr + * + * Description: + * Set the transmit power of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_settxpwr(FAR struct rn2xx3_dev_s *priv, FAR float *pwr) +{ + char powerstr[16]; + ssize_t length; + int8_t txpwr; + + if (priv->model == RN2903) + { + txpwr = rn2903_txpwrlevel(pwr); + } + else + { + txpwr = rn2483_txpwrlevel(pwr); + } + + length = file_write(&priv->uart, "radio set pwr ", + sizeof("radio set pwr ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(powerstr, sizeof(powerstr), "%d\r\n", txpwr); + length = file_write(&priv->uart, powerstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_gettxpwr + * + * Description: + * Get the transmission power of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_gettxpwr(FAR struct rn2xx3_dev_s *priv, FAR int8_t *txpwr) +{ + int err; + char response[20]; + + err = file_write(&priv->uart, "radio get pwr\r\n", + sizeof("radio get pwr\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + response[err] = '\0'; + *txpwr = atoi(response); + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_setbw + * + * Description: + * Set the bandwidth of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setbw(FAR struct rn2xx3_dev_s *priv, uint32_t bw) +{ + char bwstr[16]; + ssize_t length; + length = + file_write(&priv->uart, "radio set bw ", sizeof("radio set bw ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(bwstr, sizeof(bwstr), "%lu\r\n", bw); + length = file_write(&priv->uart, bwstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getbw + * + * Description: + * Get the bandwidth of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getbw(FAR struct rn2xx3_dev_s *priv, FAR uint32_t *bw) +{ + int err; + char response[10]; + + err = file_write(&priv->uart, "radio get bw\r\n", + sizeof("radio get bw\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + response[err] = '\0'; + *bw = strtoul(response, NULL, 10); + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_setsf + * + * Description: + * Set the spread factor of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setsf(FAR struct rn2xx3_dev_s *priv, uint8_t sf) +{ + char sfstr[15]; + ssize_t length; + + length = + file_write(&priv->uart, "radio set sf ", sizeof("radio set sf ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(sfstr, sizeof(sfstr), "sf%u\r\n", sf); + length = file_write(&priv->uart, sfstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getsf + * + * Description: + * Get the spread factor of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getsf(FAR struct rn2xx3_dev_s *priv, FAR uint8_t *sf) +{ + int err; + char response[20]; + + err = file_write(&priv->uart, "radio get sf\r\n", + sizeof("radio get sf\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + response[err] = '\0'; + *sf = strtoul(&response[2], NULL, 10); /* Skip 'sf' */ + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_setprlen + * + * Description: + * Set the preamble length of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setprlen(FAR struct rn2xx3_dev_s *priv, uint16_t prlen) +{ + char prstr[16]; + ssize_t length; + + length = file_write(&priv->uart, "radio set prlen ", + sizeof("radio set prlen ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(prstr, sizeof(prstr), "%u\r\n", prlen); + length = file_write(&priv->uart, prstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getprlen + * + * Description: + * Get the preamble length of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getprlen(FAR struct rn2xx3_dev_s *priv, + FAR uint16_t *prlen) +{ + int err; + char response[20]; + + err = file_write(&priv->uart, "radio get prlen\r\n", + sizeof("radio get prlen\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + response[err] = '\0'; + *prlen = strtoul(response, NULL, 10); + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_setmod + * + * Description: + * Set the modulation of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setmod(FAR struct rn2xx3_dev_s *priv, + enum rn2xx3_mod_e mod) +{ + int err; + char modstr[16]; + ssize_t length; + + DEBUGASSERT(mod == RN2XX3_MOD_LORA || mod == RN2XX3_MOD_FSK); + + length = file_write(&priv->uart, "radio set mod ", + sizeof("radio set mod ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(modstr, sizeof(modstr), "%s\r\n", MODULATIONS[mod]); + length = file_write(&priv->uart, modstr, length); + if (length < 0) + { + return length; + } + + err = get_command_err(priv); + if (err < 0) + { + return err; + } + + priv->mod = mod; + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_getmod + * + * Description: + * Get the modulation type of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getmod(FAR struct rn2xx3_dev_s *priv, FAR char *mod) +{ + int err; + char response[12]; + + err = file_write(&priv->uart, "radio get mod\r\n", + sizeof("radio get mod\r\n") - 1); + if (err < 0) + { + return err; + } + + err = read_line(priv, response, sizeof(response)); + + if (err < 0) + { + return err; + } + + /* Copy everything except \r\n */ + + memcpy(mod, response, err - 2); + response[err - 2] = '\0'; + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_read + * + * Description: + * Puts the RN2xx3 into continuous receive mode and waits for data to be + * received before returning it. + * + ****************************************************************************/ + +static ssize_t rn2xx3_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + ssize_t length = 0; + ssize_t received = 0; + static const char receive_cmd[] = "radio rx 0\r\n"; + char response[30]; + char hex_byte[3] = + { + 0, 0 /* Extra space for null terminator */ + }; + + /* Exclusive access */ + + length = nxmutex_lock(&priv->devlock); + if (length < 0) + { + return length; + } + + /* If we're still marked as receiving and the file position in non-zero, we + * can skip initiating receive mode and just continue parsing the packet. + * + * If the file position is non-zero and we're not receiving, return 0 to + * indicate the end of the last packet. + * + * Otherwise, we're not receiving and haven't been receiving, so we + * initiate a new receive mode. + */ + + if (filep->f_pos > 0 && !priv->receiving) + { + filep->f_pos = 0; + return 0; + } + else if (filep->f_pos > 0 && priv->receiving) + { + goto parse_packet; + } + + priv->receiving = true; /* About to receive */ + + /* Flush data */ + + length = radio_flush(priv); + if (length < 0) + { + wlerr("Could not flush RN2xx3\n"); + } + + /* Pause the MAC layer */ + + length = mac_pause(priv); + if (length < 0) + { + wlerr("Couldn't pause MAC layer\n"); + goto early_ret; + } + + /* Put the radio into indefinite receive mode */ + + length = file_write(&priv->uart, receive_cmd, sizeof(receive_cmd) - 1); + if (length < 0) + { + goto early_ret; + } + + /* Check for okay receipt of command */ + + length = get_command_err(priv); + if (length < 0) + { + goto early_ret; + } + + /* Begin slowly parsing the response to check if we either received data or + * had an error (timeout) + * 9 is the size of 'radio_rx ' or 'radio_err'. + */ + + for (uint8_t i = 0; i < 10; i++) + { + length = file_read(&priv->uart, &response[i], 1); + if (length < 0) + { + goto early_ret; + } + } + + response[10] = '\0'; + + /* Check for either error or received message */ + + if (strstr(response, "radio_err") != NULL) + { + length = -EAGAIN; /* Timeout */ + wlerr("Timeout during receive\n"); + goto early_ret; + } + else if (strstr(response, "radio_rx ") == NULL) + { + /* Unknown error, we didn't expect this output */ + + wlerr("Unknown error during receive\n"); + length = -EIO; + goto early_ret; + } + + /* Here we've received some actual data, and that data is what's currently + * pending to be read. + * We read until the end of message is signalled with \r\n. + */ + +parse_packet: + + hex_byte[2] = '\0'; /* Appease strtoul */ + + while (received < buflen) + { + length = file_read(&priv->uart, &hex_byte, 2); + if (length < 2) + { + goto early_ret; + } + + if (hex_byte[0] == '\r' && hex_byte[1] == '\n') + { + break; + } + + /* Convert ASCII hex byte into real bytes to store in the buffer */ + + buffer[received] = strtoul(hex_byte, NULL, 16); + received++; + } + + /* We've received everything we can possibly receive, let the user know by + * returning how much of the buffer we've filled. + * If we've seen the end of packet indicator (\r\n), we'll mark the receive + * as complete. However, if we only stopped to not exceed the buffer + * length, we will leave our status as receiving to continue on the next + * read call. + */ + + if (hex_byte[0] == '\r' && hex_byte[1] == '\n') + { + priv->receiving = false; + } + + length = received; + filep->f_pos += received; /* Record our position in received packet */ + +early_ret: + + /* We can't continue receiving if we had an issue */ + + if (length < 0) + { + priv->receiving = false; + } + + nxmutex_unlock(&priv->devlock); + return length; +} + +/**************************************************************************** + * Name: rn2xx3_write + * + * Description: + * Transmits the data from the user provided buffer over radio as a + * packet. + * + ****************************************************************************/ + +static ssize_t rn2xx3_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + ssize_t length = 0; + size_t i; + char response[20]; + char hexbyte[3]; /* Two hex characters per byte, plus null terminator */ + + /* Exclusive access */ + + length = nxmutex_lock(&priv->devlock); + if (length < 0) + { + return length; + } + + /* Flush data */ + + length = radio_flush(priv); + if (length < 0) + { + wlerr("Could not flush RN2xx3\n"); + } + + /* Pause the MAC layer */ + + length = mac_pause(priv); + if (length < 0) + { + wlerr("Could not pause MAC layer\n"); + goto early_ret; + } + + /* Start the transmission with the initial 'radio tx', followed by all the + * data in the user buffer converted into hexadecimal characters one by + * one + */ + + length = file_write(&priv->uart, "radio tx ", sizeof("radio tx ") - 1); + if (length < 0) + { + goto early_ret; + } + + /* Print out every character up to the buffer size or the packet size limit + */ + + for (i = 0; i < buflen && i < MAX_PKTSIZE[priv->mod]; i++) + { + snprintf(hexbyte, sizeof(hexbyte), "%02X", buffer[i]); + length = file_write(&priv->uart, hexbyte, 2); + if (length < 0) + { + goto early_ret; + } + } + + /* Complete the packet */ + + length = file_write(&priv->uart, "\r\n", sizeof("\r\n") - 1); + if (length < 0) + { + goto early_ret; + } + + /* Check for okay message */ + + length = get_command_err(priv); + if (length < 0) + { + goto early_ret; + } + + /* Check for radio_tx_ok */ + + length = read_line(priv, response, sizeof(response)); + if (length < 0) + { + goto early_ret; + } + + if (strstr(response, "radio_tx_ok") == NULL) + { + length = -EIO; + goto early_ret; + } + + /* All transmissions were nominal, so we let the user know that `i` bytes + * were sent + */ + + length = i; + +early_ret: + nxmutex_unlock(&priv->devlock); + return length; +} + +/**************************************************************************** + * Name: rn2xx3_ioctl + * + * Description: + * Execute commands on the RN2xx3. + ****************************************************************************/ + +static int rn2xx3_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + int err; + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + + /* Exclusive access */ + + err = nxmutex_lock(&priv->devlock); + if (err < 0) + { + return err; + } + + /* Flush radio to clear any lingering messages */ + + err = radio_flush(priv); + if (err < 0) + { + wlerr("Couldn't flush radio: %d\n"); + goto early_ret; + } + + switch (cmd) + { + case WLIOC_GETSNR: + { + FAR int8_t *snr = (FAR int8_t *)(arg); + DEBUGASSERT(snr != NULL); + err = rn2xx3_getsnr(priv, snr); + break; + } + + case WLIOC_SETRADIOFREQ: + { + err = rn2xx3_setfreq(priv, arg); + break; + } + + case WLIOC_GETRADIOFREQ: + { + FAR uint32_t *freq = (FAR uint32_t *)(arg); + DEBUGASSERT(freq != NULL); + err = rn2xx3_getfreq(priv, freq); + break; + } + + case WLIOC_SETTXPOWER: + { + FAR float *txpwr = (FAR float *)(arg); + DEBUGASSERT(txpwr != NULL); + err = rn2xx3_settxpwr(priv, txpwr); + break; + } + + case WLIOC_GETTXPOWER: + { + FAR int8_t *txpwr = (FAR int8_t *)(arg); + DEBUGASSERT(txpwr != NULL); + err = rn2xx3_gettxpwr(priv, txpwr); + break; + } + + case WLIOC_SETBANDWIDTH: + { + err = rn2xx3_setbw(priv, arg); + break; + } + + case WLIOC_GETBANDWIDTH: + { + FAR uint32_t *bw = (FAR uint32_t *)(arg); + DEBUGASSERT(bw != NULL); + err = rn2xx3_getbw(priv, bw); + break; + } + + case WLIOC_SETSPREAD: + { + err = rn2xx3_setsf(priv, arg); + break; + } + + case WLIOC_GETSPREAD: + { + FAR uint8_t *sf = (FAR uint8_t *)(arg); + DEBUGASSERT(sf != NULL); + err = rn2xx3_getsf(priv, sf); + break; + } + + case WLIOC_SETPRLEN: + { + err = rn2xx3_setprlen(priv, arg); + break; + } + + case WLIOC_GETPRLEN: + { + FAR uint16_t *prlen = (FAR uint16_t *)(arg); + DEBUGASSERT(prlen != NULL); + err = rn2xx3_getprlen(priv, prlen); + break; + } + + case WLIOC_SETMOD: + { + err = rn2xx3_setmod(priv, arg); + break; + } + + case WLIOC_GETMOD: + { + FAR char *mod = (FAR char *)(arg); + DEBUGASSERT(mod != NULL); + err = rn2xx3_getmod(priv, mod); + break; + } + + default: + { + err = -EINVAL; + break; + } + } + +early_ret: + nxmutex_unlock(&priv->devlock); + return err; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rn2xx3_register + * + * Description: + * Register the RN2xx3 LoRa transceiver driver. + * + * Arguments: + * devpath - The device path to use for the driver + * uartpath - The path to the UART character driver connected to the + * transceiver + * + ****************************************************************************/ + +int rn2xx3_register(FAR const char *devpath, FAR const char *uartpath) +{ + FAR struct rn2xx3_dev_s *priv = NULL; + int err = 0; + int retries = 0; + + DEBUGASSERT(uartpath != NULL); + DEBUGASSERT(devpath != NULL); + + /* Initialize device structure */ + + priv = kmm_zalloc(sizeof(struct rn2xx3_dev_s)); + if (priv == NULL) + { + wlerr("Failed to allocate instance of RN2xx3 driver."); + return -ENOMEM; + } + + priv->receiving = false; /* Not receiving yet */ + priv->mod = RN2XX3_MOD_LORA; /* Starts in LoRa */ + + /* Initialize mutex */ + + err = nxmutex_init(&priv->devlock); + if (err < 0) + { + wlerr("Failed to initialize mutex for RN2xx3 device: %d\n", err); + goto free_mem; + } + + /* Open UART interface for use */ + + err = file_open(&priv->uart, uartpath, O_RDWR | O_CLOEXEC); + if (err < 0) + { + wlerr("Failed to open UART interface %s for RN2xx3 driver: %d\n", + uartpath, err); + goto destroy_mutex; + } + + /* Check model type of the transceiver. Try a few times since the + * transceiver has some boot time delay. + */ + + do + { + err = rn2xx3_getmodel(priv, &priv->model); + retries++; + usleep(10000); /* 10ms between tries */ + } + while (retries < 3 && err < 0); + + if (err < 0) + { + wlerr("Could not detect model: %d\n", err); + goto close_file; + } + + wlinfo("Detected model %s\n", priv->model == RN2903 ? "RN2903" : "RN2483"); + + /* Register driver */ + + err = register_driver(devpath, &g_rn2xx3fops, 0666, priv); + if (err < 0) + { + wlerr("Failed to register RN2xx3 driver: %d\n", err); + goto close_file; + } + + /* Cleanup items on error */ + + if (err < 0) + { + close_file: + file_close(&priv->uart); + destroy_mutex: + nxmutex_destroy(&priv->devlock); + free_mem: + kmm_free(priv); + } + + return err; +} diff --git a/include/nuttx/wireless/ioctl.h b/include/nuttx/wireless/ioctl.h index a4c2da3969c5d..c8043e4d50fca 100644 --- a/include/nuttx/wireless/ioctl.h +++ b/include/nuttx/wireless/ioctl.h @@ -60,13 +60,31 @@ /* output power (in dBm) */ #define WLIOC_GETTXPOWER _WLCIOC(0x0006) /* arg: Pointer to int32_t, */ /* output power (in dBm) */ +#define WLIOC_SETBANDWIDTH _WLCIOC(0x0007) /* arg: Pointer to uint32_t, */ + /* bandwidth in Hz */ +#define WLIOC_GETBANDWIDTH _WLCIOC(0x0008) /* arg: Pointer to uint32_t, */ + /* bandwidth in Hz */ +#define WLIOC_SETSPREAD _WLCIOC(0x0009) /* arg: Pointer to uint8_t, */ + /* spread factor */ +#define WLIOC_GETSPREAD _WLCIOC(0x000a) /* arg: Pointer to uint8_t, */ + /* spread factor */ +#define WLIOC_GETSNR _WLCIOC(0x000b) /* arg: Pointer to int8_t, */ + /* signal to noise ratio */ +#define WLIOC_SETPRLEN _WLCIOC(0x000c) /* arg: uint16_t, */ + /* preamble length */ +#define WLIOC_GETPRLEN _WLCIOC(0x000d) /* arg: Pointer to uint16_t, */ + /* preamble length */ +#define WLIOC_SETMOD _WLCIOC(0x000e) /* arg: enum, */ + /* modulation type */ +#define WLIOC_GETMOD _WLCIOC(0x000f) /* arg: char[10] pointer, */ + /* modulation type */ /**************************************************************************** * Device-specific IOCTL commands ****************************************************************************/ #define WL_FIRST 0x0001 /* First common command */ -#define WL_NCMDS 0x0006 /* Number of common commands */ +#define WL_NCMDS 0x000f /* Number of common commands */ /* User defined ioctl commands are also supported. These will be forwarded * by the upper-half driver to the lower-half driver via the ioctl() diff --git a/include/nuttx/wireless/lpwan/rn2xx3.h b/include/nuttx/wireless/lpwan/rn2xx3.h new file mode 100644 index 0000000000000..d73e4f5434b98 --- /dev/null +++ b/include/nuttx/wireless/lpwan/rn2xx3.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * include/nuttx/wireless/lpwan/rn2xx3.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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 __INCLUDE_NUTTX_WIRELESS_LPWAN_RN2XX3_H +#define __INCLUDE_NUTTX_WIRELESS_LPWAN_RN2XX3_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-Processor Declarations + ****************************************************************************/ + +/**************************************************************************** + * Public Data Types + ****************************************************************************/ + +/* Modulation types */ + +enum rn2xx3_mod_e +{ + RN2XX3_MOD_LORA = 0, /* LoRa modulation */ + RN2XX3_MOD_FSK = 1, /* FSK modulation */ +}; + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: rn2xx3_register + * + * Description: + * Register the RN2xx3 LoRa transceiver driver. + * + * Arguments: + * devpath - The device path to use for the driver + * uartpath - The path to the UART character driver connected to the + * transceiver + * + ****************************************************************************/ + +int rn2xx3_register(FAR const char *devpath, FAR const char *uartpath); + +#endif /* __INCLUDE_NUTTX_WIRELESS_LPWAN_RN2XX3_H */