Compare commits
No commits in common. "main" and "master" have entirely different histories.
|
@ -0,0 +1,19 @@
|
|||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output)
|
||||
runner = "probe-run --chip STM32F103CB"
|
||||
rustflags = [
|
||||
"-C", "linker=flip-link",
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
||||
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
||||
"-C", "link-arg=--nmagic",
|
||||
]
|
||||
|
||||
[build]
|
||||
target = "thumbv7m-none-eabi" # Cortex-M3
|
||||
|
||||
|
||||
[alias]
|
||||
rb = "run --bin"
|
||||
rrb = "run --release --bin"
|
|
@ -1,18 +0,0 @@
|
|||
on: push
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: forgejo.zenerdio.de/sebastian/cheapsdo-ci:v0.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: audit-cheapsdo
|
||||
- run: CARGO_HOME=/root/.cargo cargo audit
|
|
@ -1,75 +0,0 @@
|
|||
on: push
|
||||
jobs:
|
||||
build-firmware:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: forgejo.zenerdio.de/sebastian/cheapsdo-ci:v0.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: build-cheapsdo-firmware
|
||||
restore-keys: audit-cheapsdo
|
||||
- run: cd firmware && CARGO_HOME=~/.cargo cargo build --target thumbv7m-none-eabi --release
|
||||
|
||||
build-linux:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: forgejo.zenerdio.de/sebastian/cheapsdo-ci:v0.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: build-cheapsdo-linux
|
||||
restore-keys: build-cheapsdo-firmware
|
||||
- run: cd hostsoftware && CARGO_HOME=~/.cargo cargo build --release
|
||||
|
||||
build-windows:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: forgejo.zenerdio.de/sebastian/cheapsdo-ci:v0.1.0
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: build-cheapsdo-windows
|
||||
restore-keys: build-cheapsdo-linux
|
||||
- run: cd hostsoftware && CARGO_HOME=~/.cargo cargo build --target x86_64-pc-windows-gnu --release
|
||||
|
||||
build-appimage:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: forgejo.zenerdio.de/sebastian/cheapsdo-ci:v0.1.0
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: build-cheapsdo-appimage
|
||||
restore-keys: build-cheapsdo-linux
|
||||
- run: cd hostsoftware && CARGO_HOME=~/.cargo PATH=$PATH:$CARGO_HOME/bin x build -r --format appimage
|
|
@ -1,33 +0,0 @@
|
|||
on:
|
||||
push:
|
||||
tags: 'v*'
|
||||
|
||||
jobs:
|
||||
publish-release:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: forgejo.zenerdio.de/sebastian/cheapsdo-ci:v0.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: build-cheapsdo
|
||||
- run: cd hostsoftware && CARGO_HOME=~/.cargo cargo build --target x86_64-pc-windows-gnu --release
|
||||
- run: cd hostsoftware && CARGO_HOME=~/.cargo PATH=$PATH:$CARGO_HOME/bin x build -r --format appimage
|
||||
- run: cd firmware && CARGO_HOME=~/.cargo cargo build --target thumbv7m-none-eabi --release
|
||||
- run: mkdir -p release
|
||||
- run: cp target/x/release/linux/x64/cheapsdo-control.AppImage release/cheapsdo-control-${{ github.ref_name }}.AppImage
|
||||
- run: cp target/x86_64-pc-windows-gnu/release/cheapsdo-control.exe release/cheapsdo-control-${{ github.ref_name }}.exe
|
||||
- run: cp target/thumbv7m-none-eabi/release/cheapsdo release/cheapsdo-firmware
|
||||
- run: cd release && zip cheapsdo-win.zip cheapsdo-control.exe && rm cheapsdo-control.exe
|
||||
- uses: actions/forgejo-release@v1
|
||||
with:
|
||||
direction: upload
|
||||
release-dir: release
|
||||
token: ${{ secrets.FORGEJO_RELEASE }}
|
78
Cargo.toml
78
Cargo.toml
|
@ -1,10 +1,76 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
[package]
|
||||
# TODO(1) fix `authors` and `name` if you didn't use `cargo-generate`
|
||||
authors = ["sebastian <sebastian@sebastians-site.de>"]
|
||||
name = "cheapsdp"
|
||||
edition = "2018"
|
||||
version = "0.1.0"
|
||||
|
||||
members = [
|
||||
"hostsoftware",
|
||||
"firmware",
|
||||
"protocol",
|
||||
[dependencies]
|
||||
cortex-m = "~0.7.1"
|
||||
cortex-m-rt = "~0.6.13"
|
||||
defmt = "~0.2.0"
|
||||
defmt-rtt = "~0.2.0"
|
||||
panic-probe = { version = "~0.2.0", features = ["print-defmt"] }
|
||||
stm32f1xx-hal = { version = "~0.6.1", features = ["stm32f103", "rt"] }
|
||||
embedded-hal = {version = "~0.2.3"}
|
||||
|
||||
[features]
|
||||
# set logging levels here
|
||||
default = [
|
||||
"defmt-default",
|
||||
# "dependency-a/defmt-trace",
|
||||
]
|
||||
|
||||
# do NOT modify these features
|
||||
defmt-default = []
|
||||
defmt-trace = []
|
||||
defmt-debug = []
|
||||
defmt-info = []
|
||||
defmt-warn = []
|
||||
defmt-error = []
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # <-
|
||||
incremental = false
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = true # <-
|
||||
|
||||
# cargo test
|
||||
[profile.test]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # <-
|
||||
incremental = false
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = true # <-
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false # <-
|
||||
incremental = false
|
||||
#lto = 'fat'
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = false # <-
|
||||
|
||||
# cargo test --release
|
||||
[profile.bench]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false # <-
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = false # <-
|
||||
|
||||
# uncomment this to switch from the crates.io version of defmt to its git version
|
||||
# check app-template's README for instructions
|
||||
# [patch.crates-io]
|
||||
# defmt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }
|
||||
# defmt-rtt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }
|
||||
# defmt-test = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }
|
||||
# panic-probe = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" }
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output)
|
||||
runner = "probe-run --chip STM32F103CB"
|
||||
rustflags = [
|
||||
"-C", "linker=flip-link",
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
||||
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
||||
"-C", "link-arg=--nmagic",
|
||||
]
|
||||
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # <-
|
||||
incremental = false
|
||||
opt-level = 'z' # <-
|
||||
overflow-checks = true # <-
|
||||
|
||||
# cargo test
|
||||
[profile.test]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # <-
|
||||
incremental = false
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = true # <-
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false # <-
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = false # <-
|
||||
|
||||
# cargo test --release
|
||||
[profile.bench]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false # <-
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = false # <-
|
|
@ -1,29 +0,0 @@
|
|||
cargo-features = ["per-package-target"]
|
||||
|
||||
[package]
|
||||
# TODO(1) fix `authors` and `name` if you didn't use `cargo-generate`
|
||||
authors = ["sebastian <sebastian@sebastians-site.de>"]
|
||||
name = "cheapsdo-firmware"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
default-target = "thumbv7m-none-eabi" # Cortex-M3
|
||||
|
||||
[dependencies]
|
||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
defmt = { version = "0.3", features = ["encoding-rzcobs"] }
|
||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
rtic = { version = "2.0.1", features = [ "thumbv7-backend" ] }
|
||||
defmt-rtt = "0.4"
|
||||
embedded-hal = "0.2.3"
|
||||
stm32f1xx-hal = { version = "0.10.0", features = ["stm32f103", "rt", "medium"] }
|
||||
nb = "1.0.0"
|
||||
systick-monotonic = "1.0.0"
|
||||
rtic-monotonics = {version = "1.4.1", features = ["cortex-m-systick"] }
|
||||
usb-device = "0.2.8"
|
||||
usbd-serial = "0.1.1"
|
||||
cheapsdo-protocol = { path = "../protocol" }
|
||||
postcard = {version = "1.0.8", features = ["use-defmt"]}
|
||||
heapless = {version = "0.8.0", features = ["defmt-03"]}
|
||||
num = {version = "0.4.1", default-features = false, features = ["libm"]}
|
||||
serde = { version = "1.0.195", default-features = false }
|
|
@ -1,37 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import time
|
||||
import serial
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def main():
|
||||
ser = serial.Serial(sys.argv[1])
|
||||
|
||||
|
||||
short_term = []
|
||||
long_term = []
|
||||
|
||||
while True:
|
||||
ser.write(b"?")
|
||||
|
||||
line = ser.readline()
|
||||
line = line.strip()
|
||||
|
||||
f1, f2 = line.split(b'|')
|
||||
short_term += [float(f1)]
|
||||
long_term += [float(f2)]
|
||||
|
||||
print(float(f2))
|
||||
|
||||
plt.clf()
|
||||
plt.ylim((10 - 0.000_001, 10 + 0.000_001))
|
||||
plt.plot(short_term)
|
||||
plt.plot(long_term)
|
||||
plt.draw()
|
||||
plt.pause(1.0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,449 +0,0 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
use panic_probe as _;
|
||||
use stm32f1xx_hal as _;
|
||||
|
||||
mod nvstate;
|
||||
mod si5153;
|
||||
|
||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||
#[defmt::panic_handler]
|
||||
fn panic() -> ! {
|
||||
cortex_m::asm::udf()
|
||||
}
|
||||
|
||||
use rtic::app;
|
||||
|
||||
#[app(device = stm32f1xx_hal::pac, peripherals = true, dispatchers = [SPI3])]
|
||||
mod app {
|
||||
|
||||
use cortex_m::asm::delay;
|
||||
use rtic_monotonics::systick::*;
|
||||
|
||||
use stm32f1xx_hal::{
|
||||
flash::{self, FlashWriter},
|
||||
gpio::{self, gpioa, gpioc, Alternate, Output, PushPull},
|
||||
i2c, pac,
|
||||
pac::{RCC, TIM2, TIM3, TIM4},
|
||||
prelude::*,
|
||||
rcc::Enable,
|
||||
rcc::Reset,
|
||||
timer::{self, Channel, PwmHz, Tim4NoRemap},
|
||||
};
|
||||
|
||||
use stm32f1xx_hal::usb::{Peripheral, UsbBus, UsbBusType};
|
||||
use usb_device::prelude::*;
|
||||
|
||||
use heapless::Vec;
|
||||
use postcard::{from_bytes_cobs, to_vec_cobs};
|
||||
|
||||
use cheapsdo_protocol::{DeviceMessage, HostMessage, StatusMessage};
|
||||
|
||||
use crate::nvstate::{self, NVState};
|
||||
use crate::si5153;
|
||||
|
||||
const USB_BUFFER_SIZE: usize = 64;
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
board_led: gpioc::PC13<Output<PushPull>>,
|
||||
tim2: TIM2,
|
||||
tim3: TIM3,
|
||||
pwm: PwmHz<TIM4, Tim4NoRemap, timer::Ch<0>, gpio::Pin<'B', 6, Alternate>>,
|
||||
nvstate: NVState,
|
||||
flash: flash::Parts,
|
||||
}
|
||||
|
||||
#[shared]
|
||||
struct Shared {
|
||||
usb_dev: UsbDevice<'static, UsbBusType>,
|
||||
serial: usbd_serial::SerialPort<'static, UsbBusType>,
|
||||
device_status: StatusMessage,
|
||||
buffer: Vec<u8, USB_BUFFER_SIZE>,
|
||||
}
|
||||
|
||||
const TARGET_FREQ: u64 = 10_000_000_000; // in millihertz
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
let rcc = cx.device.RCC.constrain();
|
||||
let mut flash = cx.device.FLASH.constrain();
|
||||
|
||||
let clocks = rcc
|
||||
.cfgr
|
||||
.use_hse(12.MHz())
|
||||
.sysclk(48.MHz())
|
||||
.pclk1(24.MHz())
|
||||
.freeze(&mut flash.acr);
|
||||
|
||||
assert!(clocks.usbclk_valid());
|
||||
defmt::info!("Clock Setup done");
|
||||
|
||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||
let systick_mono_token = rtic_monotonics::create_systick_token!();
|
||||
Systick::start(cx.core.SYST, clocks.sysclk().to_Hz(), systick_mono_token);
|
||||
|
||||
let mut gpioc = cx.device.GPIOC.split();
|
||||
|
||||
// Configure gpio C pin 13 as a push-pull output. The `crh` register is passed to the function
|
||||
// in order to configure the port. For pins 0-7, crl should be passed instead.
|
||||
let board_led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
|
||||
|
||||
let mut afio = cx.device.AFIO.constrain();
|
||||
let mut gpiob = cx.device.GPIOB.split();
|
||||
let pwm_pin = gpiob.pb6.into_alternate_push_pull(&mut gpiob.crl);
|
||||
let mut pwm =
|
||||
cx.device
|
||||
.TIM4
|
||||
.pwm_hz::<Tim4NoRemap, _, _>(pwm_pin, &mut afio.mapr, 16.kHz(), &clocks);
|
||||
pwm.enable(Channel::C1);
|
||||
|
||||
defmt::info!("Max PWM is {}", pwm.get_max_duty());
|
||||
defmt::info!("PWM Setup done");
|
||||
|
||||
let tim2 = cx.device.TIM2;
|
||||
unsafe {
|
||||
let rcc = &*RCC::ptr();
|
||||
TIM2::enable(rcc);
|
||||
TIM2::reset(rcc);
|
||||
}
|
||||
|
||||
// Enable external clocking
|
||||
tim2.smcr.write(|w| {
|
||||
w.etf().no_filter(); // No filter for to 10Mhz clock
|
||||
w.etps().div1(); // No divider
|
||||
w.etp().not_inverted(); // on rising edege at ETR pin
|
||||
w.ece().enabled() // mode 2 (use ETR pin)
|
||||
});
|
||||
|
||||
tim2.ccmr1_input().write(|w| {
|
||||
w.cc1s().ti2(); // Input capture using TI2 input
|
||||
w.ic1f().no_filter() // No filter on input capture input
|
||||
//w.ic1psc().bits(0) // Disable prescaler, not safely implement by HAL yet
|
||||
});
|
||||
|
||||
tim2.ccer.write(|w| {
|
||||
w.cc1p().set_bit(); // Use rising edge on TI
|
||||
w.cc1e().set_bit() // Enable input capture
|
||||
});
|
||||
|
||||
tim2.cr2.write(|w| {
|
||||
w.mms().update() // Trigger output on update/overflow
|
||||
});
|
||||
|
||||
tim2.ccer.write(|w| {
|
||||
w.cc1p().set_bit(); // Use rising edge on TI
|
||||
w.cc1e().set_bit() // Enable input capture
|
||||
});
|
||||
|
||||
tim2.cr2.write(|w| {
|
||||
w.mms().update() // Trigger output on update/overflow
|
||||
});
|
||||
|
||||
// Counting up to 10^7 should need 24 bits
|
||||
// Clock tim2 by tim1s overflow to make a 32bit timer
|
||||
let tim3 = cx.device.TIM3;
|
||||
unsafe {
|
||||
let rcc = &*RCC::ptr();
|
||||
TIM3::enable(rcc);
|
||||
TIM3::reset(rcc);
|
||||
}
|
||||
|
||||
tim3.smcr.write(|w| {
|
||||
w.ts().itr1(); // Trigger from internal trigger 1
|
||||
w.sms().ext_clock_mode() // Use trigger as clock
|
||||
});
|
||||
|
||||
tim3.ccmr1_input().write(|w| {
|
||||
w.cc1s().ti1(); // Input capture using TI1 input
|
||||
w.ic1f().no_filter() // No filter on input capture input
|
||||
//w.ic1psc().bits(0) // Disable prescaler, not safely implement by HAL yet
|
||||
});
|
||||
|
||||
tim3.ccer.write(|w| {
|
||||
w.cc1p().set_bit(); // Use rising edge on TI
|
||||
w.cc1e().set_bit() // Enable input capture
|
||||
});
|
||||
|
||||
tim2.cr1.write(|w| w.cen().enabled());
|
||||
tim3.cr1.write(|w| w.cen().enabled());
|
||||
|
||||
defmt::info!("Timer Setup done");
|
||||
|
||||
static mut USB_BUS: Option<usb_device::bus::UsbBusAllocator<UsbBusType>> = None;
|
||||
|
||||
let mut gpioa = cx.device.GPIOA.split();
|
||||
|
||||
let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh);
|
||||
usb_dp.set_low();
|
||||
delay(clocks.sysclk().raw() / 100);
|
||||
|
||||
let usb_dm = gpioa.pa11;
|
||||
let usb_dp = usb_dp.into_floating_input(&mut gpioa.crh);
|
||||
|
||||
let usb = Peripheral {
|
||||
usb: cx.device.USB,
|
||||
pin_dm: usb_dm,
|
||||
pin_dp: usb_dp,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
USB_BUS.replace(UsbBus::new(usb));
|
||||
}
|
||||
|
||||
let serial = usbd_serial::SerialPort::new(unsafe { USB_BUS.as_ref().unwrap() });
|
||||
|
||||
let usb_dev = UsbDeviceBuilder::new(
|
||||
unsafe { USB_BUS.as_ref().unwrap() },
|
||||
UsbVidPid(0x16c0, 0x27dd),
|
||||
)
|
||||
.manufacturer("Arbitrary Precision Instruments")
|
||||
.product("cheapsdo")
|
||||
.serial_number("1337")
|
||||
.device_class(usbd_serial::USB_CLASS_CDC)
|
||||
.build();
|
||||
|
||||
let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh);
|
||||
let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh);
|
||||
let mut i2c1 = i2c::BlockingI2c::i2c1(
|
||||
cx.device.I2C1,
|
||||
(scl, sda),
|
||||
&mut afio.mapr,
|
||||
i2c::Mode::Fast {
|
||||
frequency: 400_000.Hz(),
|
||||
duty_cycle: i2c::DutyCycle::Ratio2to1,
|
||||
},
|
||||
clocks,
|
||||
1000,
|
||||
10,
|
||||
1000,
|
||||
1000,
|
||||
);
|
||||
|
||||
defmt::info!("I2C Setup done");
|
||||
|
||||
let mut si_pll = si5153::Si5153::new(&i2c1);
|
||||
si_pll.init(&mut i2c1, 10_000_000, 800_000_000, 800_000_000);
|
||||
si_pll.set_ms_source(&mut i2c1, si5153::Multisynth::MS0, si5153::PLL::A);
|
||||
defmt::info!("si5153 Setup done");
|
||||
|
||||
si_pll.set_ms_freq(&mut i2c1, si5153::Multisynth::MS0, 100_000_000);
|
||||
si_pll.enable_ms_output(&mut i2c1, si5153::Multisynth::MS0);
|
||||
|
||||
let nvstate = nvstate::load(&mut flash);
|
||||
|
||||
defmt::info!("read nvstate from flash");
|
||||
|
||||
update_pwm::spawn().unwrap();
|
||||
|
||||
(
|
||||
Shared {
|
||||
serial,
|
||||
usb_dev,
|
||||
device_status: StatusMessage::default(),
|
||||
buffer: Vec::new(),
|
||||
},
|
||||
Local {
|
||||
board_led,
|
||||
tim2,
|
||||
tim3,
|
||||
pwm,
|
||||
nvstate,
|
||||
flash,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const WINDOW_LEN: usize = 100;
|
||||
|
||||
#[task(local=[tim2, tim3, pwm, board_led, nvstate, flash], shared=[device_status])]
|
||||
async fn update_pwm(mut cx: update_pwm::Context) {
|
||||
defmt::info!("Update Task started");
|
||||
|
||||
let tim2 = cx.local.tim2;
|
||||
let tim3 = cx.local.tim3;
|
||||
let pwm = cx.local.pwm;
|
||||
let board_led = cx.local.board_led;
|
||||
let mut nvstate = cx.local.nvstate;
|
||||
let mut flash = cx.local.flash;
|
||||
|
||||
let max_pwm = pwm.get_max_duty() as i32;
|
||||
let mut cur_pwm = nvstate.pwm as i32;
|
||||
cur_pwm = if cur_pwm < 0 { 0 } else { cur_pwm };
|
||||
cur_pwm = if cur_pwm > max_pwm { max_pwm } else { cur_pwm };
|
||||
|
||||
pwm.set_duty(Channel::C1, cur_pwm as u16);
|
||||
defmt::info!("pwm:\t{}", cur_pwm);
|
||||
|
||||
// Inialize last_ic
|
||||
while !tim2.sr.read().cc1if().bit_is_set() || !tim3.sr.read().cc1if().bit_is_set() {
|
||||
Systick::delay(10.millis()).await;
|
||||
}
|
||||
let ic1 = tim2.ccr1().read().bits();
|
||||
let ic2 = tim3.ccr1().read().bits();
|
||||
|
||||
let mut last_ic = ic2 << 16 | ic1;
|
||||
|
||||
let mut samples: [u64; WINDOW_LEN] = [10_000_000_000; WINDOW_LEN];
|
||||
let mut short_avg: u64 = 10_000_000_000;
|
||||
|
||||
loop {
|
||||
let mut last_freq: u64 = 10_000_000_000;
|
||||
|
||||
let mut count = 0;
|
||||
while count < WINDOW_LEN {
|
||||
while !tim3.sr.read().cc1if().bit_is_set() || !tim3.sr.read().cc1if().bit_is_set() {
|
||||
Systick::delay(10.millis()).await;
|
||||
}
|
||||
let ic1 = tim2.ccr1().read().bits();
|
||||
let ic2 = tim3.ccr1().read().bits();
|
||||
|
||||
let sum_ic = ic2 << 16 | ic1;
|
||||
|
||||
let diff_ic = if sum_ic > last_ic {
|
||||
sum_ic - last_ic
|
||||
} else {
|
||||
u32::MAX - last_ic + sum_ic
|
||||
};
|
||||
|
||||
defmt::info!("ic1:\t{}", ic1);
|
||||
defmt::info!("ic2:\t{}", ic2);
|
||||
defmt::info!("sum_ic:\t{}", sum_ic);
|
||||
defmt::info!("diff_ic:\t{}", diff_ic);
|
||||
|
||||
last_ic = sum_ic;
|
||||
|
||||
let freq = (diff_ic as u64) * 1000;
|
||||
defmt::info!("freq:\t{} mHz", freq as i64);
|
||||
defmt::info!("last_freq:\t{} mHz", last_freq as i64);
|
||||
|
||||
let diff = freq as i64 - last_freq as i64;
|
||||
last_freq = freq;
|
||||
if diff.abs() > 50_000 {
|
||||
defmt::info!("Out of range, dropping sample.");
|
||||
continue;
|
||||
}
|
||||
|
||||
samples[count] = freq;
|
||||
short_avg = 0;
|
||||
for i in 0..WINDOW_LEN {
|
||||
short_avg += samples[i];
|
||||
}
|
||||
short_avg = short_avg / WINDOW_LEN as u64;
|
||||
defmt::info!("short_avg:\t{} mHz", short_avg);
|
||||
|
||||
cx.shared.device_status.lock(|device_status| {
|
||||
device_status.measured_frequency = freq;
|
||||
device_status.average_frequency = short_avg;
|
||||
device_status.pwm = cur_pwm as u16;
|
||||
});
|
||||
|
||||
count += 1;
|
||||
board_led.toggle();
|
||||
}
|
||||
|
||||
let diff = TARGET_FREQ as i64 - short_avg as i64;
|
||||
if diff.abs() > 100 {
|
||||
cur_pwm += (diff * 30 / 1000) as i32;
|
||||
} else if diff < -10 {
|
||||
cur_pwm -= 1;
|
||||
} else if diff > 10 {
|
||||
cur_pwm += 1;
|
||||
}
|
||||
|
||||
cur_pwm = if cur_pwm < 0 { 0 } else { cur_pwm };
|
||||
cur_pwm = if cur_pwm > max_pwm { max_pwm } else { cur_pwm };
|
||||
|
||||
pwm.set_duty(Channel::C1, cur_pwm as u16);
|
||||
defmt::info!("pwm:\t{}", cur_pwm);
|
||||
|
||||
nvstate.pwm = cur_pwm as u16;
|
||||
nvstate.save(&mut flash);
|
||||
|
||||
Systick::delay(500.millis()).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = USB_HP_CAN_TX, shared = [usb_dev, serial, buffer, device_status])]
|
||||
fn usb_tx(cx: usb_tx::Context) {
|
||||
let mut usb_dev = cx.shared.usb_dev;
|
||||
let mut serial = cx.shared.serial;
|
||||
let mut buffer = cx.shared.buffer;
|
||||
let mut device_status = cx.shared.device_status;
|
||||
|
||||
(&mut usb_dev, &mut serial, &mut buffer, &mut device_status).lock(
|
||||
|usb_dev, serial, buffer, device_status| {
|
||||
usb_poll(usb_dev, serial, buffer, device_status);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[task(binds = USB_LP_CAN_RX0, shared = [usb_dev, serial, buffer, device_status])]
|
||||
fn usb_rx0(cx: usb_rx0::Context) {
|
||||
let mut usb_dev = cx.shared.usb_dev;
|
||||
let mut serial = cx.shared.serial;
|
||||
let mut buffer = cx.shared.buffer;
|
||||
let mut device_status = cx.shared.device_status;
|
||||
|
||||
(&mut usb_dev, &mut serial, &mut buffer, &mut device_status).lock(
|
||||
|usb_dev, serial, buffer, device_status| {
|
||||
usb_poll(usb_dev, serial, buffer, device_status);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn usb_poll<B: usb_device::bus::UsbBus>(
|
||||
usb_dev: &mut usb_device::prelude::UsbDevice<'static, B>,
|
||||
serial: &mut usbd_serial::SerialPort<'static, B>,
|
||||
buffer: &mut Vec<u8, USB_BUFFER_SIZE>,
|
||||
device_status: &StatusMessage,
|
||||
) {
|
||||
if !usb_dev.poll(&mut [serial]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut tmp = [0u8; 16];
|
||||
match serial.read(&mut tmp) {
|
||||
Ok(count) if count > 0 => {
|
||||
if buffer.extend_from_slice(&tmp[0..count]).is_err() {
|
||||
buffer.clear();
|
||||
defmt::error!("Buffer overflow while waiting for the end of the packet");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
loop {
|
||||
if let Some(idx) = buffer.iter().position(|&x| x == 0) {
|
||||
let (msg, rest) = buffer.split_at(idx + 1);
|
||||
|
||||
let mut message = [0u8; 128];
|
||||
message[0..msg.len()].clone_from_slice(msg);
|
||||
let host_msg = from_bytes_cobs::<HostMessage>(&mut message);
|
||||
|
||||
match host_msg {
|
||||
Ok(host_msg) => match host_msg {
|
||||
HostMessage::RequestStatus => {
|
||||
let device_msg = DeviceMessage::Status(device_status.clone());
|
||||
let bytes =
|
||||
to_vec_cobs::<DeviceMessage, USB_BUFFER_SIZE>(&device_msg).unwrap();
|
||||
serial.write(bytes.as_slice()).unwrap();
|
||||
}
|
||||
HostMessage::SetPLLOutputs => {
|
||||
defmt::error!("PLL output is not implemented yet")
|
||||
}
|
||||
},
|
||||
Err(err) => defmt::error!("Unable to parse host message: {}", err),
|
||||
};
|
||||
|
||||
*buffer = Vec::<u8, USB_BUFFER_SIZE>::from_slice(rest).unwrap();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
use postcard::{from_bytes_cobs, to_slice_cobs};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stm32f1xx_hal::flash::{self, FlashSize, FlashWriter, SectorSize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct NVState {
|
||||
pub pwm: u16,
|
||||
}
|
||||
|
||||
impl Default for NVState {
|
||||
fn default() -> Self {
|
||||
Self { pwm: 1500u16 }
|
||||
}
|
||||
}
|
||||
|
||||
const PAGE_SIZE: usize = 1024;
|
||||
const NVSTATE_OFFSET: u32 = 63 * PAGE_SIZE as u32;
|
||||
|
||||
const FLASH_SIZE: FlashSize = FlashSize::Sz64K;
|
||||
const SECTOR_SIZE: SectorSize = SectorSize::Sz1K;
|
||||
|
||||
pub fn load(flash: &mut flash::Parts) -> NVState {
|
||||
let mut flash_writer = flash.writer(SECTOR_SIZE, FLASH_SIZE);
|
||||
|
||||
let tmp = flash_writer.read(NVSTATE_OFFSET, PAGE_SIZE).unwrap();
|
||||
let mut nvstate_page = [0u8; PAGE_SIZE];
|
||||
nvstate_page.copy_from_slice(tmp);
|
||||
|
||||
match from_bytes_cobs::<NVState>(&mut nvstate_page) {
|
||||
Ok(nvstate) => nvstate,
|
||||
Err(_) => NVState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl NVState {
|
||||
pub fn save(&self, flash: &mut flash::Parts) {
|
||||
let mut flash_writer = flash.writer(SECTOR_SIZE, FLASH_SIZE);
|
||||
|
||||
let mut page = [0u8; PAGE_SIZE];
|
||||
let used = to_slice_cobs(self, &mut page).unwrap();
|
||||
|
||||
flash_writer.page_erase(NVSTATE_OFFSET).unwrap();
|
||||
flash_writer.write(NVSTATE_OFFSET, &used).unwrap();
|
||||
}
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
use core::marker::PhantomData;
|
||||
use embedded_hal::blocking::i2c;
|
||||
|
||||
const I2C_ADDR: u8 = 96;
|
||||
|
||||
const CLK_ENABLE_CONTROL: u8 = 3;
|
||||
//const PLLX_SRC: u8 = 15;
|
||||
const PLL_RESET: u8 = 177;
|
||||
const XTAL_LOAD_CAP: u8 = 183;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum PLL {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
const PLL_BASE_ADDR: [u8; 2] = [26, 34];
|
||||
|
||||
impl PLL {
|
||||
fn base_address(&self) -> u8 {
|
||||
return PLL_BASE_ADDR[*self as usize];
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Multisynth {
|
||||
MS0,
|
||||
MS1,
|
||||
MS2,
|
||||
}
|
||||
|
||||
const MS_BASE_ADDR: [u8; 3] = [42, 50, 58];
|
||||
const MS_CTRL_ADDR: [u8; 3] = [16, 17, 18];
|
||||
const CLK_PHOFF_ADDR: [u8; 3] = [165, 166, 167];
|
||||
|
||||
impl Multisynth {
|
||||
fn base_address(&self) -> u8 {
|
||||
return MS_BASE_ADDR[*self as usize];
|
||||
}
|
||||
|
||||
fn ctrl_address(&self) -> u8 {
|
||||
return MS_CTRL_ADDR[*self as usize];
|
||||
}
|
||||
|
||||
fn phoff_address(&self) -> u8 {
|
||||
defmt::debug!("Adress: {}", CLK_PHOFF_ADDR[*self as usize]);
|
||||
return CLK_PHOFF_ADDR[*self as usize];
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PllParams {
|
||||
pub p1: u32,
|
||||
pub p2: u32,
|
||||
pub p3: u32,
|
||||
}
|
||||
|
||||
pub struct Si5153<I2C> {
|
||||
// Marker that makes sure we always get the same I2C
|
||||
i2c: PhantomData<I2C>,
|
||||
freq_xtal: u32,
|
||||
pll_freqs: [u32; 2],
|
||||
outputs: u8,
|
||||
ms_srcs: [PLL; 3],
|
||||
}
|
||||
|
||||
impl<I2C, E> Si5153<I2C>
|
||||
where
|
||||
I2C: i2c::Write<Error = E>,
|
||||
{
|
||||
pub fn new(_i2c: &I2C) -> Self {
|
||||
Si5153 {
|
||||
i2c: PhantomData,
|
||||
freq_xtal: 0,
|
||||
pll_freqs: [0, 0],
|
||||
outputs: 0,
|
||||
ms_srcs: [PLL::A, PLL::A, PLL::A],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, i2c: &mut I2C, freq_xtal: u32, freq_a: u32, freq_b: u32) {
|
||||
self.freq_xtal = freq_xtal;
|
||||
|
||||
self.pll_freqs[PLL::A as usize] = freq_a;
|
||||
self.pll_freqs[PLL::B as usize] = freq_b;
|
||||
|
||||
self.outputs = 0xFF;
|
||||
self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs); // Disable all outputs
|
||||
self.write_byte_reg(i2c, XTAL_LOAD_CAP, 0xD2); //crystal load capacitor = 10pF
|
||||
|
||||
self.set_pll_freq(i2c, PLL::A, freq_a);
|
||||
self.set_pll_freq(i2c, PLL::B, freq_b);
|
||||
|
||||
for ms in [Multisynth::MS0, Multisynth::MS1, Multisynth::MS2].iter() {
|
||||
self.ms_srcs[*ms as usize] = PLL::A;
|
||||
self.write_byte_reg(i2c, ms.ctrl_address(), 0x0F); // MSi as Source, PLLA to MSi, 8 mA output
|
||||
self.write_byte_reg(i2c, ms.phoff_address(), 0); // Phase offset to 0.
|
||||
}
|
||||
|
||||
self.write_byte_reg(i2c, PLL_RESET, 0xA0); // Reset both PLLs
|
||||
}
|
||||
|
||||
pub fn set_pll_freq(&mut self, i2c: &mut I2C, pll: PLL, freq: u32) {
|
||||
// Divider is a + (b/c)
|
||||
|
||||
let (a, b, c) = as_fraction(freq, self.freq_xtal);
|
||||
|
||||
let params = PllParams {
|
||||
p1: 128 * a + (128 * b / c) - 512,
|
||||
p2: 128 * b - c * (128 * b / c),
|
||||
p3: c,
|
||||
};
|
||||
|
||||
self.write_params(i2c, pll.base_address(), ¶ms);
|
||||
self.pll_freqs[pll as usize] = freq;
|
||||
}
|
||||
|
||||
pub fn enable_ms_output(&mut self, i2c: &mut I2C, synth: Multisynth) {
|
||||
self.outputs &= !(1 << (synth as u8));
|
||||
self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs);
|
||||
}
|
||||
|
||||
pub fn disable_ms_output(&mut self, i2c: &mut I2C, synth: Multisynth) {
|
||||
self.outputs |= 1 << (synth as u8);
|
||||
self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs);
|
||||
}
|
||||
|
||||
pub fn set_ms_source(&mut self, i2c: &mut I2C, synth: Multisynth, pll: PLL) {
|
||||
let value: u8 = if pll == PLL::A {
|
||||
self.ms_srcs[synth as usize] = PLL::A;
|
||||
0x0F // MS as Source, PLLA to MS, 8 mA output
|
||||
} else {
|
||||
self.ms_srcs[synth as usize] = PLL::B;
|
||||
0x2F // MS as Source, PLLB to MS, 8 mA output
|
||||
};
|
||||
|
||||
self.write_byte_reg(i2c, synth.ctrl_address(), value);
|
||||
}
|
||||
|
||||
pub fn set_ms_freq(&mut self, i2c: &mut I2C, synth: Multisynth, freq: u32) {
|
||||
let pll = self.ms_srcs[synth as usize];
|
||||
let pll_freq = self.pll_freqs[pll as usize];
|
||||
|
||||
let (a, b, c) = as_fraction(pll_freq, freq);
|
||||
|
||||
let params = PllParams {
|
||||
p1: 128 * a + (128 * b / c) - 512,
|
||||
p2: 128 * b - c * (128 * b / c),
|
||||
p3: c,
|
||||
};
|
||||
self.write_params(i2c, synth.base_address(), ¶ms)
|
||||
}
|
||||
|
||||
pub fn set_ms_phase(&mut self, i2c: &mut I2C, synth: Multisynth, phase: u8) {
|
||||
self.write_byte_reg(i2c, synth.phoff_address(), phase);
|
||||
|
||||
match self.ms_srcs[synth as usize] {
|
||||
PLL::A => {
|
||||
self.write_byte_reg(i2c, PLL_RESET, 1 << 5);
|
||||
}
|
||||
PLL::B => {
|
||||
self.write_byte_reg(i2c, PLL_RESET, 1 << 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pll_reset(&mut self, i2c: &mut I2C) {
|
||||
self.write_byte_reg(i2c, PLL_RESET, 0xA0);
|
||||
}
|
||||
|
||||
fn write_byte_reg(&self, i2c: &mut I2C, reg_addr: u8, data: u8) {
|
||||
let res = i2c.write(I2C_ADDR, &[reg_addr, data]);
|
||||
if res.is_err() {
|
||||
panic!("i2c write failed. regAdder: {}", reg_addr)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_params(&self, i2c: &mut I2C, base: u8, params: &PllParams) {
|
||||
let data: [u8; 9] = [
|
||||
base,
|
||||
((params.p3 & 0x00FF00) >> 8) as u8,
|
||||
(params.p3 & 0x0000FF) as u8,
|
||||
((params.p1 & 0x030000) >> 16) as u8,
|
||||
((params.p1 & 0x00FF00) >> 8) as u8,
|
||||
(params.p1 & 0x0000FF) as u8,
|
||||
(((params.p3 & 0x0F0000) >> 12) | ((params.p2 & 0x0F0000) >> 16)) as u8,
|
||||
((params.p2 & 0x00FF00) >> 8) as u8,
|
||||
(params.p2 & 0x0000FF) as u8,
|
||||
];
|
||||
|
||||
let res = i2c.write(I2C_ADDR, &data);
|
||||
if res.is_err() {
|
||||
panic!("i2c write failed. regAdder: {}", base)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_synth_params(&self, i2c: &mut I2C, synth: Multisynth, params: &PllParams) {
|
||||
self.write_params(i2c, synth.base_address(), params);
|
||||
}
|
||||
}
|
||||
|
||||
// Turns a fraction x/y into a + b/c with minimal c.
|
||||
// If c is larger than 0x0FFFFF, c will be limited to that value and
|
||||
// a best effort approximation for b will be computed
|
||||
fn as_fraction(x: u32, y: u32) -> (u32, u32, u32) {
|
||||
let gcd = num::integer::gcd(x, y);
|
||||
|
||||
let num = x / gcd;
|
||||
let denom = y / gcd;
|
||||
|
||||
let a = num / denom;
|
||||
|
||||
if denom < 0x0FFFFF {
|
||||
let rm = num % denom;
|
||||
let c = denom;
|
||||
let b = rm;
|
||||
(a, b, c)
|
||||
} else {
|
||||
let rm = x % y;
|
||||
let c = 0x0FFFFF;
|
||||
let b = ((rm as u64) * (c as u64) / (y as u64)) as u32;
|
||||
(a, b, c)
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
[package]
|
||||
name = "cheapsdo-control"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cheapsdo-protocol = { path = "../protocol" }
|
||||
crossbeam-channel = "0.5.11"
|
||||
eframe = "0.24.1"
|
||||
egui_plot = "0.24.1"
|
||||
postcard = {version = "1.0.8", features = ["use-std"]}
|
||||
serialport = "4.3.0"
|
|
@ -1,12 +0,0 @@
|
|||
pub fn frequency(freq: f64) -> String {
|
||||
let rest = (freq * 1_000_000_000.0) as u64;
|
||||
let milli_hz = rest % 1000;
|
||||
let rest = rest / 1000;
|
||||
let hz = rest % 1000;
|
||||
let rest = rest / 1000;
|
||||
let khz = rest % 1000;
|
||||
let rest = rest / 1000;
|
||||
let mhz = rest % 1000;
|
||||
|
||||
format!("{:02}.{:03} {:03} {:03} MHz", mhz, khz, hz, milli_hz)
|
||||
}
|
|
@ -1,262 +0,0 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use std::io;
|
||||
|
||||
use std::io::Write;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam_channel::{select, unbounded, Receiver, Sender};
|
||||
|
||||
use eframe::egui;
|
||||
use egui_plot::{Line, Plot, PlotPoints};
|
||||
|
||||
use cheapsdo_protocol::*;
|
||||
use postcard::{from_bytes_cobs, to_stdvec_cobs};
|
||||
|
||||
mod formatters;
|
||||
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default(),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"Cheapsdo Control",
|
||||
options,
|
||||
Box::new(|_cc| Box::<CheapsdoControl>::default()),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum SerialPortCmd {
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum SerialPortData {
|
||||
DeviceState(StatusMessage),
|
||||
}
|
||||
|
||||
struct CheapsdoControl {
|
||||
serial_device: String,
|
||||
serial_connected: bool,
|
||||
|
||||
data_rx: Option<Receiver<SerialPortData>>,
|
||||
cmd_tx: Option<Sender<SerialPortCmd>>,
|
||||
|
||||
device_state: StatusMessage,
|
||||
average_points: Vec<[f64; 2]>,
|
||||
pwm_points: Vec<[f64; 2]>,
|
||||
}
|
||||
|
||||
impl Default for CheapsdoControl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
serial_device: "".to_owned(),
|
||||
serial_connected: false,
|
||||
data_rx: None,
|
||||
cmd_tx: None,
|
||||
|
||||
device_state: StatusMessage::default(),
|
||||
average_points: Vec::new(),
|
||||
pwm_points: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for CheapsdoControl {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
let ports =
|
||||
serialport::available_ports().unwrap_or(Vec::<serialport::SerialPortInfo>::new());
|
||||
|
||||
if let Some(data_rx) = &self.data_rx {
|
||||
loop {
|
||||
select! {
|
||||
recv(data_rx) -> data => {
|
||||
match data {
|
||||
Ok(SerialPortData::DeviceState(status_msg)) => {
|
||||
self.device_state = status_msg.clone();
|
||||
self.average_points.push([
|
||||
self.average_points.len() as f64,
|
||||
status_msg.average_frequency as f64 / 1_000_000_000.0
|
||||
]);
|
||||
self.pwm_points.push([
|
||||
self.pwm_points.len() as f64,
|
||||
status_msg.pwm as f64
|
||||
]);
|
||||
},
|
||||
Err(_) => break,
|
||||
};
|
||||
},
|
||||
default => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
egui::ComboBox::from_label("Select Serialport")
|
||||
.selected_text(self.serial_device.to_owned())
|
||||
.show_ui(ui, |ui| {
|
||||
for port in ports {
|
||||
ui.selectable_value(
|
||||
&mut self.serial_device,
|
||||
port.port_name.to_owned(),
|
||||
port.port_name,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if ui
|
||||
.add_enabled(
|
||||
!self.serial_connected && !self.serial_device.is_empty(),
|
||||
egui::Button::new("Open"),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
let serial_device = self.serial_device.clone();
|
||||
let ctx = ctx.clone();
|
||||
|
||||
self.serial_connected = true;
|
||||
self.average_points.clear();
|
||||
self.pwm_points.clear();
|
||||
|
||||
let (cmd_tx, cmd_rx) = unbounded();
|
||||
let (data_tx, data_rx) = unbounded();
|
||||
|
||||
self.cmd_tx = Some(cmd_tx);
|
||||
self.data_rx = Some(data_rx);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
poll_device(serial_device, cmd_rx, data_tx, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
if ui
|
||||
.add_enabled(self.serial_connected, egui::Button::new("Close"))
|
||||
.clicked()
|
||||
{
|
||||
if let Some(cmd_tx) = &self.cmd_tx {
|
||||
cmd_tx.send(SerialPortCmd::Disconnect).unwrap();
|
||||
}
|
||||
self.serial_connected = false;
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
"Measured: {}",
|
||||
formatters::frequency(
|
||||
self.device_state.measured_frequency as f64 / 1_000_000_000.0
|
||||
)
|
||||
))
|
||||
.family(egui::FontFamily::Monospace)
|
||||
.size(20.0),
|
||||
);
|
||||
|
||||
ui.add_space(100.0);
|
||||
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
"Average: {}",
|
||||
formatters::frequency(
|
||||
self.device_state.average_frequency as f64 / 1_000_000_000.0
|
||||
)
|
||||
))
|
||||
.family(egui::FontFamily::Monospace)
|
||||
.size(20.0),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(20.0);
|
||||
|
||||
let average_line = Line::new(PlotPoints::new(self.average_points.clone()));
|
||||
//let measured_line = Line::new(PlotPoints::new(self.measured_points.clone()));
|
||||
Plot::new("frequency_plot")
|
||||
.view_aspect(4.0)
|
||||
.allow_zoom(false)
|
||||
.allow_scroll(false)
|
||||
.allow_drag(false)
|
||||
.allow_boxed_zoom(false)
|
||||
.y_axis_width(12)
|
||||
.y_axis_formatter(|val, _, _| formatters::frequency(val))
|
||||
.label_formatter(|name, value| {
|
||||
format!("{}: {}", name, formatters::frequency(value.y))
|
||||
})
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.set_auto_bounds([true, true].into());
|
||||
plot_ui.line(average_line);
|
||||
//plot_ui.line(measured_line);
|
||||
});
|
||||
|
||||
ui.add_space(20.0);
|
||||
|
||||
let pwm_line = Line::new(PlotPoints::new(self.pwm_points.clone()));
|
||||
Plot::new("pwm_plot")
|
||||
.view_aspect(4.0)
|
||||
.allow_zoom(false)
|
||||
.allow_scroll(false)
|
||||
.allow_drag(false)
|
||||
.allow_boxed_zoom(false)
|
||||
.y_axis_width(12)
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.set_auto_bounds([true, true].into());
|
||||
plot_ui.line(pwm_line);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_device(
|
||||
port: String,
|
||||
cmd_rx: Receiver<SerialPortCmd>,
|
||||
data_tx: Sender<SerialPortData>,
|
||||
ctx: egui::Context,
|
||||
) {
|
||||
let mut port = serialport::new(port, 115_200)
|
||||
.timeout(Duration::from_millis(10))
|
||||
.open()
|
||||
.expect("Failed to open port");
|
||||
|
||||
let host_msg = HostMessage::RequestStatus;
|
||||
let msg_bytes = to_stdvec_cobs(&host_msg).unwrap();
|
||||
port.write_all(&msg_bytes).unwrap();
|
||||
|
||||
loop {
|
||||
let mut serial_buf: Vec<u8> = vec![0; 128];
|
||||
match port.read(serial_buf.as_mut_slice()) {
|
||||
Ok(t) => {
|
||||
serial_buf.truncate(t);
|
||||
println!("Data: {:?}", serial_buf);
|
||||
|
||||
let dev_msg = from_bytes_cobs::<DeviceMessage>(&mut serial_buf).unwrap();
|
||||
|
||||
match dev_msg {
|
||||
DeviceMessage::Status(status_msg) => {
|
||||
data_tx
|
||||
.send(SerialPortData::DeviceState(status_msg))
|
||||
.unwrap();
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (),
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
|
||||
select! {
|
||||
recv(cmd_rx) -> cmd => match cmd {
|
||||
Ok(SerialPortCmd::Disconnect) => return,
|
||||
Err(_) => {},
|
||||
},
|
||||
default(Duration::from_secs(1)) => {
|
||||
let host_msg = HostMessage::RequestStatus;
|
||||
let msg_bytes = to_stdvec_cobs(&host_msg).unwrap();
|
||||
port.write_all(&msg_bytes).unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
3
memory.x
3
memory.x
|
@ -1,7 +1,6 @@
|
|||
/* Linker script for the STM32F103C8T6 */
|
||||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x08000000, LENGTH = 63K
|
||||
NVSTATE : ORIGIN = FLASH + 63K, LENGTH = 1K
|
||||
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 20K
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
[package]
|
||||
name = "cheapsdo-protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = {version = "1.0.193", default-features = false, features = ["derive"]}
|
|
@ -1,31 +0,0 @@
|
|||
#![no_std]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum DeviceMessage {
|
||||
Status(StatusMessage),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum HostMessage {
|
||||
RequestStatus,
|
||||
SetPLLOutputs,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct StatusMessage {
|
||||
pub measured_frequency: u64,
|
||||
pub average_frequency: u64,
|
||||
pub pwm: u16,
|
||||
}
|
||||
|
||||
impl Default for StatusMessage {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
measured_frequency: 0u64,
|
||||
average_frequency: 0u64,
|
||||
pwm: 0u16,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
#![deny(unsafe_code)]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
use panic_probe as _;
|
||||
use stm32f1xx_hal as _;
|
||||
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use stm32f1xx_hal::{
|
||||
delay::Delay,
|
||||
pac,
|
||||
pac::TIM1,
|
||||
pac::TIM2,
|
||||
prelude::*,
|
||||
rcc::Enable,
|
||||
rcc::Reset,
|
||||
timer::{Tim3NoRemap, Timer},
|
||||
};
|
||||
|
||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||
#[defmt::panic_handler]
|
||||
fn panic() -> ! {
|
||||
cortex_m::asm::udf()
|
||||
}
|
||||
|
||||
static COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
defmt::timestamp!("{=u32}", COUNT.fetch_add(1, Ordering::Relaxed));
|
||||
|
||||
/// Terminates the application and makes `probe-run` exit with exit-code = 0
|
||||
pub fn exit() -> ! {
|
||||
loop {
|
||||
cortex_m::asm::bkpt();
|
||||
}
|
||||
}
|
||||
|
||||
const target_freq: f64 = 10.0f64;
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
// Get access to the core peripherals from the cortex-m crate
|
||||
let cp = cortex_m::Peripherals::take().unwrap();
|
||||
// Get access to the device specific peripherals from the peripheral access crate
|
||||
let dp = pac::Peripherals::take().unwrap();
|
||||
|
||||
// Take ownership over the raw flash and rcc devices and convert them into the corresponding
|
||||
// HAL structs
|
||||
let mut flash = dp.FLASH.constrain();
|
||||
let mut rcc = dp.RCC.constrain();
|
||||
|
||||
let clocks = rcc
|
||||
.cfgr
|
||||
.use_hse(8.mhz())
|
||||
.sysclk(48.mhz())
|
||||
.pclk1(24.mhz())
|
||||
.freeze(&mut flash.acr);
|
||||
|
||||
// Freeze the configuration of all the clocks in the system and store the frozen frequencies in
|
||||
// `clocks`
|
||||
//let clocks = rcc.cfgr.freeze(&mut flash.acr);
|
||||
|
||||
// Acquire the GPIOC peripheral
|
||||
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
|
||||
|
||||
// Configure gpio C pin 13 as a push-pull output. The `crh` register is passed to the function
|
||||
// in order to configure the port. For pins 0-7, crl should be passed instead.
|
||||
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
|
||||
|
||||
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
|
||||
let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
|
||||
let pwm_pin = gpioa.pa6.into_alternate_push_pull(&mut gpioa.crl);
|
||||
let mut pwm = Timer::tim3(dp.TIM3, &clocks, &mut rcc.apb1)
|
||||
.pwm::<Tim3NoRemap, _, _, _>(pwm_pin, &mut afio.mapr, 10.khz())
|
||||
.split();
|
||||
|
||||
pwm.enable();
|
||||
|
||||
// Setup timers
|
||||
let tim1 = dp.TIM1;
|
||||
|
||||
TIM1::enable(&mut rcc.apb2);
|
||||
TIM1::reset(&mut rcc.apb2);
|
||||
|
||||
// Enable external clocking
|
||||
tim1.smcr.write(|w| {
|
||||
w.etf().no_filter(); // No filter for to 10Mhz clock
|
||||
w.etps().div1(); // No divider
|
||||
w.etp().not_inverted(); // on rising edege at ETR pin
|
||||
w.ece().enabled() // mode 2 (use ETR pin)
|
||||
});
|
||||
|
||||
tim1.ccmr1_input().write(|w| {
|
||||
w.cc1s().ti1(); // Input capture using T1 input
|
||||
w.ic1f().no_filter() // No filter on input capture input
|
||||
|
||||
//w.ic1psc().bits(0) // Disable prescaler, not safely implement by HAL yet
|
||||
});
|
||||
|
||||
tim1.ccer.write(|w| {
|
||||
w.cc1p().set_bit(); // Use rising edge on TI
|
||||
w.cc1e().set_bit() // Enable input capture
|
||||
});
|
||||
|
||||
tim1.cr2.write(|w| {
|
||||
w.mms().update() // Trigger output on update/overflow
|
||||
});
|
||||
|
||||
// Counting up to 10^7 should need 24 bits
|
||||
// Clock tim2 by tim1s overflow to make a 32bit timer
|
||||
|
||||
let tim2 = dp.TIM2;
|
||||
|
||||
TIM2::enable(&mut rcc.apb1);
|
||||
TIM2::reset(&mut rcc.apb1);
|
||||
|
||||
tim2.smcr.write(|w| {
|
||||
w.ts().itr0(); // Trigger from internal trigger 0
|
||||
w.sms().ext_clock_mode() // Use trigger as clock
|
||||
});
|
||||
|
||||
tim2.ccmr1_input().write(|w| {
|
||||
w.cc1s().ti1(); // Input capture using T1 input
|
||||
w.ic1f().no_filter() // No filter on input capture input
|
||||
|
||||
//w.ic1psc().bits(0) // Disable prescaler, not safely implement by HAL yet
|
||||
});
|
||||
|
||||
tim2.ccer.write(|w| {
|
||||
w.cc1p().set_bit(); // Use rising edge on TI
|
||||
w.cc1e().set_bit() // Enable input capture
|
||||
});
|
||||
|
||||
tim1.cr1.write(|w| w.cen().enabled());
|
||||
tim2.cr1.write(|w| w.cen().enabled());
|
||||
|
||||
let mut delay = Delay::new(cp.SYST, clocks);
|
||||
|
||||
let mut last_ic = 0u32;
|
||||
let mut avg = 10f64;
|
||||
let max_pwm = pwm.get_max_duty() as u32;
|
||||
let mut cur_pwm = 3000u32; //max_pwm / 2;
|
||||
|
||||
// Skip the first measurement, it will be garbage
|
||||
while !tim1.sr.read().cc1if().bit_is_set() || !tim2.sr.read().cc1if().bit_is_set() {
|
||||
delay.delay_ms(10u16);
|
||||
}
|
||||
let ic1 = tim1.ccr1.read().bits();
|
||||
let ic2 = tim2.ccr1.read().bits();
|
||||
|
||||
last_ic = ic2 << 16 | ic1;
|
||||
|
||||
loop {
|
||||
while !tim1.sr.read().cc1if().bit_is_set() || !tim2.sr.read().cc1if().bit_is_set() {
|
||||
delay.delay_ms(10u16);
|
||||
}
|
||||
|
||||
let ic1 = tim1.ccr1.read().bits();
|
||||
let ic2 = tim2.ccr1.read().bits();
|
||||
|
||||
let sum_ic = ic2 << 16 | ic1;
|
||||
|
||||
let diff_ic = if sum_ic > last_ic {
|
||||
sum_ic - last_ic
|
||||
} else {
|
||||
u32::MAX - last_ic + sum_ic
|
||||
};
|
||||
|
||||
last_ic = sum_ic;
|
||||
|
||||
let freq = (diff_ic as f64) / 1_000_000f64;
|
||||
let diff = freq - avg;
|
||||
|
||||
led.toggle().unwrap();
|
||||
|
||||
if diff > 0.000_100 || diff < -0.000_100 {
|
||||
continue;
|
||||
}
|
||||
|
||||
avg = avg * 0.999 + freq * 0.001;
|
||||
|
||||
cur_pwm = if 10_000_000 >= diff_ic {
|
||||
cur_pwm + (10_000_000 - diff_ic)
|
||||
} else {
|
||||
cur_pwm - (diff_ic - 10_000_000)
|
||||
};
|
||||
cur_pwm = if cur_pwm > max_pwm { max_pwm } else { cur_pwm };
|
||||
|
||||
pwm.set_duty(cur_pwm as u16);
|
||||
|
||||
defmt::info!("ic1:\t{}", ic1);
|
||||
defmt::info!("ic2:\t{}", ic2);
|
||||
defmt::info!("sum_ic:\t{}", sum_ic);
|
||||
defmt::info!("diff_ic:\t{}", diff_ic);
|
||||
defmt::info!("freq:\t{} MHz", freq);
|
||||
defmt::info!("avg:\t{} MHz", avg);
|
||||
defmt::info!("pwm:\t{}", cur_pwm);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue