Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with APRS using AFSK on the RPi Pico #1230

Closed
mosa11aei opened this issue Sep 24, 2024 · 9 comments · Fixed by #1239
Closed

Issue with APRS using AFSK on the RPi Pico #1230

mosa11aei opened this issue Sep 24, 2024 · 9 comments · Fixed by #1239
Labels
enhancement New feature or request resolved Issue was resolved (e.g. bug fixed, or feature implemented)

Comments

@mosa11aei
Copy link
Contributor

mosa11aei commented Sep 24, 2024

Hi there,

I am trying to transmit APRS packets using AFSK on the RPi Pico (RP2040) and SX1278 module. I am starting from the HAL provided in the examples/NonArduino/Pico.

My first step was to override the tone and noTone functions so that they can generate square waves at given frequencies:

// in PicoHal.h
void set_pwm_frequency(uint gpio, unsigned int frequency) {
    uint slice_num = pwm_gpio_to_slice_num(gpio);

    // Calculate the PWM wrap value based on the desired frequency
    pwm_config config = pwm_get_default_config();
    uint32_t clock_freq = clock_get_hz(clk_sys);

    float divider = clock_freq / (frequency * 65536.0);
    pwm_config_set_clkdiv(&config, divider);
    pwm_init(slice_num, &config, true);

    uint32_t wrap = clock_freq / frequency;

    uint16_t level = (1 << 15);
    pwm_set_gpio_level(gpio, level);
}

void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) override {
      printf("Using pin %d at freq %d...", pin, frequency);
      gpio_set_function(pin, GPIO_FUNC_PWM);
      set_pwm_frequency(pin, frequency);

      if (duration > 0){
              sleep_ms(duration);
              pwm_set_enabled(pwm_gpio_to_slice_num(pin), false);
      }
}

void noTone(uint32_t pin) override {
      pwm_set_enabled(pwm_gpio_to_slice_num(pin), false);
}

I have tried the AFSK_Tone and AFSK_Imperial_March examples, and they work fine with this implementation. However, when I try to transmit APRS using AFSK, it sounds like its just using FSK: like it's not using my tone generators at all. Here's my code for that"

// define pins to be used
#define SPI_PORT spi1
#define SPI_MISO 12
#define SPI_MOSI 11
#define SPI_SCK 10

#define RFM_NSS 13
#define RFM_RST 22
#define RFM_DIO0 14
#define RFM_DIO1 15

#include <pico/stdlib.h>
#include <cstdlib>
// include the library
#include <RadioLib.h>
#include "melody.h"
#include "hardware/pwm.h"

// include the hardware abstraction layer
#include "PicoHal.h"

PicoHal* hal = new PicoHal(SPI_PORT, SPI_MISO, SPI_MOSI, SPI_SCK);
SX1278 radio = new Module(hal, RFM_NSS, RFM_DIO0, RFM_RST, RFM_DIO1);

AFSKClient audio(&radio, 28);
AX25Client ax25(&audio);
APRSClient aprs(&ax25);

int main() {
  stdio_init_all();

  printf("[SX1278] Initializing ... ");
  int state = radio.beginFSK(434.0, 48, 2.2);
  if (state != RADIOLIB_ERR_NONE) {
    printf("failed, code %d\n", state);
    return(1);
  }

  state = ax25.begin("MXLMX");
  if (state != RADIOLIB_ERR_NONE) {
    printf("failed, code %d\n", state);
    return(1);
  }

  aprs.begin('>');

  // loop forever
  for(;;) {
    // send a packet
    printf("[SX1278] Transmitting tone ... ");
    int state = aprs.sendPosition("N0CALL", 0, "4911.67N", "01635.96E");
    
    sleep_ms(3000);
  }

  return(0);
}

I have tried different configurations of the SX1278, to no avail. I have checked the state at radio initialization, ax25 initialization, and aprs initialization. Nothing wrong there. This is what a waterfall plot of what I'm seeing looks like:

image

I know this library is targeted to more Arduino users. However, I feel like I am at the finish line with getting this to work on the RPi Pico and SX1278. Is there anything glaring I'm missing? I'll include the full HAL file as a comment to this issue so it doesn't get too unwieldy. Thanks!

@mosa11aei
Copy link
Contributor Author

mosa11aei commented Sep 24, 2024

Full HAL implementation

#ifndef PICO_HAL_H
#define PICO_HAL_H

// include RadioLib
#include <RadioLib.h>

// include the necessary Pico libraries
#include <pico/stdlib.h>
#include "hardware/spi.h"
#include "hardware/timer.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"
#include "pico_tone.hpp"

// create a new Raspberry Pi Pico hardware abstraction 
// layer using the Pico SDK
// the HAL must inherit from the base RadioLibHal class
// and implement all of its virtual methods
class PicoHal : public RadioLibHal {
public:
  PicoHal(spi_inst_t *spiChannel, uint32_t misoPin, uint32_t mosiPin, uint32_t sckPin, uint32_t spiSpeed = 500 * 1000)
    : RadioLibHal(GPIO_IN, GPIO_OUT, 0, 1, GPIO_IRQ_EDGE_RISE, GPIO_IRQ_EDGE_FALL),
    _spiChannel(spiChannel),
    _spiSpeed(spiSpeed),
    _misoPin(misoPin),
    _mosiPin(mosiPin),
    _sckPin(sckPin) {
    }

  void init() override {
    stdio_init_all();
    spiBegin();
  }

  void term() override {
    spiEnd();
  }

  // GPIO-related methods (pinMode, digitalWrite etc.) should check
  // RADIOLIB_NC as an alias for non-connected pins
  void pinMode(uint32_t pin, uint32_t mode) override {
    if (pin == RADIOLIB_NC) {
      return;
    }

    gpio_init(pin);
    gpio_set_dir(pin, mode);
  }

  void digitalWrite(uint32_t pin, uint32_t value) override {
    if (pin == RADIOLIB_NC) {
      return;
    }

    gpio_put(pin, (bool)value);
  }

  uint32_t digitalRead(uint32_t pin) override {
    if (pin == RADIOLIB_NC) {
      return 0;
    }

    return gpio_get(pin);
  }

  void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override {
    if (interruptNum == RADIOLIB_NC) {
      return;
    }

    gpio_set_irq_enabled_with_callback(interruptNum, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, (gpio_irq_callback_t)interruptCb);
  }

  void detachInterrupt(uint32_t interruptNum) override {
    if (interruptNum == RADIOLIB_NC) {
      return;
    }

    gpio_set_irq_enabled_with_callback(interruptNum, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, false, NULL);
  }

  void delay(unsigned long ms) override {
    sleep_ms(ms);
  }

  void delayMicroseconds(unsigned long us) override {
    sleep_us(us);
  }

  unsigned long millis() override {
    return to_ms_since_boot(get_absolute_time());
  }

  unsigned long micros() override {
    return to_us_since_boot(get_absolute_time());
  }

  long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override {
    if (pin == RADIOLIB_NC) {
      return 0;
    }

    this->pinMode(pin, GPIO_IN);
    uint32_t start = this->micros();
    uint32_t curtick = this->micros();

    while (this->digitalRead(pin) == state) {
      if ((this->micros() - curtick) > timeout) {
        return 0;
      }
    }

    return (this->micros() - start);
  }

void set_pwm_frequency(uint gpio, unsigned int frequency) {
    uint slice_num = pwm_gpio_to_slice_num(gpio);

    // Calculate the PWM wrap value based on the desired frequency
    pwm_config config = pwm_get_default_config();
    uint32_t clock_freq = clock_get_hz(clk_sys);

    float divider = clock_freq / (frequency * 65536.0);
    pwm_config_set_clkdiv(&config, divider);
    pwm_init(slice_num, &config, true);

    uint32_t wrap = clock_freq / frequency;

    // pwm_set_wrap(slice_num, wrap - 1);
    // pwm_set_chan_level(slice_num, pwm_gpio_to_channel(gpio), wrap / 2); // 50% duty cycle
    // pwm_set_enabled(slice_num, true);

    uint16_t level = (1 << 15);
    pwm_set_gpio_level(gpio, level);
}

  void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) override {
	printf("Using pin %d at freq %d...", pin, frequency);

	gpio_set_function(pin, GPIO_FUNC_PWM);
	set_pwm_frequency(pin, frequency);

        if (duration > 0){
		sleep_ms(duration);
		pwm_set_enabled(pwm_gpio_to_slice_num(pin), false);
	}
  }

  void noTone(uint32_t pin) override {
	pwm_set_enabled(pwm_gpio_to_slice_num(pin), false);
  }

  void spiBegin() {
    spi_init(_spiChannel, _spiSpeed);
    spi_set_format(_spiChannel, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);

    gpio_set_function(_sckPin, GPIO_FUNC_SPI);
    gpio_set_function(_mosiPin, GPIO_FUNC_SPI);
    gpio_set_function(_misoPin, GPIO_FUNC_SPI);
  }

  void spiBeginTransaction() {}

  void spiTransfer(uint8_t *out, size_t len, uint8_t *in) {
    spi_write_read_blocking(_spiChannel, out, in, len);
  }

  void spiEndTransaction() {}

  void spiEnd() {
    spi_deinit(_spiChannel);
  }

protected:
  Tone tonePin = Tone(28);

private:
  // the HAL can contain any additional private members
  spi_inst_t *_spiChannel;
  uint32_t _spiSpeed;
  uint32_t _misoPin;
  uint32_t _mosiPin;
  uint32_t _sckPin;
};

#endif

@jgromes
Copy link
Owner

jgromes commented Sep 24, 2024

It looks like the signal is not modulated at all. That could be caused by a few things:

  1. DIO2 not being connected or not being able to ouput PWM - I'm guessing you have ruled that out by testing the tone/melody.
  2. Pico not being able to change frequency at the required rate. The melody changes only a couple of times per second at most, APRS is at 1200 bps. Related to this, the ESP32 had an issue where the tone output would break if called twice with the same value, that's why ArduinoHal::tone for ESP32 only sets new tone when the frequency changes.
  3. This: int state = radio.beginFSK(434.0, 48, 2.2); is weird. Why are you setting it to 48 kbps?

@mosa11aei
Copy link
Contributor Author

  1. Yep, this should be ruled out since tone and melody worked.
  2. Interesting, I'll give this a try. I'll add some state in the Hal class that keeps track of the current frequency, and only changes it if it is different than the current frequency.
  3. This was something I encountered when debugging. Just something I found on a forum, I have tried: radio.beginFSK(434, 1.2) as I thought this might put it at 1200 baud. I have also tried just radio.beginFSK(434) and radio.beginFSK().

@jgromes
Copy link
Owner

jgromes commented Sep 24, 2024

It might also be a good idea to check the PWM output is as expceted, i.e. a sequence of 1200 Hz and 2200 Hz tones, e.g. with an oscilloscope.

@mosa11aei
Copy link
Contributor Author

Good call with checking with oscilloscope. It looks like my code does not work for 2200Hz; PWM outputs ~39Hz. I can sweep through 400-1200 fine though. Let me work through this error and see if I get anywhere.

@mosa11aei
Copy link
Contributor Author

Ok, made modifications to the code, and I can transmit both 1200 and 2200 Hz tones fine. However, they're a little off-skew; so it's more like 1.21kHz and 2.21kHz. I don't think that should be a huge issue, should it?

This is what I'm transmitting, yet nothing being received by my receiver (Kenwood radio). Any other thoughts?

image

@jgromes
Copy link
Owner

jgromes commented Sep 24, 2024

From the SDR software screenshot it looks like you're using wideband FM mode. Can you try with narrowband FM?

@mosa11aei
Copy link
Contributor Author

As in 2500Hz? Unfortunately CubicSDR does not let me go that low. It exits prematurely. I don't think it can handle narrowband. Nonetheless, I am not receiving anything on my Kenwoods, which should be on narrowband.

@mosa11aei
Copy link
Contributor Author

@jgromes, you were right in your second thought! The RPi Pico took too long to switch between PWM frequencies. Too long as in 1ms compares to 800us.

I changed the way I did tones, and now it works great! See #1239.

@jgromes jgromes added enhancement New feature or request resolved Issue was resolved (e.g. bug fixed, or feature implemented) labels Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request resolved Issue was resolved (e.g. bug fixed, or feature implemented)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants