LoRa Page: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
 
(20 intermediate revisions by the same user not shown)
Line 52: Line 52:
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
cargo install cargo-generate
cargo install cargo-generate
# Name the project helloworld
 
# Now run the command and it will prompt for name of project and esp version
# For my Lora controller is Lora T3 v1.6.1 it is ESP32 pico D4 [https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/boards/lilygo/ttgo_lora32/doc/index.html here]
cargo generate esp-rs/esp-template
 
# cd into the resulting project
cd helloworld
cd helloworld
cargo run
cargo run
</syntaxhighlight>
</syntaxhighlight>
On '''10 April 2025''' this all worked with no issues. I have left the other info below in case thing don't go so well next time.<br>
<br>
So it did not run. This is because we used espup install and we need to set up the environment. We do this running
So it did not run. This is because we used espup install and we need to set up the environment. We do this running
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
Line 81: Line 88:
And running the hello world example. For me getting the various elements running together gives me joy. Probably not that special for some people.<br>
And running the hello world example. For me getting the various elements running together gives me joy. Probably not that special for some people.<br>
[[File:Xtensa hello world.png]]
[[File:Xtensa hello world.png]]
=Time for some Pictures=
Well we now have a the basic working. Next we need a picture. Once this is working Debugging will be quite easy or so he said
<syntaxhighlight lang='bash'>
cargo add ssd1306
cargo add  embedded_graphics
</syntaxhighlight>
I run the code again after installing anything. Paranoid or what. The samples kind of worked but here is the code which work on this specific day and hour.
<syntaxhighlight lang='rust'>
fn main() -> ! {
    let peripherals = Peripherals::take();
    let system = SystemControl::new(peripherals.SYSTEM);
    let clocks = ClockControl::max(system.clock_control).freeze();
    let delay = Delay::new(&clocks);
    esp_println::logger::init_logger_from_env();
    let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
    // Create a new peripheral object with the described wiring
    // and standard I2C clock speed
    let i2c = I2C::new(
        peripherals.I2C0,
        io.pins.gpio21,
        io.pins.gpio22,
        100.kHz(),
        &clocks,
        None,
    );
    let interface = I2CDisplayInterface::new(i2c);
    let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
        .into_buffered_graphics_mode();
    display.init().unwrap();
    let text_style = MonoTextStyleBuilder::new()
        .font(&FONT_6X10)
        .text_color(BinaryColor::On)
        .build();
    Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top)
        .draw(&mut display)
        .unwrap();
    Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top)
        .draw(&mut display)
        .unwrap();
    display.flush().unwrap();
    loop {
        log::info!("Hello world!");
        delay.delay(500.millis());
    }
}
</syntaxhighlight>
There is always a trick it seems to eat your weekend away. For this piece of code it was I needed to enable the embedded-hal-02 feature flag in the esp-hal
<syntaxhighlight lang='toml'>
[dependencies]
embedded-graphics = "0.8.1"
esp-backtrace = { version = "0.13.0", features = [
    "esp32",
    "exception-handler",
    "panic-handler",
    "println",
] }
esp-hal = { version = "0.19.0", features = ["esp32", "embedded-hal-02"] }
esp-println = { version = "0.10.0", features = ["esp32", "log"] }
log = { version = "0.4.21" }
ssd1306 = "0.8.4"
</syntaxhighlight>
And here we are<br>
[[File:Lora ssd1306 hello.png]]<br>
=Time to Get Connected=
Let see if we can connect to the Wifi before we go for LoRa. This probably is more a case of understanding Rust and WiFi libraries so lets do it.
==Configure Wifi Peripheral==
We need to add and configure the wifi crate
<syntaxhighlight lang="toml">
esp-wifi = { version = "0.7.1", features = ["esp32", "wifi-default"] }
heapless = "0.8.0"
</syntaxhighlight>
The biggest part of getting this to work was dealing with the timer. The Xtensa build uses a different timer, as your would expect, than RISC-V. Searching the Web, as ever solved this.
<syntaxhighlight lang="rust">
    let timer = esp_hal::timer::PeriodicTimer::new(
        esp_hal::timer::timg::TimerGroup::new(peripherals.TIMG1, &clocks, None)
            .timer0
            .into(),
    );
    let init = initialize(
        EspWifiInitFor::Wifi,
        timer,
        Rng::new(peripherals.RNG),
        peripherals.RADIO_CLK,
        &clocks,
    )
    .unwrap();
    // Configure Wifi
    let wifi = peripherals.WIFI;
    let mut socket_set_entries: [SocketStorage; 3] = Default::default();
    let (iface, device, mut controller, sockets) =
        create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap();
</syntaxhighlight>
==Configure Authentication==
This also was a little bit harder than expected. So set the password and not have it appear in github I used
<syntaxhighlight lang="rust">
const SSID: &str = env!("SSID");
const PASSWORD: &str = env!("PASSWORD");
</syntaxhighlight>
And for the SSID this worked well be doing
<syntaxhighlight lang="bash">
export SSID=YourKidding
</syntaxhighlight>
But for the password not so well because of the special characters.
<syntaxhighlight lang="bash">
export SSID=YourKidding!!!
</syntaxhighlight>
The fix was easy, just set the values outside of VS Code. Anyway here is the code for Authentication
<syntaxhighlight lang="rust">
    // Configure Authentification Configuration
    let mut auth_method = AuthMethod::WPA2Personal;
    if PASSWORD.is_empty() {
        auth_method = AuthMethod::None;
    }
    let client_config = Configuration::Client(ClientConfiguration {
        ssid: SSID.try_into().unwrap(),
        password: PASSWORD.try_into().unwrap(),
        auth_method,
        ..Default::default()
    });
    let res = controller.set_configuration(&client_config);
    log::info!("Wi-Fi set_configuration returned {:?}", res);
</syntaxhighlight>
==Connect to the Controller==
No drama doing this in no_std
<syntaxhighlight lang="rust">
    controller.start().unwrap();
    log::info!("Is wifi started: {:?}", controller.is_started());
    log::info!("Start Wifi Scan");
    let res: Result<(heapless::Vec<AccessPointInfo, 10>, usize), WifiError> = controller.scan_n();
    if let Ok((res, _count)) = res {
        for ap in res {
            log::info!("{:?}", ap);
        }
    }
    log::info!("{:?}", controller.get_capabilities());
    log::info!("Wi-Fi connect: {:?}", controller.connect());
    // Wait to get connected
    log::info!("Wait to get connected");
    loop {
        let res = controller.is_connected();
        match res {
            Ok(connected) => {
                if connected {
                    break;
                }
            }
            Err(err) => {
                log::info!("{:?}", err);
                loop {}
            }
        }
    }
    log::info!("{:?}", controller.is_connected());
</syntaxhighlight>
==Get an IP address==
Again no issue with this part
<syntaxhighlight lang="rust">
    // Wait for getting an ip address
    let wifi_stack = WifiStack::new(iface, device, sockets, current_millis);
    log::info!("Wait to get an ip address");
    loop {
        wifi_stack.work();
        if wifi_stack.is_iface_up() {
            log::info!("got ip {:?}", wifi_stack.get_ip_info());
            break;
        }
    }
</syntaxhighlight>
==Revisiting in 2025==
This is now on my git server with examples for
*Connecting to a gateway and serving a page [https://git.bibble.co.nz/bibble235/esp32-static-ip esp32-static-ip]
*Creating an AP and serving a page [https://git.bibble.co.nz/bibble235/esp-emb-wifi esp-emb-wifi]
=SD Card Reader=
This is the first time dabbling with a card reader so maybe my page will not be updated for a while - hang on. Back now. This was relatively easy. Just create an SPI device and use the MMC library.
==Create the SPI Device==
This is always a trial the first time through. The trick to this is understanding that to make a bus you need the embedded-hal-bus
<syntaxhighlight lang="toml">
embedded-hal-bus = "0.2.0"
</syntaxhighlight>
This allows you to pass a bus to the SdCard
<syntaxhighlight lang="rust">
    // Set up the SPI bus
    let sclk = io.pins.gpio14;
    let miso = io.pins.gpio2;
    let mosi = io.pins.gpio15;
    let cs = Output::new(io.pins.gpio13, Level::Low);
    log::info!("Exclusive access to the SPI bus selected");
    let spi_driver =
        Spi::<SPI2, FullDuplexMode>::new(peripherals.SPI2, 1_000u32.kHz(), SpiMode::Mode0, &clocks)
            .with_sck(sclk)
            .with_mosi(mosi)
            .with_miso(miso);
    let time_source = FakeTimesource();
    let sd_spi_device = ExclusiveDevice::new(spi_driver, cs, delay).unwrap();
    read_mmc(sd_spi_device, delay, time_source).unwrap();
</syntaxhighlight>
==Reading the Card Reader==
First time through for me but this too was straight forward. Place a file in the root directory formatted as fat. I fsck /dev/sdb1 to make sure all good. But it worked with no issue. Add the library to the toml
<syntaxhighlight lang="toml">
embedded-sdmmc = "0.8.0"
</syntaxhighlight>
And use the example from the site
<syntaxhighlight lang="rust">
fn read_mmc<T>(
    spi: ExclusiveDevice<Spi<SPI2, FullDuplexMode>, Output<GpioPin<13>>, Delay>,
    delay: Delay,
    ts: T,
) -> Result<(), Error<SdCardError>>
where
    T: TimeSource,
{
    let sdcard = SdCard::new(spi, delay);
    // let sdcard = SdCard::new(spi, delay);
    log::info!("Card size is {} bytes", sdcard.num_bytes()?);
    let mut volume_mgr = VolumeManager::new(sdcard, ts);
    let mut volume0 = volume_mgr.open_volume(VolumeIdx(0))?;
    log::info!("Volume 0: {:?}", volume0);
    let mut root_dir = volume0.open_root_dir()?;
    let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", Mode::ReadOnly)?;
    while !my_file.is_eof() {
        let mut buffer = [0u8; 32];
        let num_read = my_file.read(&mut buffer)?;
        for b in &buffer[0..num_read] {
            log::info!("{}", *b as char);
        }
    }
    Ok(())
}
</syntaxhighlight>
And here is the proof<br>
[[File:Esp32 mmc.png]]

Latest revision as of 03:31, 16 April 2025

Introduction

I know nothing of LoRa but like putting boxes together and getting them to talk so lets do it.

Equipment

So I thought I would start with two LilyGO Model T3 v1.6.1. I think they were $30 NZD

Environment

Operating System

Been Ubuntu for a while. Mainly because it still supports team and I guess I know the most about it. So for now Ubuntu 24.04

IDE Arduino 2.3.3

Well I dislike the Arduino software with a passion. I know it is free and someone has clearly put a lot of effort into making it and a lot of people use it but it is just horrible to use in a project for me. All my problem not yours.

I did try with both the snap and the apt installs for version 1 but could not get it to work. Then second approach resulted in having to clear the /tmp directory each time I built. So went for the zip file. My preference is apt or snap as you can put the software on and take it off and (hopefully) you are back to where you started

Always a challenge run Arduino for me but the way I did it this time was download it from https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.zip

I unzipped it to and set the permission. No idea it this is a good thing or bad so don't do it on something you care about

cd ~/bin
unzip arduino-ide_nightly-20240630_Linux_64bit.zip
cd ~/bin/arduino-ide_nightly-20240630_Linux_64bit
sudo chown root:root chrome-sandbox 
sudo chmod 4755 chrome-sandbox
# Put this in your bash profile ~/.bashrc or run here 
export PATH=~/bin/arduino-ide_nightly-20240630_Linux_64bit:$PATH

First Attempt

Well the first thing to do is to take something that works and demonstrate it works on you environment/equipment for me I started with https://github.com/Xinyuan-LilyGo/TTGO-LoRa-Series

git clone https://github.com/Xinyuan-LilyGo/TTGO-LoRa-Series
cd LoRa
arduino-ide LoRa.ino

Just adding the line

#define LORA_SENDER 1

Make the code the sender. I compiled the two versions and all worked.

Let's get Rusty

Now we need to get rust

cargo install espup
espup install
rustup toolchain list
# List should show
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
esp (override)

Now lest make a hello-world

cargo install cargo-generate

# Now run the command and it will prompt for name of project and esp version 
# For my Lora controller is Lora T3 v1.6.1 it is ESP32 pico D4 [https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/boards/lilygo/ttgo_lora32/doc/index.html here]
cargo generate esp-rs/esp-template

# cd into the resulting project
cd helloworld
cargo run

On 10 April 2025 this all worked with no issues. I have left the other info below in case thing don't go so well next time.

So it did not run. This is because we used espup install and we need to set up the environment. We do this running

. $HOME/export-esp.sh

# And again
cargo run
# could not execute process `espflash flash

Still not working. This is the nature of using this environment. I quite like the chase of it and the gaining of knowledge when the solution is explained. So next

cargo install espflash
# warning: build failed, waiting for other jobs to finish...
# error: failed to compile `espflash v3.1.1`, intermediate artifacts can be found at `/tmp/cargo-install20pGtu`.

If you review previous errors you will see

# error: failed to run custom build command for `libudev-sys v0.1.4`
# And googling gives
# https://stackoverflow.com/questions/73965977/error-failed-to-run-custom-build-command-for-libudev-sys-v0-1-4
sudo apt-get install -y  libudev-dev

Now we have another go and finally it is flashing to the ESP32

And running the hello world example. For me getting the various elements running together gives me joy. Probably not that special for some people.

Time for some Pictures

Well we now have a the basic working. Next we need a picture. Once this is working Debugging will be quite easy or so he said

cargo add ssd1306
cargo add  embedded_graphics

I run the code again after installing anything. Paranoid or what. The samples kind of worked but here is the code which work on this specific day and hour.

fn main() -> ! {
    let peripherals = Peripherals::take();
    let system = SystemControl::new(peripherals.SYSTEM);

    let clocks = ClockControl::max(system.clock_control).freeze();
    let delay = Delay::new(&clocks);

    esp_println::logger::init_logger_from_env();

    let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);

    // Create a new peripheral object with the described wiring
    // and standard I2C clock speed
    let i2c = I2C::new(
        peripherals.I2C0,
        io.pins.gpio21,
        io.pins.gpio22,
        100.kHz(),
        &clocks,
        None,
    );

    let interface = I2CDisplayInterface::new(i2c);
    let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
        .into_buffered_graphics_mode();
    display.init().unwrap();

    let text_style = MonoTextStyleBuilder::new()
        .font(&FONT_6X10)
        .text_color(BinaryColor::On)
        .build();

    Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top)
        .draw(&mut display)
        .unwrap();

    Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top)
        .draw(&mut display)
        .unwrap();

    display.flush().unwrap();
    loop {
        log::info!("Hello world!");
        delay.delay(500.millis());
    }
}

There is always a trick it seems to eat your weekend away. For this piece of code it was I needed to enable the embedded-hal-02 feature flag in the esp-hal

[dependencies]
embedded-graphics = "0.8.1"
esp-backtrace = { version = "0.13.0", features = [
    "esp32",
    "exception-handler",
    "panic-handler",
    "println",
] }
esp-hal = { version = "0.19.0", features = ["esp32", "embedded-hal-02"] }
esp-println = { version = "0.10.0", features = ["esp32", "log"] }
log = { version = "0.4.21" }
ssd1306 = "0.8.4"

And here we are

Time to Get Connected

Let see if we can connect to the Wifi before we go for LoRa. This probably is more a case of understanding Rust and WiFi libraries so lets do it.

Configure Wifi Peripheral

We need to add and configure the wifi crate

esp-wifi = { version = "0.7.1", features = ["esp32", "wifi-default"] }
heapless = "0.8.0"

The biggest part of getting this to work was dealing with the timer. The Xtensa build uses a different timer, as your would expect, than RISC-V. Searching the Web, as ever solved this.

    let timer = esp_hal::timer::PeriodicTimer::new(
        esp_hal::timer::timg::TimerGroup::new(peripherals.TIMG1, &clocks, None)
            .timer0
            .into(),
    );

    let init = initialize(
        EspWifiInitFor::Wifi,
        timer,
        Rng::new(peripherals.RNG),
        peripherals.RADIO_CLK,
        &clocks,
    )
    .unwrap();

    // Configure Wifi
    let wifi = peripherals.WIFI;
    let mut socket_set_entries: [SocketStorage; 3] = Default::default();
    let (iface, device, mut controller, sockets) =
        create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap();

Configure Authentication

This also was a little bit harder than expected. So set the password and not have it appear in github I used

const SSID: &str = env!("SSID");
const PASSWORD: &str = env!("PASSWORD");

And for the SSID this worked well be doing

export SSID=YourKidding

But for the password not so well because of the special characters.

export SSID=YourKidding!!!

The fix was easy, just set the values outside of VS Code. Anyway here is the code for Authentication

    // Configure Authentification Configuration
    let mut auth_method = AuthMethod::WPA2Personal;
    if PASSWORD.is_empty() {
        auth_method = AuthMethod::None;
    }

    let client_config = Configuration::Client(ClientConfiguration {
        ssid: SSID.try_into().unwrap(),
        password: PASSWORD.try_into().unwrap(),
        auth_method,
        ..Default::default()
    });

    let res = controller.set_configuration(&client_config);
    log::info!("Wi-Fi set_configuration returned {:?}", res);

Connect to the Controller

No drama doing this in no_std

    controller.start().unwrap();
    log::info!("Is wifi started: {:?}", controller.is_started());

    log::info!("Start Wifi Scan");
    let res: Result<(heapless::Vec<AccessPointInfo, 10>, usize), WifiError> = controller.scan_n();
    if let Ok((res, _count)) = res {
        for ap in res {
            log::info!("{:?}", ap);
        }
    }

    log::info!("{:?}", controller.get_capabilities());
    log::info!("Wi-Fi connect: {:?}", controller.connect());

    // Wait to get connected
    log::info!("Wait to get connected");
    loop {
        let res = controller.is_connected();
        match res {
            Ok(connected) => {
                if connected {
                    break;
                }
            }
            Err(err) => {
                log::info!("{:?}", err);
                loop {}
            }
        }
    }
    log::info!("{:?}", controller.is_connected());

Get an IP address

Again no issue with this part

    // Wait for getting an ip address
    let wifi_stack = WifiStack::new(iface, device, sockets, current_millis);
    log::info!("Wait to get an ip address");
    loop {
        wifi_stack.work();

        if wifi_stack.is_iface_up() {
            log::info!("got ip {:?}", wifi_stack.get_ip_info());
            break;
        }
    }

Revisiting in 2025

This is now on my git server with examples for

SD Card Reader

This is the first time dabbling with a card reader so maybe my page will not be updated for a while - hang on. Back now. This was relatively easy. Just create an SPI device and use the MMC library.

Create the SPI Device

This is always a trial the first time through. The trick to this is understanding that to make a bus you need the embedded-hal-bus

embedded-hal-bus = "0.2.0"

This allows you to pass a bus to the SdCard

    // Set up the SPI bus
    let sclk = io.pins.gpio14;
    let miso = io.pins.gpio2;
    let mosi = io.pins.gpio15;
    let cs = Output::new(io.pins.gpio13, Level::Low);

    log::info!("Exclusive access to the SPI bus selected");
    let spi_driver =
        Spi::<SPI2, FullDuplexMode>::new(peripherals.SPI2, 1_000u32.kHz(), SpiMode::Mode0, &clocks)
            .with_sck(sclk)
            .with_mosi(mosi)
            .with_miso(miso);

    let time_source = FakeTimesource();
    let sd_spi_device = ExclusiveDevice::new(spi_driver, cs, delay).unwrap();
    read_mmc(sd_spi_device, delay, time_source).unwrap();

Reading the Card Reader

First time through for me but this too was straight forward. Place a file in the root directory formatted as fat. I fsck /dev/sdb1 to make sure all good. But it worked with no issue. Add the library to the toml

embedded-sdmmc = "0.8.0"

And use the example from the site

fn read_mmc<T>(
    spi: ExclusiveDevice<Spi<SPI2, FullDuplexMode>, Output<GpioPin<13>>, Delay>,
    delay: Delay,
    ts: T,
) -> Result<(), Error<SdCardError>>
where
    T: TimeSource,
{
    let sdcard = SdCard::new(spi, delay);
    // let sdcard = SdCard::new(spi, delay);
    log::info!("Card size is {} bytes", sdcard.num_bytes()?);
    let mut volume_mgr = VolumeManager::new(sdcard, ts);
    let mut volume0 = volume_mgr.open_volume(VolumeIdx(0))?;
    log::info!("Volume 0: {:?}", volume0);
    let mut root_dir = volume0.open_root_dir()?;
    let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", Mode::ReadOnly)?;
    while !my_file.is_eof() {
        let mut buffer = [0u8; 32];
        let num_read = my_file.read(&mut buffer)?;
        for b in &buffer[0..num_read] {
            log::info!("{}", *b as char);
        }
    }
    Ok(())
}

And here is the proof