This commit is contained in:
malled 2024-09-24 18:31:05 +02:00
commit 0f6c9fe97a
13 changed files with 1631 additions and 0 deletions

8
.cargo/config.toml Normal file
View file

@ -0,0 +1,8 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs run --chip stm32l431CCUx"
[build]
target = "thumbv7em-none-eabihf"
[env]
DEFMT_LOG = "info"

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

1007
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

80
Cargo.toml Normal file
View file

@ -0,0 +1,80 @@
[package]
edition = "2021"
name = "go-tracker"
version = "0.1.0"
authors = ["malled <malled@flipdot.org>"]
resolver = "2"
[dependencies]
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }
embedded-hal = "1.0.0"
embedded-hal-async = "1.0.0"
embedded-io = "0.6.1"
embedded-io-async = "0.6.1"
embedded-storage = "0.3.1"
cortex-m-rt = "0.7.3"
embassy-executor = { version = "0.6.0", features = [
"task-arena-size-8192",
"arch-cortex-m",
"executor-thread",
"defmt",
"integrated-timers",
"executor-interrupt",
] }
embassy-sync = { version = "0.6.0" }
embassy-time = { version = "0.3.0", features = [
"defmt",
"defmt-timestamp-uptime",
] }
embassy-net = { version = "0.4.0", features = [
"dhcpv4",
"tcp",
"dns",
"defmt",
"proto-ipv4",
"medium-ip",
] }
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
embassy-stm32 = { version = "0.1.0", features = [
"defmt",
"stm32l431cc",
"unstable-pac",
"memory-x",
"time-driver-any",
] }
embassy-net-ppp = { version = "0.1.0", features = ["defmt"] }
embassy-futures = "0.1.1"
heapless = { version = "0.8.0", features = ["defmt-03"] }
static_cell = "2.1.0"
rand = { version = "0.8.5", default-features = false }
rust-mqtt = { version = "0.3.0", default-features = false, features = [
"no_std",
] }
#minimq = "0.9.0"
#embedded-nal = "0.8.0"
[patch.crates-io]
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-net-ppp = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "45cbcb513dc0bbf3e12c102df0db8c15643cc78b" }
[profile.release]
lto = true
opt-level = "s"
incremental = false
codegen-units = 1
# note: debug = true is okay - debuginfo isn't flashed to the device!
debug = true

44
README.md Normal file
View file

@ -0,0 +1,44 @@
# Emerson GO Tracker 1.7 (TXC-TH04) #
## Parts ##
| Part Number | Type | Interface |
|-------------|------|-----------|
| STM32L431CCU | MCU | |
| HDC2010 | Humidity and Temperature Sensor | I2C |
| TMP112 | Temperature Sensor | I2C |
| ????? | Light Sensor | I2C |
| Quectel BG95-M5 | GSM + GNSS | UART |
| MX25L3233F | Flash | SPI |
| I2C Address | Device | Function |
|-------------|--------|----------|
| 0x40 | HDC2010 | Humidity and Temperature Sensor |
| 0x44 | ????? | Light Sensor? |
| 0x48 | TMP112 | Temperature Sensor |
## Light Sensor ##
```
---------------
GND - | 1 6 | - INT (PB9)
VCC - | 2 5 | - SDA
GND - | 3 4 | - SCL
---------------
```
## STM32L431CCU Pinout ##
| STM Pin | Function | Remark |
|---------|---------------|--------|
| PA1 | GSM NetKey | - |
| PA2 | UART2_TX GSM | - |
| PA3 | UART2_RX GSM | - |
| PB2 | GSM PWR Enable| - |
| PB5 | TMP112 enable | - |
| PB6 | I2C1_SCL | - |
| PB7 | I2C1_SDA | - |
| PB9 | Light INT | not verified |
| PB12 | SPI Flash CS | - |

35
build.rs Normal file
View file

@ -0,0 +1,35 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
}

14
memory.x Normal file
View file

@ -0,0 +1,14 @@
MEMORY
{
/* NOTE K = KiBi = 1024 bytes */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

10
rust-toolchain.toml Normal file
View file

@ -0,0 +1,10 @@
# Before upgrading check that everything is available on all tier1 targets here:
# https://rust-lang.github.io/rustup-components-history
[toolchain]
channel = "stable"
components = [ "rustfmt" ]
targets = [
"thumbv6m-none-eabi",
"thumbv7em-none-eabihf",
"riscv32imac-unknown-none-elf",
]

23
src/gnss.rs Normal file
View file

@ -0,0 +1,23 @@
use defmt::info;
use embassy_stm32::{mode::Async, usart::Uart};
use embassy_time::{Duration, Timer};
use heapless::String;
#[embassy_executor::task]
pub async fn gnss_task(mut gnss_uart: Uart<'static, Async>) {
let mut buf = [0; 90];
loop {
let rx = gnss_uart.read_until_idle(&mut buf).await;
match rx {
Ok(rx) => {
let buf = core::str::from_utf8(&buf[..rx]).unwrap();
info!("RX: {}", buf);
}
Err(_) => {
info!("Error reading from GNSS");
}
}
// Perform some asynchronous work here
Timer::after(Duration::from_secs(1)).await;
}
}

154
src/main.rs Normal file
View file

@ -0,0 +1,154 @@
#![no_std]
#![no_main]
use defmt::*;
use embassy_executor::Spawner;
use embassy_net::StackResources;
use embassy_net_ppp;
use embassy_stm32::i2c::{self, I2c};
use embassy_stm32::rng::{self, Rng};
use embassy_stm32::time::Hertz;
use embassy_stm32::usart::Config;
use embassy_stm32::{bind_interrupts, peripherals, usart};
use embassy_stm32::{
gpio::{Level, Output, Speed},
usart::BufferedUart,
};
use embassy_time::{Duration, Timer};
use net::net_init;
use rand::RngCore;
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};
mod gnss;
mod mqtt;
mod net;
mod sensors;
bind_interrupts!(struct Irqs {
USART2 => usart::BufferedInterruptHandler<peripherals::USART2>;
USART3 => usart::InterruptHandler<peripherals::USART3>;
RNG => rng::InterruptHandler<peripherals::RNG>;
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
});
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let mut stm_config = embassy_stm32::Config::default();
{
use embassy_stm32::rcc::*;
stm_config.rcc.sys = Sysclk::PLL1_R;
stm_config.rcc.hsi = false;
stm_config.rcc.hse = Some(Hse {
freq: embassy_stm32::time::Hertz(12_000_000),
mode: HseMode::Oscillator,
});
stm_config.rcc.pll = Some(Pll {
source: PllSource::HSE, // 12 MHz
prediv: PllPreDiv::DIV1,
mul: PllMul::MUL8, // 12 * 8 = 96 MHz
divp: None,
divq: None,
divr: Some(PllRDiv::DIV2), // 96 / 2 = 48 MHz
});
stm_config.rcc.ls = LsConfig::default();
}
let p = embassy_stm32::init(stm_config);
let mut config = Config::default();
config.baudrate = 115_200;
config.stop_bits = embassy_stm32::usart::StopBits::STOP1;
defmt::info!("Moin");
let mut rng = Rng::new(p.RNG, Irqs);
let mut net_key = Output::new(p.PA1, Level::Low, Speed::Low);
let mut pwr_en = Output::new(p.PB2, Level::Low, Speed::Low);
let mut pwr_tmp = Output::new(p.PB5, Level::Low, Speed::Low);
// Power on modem
pwr_en.set_high();
pwr_tmp.set_high();
static RX_BUF: StaticCell<[u8; 512]> = StaticCell::new();
static TX_BUF: StaticCell<[u8; 512]> = StaticCell::new();
let mut usart_modem = BufferedUart::new(
p.USART2,
Irqs,
p.PA3,
p.PA2,
RX_BUF.init([0; 512]),
TX_BUF.init([0; 512]),
config,
)
.unwrap();
// TODO untested
let gnss_uart = usart::Uart::new(
p.USART3,
p.PB11,
p.PB10,
Irqs,
p.DMA1_CH2,
p.DMA1_CH3,
Config::default(),
)
.unwrap();
let i2c = I2c::new(
p.I2C1,
p.PB6,
p.PB7,
Irqs,
p.DMA1_CH6,
p.DMA1_CH7,
Hertz(100_000),
Default::default(),
);
net_init(&mut net_key, &mut usart_modem).await;
// spawner.spawn(gnss::gnss_task(gnss_uart)).unwrap();
static STATE: StaticCell<embassy_net_ppp::State<8, 8>> = StaticCell::new();
let state = STATE.init(embassy_net_ppp::State::<8, 8>::new());
let (device, runner) = embassy_net_ppp::new(state);
// Generate random seed
let seed = rng.next_u64();
// Init network stack
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
let (stack, runner_net) = embassy_net::new(
device,
embassy_net::Config::default(),
RESOURCES.init(StackResources::new()),
seed,
);
// Launch network task
spawner.spawn(net::net_task(runner_net)).unwrap();
spawner
.spawn(net::ppp_task(stack, runner, usart_modem))
.unwrap();
loop {
Timer::after(Duration::from_secs(1)).await;
if stack.is_link_up() {
info!("Ready");
break;
}
}
spawner.spawn(sensors::sensors_task(i2c)).unwrap();
spawner.spawn(mqtt::mqtt_task(stack)).unwrap();
loop {
Timer::after(Duration::from_secs(1)).await;
}
}

61
src/mqtt.rs Normal file
View file

@ -0,0 +1,61 @@
use defmt::{info, warn};
use embassy_net::Stack;
use embassy_time::{Duration, Timer};
use rust_mqtt::{
client::{client::MqttClient, client_config::ClientConfig},
utils::rng_generator::CountingRng,
};
#[embassy_executor::task]
pub async fn mqtt_task(stack: Stack<'static>) {
info!("Connecting...");
let mut rx_buffer = [0; 128];
let mut tx_buffer = [0; 128];
loop {
let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
socket.set_timeout(Some(Duration::from_secs(10)));
let host_addrs = stack
.dns_query("test.mosquitto.org", embassy_net::dns::DnsQueryType::A)
.await
.unwrap();
let host_addr = host_addrs.iter().next().unwrap();
if let Err(e) = socket.connect((*host_addr, 1883)).await {
warn!("connect error: {:?}", e);
continue;
}
info!("Connected to {:?}", socket.remote_endpoint());
let mut config = ClientConfig::new(
rust_mqtt::client::client_config::MqttVersion::MQTTv5,
CountingRng(20000),
);
config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS1);
config.add_client_id("client");
// config.add_username(USERNAME);
// config.add_password(PASSWORD);
config.max_packet_size = 100;
let mut recv_buffer = [0; 80];
let mut write_buffer = [0; 80];
let mut client =
MqttClient::<_, 5, _>::new(socket, &mut write_buffer, 80, &mut recv_buffer, 80, config);
client.connect_to_broker().await.unwrap();
loop {
client
.send_message(
"/malled",
b"hello2",
rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0,
true,
)
.await
.unwrap();
Timer::after_millis(2000).await;
}
}
}

89
src/net.rs Normal file
View file

@ -0,0 +1,89 @@
use defmt::{info, warn};
use embassy_net::{ConfigV4, Ipv4Address, Ipv4Cidr, Stack};
use embassy_stm32::{gpio::Output, usart::BufferedUart};
use embassy_time::Timer;
use embedded_io::Write;
use embedded_io_async::Read;
use heapless::{String, Vec};
pub async fn net_init(net_key: &mut Output<'static>, usart_modem: &mut BufferedUart<'static>) {
// Enable the modem
Timer::after_millis(750).await;
net_key.set_high();
Timer::after_millis(750).await;
net_key.set_low();
let mut rx_buf = String::<128>::new();
loop {
let mut buf = [0u8; 8];
let result = usart_modem.read(&mut buf).await;
match result {
Ok(size) => {
for i in 0..size {
rx_buf.push(buf[i] as char).unwrap();
}
if !rx_buf.contains("\r") {
continue;
}
info!("RX: {}", rx_buf);
if rx_buf.contains("CTZV") {
// usart_modem.write_all(b"AT+QGPS=1\r").unwrap();
usart_modem
.write_all(b"AT+CGDCONT=1,\"ip\",\"pinternet.interkom.de\"\r")
.unwrap();
usart_modem.flush().unwrap();
Timer::after_millis(500).await;
usart_modem.write_all(b"ATD*99#\r").unwrap();
usart_modem.flush().unwrap();
Timer::after_millis(500).await;
break;
}
rx_buf.clear();
}
Err(_err) => {
//Ignore eg. framing errors
defmt::error!("Modem RX Error");
}
}
}
}
#[embassy_executor::task]
pub async fn net_task(
mut runner: embassy_net::Runner<'static, embassy_net_ppp::Device<'static>>,
) -> ! {
info!("Starting Ethernet");
runner.run().await
}
#[embassy_executor::task]
pub async fn ppp_task(
stack: Stack<'static>,
mut runner: embassy_net_ppp::Runner<'static>,
uart: BufferedUart<'static>,
) {
let cfg = embassy_net_ppp::Config {
username: "foo".as_bytes(),
password: "bar".as_bytes(),
};
info!("Starting PPP");
runner
.run(uart, cfg, |ipv4| {
let Some(addr) = ipv4.address else {
warn!("PPP did not provide an IP address.");
return;
};
let mut dns_servers = Vec::new();
for s in ipv4.dns_servers.iter().flatten() {
let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0));
}
let config = ConfigV4::Static(embassy_net::StaticConfigV4 {
address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0),
gateway: None,
dns_servers,
});
stack.set_config_v4(config);
})
.await
.unwrap();
}

105
src/sensors.rs Normal file
View file

@ -0,0 +1,105 @@
use defmt::info;
use embassy_stm32::i2c::I2c;
use embassy_stm32::mode::Async;
use embassy_time::{Duration, Timer};
const ADDRESS_TMP112: u8 = 0x48;
const ADDRESS_HDC2010: u8 = 0x40;
const ADDRESS_LIGHT: u8 = 0x44;
#[embassy_executor::task]
pub async fn sensors_task(mut i2c: I2c<'static, Async>) {
info!("Sensors task started");
Timer::after(Duration::from_secs(1)).await;
loop {
info!("TMP112 {}", read_temp_tmp112(&mut i2c).await.unwrap());
info!("HDC2010 {}", read_temp_hdc2010(&mut i2c).await.unwrap());
info!("Light {}", read_light(&mut i2c).await.unwrap());
Timer::after(Duration::from_secs(1)).await;
}
}
async fn read_light(i2c: &mut I2c<'static, Async>) -> Result<i16, embassy_stm32::i2c::Error> {
let mut data = [0u8; 2];
// let _request = i2c.write(ADDRESS_LIGHT, &[0x7E]).await;
Timer::after(Duration::from_millis(50)).await;
for i in 1..255 {
let resp = i2c.write_read(ADDRESS_LIGHT, &[i], &mut data).await;
match resp {
Ok(_) => {
info!("Addr {:02X} Data: {:02X} {:02X}", i, data[0], data[1],);
return Ok(42);
}
Err(e) => match e {
embassy_stm32::i2c::Error::Nack => {
info!("NAK");
Timer::after(Duration::from_millis(10)).await;
}
_ => {
info!("custom err");
return Err(e);
}
},
}
}
Err(embassy_stm32::i2c::Error::Timeout)
}
async fn read_temp_hdc2010(
i2c: &mut I2c<'static, Async>,
) -> Result<i16, embassy_stm32::i2c::Error> {
let mut data = [0u8; 4];
// let _request = i2c.write(ADDRESS_HUM, &[0x00]).await;
Timer::after(Duration::from_millis(50)).await;
for _ in 1..10 {
let resp = i2c.write_read(ADDRESS_HDC2010, &[0xFC], &mut data).await;
match resp {
Ok(_) => {
info!(
"Data: {:02X} {:02X} {:02X} {:02X}",
data[0], data[1], data[2], data[3]
);
return Ok(42);
}
Err(e) => match e {
embassy_stm32::i2c::Error::Nack => {
info!("NAK");
Timer::after(Duration::from_millis(10)).await;
}
_ => {
info!("custom err");
return Err(e);
}
},
}
}
Err(embassy_stm32::i2c::Error::Timeout)
}
async fn read_temp_tmp112(i2c: &mut I2c<'static, Async>) -> Result<f32, embassy_stm32::i2c::Error> {
let mut data = [0u8; 2];
// Timer::after(Duration::from_millis(50)).await;
for _ in 1..10 {
let resp = i2c.write_read(ADDRESS_TMP112, &[0], &mut data).await;
match resp {
Ok(_) => {
let temp = ((data[0] as u16) << 4) | (data[1] as u16) >> 4;
let temp = temp as i16;
let temp = temp as f32;
let temp = temp * 0.0625;
return Ok(temp);
}
Err(e) => match e {
embassy_stm32::i2c::Error::Nack => {
info!("NAK");
Timer::after(Duration::from_millis(10)).await;
}
_ => {
info!("custom err");
return Err(e);
}
},
}
}
Err(embassy_stm32::i2c::Error::Timeout)
}