Conversation
jia200x
left a comment
There was a problem hiding this comment.
The driver implementation makes sense. It requires a round of functional changes and the rest should be only integration/cosmetic changes. Great job!
Here's the initial review.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| #define MAC_TIMER_CHAN_ACK (0U) /**< MAC timer channel for transmitting an ACK frame */ | ||
| #define MAC_TIMER_CHAN_IFS (1U) /**< MAC timer channel for handling IFS logic */ | ||
|
|
||
| static uint8_t rxbuf[IEEE802154_FRAME_LEN_MAX]; /* len PHR + PSDU + LQI */ |
There was a problem hiding this comment.
you can probably remove these RX and TX buffers, since the _read function can copy directly from the internal framebuffer (by calling sx126x_reead_buffer) and the _write function can write directly to the TX framebuffer.
drivers/sx126x/sx126x_radio_hal.c
Outdated
|
|
||
| static uint8_t rxbuf[IEEE802154_FRAME_LEN_MAX]; /* len PHR + PSDU + LQI */ | ||
| static uint8_t txbuf[IEEE802154_FRAME_LEN_MAX]; /* len PHR + PSDU + LQI */ | ||
| static uint8_t ack[IEEE802154_ACK_FRAME_LEN]; |
There was a problem hiding this comment.
you can also allocate uint8_t ack[IEEE802154_ACK_FRAME_LEN in the stack, instead of adding it as a global variable.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| static uint8_t rxbuf[IEEE802154_FRAME_LEN_MAX]; /* len PHR + PSDU + LQI */ | ||
| static uint8_t txbuf[IEEE802154_FRAME_LEN_MAX]; /* len PHR + PSDU + LQI */ | ||
| static uint8_t ack[IEEE802154_ACK_FRAME_LEN]; | ||
| static uint8_t _size; |
There was a problem hiding this comment.
for all these global variables: please try to add them to the device descriptor (sx126x_t). Otherwise this only allows one instance of the driver.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| static uint8_t sx126x_long_addr[IEEE802154_LONG_ADDRESS_LEN]; | ||
| static uint16_t sx126x_pan_id; | ||
|
|
||
| #define SX126X_TIMER TIMER_DEV(1) |
There was a problem hiding this comment.
if possible, prefer ztimer here. Although it might be overkill to have a high level timer API here, it makes this driver 100% hardware independent. See e.g https://github.com/RIOT-OS/RIOT/blob/master/drivers/sx127x/sx127x_netdev.c#L86.
There was a problem hiding this comment.
BTW I suspect this is the reason of the "NO ACK" you receive.
The timer frequency is in the order of a IEEE 802.15.4 2.4 GHz (O-QPSK) symbol time (16 us).
The symbol time of LoRa SF7/BW125 is 1 ms. Since all timeouts are in symbols, the radio tries to send an ACK immediately.
Since the symbol time vary depending on the PHY configuration, this timeouts should probably be calculated on demand.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| bool ack_filter : 1; /**< whether the ACK filter is activated or not */ | ||
| bool promisc : 1; /**< whether the device is in promiscuous mode or not */ | ||
| bool pending : 1; /**< whether there pending bit should be set in the ACK frame or not */ | ||
| } cfg = { |
There was a problem hiding this comment.
please add this cfg to the device descriptor (sx126x).
The radio that initially used this pattern (nrf802154) it's a periph radio, so it's ensured there will be only one instance. That's not the case for sx126x.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| uint8_t timeout; | ||
| cfg.ifs = true; | ||
| if (lifs) { | ||
| timeout = IEEE802154_LIFS_SYMS; |
There was a problem hiding this comment.
For typical IEEE 802.15.4 transceivers the IFS is meant to be a processing time. For LoRa transceivers (whose transmission time is very large compared to common IEEE 802.15.4 transceivers) we can probably reduce these values. I propose to use something like:
#ifndef LORA_SIFS_SYMS
#define LORA_SIFS_SYMS ...
#endif
#ifndef LORA_LIFS_SYMS
#define LORA_LIFS_SYMS ...
#endif
drivers/sx126x/sx126x_radio_hal.c
Outdated
|
|
||
| /* Ignore send if packet size is 0 */ | ||
| if (!pos) { | ||
| return 0; |
There was a problem hiding this comment.
the Radio HAL assumes the packet is valid (e.g not null). Therefore you can skip these lines
drivers/sx126x/sx126x_radio_hal.c
Outdated
| return -EBADMSG; | ||
| } | ||
|
|
||
| memcpy(buf, rxbuf, size-2); |
There was a problem hiding this comment.
as mentioned above, you can just call sx126x_read_buffer here. Then you don't need an extra buffer.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) { | ||
| if (iol->iol_len > 0) { | ||
| sx126x_write_buffer(dev, pos, iol->iol_base, iol->iol_len); | ||
| memcpy((&txbuf[pos]), iol->iol_base, iol->iol_len); |
There was a problem hiding this comment.
you can just drop this TX buffer, as you are already copying the content to the internal framebuffer.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| (void)cmd; | ||
| (void)value; | ||
|
|
||
| return 0; |
There was a problem hiding this comment.
| return 0; | |
| return -ENOTSUP; |
|
BTW we will need to find a way to allow both interfaces ( |
|
@jia200x Wow, I didn't expect that your review will be so detailed. Thank you very much! |
|
Hello! I made some changes into this implementation:
P.S. |
|
Great to hear!! Thanks to you for the contribution.
So far this has been hardcoded because there SubMAC only worked for O-QPSK radios. But now that there are more alternatives, we should probably calculate it on demand. Otherwise, just changing this value will break existing O-QPSK radios. We can probably get it from CAPS for now. |
|
btw could you rebase this branch on top of |
| #include "xtimer.h" | ||
|
|
||
| #define SYMBOL_TIME (256U) /**< 16 us */ | ||
| #define SYMBOL_TIME (1024U) /**< 16 us */ |
There was a problem hiding this comment.
since this application should also work for O-QPSK radios, we will need to calculate this on demand. I plan to propose a PR with a mechanism to do that (since we also need it for other SubGHz radios).
jia200x
left a comment
There was a problem hiding this comment.
Thanks for pushing the latest changes!
Here's a more detailed review of the technical aspects. After addressing these changes we should then focus on:
- Be able to run the HAL version as well as the
netdevversion of this radio - Fix style issues/missing documentation
- Create semantic commits: one for the radio, one for the board, etc.
As mention before, I will propose in the meantime a mechanism to get the symbol time depending on the PHY.
|
|
||
| #include "net/ieee802154/radio.h" | ||
|
|
||
| #include "event/thread.h" |
There was a problem hiding this comment.
| #include "event/thread.h" |
There shouldn't be a dependency to the event_thread module, because the upper layer may decide which queue should be used.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| #define ENABLE_DEBUG 0 | ||
| #include "debug.h" | ||
|
|
||
| #include "net/netdev/lora.h" |
There was a problem hiding this comment.
there shouldn't be any netdev dependency on this radio. If there's an existing macro in this file, we should consider moving it ti net/lora.h since it might indicate it's not specific to netdev.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| #include <errno.h> | ||
| #include <stdio.h> | ||
|
|
||
| #include "net/gnrc.h" |
There was a problem hiding this comment.
There shouldn't be a dependency to GNRC here, because this device can be used with other network stacks.
| #include "net/gnrc.h" |
drivers/sx126x/sx126x_radio_hal.c
Outdated
| #include "ztimer.h" | ||
|
|
||
| #include "thread.h" | ||
| #include "periph/timer.h" |
There was a problem hiding this comment.
| #include "periph/timer.h" |
Since you are already using ztimer, you should remove this line too.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| const uint8_t llcc68_max_sf = LORA_SF11; | ||
| const uint8_t sx126x_max_sf = LORA_SF12; |
There was a problem hiding this comment.
| const uint8_t llcc68_max_sf = LORA_SF11; | |
| const uint8_t sx126x_max_sf = LORA_SF12; |
This lines are not being used in the code. Since you are already using a fixed PHY setting, I would propose to simply remove these.
| (void)hal; | ||
| (void)threshold; | ||
|
|
||
| return 0; |
There was a problem hiding this comment.
Since it's not implemented:
| return 0; | |
| return -ENOTSUP; |
drivers/sx126x/sx126x_radio_hal.c
Outdated
| sx126x_set_channel(dev, (channel-11)*200000LU + 865500000LU); | ||
| } | ||
|
|
||
| assert(pow >= -14 && pow <= 22); |
There was a problem hiding this comment.
Instead of asserting, you should return -EINVAL and don't apply any PHY settings. That gives the upper layer the opportunity to react.
drivers/sx126x/sx126x_radio_hal.c
Outdated
| assert(channel >= 11 && channel <= 26); | ||
|
|
||
| if (channel == 26) { | ||
| sx126x_set_channel(dev, 869525000LU); |
There was a problem hiding this comment.
Please use macros for these frequency, channel and power values. You can do something like:
#define SX126X_HAL_CHAN_26 (869525000LU)
#define SX126X_HAL_CHAN_BASE (865500000LU)
#define SX126X_HAL_CHAN_SPACING (200000LU)
#define SX126X_CHAN_MIN (11)
#define SX126X_CHAN_MAX (26)
#define SX126X_POWER_MIN (-14)
#define SX126X_POWER_MAX (22)
drivers/sx126x/sx126x_radio_hal.c
Outdated
| | IEEE802154_CAP_IRQ_RX_START | ||
| | IEEE802154_CAP_IRQ_TX_DONE | ||
| | IEEE802154_CAP_IRQ_CCA_DONE | ||
| //| IEEE802154_CAP_IRQ_ACK_TIMEOUT |
There was a problem hiding this comment.
| //| IEEE802154_CAP_IRQ_ACK_TIMEOUT |
| @@ -0,0 +1,37 @@ | |||
| /* | |||
There was a problem hiding this comment.
We still need this file for the netdev implementation of this driver
| @@ -0,0 +1,647 @@ | |||
| #include <assert.h> | |||
There was a problem hiding this comment.
You will also need to add the copyright header here. You can copy it from any RIOT C file and adjust it as needed. Don't forget to add your name to the author list 😉
|
Hello @jia200x Thanks for your review! All changes accepted, I will work on it :) |
| #define ACK_TIMEOUT_US (864U) | ||
|
|
||
| //#define ACK_TIMEOUT_US (864U) | ||
| #define ACK_TIMEOUT_US (50000U) |
There was a problem hiding this comment.
I already adapted the SubMAC in #19198 to calculate the timeout on demand.
You would just need to define a channel page for LoRa and adapt the ieee802154_get_symbol_time function. For the LoRa channel page, I would try to define a very high value to avoid colliding with future 802.15.4 PHY specifications... let's say something in the range of 0xFF00 to 0xFFFF.
|
hi @3JIouHoCoK , thank you so much for the last commits. I will try to come back during the week. |
jia200x
left a comment
There was a problem hiding this comment.
Sorry for the delay, I'm back with some change requests.
I tested the driver on a nucleo-.wl55jc and it works fine, so we should probably focus on code style and nits.
For the final PR there shouldn't be file changes that don't belong to IEEE 802.15.4 (e.g the RAK board should be on its own PR).
| #include "sx126x_params.h" | ||
| #include "sx126x_internal.h" | ||
|
|
||
| #define LORA_ACK_REPLY_US 1024 |
There was a problem hiding this comment.
to align it with IEEE 802.15.4, this should be 54 symbols.
If using SF7BW125, this means 54 * 1024[us] = 55.3 [ms]
| } | ||
| } | ||
|
|
||
| sx126x_set_lora_payload_length(dev, pos); |
There was a problem hiding this comment.
the current SubMAC assumes the radio adds the CRC.
This means, the write function should calculate it:
static int _write(ieee802154_dev_t *hal, const iolist_t *iolist){
+
sx126x_t *dev = hal->priv;
(void)dev;
size_t pos = 0;
+ uint16_t chksum = 0;
/* Full buffer used for Tx */
sx126x_set_buffer_base_address(dev, 0x80, 0x00);
/* Write payload buffer */
for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) {
if (iol->iol_len > 0) {
sx126x_write_buffer(dev, pos + 0x80, iol->iol_base, iol->iol_len);
+ memcpy(buffer + pos, iol->iol_base, iol->iol_len);
DEBUG("[sx126x] netdev: send: wrote data to payload buffer.\n");
pos += iol->iol_len;
+ chksum = ucrc16_calc_le(iol->iol_base, iol->iol_len,
+ UCRC16_CCITT_POLY_LE, chksum);
}
}
-
+ chksum = byteorder_htols(chksum).u16;
+ /* Include CRC */
+ sx126x_write_buffer(dev, pos + 0x80, (uint8_t*) &chksum, sizeof(chksum));
+ memcpy(buffer + pos, &chksum, sizeof(chksum));
+ pos += 2;
sx126x_set_lora_payload_length(dev, pos);
DEBUG("[sx126x] writing (size: %d).\n", (pos));
return 0;
}| (void)hal; | ||
| sx126x_t *dev = hal->priv; | ||
| sx126x_rx_buffer_status_t rx_buffer_status; | ||
| sx126x_get_rx_buffer_status(dev, &rx_buffer_status); |
There was a problem hiding this comment.
If the transceiver build the CRC footer (see above), this function should return the length of the packet without the 2 bytes of the CRC:
sx126x_get_rx_buffer_status(dev, &rx_buffer_status);
- dev->size = rx_buffer_status.pld_len_in_bytes;
+ /* Exclude CRC */
+ dev->size = rx_buffer_status.pld_len_in_bytes - 2;| return rx_buffer_status.pld_len_in_bytes; | ||
| } | ||
|
|
||
| static int _read(ieee802154_dev_t *hal, void *buf, size_t max_size, ieee802154_rx_info_t *info) |
There was a problem hiding this comment.
Same here for the CRC: here we calculate the CRC and compare it with the one from the frame.
You can base on this snippet (please ignore the debug messages, and the code is a bit old)
@@ -450,21 +351,34 @@ static int _read(ieee802154_dev_t *hal, void *buf, size_t max_size, ieee802154_r
(void)hal;
(void)buf;
(void)info;
+ uint16_t chksum = 0;
+ uint16_t exp_chksum;
+ DEBUG("[sx126x] _read\n");
sx126x_t* dev = hal->priv;
/* Getting information about last received packet */
- netdev_lora_rx_info_t *packet_info = (void*)info;
+ //netdev_lora_rx_info_t *packet_info = (void*)info;
sx126x_rx_buffer_status_t rx_buffer_status;
sx126x_pkt_status_lora_t pkt_status;
sx126x_get_rx_buffer_status(dev, &rx_buffer_status);
+ /* Size including CRC */
dev->size = rx_buffer_status.pld_len_in_bytes;
+
sx126x_get_lora_pkt_status(dev, &pkt_status);
+
+ /* TODO */
+ if (info) {
+ info->lqi = 255;
+ info->rssi = 100;
+ }
+ /*
if (packet_info) {
packet_info->snr = pkt_status.snr_pkt_in_db;
packet_info->rssi = pkt_status.rssi_pkt_in_dbm;
}
+ */
/* Put PSDU to the output buffer */
@@ -472,14 +386,26 @@ static int _read(ieee802154_dev_t *hal, void *buf, size_t max_size, ieee802154_r
return dev->size;
}
- if (dev->size > max_size) {
+ /* Exclude CRC */
+ if (dev->size > max_size + 2) {
return -ENOBUFS;
}
if (dev->size < 3) {
return -EBADMSG;
}
- sx126x_read_buffer(dev, rx_buffer_status.buffer_start_pointer, (uint8_t*)buf, dev->size);
+ /* Copy to buffer */
+ sx126x_read_buffer(dev, rx_buffer_status.buffer_start_pointer, (uint8_t*)buf, dev->size-2);
+ sx126x_read_buffer(dev, rx_buffer_status.buffer_start_pointer + dev->size-2, (uint8_t*)&exp_chksum, sizeof(exp_chksum));
+ chksum = ucrc16_calc_le(buf, dev->size-2,
+ UCRC16_CCITT_POLY_LE, chksum);
+ chksum = byteorder_htols(chksum).u16;
+ /* Validate checksum */
+ if (chksum != exp_chksum) {
+ puts("CRC_F");
+ return -EBADMSG;
+ }
+
DEBUG("[sx126x] first 3 bytes of received packet: %d %d %d\n", *(uint8_t*)buf, *((uint8_t*)buf+1), *((uint8_t*)buf+2));
return dev->size;
}
drivers/sx126x/sx126x_radio_hal.c
Outdated
| { | ||
| (void)hal; | ||
| sx126x_t *dev = hal->priv; | ||
| sx126x_set_sleep(dev, SX126X_SLEEP_CFG_COLD_START); |
There was a problem hiding this comment.
| sx126x_set_sleep(dev, SX126X_SLEEP_CFG_COLD_START); | |
| sx126x_set_sleep(dev, SX126X_SLEEP_CFG_WARM_START); |
Cold start does not have RAM retention, which means the transceiver losses its state every time it sleeps. It is possible to implement a wake up procedure that recovers the radio, but it's probably easier to simply go to warm sleep.
| dev->cad_params.cad_detect_min = 10, | ||
| dev->cad_params.cad_detect_peak = 22, | ||
| dev->cad_params.cad_symb_nb = SX126X_CAD_02_SYMB, | ||
| dev->cad_params.cad_timeout = 0x000F00; |
There was a problem hiding this comment.
is cad_timeout in symbols? If so, this value is too big. In case of IEEE 802.15.4, I would set it to 0x08
| | IEEE802154_CAP_IRQ_RX_START | ||
| | IEEE802154_CAP_IRQ_TX_DONE | ||
| | IEEE802154_CAP_IRQ_CCA_DONE | ||
| | IEEE802154_CAP_PHY_BPSK, |
There was a problem hiding this comment.
This driver exposes only PHY. We should consider adding a IEEE802154_CAP_PHY_LORA for this.
drivers/sx126x/sx126x_netdev.c
Outdated
| if (sx126x_is_stm32wl(dev)) { | ||
| #if IS_USED(MODULE_SX126X_STM32WL) | ||
| _dev = netdev; | ||
| event_callback_init(&sx126x_ev_callback, (void*)_sx126x_handler, (void*)netdev); |
There was a problem hiding this comment.
the netdev implementation already has its mechanism to offload IRQ. Therefore, this could break such mechanism. I propose to leave it as it was before
| void isr_subghz_radio(void) | ||
| { | ||
| /* Disable NVIC to avoid ISR conflict in CPU. */ | ||
| NVIC_DisableIRQ(SUBGHZ_Radio_IRQn); | ||
| NVIC_ClearPendingIRQ(SUBGHZ_Radio_IRQn); | ||
| netdev_trigger_event_isr(_dev); | ||
| cortexm_isr_end(); | ||
| } | ||
| #endif |
There was a problem hiding this comment.
this should still be there, otherwise the IRQ offloading mechanism of netdev won't work.
Since the HAL and netdev implementation will never be compiled together, you can just leave this function there
|
Hello @jia200x |
|
Hello @jia200x gnrc_netif: can't queue packet for sending. I think, multiple threads try to use the driver when it sends a reply. Maybe I have an issue in interrupt context when ACK is received (or not). |
|
Hello @jia200x ! |
| static void _dio1_isr(void *arg) | ||
| { | ||
| netdev_trigger_event_isr(arg); | ||
| _sx126x_handler(arg); |
There was a problem hiding this comment.
this is calling the handler on interrupt context
|
Hello,
Could you please send around the ping parameters? What is important to check is whether there are pktbuf leaks or not. You can use the
I will give it a look. |
Hmmm it might be easier to open a new PR and use this one as a reference. |
Default 1s ping . The issue shows randomly (mb). So I could get this after 5-7 packets and didn't after 10000. SF7BW125 ACK on. |
What does the output of |
I'll try to check it tomorrow. |
|
Hello, @jia200x
pktbuf after the problem:
I had waited about 15 minutes and the board didn't recover. But the board still have RF switch staying in TX state. |
|
hmmmm ok, there's definitely something wrong... I suspect there could be an issue with the IRQ processing, as this usually happen when the SubMAC is not informed about a TX Done event. |
|
I'm not sure but current ack realization by ztimer doesn't look safe :) Today I tested TCP connection by Sock API without ACK replies by sending a simple string with 2s delay (gnrc_border_router connected to the host machine). After a couple of hours TCP connection was broken and global address (given by dpcp6) was deprecated. And I'm not sure what happened. |
| /* Disable NVIC to avoid ISR conflict in CPU. */ | ||
| NVIC_DisableIRQ(SUBGHZ_Radio_IRQn); | ||
| NVIC_ClearPendingIRQ(SUBGHZ_Radio_IRQn); | ||
| event_post(EVENT_PRIO_MEDIUM, (event_t*)&sx126x_ev_callback); |
There was a problem hiding this comment.
I just realized this is being posted to a queue running in a thread that has different priority than the thread that runs the driver (gnrc_netif_ieee802154).
To solve that, you should probably set an event_queue_t pointer (e.g in sx126x_init) and do:
| event_post(EVENT_PRIO_MEDIUM, (event_t*)&sx126x_ev_callback); | |
| event_post(sx126x_dev->evq, (event_t*)&sx126x_ev_callback); |
This sx126x_dev variable should point to the (unique) device descriptor (available only when using the perioph version of the driver)
There was a problem hiding this comment.
could you check if that solves the issue?
There was a problem hiding this comment.
Hello, @jia200x
Yes, I check it in a couple of days.
There was a problem hiding this comment.
Hello @jia200x
Sorry for the delay.
I changed priority of the event thread by USEMODULE += event_thread_highest but it didn't solve the problem with acks. Probably I don't understand the way that you suggest. I just don't know how I should put the hal pointer to ISR handler except the event_callback mechanism. And I have not periph version of this module to check this idea with queue :(
| #endif | ||
| } | ||
|
|
||
| static void ack_timer_cb(void *arg) |
There was a problem hiding this comment.
The transceiver is so slow, that you might consider offloading this using event_post.
This was not possible with e.g the nrf802154, as the reply should take less than 192 us.
Doing that will definitely prevent any synchronization issues
Contribution description
It's a HAL implementation for Semtech SX126x driver. Basically created for RAK3172 module (STM32WLE5CC).
Testing procedure
gnrc_networking:
All basic functions work correctly.
tests/ieee802154_submac:
All should work correctly, if SF<=7 and BW>=125KHz, because ack timer can't wait more than ~65ms.
tests/ieee802154_hal:
Run and you should see:
ieee802154_hal transmission
Problems
Issues/PRs references
#19198
UDP: I accidently wiped the repo, so I create a new branch. Code is not changed.