rust-iot / rust-radio-sx127x

Rust driver for the Semtech SX127x series of Sub-GHz LoRa/ISM radio transceivers
Mozilla Public License 2.0
32 stars 16 forks source link

no_std newbie questions #11

Closed pdgilbert closed 3 years ago

pdgilbert commented 3 years ago

I realize this is work in progress, so maybe you are not ready for newbies like me. Don't hesitate to tell me I need to be patient, but if your direction is not consistent with what I am trying to do then it would be nice to know that I should be looking elsewhere.

I am trying to build the LoRa sending side of a sensor to base station connection. I have both sides working on R Pi with python, but would like to run at least the sending side with rust on no_std stm32xxx. So I do have a working receiving side to test against. I am currently using an RFM95 sx1276 style radio.

Thanks very much for your work on this crate.

#![no_std]
#![no_main]

#[cfg(debug_assertions)]
extern crate panic_semihosting;

#[cfg(not(debug_assertions))]
extern crate panic_halt;

// use nb::block;
use cortex_m_rt::entry;
use cortex_m_semihosting::*;
//use asm_delay::{ AsmDelay, bitrate, };

extern crate radio_sx127x;
use radio_sx127x::{prelude::*,
                   LoRaConfig,
                   LoRaChannel,
           };

extern crate radio;
use radio::{Receive, Transmit};

use stm32f1xx_hal::{prelude::*,   
                    pac::Peripherals, 
                    spi::{Spi, Spi1NoRemap},
                    delay::Delay,
                    gpio::{gpioa::{PA5, PA6, PA7}, Alternate, Input, Floating,  
                           gpioa::{PA0, PA1}, Output, PushPull},
                    device::SPI1,
                    }; 

const FREQUENCY: u32 = 915_000_000;  // frequency in hertz

#[entry]
fn main() -> !{

    // base setup  ( using stm32f1xx_hal )

    let cp = cortex_m::Peripherals::take().unwrap();
    let p  = Peripherals::take().unwrap();

    let mut rcc   = p.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(64.mhz()).pclk1(32.mhz()).freeze(&mut p.FLASH.constrain().acr);

    let mut afio = p.AFIO.constrain(&mut rcc.apb2);
    let mut gpioa = p.GPIOA.split(&mut rcc.apb2);
    let mut gpiob = p.GPIOB.split(&mut rcc.apb2);

    // stm32f1xx_hal spi setup
    let spi = Spi::spi1(
        p.SPI1,
        (gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl),  //   sck   on PA5
         gpioa.pa6.into_floating_input(&mut gpioa.crl),       //   miso  on PA6
         gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl)   //   mosi  on PA7
         ),
        &mut afio.mapr,
        sx127x_lora::MODE, 
        8.mhz(),    
        clocks, 
        &mut rcc.apb2,
        );

    let mut delay = Delay::new(cp.SYST, clocks);

    /// Create lora radio instance 

    let config = LoRaConfig {
        preamble_len: 0x8,
        symbol_timeout: 0x64,
        payload_len: PayloadLength::Variable,
        payload_crc: PayloadCrc::Enabled,
        frequency_hop: FrequencyHopping::Disabled,
        invert_iq: false,
    }

    //   other settings?
    //    lora.set_mode(sx127x_lora::RadioMode::Stdby).unwrap();
    //    set_tx_power(level, output_pin) level >17 => PA_BOOST. 
    //    lora.set_tx_power(17,1).unwrap();  
    //    lora.set_tx_power(15,1).unwrap();  

    let ch = LoRaChannel {
            freq: FREQUENCY as u32,        // frequency in hertz
            bw: Bandwidth::Bw125kHz,
            sf: SpreadingFactor::Sf7,
            cr: CodingRate::Cr4_8,        
            }

    //baud = 1000000 is this needed for spi or just USART

          (gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh),   // scl on PB8
           gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh)),  // sda on PB9

    let mut lora = radio_sx127x::spi(
            spi,                           //Spi,
            gpioa.pa1.into_push_pull_output(&mut gpioa.crl),   //CsPin,   on PA1
            gpiob.pb8.into_push_pull_output(&mut gpiob.crh),   //BusyPin, D00 on PB8
            gpiob.pb9.into_push_pull_output(&mut gpiob.crh)),  ///ReadyPin, D01 ? on PB9
            gpioa.pa0.into_push_pull_output(&mut gpioa.crl),   //ResetPin, on PA0
            delay,                         //Delay,
            &config,                           //&Config,
            ).unwrap();      // should handle error

    //DIO0  triggers RxDone/TxDone status.
    //DIO1  triggers RxTimeout and other errors status.
    //D02, D03 ?

    // or ?
    //   pub fn new(hal: Base, config: &Config) -> Result<Self, Error<CommsError, PinError>> {
    //let mut lora = Sx127x::new( w2, &config ).unwrap();   // should handle error

    // or ?
    // lora.lora_configure( config, channel: &LoRaChannel, ).unwrap()

    // print out configuration (for debugging)

    let v = lora.lora_get_config():
    hprintln!("configuration {}", v).unwrap()

    hprintln!("chammel    {}", lora.get_chammel()).unwrap();

    //hprintln!("mode         {}", lora.get_mode()).unwrap();
    //hprintln!("mode         {}", lora.read_register(Register::RegOpMode.addr())).unwrap();
    //hprintln!("bandwidth    {:?}", lora.get_signal_bandwidth()).unwrap();
    //hprintln!("coding_rate      {:?}",  lora.get_coding_rate_4()).unwrap();
    //hprintln!("spreading_factor {:?}",  lora.get_spreading_factor()).unwrap();
    //hprintln!("spreading_factor {:?}",  
    //hprintln!("invert_iq    {:?}",  lora.get_invert_iq()).unwrap();
    //hprintln!("tx_power     {:?}",  lora.get_tx_power()).unwrap();

    // transmit something

    //let buffer = &[0xaa, 0xbb, 0xcc];

    let message = "Hello, LoRa!";

    let mut buffer = [0;255];
    for (i,c) in message.chars().enumerate() {
        buffer[i] = c as u8;
        }

    lora.start_transmit(buffer).unwrap();    // should handle error

    if lora.check_transmit().unwrap() {
            hprintln!("TX complete");
            };

    loop {};
}
ryankurte commented 3 years ago

hey thanks for the questions! all of this should work (and we've been using it under linux for a couple of years now), but, there's also plenty of room for improvement and i have a bunch of updates that i haven't had a chance to make just yet, so, you might experience a bit of flux while I do this (sorry).

Should this crate work with no_std (I hope so)? There seems to be some indirect dependency on ansi_term, lazy_static and termcolor which I think need std so I get an error about can't find crate std. I am using master branch.

yep, it should! though it's possible i have made a mistake in there. for no_std it's always worth checking Cargo.toml to see what features are available, if you specify default-features=false it should drop all std requirements.

With no luck finding an example, I've looked at the integration test, but it uses std. Is there another example I should be looking at?

the utility is probably the best place to look, again this runs on linux so it's not quite no_std but the fundamentals are all the same.

Should the crate already work if I only have one radio, so do not have the problem described in issue #10 ?

yep, even multiple radios should be okay provided there's no multithreading.

I think the frequency is a u32 indicating hertz but somewhere I saw something that that seemed like it might be megahertz. Megahertz would mean only some channels can be used (867.0, 868.0, 915.0). Am I correct that the frequency in hertz?

it should be in Hz yes, you can see this in the docs

The integration test loads a configuration from a file. Thinking no_std I would like to just specify the configuration in the code, but I cannot figure out if I should be setting this with radio_sx127x::spi(), Sx127x::new(), or with lora_configure() after I have an object. I am confused about what is intended to be the user API and what are internal utilities. A quick reaction to my attempt (code below) would be much appreciated.

::spi() is used to construct an SPI based instance (as opposed to UART which i haven't yet implemented) so, that's the right one, and Config::default() should get you most of the way, which you can modify as required

I am also very unsure about the hardware wiring, so I have added comments in the code. From #7 I see that "busy" should DIO0, I guess on a gpio pin configured as push_pull_output? I am also unsure about the pin for "ready". Should it be another of the DIOx pins?

BUSY and READY need to be configured as input pins for the radio to indicate state to the micro. I don't have time to look at the code at the moment sorry but, hopefully that's enough to get you started!

pdgilbert commented 3 years ago

Thank you for the quick response. The default-features=false was the key to remove the need forstd, and the other hints have been very useful. I have resolved many things.

Also thank you for warning about "experience a bit of flux". I fully anticipate that. I hope when I get my examples working they might be helpful to you while you make changes.

I can setup LoRaChannel{} which forms part of radio_sx127x::device::Config{}, and then use that in Sx127x::spi() to get the object I use to transmit. And I can build the structure LoRaConfig{}, but it never gets used. The code now builds but since part of the configuration is not used I don't think it can work properly. (I had to switch from blue pill to stm32f411 because the code was too large to load.)

There is an associated function for the Sx127x::spi() object that looks like it might be able to set the LoRaConfig but it is not public:

 lora.lora_configure( config_lora, &config_ch ).unwrap();
    |          ^^^^^^^^^^^^^^ private associated function

Also, the associated function lora_get_config() is not public and it would be useful for debugging.

The code is below for reference.

If you know which DIOx connection should be connected for ReadyPin I would appreciate hearing.

Finally, I find I need use radio::Transmit; because the trait needs to be in scope to find methods start_transmit and check_transmit. Could this (and Receive) not be re-exported from radio_sx127x so the user does not need to use crate radio?

Thanks again.

#![no_std]
#![no_main]

#[cfg(debug_assertions)]
extern crate panic_semihosting;

#[cfg(not(debug_assertions))]
extern crate panic_halt;

// use nb::block;
use cortex_m_rt::entry;
use cortex_m_semihosting::*;
//use asm_delay::{ AsmDelay, bitrate, }; 

extern crate radio_sx127x;
use radio_sx127x::{prelude::*,
                   device::{Modem, Channel, PaConfig, },
                   device::lora::{LoRaConfig, LoRaChannel, Bandwidth, SpreadingFactor, CodingRate,
                                  PayloadLength, PayloadCrc, FrequencyHopping, },
           };

extern crate radio;
//use radio::{Receive, Transmit}; 
use radio::{Transmit}; // trait needs to be in scope to find  methods start_transmit and check_transmit.

use stm32f4xx_hal::{prelude::*,  
                    pac::Peripherals, 
                    spi::{Spi},
                    delay::Delay,
                    time::MegaHertz,
                    }; 

const FREQUENCY: u32 = 915_000_000;  // frequency in hertz

#[entry]
fn main() -> !{

    // base setup  ( using stm32f4xx_hal )

       let cp = cortex_m::Peripherals::take().unwrap();
       let p  = Peripherals::take().unwrap();

       let rcc   = p.RCC.constrain();
       let clocks = rcc.cfgr.sysclk(64.mhz()).pclk1(32.mhz()).freeze();

       let gpioa = p.GPIOA.split();
       let gpiob = p.GPIOB.split();

       let spi = Spi::spi1(
           p.SPI1,
           (gpioa.pa5.into_alternate_af5(),  // sck   on PA5
            gpioa.pa6.into_alternate_af5(),  // miso  on PA6
            gpioa.pa7.into_alternate_af5()   // mosi  on PA7
            ),
           sx127x_lora::MODE,
           MegaHertz(8).into(),
           clocks,
           );

       let delay = Delay::new(cp.SYST, clocks);

    // Create lora radio instance 

    let config_ch = LoRaChannel {
            freq: FREQUENCY as u32,        // frequency in hertz
            bw:   Bandwidth::Bw125kHz,
            sf:   SpreadingFactor::Sf7,
            cr:   CodingRate::Cr4_8,      
            };

//    let config_radio = Config::default() ;

    let config_radio = radio_sx127x::device::Config {
            modem:      Modem::LoRa(LoRaConfig::default()),
            channel:    Channel::LoRa(config_ch),
            pa_config:  PaConfig::default(),
            xtal_freq:  32000000,
            timeout_ms: 100,
            };

    let config_lora = LoRaConfig {
        preamble_len:   0x8,
        symbol_timeout: 0x64,
        payload_len:    PayloadLength::Variable,
        payload_crc:    PayloadCrc::Enabled,
        frequency_hop:  FrequencyHopping::Disabled,
        invert_iq:      false,
        };

    //   other settings?
    //    lora.set_mode(sx127x_lora::RadioMode::Stdby).unwrap();

    //baud = 1000000 is this needed for spi or just USART

    // open_drain_output is really input and output. BusyPin is just input, but I think this should work
    //      gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh),     
    // however, gives trait bound  ... InputPin` is not satisfied

    let mut lora = Sx127x::spi(
            spi,                                 //Spi,
            gpioa.pa1.into_push_pull_output(),         //CsPin,   on PA1
            gpiob.pb8.into_floating_input(),           //BusyPin, D00 on PB8
            gpiob.pb9.into_floating_input(),           //ReadyPin, D01 ? on PB9
            gpioa.pa0.into_push_pull_output(),         //ResetPin, on PA0
            delay,                               //Delay,
            &config_radio,                       //&Config,
            ).unwrap();      // should handle error

    //DIO0  triggers RxDone/TxDone status.
    //DIO1  triggers RxTimeout and other errors status.
    //D02, D03 ?

    //lora.lora_configure( config_lora, &config_ch ).unwrap();

    // print out configuration (for debugging)

//    let v = lora.lora_get_config();
//    hprintln!("configuration {}", v).unwrap();

//    hprintln!("channel      {}", lora.get_channel()).unwrap();

    // transmit something

    //let buffer = &[0xaa, 0xbb, 0xcc];

    let message = "Hello, LoRa!";

    let mut buffer = [0;255];
    for (i,c) in message.chars().enumerate() {
        buffer[i] = c as u8;
        }

    lora.start_transmit(&buffer).unwrap();    // should handle error

    if lora.check_transmit().unwrap() {
            hprintln!("TX complete").unwrap();
            };

    loop {};
}
ryankurte commented 3 years ago

I can setup LoRaChannel{} which forms part of radio_sx127x::device::Config{}, and then use that in Sx127x::spi() to get the object I use to transmit. And I can build the structure LoRaConfig{}, but it never gets used

you're on the right path. LoRaConfig goes in Config::Modem(Modem::Lora(..)), same as you've done with LoRaChannel.

(I had to switch from blue pill to stm32f411 because the code was too large to load.)

are you building in release mode? this makes a huge difference, though rust builds can be kindof large. you may find useful guidance here or here, and it's worth noting that any print functions or panics (ie. anywhere you have .unwrap()) can add rather a lot of overhead to the binary. there's also cargo-bloat for investigating this but i haven't used it heavily.

If you know which DIOx connection should be connected for ReadyPin I would appreciate hearing.

Busy is DIO0, Ready is DIO1 (and these should be at some point renamed...)

Could this (and Receive) not be re-exported from radio_sx127x so the user does not need to use crate radio?

Probably could be from the prelude yeah, the radio crate is somewhat likely to be required anyway though.

pdgilbert commented 3 years ago

Thank you for all your help. I have now tested with stm32f411 on 900Mhz band channels 12 and 02 (915_000_000 hz and 907_400_000hz) and can receive transmission with my base station python code on R Pi. That is to say, it not only compiles but also works. For reference my code is below, but I do realize your crate is in flux and will probably change soon.

Thank you for the pointer about building in release mode for bluepill. Yes, I can fit the code on bluepill this way. I have not yet done a test to see if it actually works, but will report back when I do.

A remaining question is regarding using a borrowed copy of delay. With some other crates I have been able to pass &delay in the setup, but I have not been able to do that with Sx127x::spi. This means I had to set up a second delay function to slow down my loop. Is there another way to do this?

#![no_std]
#![no_main]

#[cfg(debug_assertions)]
extern crate panic_semihosting;

#[cfg(not(debug_assertions))]
extern crate panic_halt;

use cortex_m_rt::entry;
use cortex_m_semihosting::*;

use asm_delay::{ AsmDelay, bitrate, };

extern crate radio_sx127x;
use radio_sx127x::{prelude::*,
                   device::{Modem, Channel, PaConfig, },
                   device::lora::{LoRaConfig, LoRaChannel, Bandwidth, SpreadingFactor, CodingRate,
                                  PayloadLength, PayloadCrc, FrequencyHopping, },
           };

extern crate radio;
use radio::{Transmit}; // trait needs to be in scope to find  methods start_transmit and check_transmit.

use stm32f4xx_hal::{prelude::*,  
                    pac::Peripherals, 
                    spi::{Spi},
                    delay::Delay,
                    time::MegaHertz,
                    }; 

const FREQUENCY: u32 = 907_400_000;     // frequency in hertz ch_12: 915_000_000, ch_2: 907_400_000

#[entry]
fn main() -> !{

    // base setup  ( using stm32f4xx_hal )

    let cp = cortex_m::Peripherals::take().unwrap();
    let p  = Peripherals::take().unwrap();

    let rcc   = p.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(64.mhz()).pclk1(32.mhz()).freeze();

    let gpioa = p.GPIOA.split();
    let gpiob = p.GPIOB.split();

    let spi = Spi::spi1(
        p.SPI1,
        (gpioa.pa5.into_alternate_af5(),  // sck   on PA5
         gpioa.pa6.into_alternate_af5(),  // miso  on PA6
         gpioa.pa7.into_alternate_af5()   // mosi  on PA7
         ),
        sx127x_lora::MODE,
        MegaHertz(8).into(),
        clocks,
        );

    let delay  = Delay::new(cp.SYST, clocks);
    let mut delay2 = AsmDelay::new(bitrate::U32BitrateExt::mhz(32));

    // Create lora radio instance 

    let config_ch = LoRaChannel {
            freq: FREQUENCY as u32,        // frequency in hertz
            bw:   Bandwidth::Bw125kHz,
            sf:   SpreadingFactor::Sf7,
            cr:   CodingRate::Cr4_8,      
            };

    let config_lora = LoRaConfig {
        preamble_len:   0x8,
        symbol_timeout: 0x64,
        payload_len:    PayloadLength::Variable,
        payload_crc:    PayloadCrc::Enabled,
        frequency_hop:  FrequencyHopping::Disabled,
        invert_iq:      false,
        };

    //let config_radio = Config::default() ;

    let config_radio = radio_sx127x::device::Config {
            modem:      Modem::LoRa(config_lora),
            channel:    Channel::LoRa(config_ch),
            pa_config:  PaConfig::default(),
            xtal_freq:  32000000,
            timeout_ms: 100,
            };

    let mut lora = Sx127x::spi(
            spi,                       //Spi,
            gpioa.pa1.into_push_pull_output(),         //CsPin,         on PA1
            gpiob.pb8.into_floating_input(),           //BusyPin,  DIO0 on PB8
            gpiob.pb9.into_floating_input(),           //ReadyPin, DIO1 on PB9
            gpioa.pa0.into_push_pull_output(),         //ResetPin,      on PA0
            delay,                     //Delay,
            &config_radio,                 //&Config,
            ).unwrap();      // should handle error

    //hprintln!("lora  object returned.").unwrap();

    // print out configuration (for debugging)
    // let v = lora.lora_get_config();
    // hprintln!("configuration {}", v).unwrap();
    // hprintln!("chammel     {}", lora.get_chammel()).unwrap();

    // transmit something 
    let message = b"Hello, LoRa!";

    hprintln!("entering loop. ^C to quit.").unwrap();

    loop {
       lora.start_transmit(message).unwrap();    // should handle error

       match lora.check_transmit() {
           Ok(b) => if b {hprintln!("TX complete").unwrap()} 
                    else {hprintln!("TX not complete").unwrap()},

           Err(_err) => hprintln!("Error in lora.check_transmit(). Should return True or False.").unwrap(),
           };

       delay2.delay_ms(5000u32);
       };
}
ryankurte commented 3 years ago

I have now tested with stm32f411 on 900Mhz band channels 12 and 02 (915_000_000 hz and 907_400_000hz) and can receive transmission with my base station python code on R Pi

wow v exciting! what's the plan now?

A remaining question is regarding using a borrowed copy of delay. With some other crates I have been able to pass &delay in the setup, but I have not been able to do that with Sx127x::spi. This means I had to set up a second delay function to slow down my loop. Is there another way to do this?

unfortunately DelayMs takes &mut self so you do need to transfer ownership to keep the borrow checker happy... that said the Sx127x instance re-implements this trait so you should be able to call lora.delay_ms(500u32);?

pdgilbert commented 3 years ago

I switched to using lora.delay_ms(5000u32); and that works fine.

Regarding plans, I have revised this example so it works with multiple embedded stm hal crates and will shortly check those that I have hardware for. I am also working on an example that reads and transmits GPS and another to receive LoRa. I have a CI generated scoreboard for my examples at https://pdgilbert.github.io/eg_stm_hal/. (See lora_spi_* examples in the second table.) There is still some cleaning up to do.

If you want to put these in an examples/directory in your crate or rework them into tests please do not hesitate to use them. If you like I could fork your repository, addexamples/ and send a pull request.

I do have some other issues/questions but they are enough different that I think it is best to close this issue.

ryankurte commented 3 years ago

If you want to put these in an examples/ directory in your crate or rework them into tests please do not hesitate to use them. If you like I could fork your repository, add examples/ and send a pull request.

some examples would be super neat! i wonder whether there's a standard-ish board/combination that's easy to get hold of to reproduce / use these.

pdgilbert commented 3 years ago

I am not aware of pre-build standard-ish boards with LoRa and GPS. So there is some wiring involved, but the pieces I am using are all pretty standard and cheap. (Roughly $5 each for the three pieces.) I am planning on documenting this more on my github site, but for an example in your crate I would put more comments in the files. The code should actually compile for many (most?) of the embedded-hal crates and, sometime, I will test with other GPS and LoRa. BTW, I just ran the GPS version of the code on battery powered blackpill stm32f411 without semihosting and it works.