diff options
author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2020-06-28 16:42:21 +0200 |
---|---|---|
committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2020-06-28 16:42:21 +0200 |
commit | 5d1cff57f9f5acd740a8b5f8c941beefdcc00176 (patch) | |
tree | bdfd0e394d6333aead7d3a0295ba3457bd68275d | |
parent | 93220f99a52dc93f9a2d5b11074f60156ef70210 (diff) | |
download | picardy-5d1cff57f9f5acd740a8b5f8c941beefdcc00176.tar.gz picardy-5d1cff57f9f5acd740a8b5f8c941beefdcc00176.tar.bz2 picardy-5d1cff57f9f5acd740a8b5f8c941beefdcc00176.zip |
sw: configure si5351
59 files changed, 3265 insertions, 8 deletions
@@ -32,9 +32,12 @@ TODO before ordering the PCB * Si5153 * Check desired frequencies can be achieved: - * LO1 = 28 - 4.9152 + VFO - * BFO = 4.91521 - * VHF-LO = 144 + 28 and 144 - 28 + * clk0: LO1 = 28 - 4.9152 + VFO + * clk1: VHF-LO = 144 + 28 and 144 - 28 + * clk2: BFO = 4.91521 + * See `freqplan.py` + * If not, generate LO1 and BFO with Si5153, and connect an external LO to the VHF LO u.FL + * No 116MHz crystals on mouser, but 114.285MHz are available, HF bandpass filters recalculated. PCB Assembly Plan ================= diff --git a/freqplan.py b/freqplan.py new file mode 100644 index 0000000..8ad520f --- /dev/null +++ b/freqplan.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +from fractions import Fraction +from math import floor + +xtal = 25 + +print() +print("# 160MHz and 4.915 MHz on PLLa") + +PLLa = xtal * 32 +print(f"PLLa = xtal * 32 = {PLLa} MHz") + +# x/y = a + b/y +# a = floor(x/y) +# b = x - (a * y) + +f1 = 116 +a = floor(PLLa / f1) +b = PLLa - (a * f1) +div1 = Fraction(b, f1) +print(f"CLK1: f1 = PLLa / ({a} + {div1.numerator} / {div1.denominator}) = {f1}") + +f2 = 4.91521 +a = floor(PLLa / f2) +b = PLLa - (a * f2) +div1 = Fraction(round(b * 10000), round(f2 * 10000)) +print(f"CLK2: f2 = PLLa / ({a} + {div1.numerator} / {div1.denominator}) = {f2}") + + +print() +print("# VFO between 23 and 25 MHz on PLLb") + +def calculate(frequency): + divider = floor(900000000 / frequency) # Calculate the division ratio. 900,000,000 is the maximum internal + + if divider % 2: + divider -= 1 # Ensure an even integer division ratio + + pllFreq = round(divider * frequency) # Calculate the pllFrequency: the divider * desired output frequency + + xtalFreq = xtal * 1000000 + mult = floor(pllFreq / xtalFreq) # Determine the multiplier to get to the required pllFrequency + + l = pllFreq % xtalFreq # It has three parts: + + f = floor(l) # mult is an integer that must be in the range 15..90 + denom = 1048575 + f *= denom # num and denom are the fractional parts, the numerator and denominator + f /= xtalFreq # each is 20 bits (range 0..1048575) + + num = floor(f) # the actual multiplier is mult + num / denom + return (divider, mult, num, denom) + +vfo = 23000000 +for i in range(10): + divider, mult, num, denom = calculate(vfo) + + PLLb = xtal * 1000000 * (mult + num/denom) + + freq = PLLb / divider + + delta = vfo - freq + + print(f"CLK0: VFO {vfo}, PLLb = xtal * ({mult} + {num} / {denom}) = {PLLb}, PLLb / {divider} = {freq}, delta = {delta:.03} Hz") + vfo += 100000 diff --git a/sw/demo1/.gdbinit b/sw/demo1/.gdbinit new file mode 100644 index 0000000..08b6398 --- /dev/null +++ b/sw/demo1/.gdbinit @@ -0,0 +1,18 @@ +target remote :3333 + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.fifo uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +#load +#step diff --git a/sw/demo1/Cargo.lock b/sw/demo1/Cargo.lock index f3bfa79..89d8829 100644 --- a/sw/demo1/Cargo.lock +++ b/sw/demo1/Cargo.lock @@ -238,7 +238,6 @@ dependencies = [ [[package]] name = "si5351" version = "0.2.0" -source = "git+https://github.com/ilya-epifanov/si5351?rev=e509c36#e509c36df9b7521ffb253079e3226d6b870f0b4d" dependencies = [ "bitflags", "embedded-hal", diff --git a/sw/demo1/Cargo.toml b/sw/demo1/Cargo.toml index cf33da1..28c9e67 100644 --- a/sw/demo1/Cargo.toml +++ b/sw/demo1/Cargo.toml @@ -19,13 +19,15 @@ stm32f1xx-hal = { version = "0.5", features = ["rt", "stm32f103"] } embedded-hal = { version = "0.2", features = [] } shared-bus = { version = "0.1.4", features = ["cortexm"] } hd44780-driver = { path = "../deps/hd44780-driver"} -si5351 = { git = "https://github.com/ilya-epifanov/si5351", rev = "e509c36" } +si5351 = { path = "../deps/si5351" } [profile.dev] codegen-units = 1 incremental = false +opt-level = 'z' [profile.release] codegen-units = 1 debug = true lto = true +opt-level = 'z' diff --git a/sw/demo1/src/main.rs b/sw/demo1/src/main.rs index 4f8ce6e..0b9f965 100644 --- a/sw/demo1/src/main.rs +++ b/sw/demo1/src/main.rs @@ -1,6 +1,8 @@ #![no_main] #![no_std] +use core::convert::TryInto; + use cortex_m_rt::ExceptionFrame; use cortex_m_semihosting::hio; use panic_semihosting as _; @@ -18,6 +20,45 @@ use si5351::{Si5351, Si5351Device}; use core::fmt::Write; +fn gcd(x: u32, y: u32) -> u32 { + let mut x = x; + let mut y = y; + while y != 0 { + let t = y; + y = x % y; + x = t; + } + x +} + +fn clock_settings_for_pll(freq: u32, pll: u32) -> (u16, u32, u32) { + let a = pll / freq; + let b = pll - (a * freq); + let gcd = gcd(b, freq); + + let b = b / gcd; + let c = freq / gcd; + (a.try_into().unwrap(), b, c) +} + +fn clock_settings_with_pll_calculation(freq: u32, ref_clock: u32) -> (u16, u8, u32, u32) { + let mut divider : u32 = 900_000_000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal + + if (divider % 2) == 1 { + divider -= 1 // Ensure an even integer division ratio + } + + let pll_freq = divider * freq; + // mult is an integer that must be in the range 15..90 + let mult = pll_freq / ref_clock; + let l = pll_freq % ref_clock; + + let denom = 1048575; + let num = f64::from(l) * f64::from(denom) / f64::from(ref_clock); + + (divider.try_into().unwrap(), mult.try_into().unwrap(), num as u32, denom) +} + fn print(step: usize) -> Result<(), core::fmt::Error> { let mut stdout = match hio::hstdout() { Ok(fd) => fd, @@ -112,9 +153,49 @@ fn main() -> ! { let ref_clock = 25_000_000; let mut siclock = Si5351Device::new(i2c_busmanager.acquire(), false, ref_clock); siclock.init(si5351::CrystalLoad::_10).unwrap(); - siclock.set_frequency(si5351::PLL::B, si5351::ClockOutput::Clk0, 28_000_000 + 4_915_200).unwrap(); - siclock.set_frequency(si5351::PLL::A, si5351::ClockOutput::Clk1, 144_000_000 + 28_000_000).unwrap(); - siclock.set_frequency(si5351::PLL::A, si5351::ClockOutput::Clk2, 4_915_100).unwrap(); + + // See freqplan.py for Si5351 frequency plan + // CLK1 = 116MHz + let pll_a_mult = 32; + siclock.setup_pll_int(si5351::PLL::A, 32).unwrap(); + + { + let clk1 = 116_000_000; + let (a, b, c) = clock_settings_for_pll(clk1, pll_a_mult * ref_clock); + siclock.setup_multisynth(si5351::Multisynth::MS1, a, b, c, si5351::OutputDivider::Div1).unwrap(); + siclock.select_clock_pll(si5351::ClockOutput::Clk1, si5351::PLL::A); + siclock.set_clock_enabled(si5351::ClockOutput::Clk1, true); + siclock.flush_clock_control(si5351::ClockOutput::Clk1).unwrap(); + } + + { + let clk2 = 4_195_210; + let (a, b, c) = clock_settings_for_pll(clk2, pll_a_mult * ref_clock); + siclock.setup_multisynth(si5351::Multisynth::MS2, a, b, c, si5351::OutputDivider::Div1).unwrap(); + siclock.select_clock_pll(si5351::ClockOutput::Clk2, si5351::PLL::A); + siclock.set_clock_enabled(si5351::ClockOutput::Clk2, true); + siclock.flush_clock_control(si5351::ClockOutput::Clk2).unwrap(); + } + + siclock.reset_pll(si5351::PLL::A).unwrap(); + + { + let clk0 = 23_000_000; + + let (div, mult, num, denom) = clock_settings_with_pll_calculation(clk0, ref_clock); + + siclock.setup_pll(si5351::PLL::B, mult, num, denom).unwrap(); + siclock.setup_multisynth_int(si5351::Multisynth::MS0, div, si5351::OutputDivider::Div1).unwrap(); + + siclock.select_clock_pll(si5351::ClockOutput::Clk0, si5351::PLL::B); + siclock.set_clock_enabled(si5351::ClockOutput::Clk0, true); + siclock.flush_clock_control(si5351::ClockOutput::Clk0).unwrap(); + } + + siclock.reset_pll(si5351::PLL::B).unwrap(); + + siclock.flush_output_enabled().unwrap(); + lcd.set_cursor_pos(0, &mut delay).unwrap(); lcd.write_str("Clocks set. ", &mut delay).unwrap(); diff --git a/sw/deps/hd44780-driver/.gitignore b/sw/deps/hd44780-driver/.gitignore new file mode 100644 index 0000000..c8bdb63 --- /dev/null +++ b/sw/deps/hd44780-driver/.gitignore @@ -0,0 +1,13 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +/target +**/*.rs.bk diff --git a/sw/deps/hd44780-driver/.travis.yml b/sw/deps/hd44780-driver/.travis.yml new file mode 100644 index 0000000..bc6c006 --- /dev/null +++ b/sw/deps/hd44780-driver/.travis.yml @@ -0,0 +1,8 @@ +language: rust + +rust: + - stable + - beta + - nightly + +cache: cargo
\ No newline at end of file diff --git a/sw/deps/hd44780-driver/Cargo.toml b/sw/deps/hd44780-driver/Cargo.toml new file mode 100644 index 0000000..b6bd9e8 --- /dev/null +++ b/sw/deps/hd44780-driver/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hd44780-driver" +version = "0.3.0" +edition = "2018" +keywords = ["no-std", "lcd", "embedded-hal-driver", "embedded-hal", "hd44780"] +categories = ["embedded", "hardware-support", "no-std"] +description = "A crate to use HD44780 compliant displays with embedded-hal" +authors = ["John Doneth <doneth7@gmail.com>"] +license = "MIT" +repository = "https://github.com/JohnDoneth/hd44780-driver" +documentation = "https://docs.rs/hd44780-driver" +readme = "README.md" + +[dependencies] +embedded-hal = "0.2.3" diff --git a/sw/deps/hd44780-driver/LICENSE b/sw/deps/hd44780-driver/LICENSE new file mode 100644 index 0000000..3ec01c6 --- /dev/null +++ b/sw/deps/hd44780-driver/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 John Doneth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sw/deps/hd44780-driver/README.md b/sw/deps/hd44780-driver/README.md new file mode 100644 index 0000000..cd692f5 --- /dev/null +++ b/sw/deps/hd44780-driver/README.md @@ -0,0 +1,75 @@ +# hd44780-driver + +[![crates.io](https://img.shields.io/crates/v/hd44780-driver.svg)](https://crates.io/crates/hd44780-driver) +[![crates.io](https://img.shields.io/crates/d/hd44780-driver.svg)](https://crates.io/crates/hd44780-driver) +[![crates.io](https://img.shields.io/crates/l/hd44780-driver.svg)](https://crates.io/crates/hd44780-driver) +[![travis-ci.org](https://travis-ci.org/JohnDoneth/hd44780-driver.svg?branch=master)](https://travis-ci.org/JohnDoneth/hd44780-driver) + +Implementation of the `embedded-hal` traits for the HD44780. + +![](/header.gif) + + +### Documentation + +Crates.io - https://docs.rs/hd44780-driver + +### Examples + +Currently there are basic examples for **Raspberry Pi** as well as the **Adafruit Metro Express M0** as those are the devices I currently have on hand. + +Any platform that implements the [embedded-hal](https://github.com/rust-embedded/embedded-hal) traits is supported by this library! See [awesome-embedded-rust](https://github.com/rust-embedded/awesome-embedded-rust#hal-implementation-crates) for a list of supported platforms. + +### Getting Started + +This library aims to keep it simple in that to get started all you will have to do is supply the `HD44780::new` function a bunch of pins from your platform that implement the `OutputPin` trait for [embedded-hal](https://github.com/rust-embedded/embedded-hal) as well as a struct that implements the delay traits `DelayUs<u16>` and `DelayMs<u8>`. + +```rust +// Code grabbed from the metro_m0 example +let mut lcd = HD44780::new_4bit( + pins.d4.into_open_drain_output(&mut pins.port), // Register Select pin + pins.d3.into_open_drain_output(&mut pins.port), // Enable pin + + pins.d9.into_open_drain_output(&mut pins.port), // d4 + pins.d10.into_open_drain_output(&mut pins.port), // d5 + pins.d11.into_open_drain_output(&mut pins.port), // d6 + pins.d12.into_open_drain_output(&mut pins.port), // d7 + + delay, +); + +// Unshift display and set cursor to 0 +lcd.reset(); + +// Clear existing characters +lcd.clear(); + +// Display the following string +lcd.write_str("Hello, world!"); + +// Move the cursor to the second line +lcd.set_cursor_pos(40); + +// Display the following string on the second line +lcd.write_str("I'm on line 2!"); +``` + +### Features +- 4-bit & 8-bit modes are supported +- Support for I2C backpacks based on PCF8574 and MCP23008 port expanders + +### Todo +- Busy flag support (Waiting for support from [embedded-hal](https://github.com/rust-embedded/embedded-hal) to read and write from a pin) +- Non-blocking API +- A more user-friendly API with additional features +- Custom characters + +### Contributing + +- Additional issues as well as pull-requests are welcome. + +- If you have a platform not yet covered in this repository that is supported by [embedded-hal](https://github.com/rust-embedded/embedded-hal), a pull-request of an example would be awesome! + +### License + +This project is licensed under MIT license ([LICENSE](https://github.com/kunerd/clerk/blob/master/docs/CONTRIBUTING.md) or <https://opensource.org/licenses/MIT>) diff --git a/sw/deps/hd44780-driver/examples/.gitignore b/sw/deps/hd44780-driver/examples/.gitignore new file mode 100644 index 0000000..fd8951e --- /dev/null +++ b/sw/deps/hd44780-driver/examples/.gitignore @@ -0,0 +1,2 @@ +target +cargo.lock
\ No newline at end of file diff --git a/sw/deps/hd44780-driver/examples/metro_m0/.cargo/config b/sw/deps/hd44780-driver/examples/metro_m0/.cargo/config new file mode 100644 index 0000000..ce9679d --- /dev/null +++ b/sw/deps/hd44780-driver/examples/metro_m0/.cargo/config @@ -0,0 +1,13 @@ +# samd21 is a Cortex-M0 and thus thumbv6m + +[build] +target = "thumbv6m-none-eabi" + +[target.thumbv6m-none-eabi] +runner = 'arm-none-eabi-gdb' +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "linker=lld", + "-Z", "linker-flavor=ld.lld", + "-Z", "thinlto=no", +]
\ No newline at end of file diff --git a/sw/deps/hd44780-driver/examples/metro_m0/.gdbinit b/sw/deps/hd44780-driver/examples/metro_m0/.gdbinit new file mode 100644 index 0000000..7b9b83b --- /dev/null +++ b/sw/deps/hd44780-driver/examples/metro_m0/.gdbinit @@ -0,0 +1,20 @@ +set auto-load safe-path / + +# print demangled symbols by default +set print asm-demangle on + +# JLink +target extended-remote :2331 +monitor flash breakpoints 1 +# allow hprints to show up in gdb +monitor semihosting enable +monitor semihosting IOClient 3 + +monitor reset +load + +# OpenOCD +#target extended-remote :3333 +#monitor arm semihosting enable +#load +#step
\ No newline at end of file diff --git a/sw/deps/hd44780-driver/examples/metro_m0/Cargo.toml b/sw/deps/hd44780-driver/examples/metro_m0/Cargo.toml new file mode 100644 index 0000000..bd88b22 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/metro_m0/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "metro_m0_hd44780_examples" +version = "0.2.0" +authors = ["John Doneth <doneth7@gmail.com>"] +keywords = ["no-std", "arm", "cortex-m", "embedded-hal"] +license = "MIT" + +[dependencies] +cortex-m = "~0.4" +hd44780-driver = { path = "../.." } +atsamd21-hal = "~0.1" +embedded-hal = "~0.2" +metro_m0 = { git = "https://github.com/wez/atsamd21-rs.git" } + +cortex-m-rt = "*" + +[dev-dependencies] +panic-abort = "~0.1" +panic-semihosting = "~0.1" +cortex-m-semihosting = "~0.2" +cortex-m-rtfm = "~0.3" +sx1509 = "~0.2" + +[features] +# ask the HAL to enable atsamd21g18a support +default = ["rt", "atsamd21-hal/samd21g18a"] +rt = ["atsamd21-hal/rt"] +unproven = ["atsamd21-hal/unproven"] +use_semihosting = [] diff --git a/sw/deps/hd44780-driver/examples/metro_m0/examples/4bit.rs b/sw/deps/hd44780-driver/examples/metro_m0/examples/4bit.rs new file mode 100644 index 0000000..5cf46c3 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/metro_m0/examples/4bit.rs @@ -0,0 +1,71 @@ +#![feature(used)] +#![no_std] + +extern crate cortex_m_semihosting; + +#[cfg(not(feature = "use_semihosting"))] +extern crate panic_abort; + +#[cfg(feature = "use_semihosting")] +extern crate panic_semihosting; + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate atsamd21_hal; +extern crate metro_m0; + +use metro_m0::clock::GenericClockController; +use metro_m0::delay::Delay; +use metro_m0::{CorePeripherals, Peripherals}; + +extern crate hd44780_driver; + +use hd44780_driver::HD44780; + +extern crate embedded_hal; + +fn main() { + let mut peripherals = Peripherals::take().unwrap(); + + let core = CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::new( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + let mut pins = metro_m0::pins(peripherals.PORT); + let delay = Delay::new(core.SYST, &mut clocks); + + let mut lcd = HD44780::new_4bit( + + pins.d4.into_open_drain_output(&mut pins.port), // Register Select pin + pins.d3.into_open_drain_output(&mut pins.port), // Enable pin + + pins.d9.into_open_drain_output(&mut pins.port), // d4 + pins.d10.into_open_drain_output(&mut pins.port), // d5 + pins.d11.into_open_drain_output(&mut pins.port), // d6 + pins.d12.into_open_drain_output(&mut pins.port), // d7 + + delay, + ); + + // Unshift display and set cursor to 0 + lcd.reset(); + + // Clear existing characters + lcd.clear(); + + // Display the following string + lcd.write_str("Hello, world!"); + + // Move the cursor to the second line + lcd.set_cursor_pos(40); + + // Display the following string on the second line + lcd.write_str("I'm on line 2!"); + + loop { } +} diff --git a/sw/deps/hd44780-driver/examples/metro_m0/examples/basic.rs b/sw/deps/hd44780-driver/examples/metro_m0/examples/basic.rs new file mode 100644 index 0000000..2adb5b0 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/metro_m0/examples/basic.rs @@ -0,0 +1,67 @@ +#![feature(used)] +#![no_std] + +extern crate cortex_m_semihosting; + +#[cfg(not(feature = "use_semihosting"))] +extern crate panic_abort; + +#[cfg(feature = "use_semihosting")] +extern crate panic_semihosting; + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate atsamd21_hal; +extern crate metro_m0; + +use metro_m0::clock::GenericClockController; +use metro_m0::delay::Delay; +use metro_m0::{CorePeripherals, Peripherals}; + +extern crate hd44780_driver; + +use hd44780_driver::HD44780; + +fn main() { + let mut peripherals = Peripherals::take().unwrap(); + + let core = CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::new( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + let mut pins = metro_m0::pins(peripherals.PORT); + let delay = Delay::new(core.SYST, &mut clocks); + + let mut lcd = HD44780::new_8bit( + + pins.d4.into_open_drain_output(&mut pins.port), // Register Select pin + pins.d3.into_open_drain_output(&mut pins.port), // Enable pin + + pins.d5.into_open_drain_output(&mut pins.port), // d0 + pins.d6.into_open_drain_output(&mut pins.port), // d1 + pins.d7.into_open_drain_output(&mut pins.port), // d2 + pins.d8.into_open_drain_output(&mut pins.port), // d3 + pins.d9.into_open_drain_output(&mut pins.port), // d4 + pins.d10.into_open_drain_output(&mut pins.port), // d5 + pins.d11.into_open_drain_output(&mut pins.port), // d6 + pins.d12.into_open_drain_output(&mut pins.port), // d7 + + delay, + ); + + // Unshift display and set cursor to 0 + lcd.reset(); + + // Clear existing characters + lcd.clear(); + + // Display the following string + lcd.write_str("Hello, world!"); + + loop { } +} diff --git a/sw/deps/hd44780-driver/examples/metro_m0/examples/scrolling.rs b/sw/deps/hd44780-driver/examples/metro_m0/examples/scrolling.rs new file mode 100644 index 0000000..f02a752 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/metro_m0/examples/scrolling.rs @@ -0,0 +1,91 @@ +#![feature(used)] +#![no_std] + +extern crate cortex_m_semihosting; + +#[cfg(not(feature = "use_semihosting"))] +extern crate panic_abort; + +#[cfg(feature = "use_semihosting")] +extern crate panic_semihosting; + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate atsamd21_hal; +extern crate metro_m0; + +use metro_m0::clock::GenericClockController; +use metro_m0::delay::Delay; +use metro_m0::{CorePeripherals, Peripherals}; + +extern crate hd44780_driver; + +use hd44780_driver::{HD44780, DisplayMode, Display, Cursor, CursorBlink}; + +extern crate embedded_hal; + +fn busy_loop(){ + #[allow(unused_variables)] + let mut i = 0; + + for _ in 0..50000 { + i += 1; + } +} + +fn main() { + let mut peripherals = Peripherals::take().unwrap(); + + let core = CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::new( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + let mut pins = metro_m0::pins(peripherals.PORT); + let delay = Delay::new(core.SYST, &mut clocks); + + let mut lcd = HD44780::new_8bit( + + pins.d4.into_open_drain_output(&mut pins.port), // Register Select pin + pins.d3.into_open_drain_output(&mut pins.port), // Enable pin + + pins.d5.into_open_drain_output(&mut pins.port), // d0 + pins.d6.into_open_drain_output(&mut pins.port), // d1 + pins.d7.into_open_drain_output(&mut pins.port), // d2 + pins.d8.into_open_drain_output(&mut pins.port), // d3 + pins.d9.into_open_drain_output(&mut pins.port), // d4 + pins.d10.into_open_drain_output(&mut pins.port), // d5 + pins.d11.into_open_drain_output(&mut pins.port), // d6 + pins.d12.into_open_drain_output(&mut pins.port), // d7 + + delay, + ); + + //lcd.set_cursor_mode(CursorMode::Increment); + lcd.set_autoscroll(true); + + lcd.set_display_mode(DisplayMode { + cursor_visible : Cursor::Invisible, + cursor_blink : CursorBlink::On, + display_visible : Display::On, + }); + + let string = "Hello, world! "; + + // Display the following string + loop { + + for c in string.chars() { + lcd.write_char(c); + + busy_loop(); + } + + } + + +} diff --git a/sw/deps/hd44780-driver/examples/metro_m0/memory.x b/sw/deps/hd44780-driver/examples/metro_m0/memory.x new file mode 100644 index 0000000..859903b --- /dev/null +++ b/sw/deps/hd44780-driver/examples/metro_m0/memory.x @@ -0,0 +1,8 @@ +MEMORY +{ + /* Leave 8k for the default bootloader on the Metro M0 */ + FLASH (rx) : ORIGIN = 0x00000000 + 8K, LENGTH = 256K - 8K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K +} +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + diff --git a/sw/deps/hd44780-driver/examples/raspberrypi/.gitignore b/sw/deps/hd44780-driver/examples/raspberrypi/.gitignore new file mode 100644 index 0000000..2bf0f84 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/raspberrypi/.gitignore @@ -0,0 +1 @@ +.cargo
\ No newline at end of file diff --git a/sw/deps/hd44780-driver/examples/raspberrypi/Cargo.toml b/sw/deps/hd44780-driver/examples/raspberrypi/Cargo.toml new file mode 100644 index 0000000..db5d98f --- /dev/null +++ b/sw/deps/hd44780-driver/examples/raspberrypi/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "raspberrypi-hd44780-example" +version = "0.2.0" +authors = ["John Doneth <Doneth7@gmail.com>"] + +[dependencies] +linux-embedded-hal = "0.2" +hd44780-driver = { path = "../.." } diff --git a/sw/deps/hd44780-driver/examples/raspberrypi/src/main.rs b/sw/deps/hd44780-driver/examples/raspberrypi/src/main.rs new file mode 100644 index 0000000..2fefcbe --- /dev/null +++ b/sw/deps/hd44780-driver/examples/raspberrypi/src/main.rs @@ -0,0 +1,75 @@ +extern crate linux_embedded_hal; +extern crate hd44780_driver; + +use linux_embedded_hal::{Delay, Pin}; +use linux_embedded_hal::sysfs_gpio::Direction; + +use hd44780_driver::{HD44780, DisplayMode, Cursor, CursorBlink, Display}; + +fn main() { + + let rs = Pin::new(26); + let en = Pin::new(22); + + let db0 = Pin::new(19); + let db1 = Pin::new(13); + let db2 = Pin::new(6); + let db3 = Pin::new(5); + let db4 = Pin::new(21); + let db5 = Pin::new(20); + let db6 = Pin::new(16); + let db7 = Pin::new(12); + + rs.export().unwrap(); + en.export().unwrap(); + + db0.export().unwrap(); + db1.export().unwrap(); + db2.export().unwrap(); + db3.export().unwrap(); + db4.export().unwrap(); + db5.export().unwrap(); + db6.export().unwrap(); + db7.export().unwrap(); + + rs.set_direction(Direction::Low).unwrap(); + en.set_direction(Direction::Low).unwrap(); + + db0.set_direction(Direction::Low).unwrap(); + db1.set_direction(Direction::Low).unwrap(); + db2.set_direction(Direction::Low).unwrap(); + db3.set_direction(Direction::Low).unwrap(); + db4.set_direction(Direction::Low).unwrap(); + db5.set_direction(Direction::Low).unwrap(); + db6.set_direction(Direction::Low).unwrap(); + db7.set_direction(Direction::Low).unwrap(); + + let mut lcd = HD44780::new_8bit( + rs, + en, + db0, + db1, + db2, + db3, + db4, + db5, + db6, + db7, + Delay, + ); + + lcd.reset(); + + lcd.clear(); + + lcd.set_display_mode( + DisplayMode { + display: Display::On, + cursor_visibility: Cursor::Visible, + cursor_blink: CursorBlink::On, + } + ); + + lcd.write_str("Hello, world!"); + +} diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/.cargo/config b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/.cargo/config new file mode 100644 index 0000000..8cb4006 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/.cargo/config @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "gdb-multiarch -q -x openocd.gdb" + +rustflags = [ + "-C", "link-arg=-Tlink.x", +] + +[build] +target = "thumbv7em-none-eabihf" diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/.gitignore b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/.gitignore new file mode 100644 index 0000000..59a4524 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/.gitignore @@ -0,0 +1,5 @@ +**/*.rs.bk +.#* +.gdb_history +Cargo.lock +target/ diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/Cargo.toml b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/Cargo.toml new file mode 100644 index 0000000..3a53b47 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/Cargo.toml @@ -0,0 +1,23 @@ +[package] +authors = ["Robin Krahl <robin.krahl@ireas.org>", "Nils Van Zuijlen <nils.van-zuijlen@mailo.com>"] +edition = "2018" +name = "stm32f30x-hd44780-i2c-example" +version = "0.1.0" +license = "MIT" +publish = false + +[dependencies] +cortex-m = "0.5.8" +cortex-m-rt = "0.6.5" +embedded-hal = "0.2.2" +panic-halt = "0.2.0" +hd44780-driver = { path = "../.." } + +[dependencies.hal] +version = "0.2.0" +package = "stm32f30x-hal" + +[profile.release] +codegen-units = 1 +debug = true +lto = true diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/README b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/README new file mode 100644 index 0000000..9fc6a18 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/README @@ -0,0 +1,3 @@ +This example is based on the cortex-m-quickstart project avaiable at +https://github.com/rust-embedded/cortex-m-quickstart under the MIT License or +the Apache License, Version 2.0. diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/build.rs b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/build.rs new file mode 100644 index 0000000..461e48f --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/build.rs @@ -0,0 +1,15 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + 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()); + + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/memory.x b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/memory.x new file mode 100644 index 0000000..2b098b1 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 512K + RAM : ORIGIN = 0x20000000, LENGTH = 64K +} diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/openocd.cfg b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/openocd.cfg new file mode 100644 index 0000000..81551c8 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/openocd.cfg @@ -0,0 +1,12 @@ +# Sample OpenOCD configuration for the STM32F3DISCOVERY development board + +# Depending on the hardware revision you got you'll have to pick ONE of these +# interfaces. At any time only one interface should be commented out. + +# Revision C (newer revision) +source [find interface/stlink-v2-1.cfg] + +# Revision A and B (older revisions) +# source [find interface/stlink-v2.cfg] + +source [find target/stm32f3x.cfg] diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/openocd.gdb b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/openocd.gdb new file mode 100644 index 0000000..a7fd5d1 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/openocd.gdb @@ -0,0 +1,32 @@ +target extended-remote :3333 + +# print demangled symbols +set print asm-demangle on + +# detect unhandled exceptions, hard faults and panics +break DefaultHandler +break UserHardFault +break rust_begin_unwind + +# *try* to stop at the user entry point (it might be gone due to inlining) +break main + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.txt uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 8000000 must match the core clock frequency +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +load + +# start the process but immediately halt the processor +stepi diff --git a/sw/deps/hd44780-driver/examples/stm32f30x-i2c/src/main.rs b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/src/main.rs new file mode 100644 index 0000000..3ecfafd --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x-i2c/src/main.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +extern crate panic_halt; + +use core::fmt::Write; +use cortex_m_rt::entry; +use hal::prelude::*; +use hal::flash::FlashExt; +use hal::i2c::I2c; +use hd44780_driver::{Cursor, CursorBlink, Display, DisplayMode, HD44780}; + +// Connections: +// VSS: GND +// VDD: 5V +// SCL: PB6 +// SDA: PB9 +// I2C address : 0x3F + +const I2C_ADDRESS: u8 = 0x3F; + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = hal::stm32f30x::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut gpiob = dp.GPIOB.split(&mut rcc.ahb); + + let clocks = rcc.cfgr.freeze(&mut flash.acr); + let delay = hal::delay::Delay::new(cp.SYST, clocks); + + let scl = gpiob.pb6.into_af4(&mut gpiob.moder, &mut gpiob.afrl); + let sda = gpiob.pb9.into_af4(&mut gpiob.moder, &mut gpiob.afrh); + + let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 400.khz(), clocks, &mut rcc.apb1); + + let mut lcd = HD44780::new_i2c(i2c, I2C_ADDRESS, delay); + lcd.reset(); + lcd.clear(); + lcd.set_display_mode( + DisplayMode { + display: Display::On, + cursor_visibility: Cursor::Visible, + cursor_blink: CursorBlink::On, + } + ); + let _ = lcd.write_str("Hello, world!"); + + loop {} +} diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/.cargo/config b/sw/deps/hd44780-driver/examples/stm32f30x/.cargo/config new file mode 100644 index 0000000..ac7edbb --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/.cargo/config @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "arm-none-eabi-gdb -q -x openocd.gdb" + +rustflags = [ + "-C", "link-arg=-Tlink.x", +] + +[build] +target = "thumbv7em-none-eabihf" diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/.gitignore b/sw/deps/hd44780-driver/examples/stm32f30x/.gitignore new file mode 100644 index 0000000..59a4524 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/.gitignore @@ -0,0 +1,5 @@ +**/*.rs.bk +.#* +.gdb_history +Cargo.lock +target/ diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/Cargo.toml b/sw/deps/hd44780-driver/examples/stm32f30x/Cargo.toml new file mode 100644 index 0000000..5e51866 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/Cargo.toml @@ -0,0 +1,23 @@ +[package] +authors = ["Robin Krahl <robin.krahl@ireas.org>"] +edition = "2018" +name = "stm32f30x-hd44780-example" +version = "0.1.0" +license = "MIT" +publish = false + +[dependencies] +cortex-m = "0.5.8" +cortex-m-rt = "0.6.5" +embedded-hal = "0.2.2" +panic-halt = "0.2.0" +hd44780-driver = { path = "../.." } + +[dependencies.hal] +version = "0.2.0" +package = "stm32f30x-hal" + +[profile.release] +codegen-units = 1 +debug = true +lto = true diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/README b/sw/deps/hd44780-driver/examples/stm32f30x/README new file mode 100644 index 0000000..9fc6a18 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/README @@ -0,0 +1,3 @@ +This example is based on the cortex-m-quickstart project avaiable at +https://github.com/rust-embedded/cortex-m-quickstart under the MIT License or +the Apache License, Version 2.0. diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/build.rs b/sw/deps/hd44780-driver/examples/stm32f30x/build.rs new file mode 100644 index 0000000..461e48f --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/build.rs @@ -0,0 +1,15 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + 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()); + + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/memory.x b/sw/deps/hd44780-driver/examples/stm32f30x/memory.x new file mode 100644 index 0000000..c49e4c3 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 40K +} diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/openocd.cfg b/sw/deps/hd44780-driver/examples/stm32f30x/openocd.cfg new file mode 100644 index 0000000..81551c8 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/openocd.cfg @@ -0,0 +1,12 @@ +# Sample OpenOCD configuration for the STM32F3DISCOVERY development board + +# Depending on the hardware revision you got you'll have to pick ONE of these +# interfaces. At any time only one interface should be commented out. + +# Revision C (newer revision) +source [find interface/stlink-v2-1.cfg] + +# Revision A and B (older revisions) +# source [find interface/stlink-v2.cfg] + +source [find target/stm32f3x.cfg] diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/openocd.gdb b/sw/deps/hd44780-driver/examples/stm32f30x/openocd.gdb new file mode 100644 index 0000000..a7fd5d1 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/openocd.gdb @@ -0,0 +1,32 @@ +target extended-remote :3333 + +# print demangled symbols +set print asm-demangle on + +# detect unhandled exceptions, hard faults and panics +break DefaultHandler +break UserHardFault +break rust_begin_unwind + +# *try* to stop at the user entry point (it might be gone due to inlining) +break main + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.txt uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 8000000 must match the core clock frequency +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +load + +# start the process but immediately halt the processor +stepi diff --git a/sw/deps/hd44780-driver/examples/stm32f30x/src/main.rs b/sw/deps/hd44780-driver/examples/stm32f30x/src/main.rs new file mode 100644 index 0000000..6b3b602 --- /dev/null +++ b/sw/deps/hd44780-driver/examples/stm32f30x/src/main.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +extern crate panic_halt; + +use cortex_m_rt::entry; +use hal::gpio::GpioExt; +use hal::flash::FlashExt; +use hal::rcc::RccExt; +use hd44780_driver::{Cursor, CursorBlink, Display, DisplayMode, HD44780}; + +// Connections: +// VSS: GND +// VDD: 5V +// V0: 10k poti between 5V and GND +// RS: PD1 +// RW: GND +// E: PD2 +// D4-D7: PD4-PD7 +// A: 5V +// K: GND + +#[entry] +fn main() -> ! { + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = hal::stm32f30x::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut gpiod = dp.GPIOD.split(&mut rcc.ahb); + + let clocks = rcc.cfgr.freeze(&mut flash.acr); + let delay = hal::delay::Delay::new(cp.SYST, clocks); + + let rs = gpiod.pd1.into_push_pull_output(&mut gpiod.moder, &mut gpiod.otyper); + let en = gpiod.pd2.into_push_pull_output(&mut gpiod.moder, &mut gpiod.otyper); + let b4 = gpiod.pd4.into_push_pull_output(&mut gpiod.moder, &mut gpiod.otyper); + let b5 = gpiod.pd5.into_push_pull_output(&mut gpiod.moder, &mut gpiod.otyper); + let b6 = gpiod.pd6.into_push_pull_output(&mut gpiod.moder, &mut gpiod.otyper); + let b7 = gpiod.pd7.into_push_pull_output(&mut gpiod.moder, &mut gpiod.otyper); + + let mut lcd = HD44780::new_4bit(rs, en, b4, b5, b6, b7, delay); + lcd.reset(); + lcd.clear(); + lcd.set_display_mode( + DisplayMode { + display: Display::On, + cursor_visibility: Cursor::Visible, + cursor_blink: CursorBlink::On, + } + ); + lcd.write_str("Hello, world!"); + + loop {} +} diff --git a/sw/deps/hd44780-driver/header.gif b/sw/deps/hd44780-driver/header.gif Binary files differnew file mode 100644 index 0000000..78b710c --- /dev/null +++ b/sw/deps/hd44780-driver/header.gif diff --git a/sw/deps/hd44780-driver/src/bus/eightbit.rs b/sw/deps/hd44780-driver/src/bus/eightbit.rs new file mode 100644 index 0000000..99aff96 --- /dev/null +++ b/sw/deps/hd44780-driver/src/bus/eightbit.rs @@ -0,0 +1,171 @@ +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; +use embedded_hal::digital::v2::OutputPin; + +use crate::{ + bus::DataBus, + error::{Error, Result}, +}; + +pub struct EightBitBus< + RS: OutputPin, + EN: OutputPin, + D0: OutputPin, + D1: OutputPin, + D2: OutputPin, + D3: OutputPin, + D4: OutputPin, + D5: OutputPin, + D6: OutputPin, + D7: OutputPin, +> { + rs: RS, + en: EN, + d0: D0, + d1: D1, + d2: D2, + d3: D3, + d4: D4, + d5: D5, + d6: D6, + d7: D7, +} + +impl< + RS: OutputPin, + EN: OutputPin, + D0: OutputPin, + D1: OutputPin, + D2: OutputPin, + D3: OutputPin, + D4: OutputPin, + D5: OutputPin, + D6: OutputPin, + D7: OutputPin, + > EightBitBus<RS, EN, D0, D1, D2, D3, D4, D5, D6, D7> +{ + pub fn from_pins( + rs: RS, + en: EN, + d0: D0, + d1: D1, + d2: D2, + d3: D3, + d4: D4, + d5: D5, + d6: D6, + d7: D7, + ) -> EightBitBus<RS, EN, D0, D1, D2, D3, D4, D5, D6, D7> { + EightBitBus { + rs, + en, + d0, + d1, + d2, + d3, + d4, + d5, + d6, + d7, + } + } + + fn set_bus_bits(&mut self, data: u8) -> Result<()> { + let db0: bool = (0b0000_0001 & data) != 0; + let db1: bool = (0b0000_0010 & data) != 0; + let db2: bool = (0b0000_0100 & data) != 0; + let db3: bool = (0b0000_1000 & data) != 0; + let db4: bool = (0b0001_0000 & data) != 0; + let db5: bool = (0b0010_0000 & data) != 0; + let db6: bool = (0b0100_0000 & data) != 0; + let db7: bool = (0b1000_0000 & data) != 0; + + if db0 { + self.d0.set_high().map_err(|_| Error)?; + } else { + self.d0.set_low().map_err(|_| Error)?; + } + + if db1 { + self.d1.set_high().map_err(|_| Error)?; + } else { + self.d1.set_low().map_err(|_| Error)?; + } + + if db2 { + self.d2.set_high().map_err(|_| Error)?; + } else { + self.d2.set_low().map_err(|_| Error)?; + } + + if db3 { + self.d3.set_high().map_err(|_| Error)?; + } else { + self.d3.set_low().map_err(|_| Error)?; + } + + if db4 { + self.d4.set_high().map_err(|_| Error)?; + } else { + self.d4.set_low().map_err(|_| Error)?; + } + + if db5 { + self.d5.set_high().map_err(|_| Error)?; + } else { + self.d5.set_low().map_err(|_| Error)?; + } + + if db6 { + self.d6.set_high().map_err(|_| Error)?; + } else { + self.d6.set_low().map_err(|_| Error)?; + } + + if db7 { + self.d7.set_high().map_err(|_| Error)?; + } else { + self.d7.set_low().map_err(|_| Error)?; + } + + Ok(()) + } +} + +impl< + RS: OutputPin, + EN: OutputPin, + D0: OutputPin, + D1: OutputPin, + D2: OutputPin, + D3: OutputPin, + D4: OutputPin, + D5: OutputPin, + D6: OutputPin, + D7: OutputPin, + > DataBus for EightBitBus<RS, EN, D0, D1, D2, D3, D4, D5, D6, D7> +{ + fn write<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + byte: u8, + data: bool, + delay: &mut D, + ) -> Result<()> { + if data { + self.rs.set_high().map_err(|_| Error)?; + } else { + self.rs.set_low().map_err(|_| Error)?; + } + + self.set_bus_bits(byte)?; + + self.en.set_high().map_err(|_| Error)?; + delay.delay_ms(2u8); + self.en.set_low().map_err(|_| Error)?; + + if data { + self.rs.set_low().map_err(|_| Error)?; + } + + Ok(()) + } +} diff --git a/sw/deps/hd44780-driver/src/bus/fourbit.rs b/sw/deps/hd44780-driver/src/bus/fourbit.rs new file mode 100644 index 0000000..27519df --- /dev/null +++ b/sw/deps/hd44780-driver/src/bus/fourbit.rs @@ -0,0 +1,144 @@ +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; +use embedded_hal::digital::v2::OutputPin; + +use crate::bus::DataBus; +use crate::error::{Error, Result}; + +pub struct FourBitBus< + RS: OutputPin, + EN: OutputPin, + D4: OutputPin, + D5: OutputPin, + D6: OutputPin, + D7: OutputPin, +> { + rs: RS, + en: EN, + d4: D4, + d5: D5, + d6: D6, + d7: D7, +} + +impl<RS: OutputPin, EN: OutputPin, D4: OutputPin, D5: OutputPin, D6: OutputPin, D7: OutputPin> + FourBitBus<RS, EN, D4, D5, D6, D7> +{ + pub fn from_pins( + rs: RS, + en: EN, + d4: D4, + d5: D5, + d6: D6, + d7: D7, + ) -> FourBitBus<RS, EN, D4, D5, D6, D7> { + FourBitBus { + rs, + en, + d4, + d5, + d6, + d7, + } + } + + fn write_lower_nibble(&mut self, data: u8) -> Result<()> { + let db0: bool = (0b0000_0001 & data) != 0; + let db1: bool = (0b0000_0010 & data) != 0; + let db2: bool = (0b0000_0100 & data) != 0; + let db3: bool = (0b0000_1000 & data) != 0; + + if db0 { + self.d4.set_high().map_err(|_| Error)?; + } else { + self.d4.set_low().map_err(|_| Error)?; + } + + if db1 { + self.d5.set_high().map_err(|_| Error)?; + } else { + self.d5.set_low().map_err(|_| Error)?; + } + + if db2 { + self.d6.set_high().map_err(|_| Error)?; + } else { + self.d6.set_low().map_err(|_| Error)?; + } + + if db3 { + self.d7.set_high().map_err(|_| Error)?; + } else { + self.d7.set_low().map_err(|_| Error)?; + } + + Ok(()) + } + + fn write_upper_nibble(&mut self, data: u8) -> Result<()> { + let db4: bool = (0b0001_0000 & data) != 0; + let db5: bool = (0b0010_0000 & data) != 0; + let db6: bool = (0b0100_0000 & data) != 0; + let db7: bool = (0b1000_0000 & data) != 0; + + if db4 { + self.d4.set_high().map_err(|_| Error)?; + } else { + self.d4.set_low().map_err(|_| Error)?; + } + + if db5 { + self.d5.set_high().map_err(|_| Error)?; + } else { + self.d5.set_low().map_err(|_| Error)?; + } + + if db6 { + self.d6.set_high().map_err(|_| Error)?; + } else { + self.d6.set_low().map_err(|_| Error)?; + } + + if db7 { + self.d7.set_high().map_err(|_| Error)?; + } else { + self.d7.set_low().map_err(|_| Error)?; + } + Ok(()) + } +} + +impl<RS: OutputPin, EN: OutputPin, D4: OutputPin, D5: OutputPin, D6: OutputPin, D7: OutputPin> + DataBus for FourBitBus<RS, EN, D4, D5, D6, D7> +{ + fn write<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + byte: u8, + data: bool, + delay: &mut D, + ) -> Result<()> { + if data { + self.rs.set_high().map_err(|_| Error)?; + } else { + self.rs.set_low().map_err(|_| Error)?; + } + + self.write_upper_nibble(byte)?; + + // Pulse the enable pin to recieve the upper nibble + self.en.set_high().map_err(|_| Error)?; + delay.delay_ms(2u8); + self.en.set_low().map_err(|_| Error)?; + + self.write_lower_nibble(byte)?; + + // Pulse the enable pin to recieve the lower nibble + self.en.set_high().map_err(|_| Error)?; + delay.delay_ms(2u8); + self.en.set_low().map_err(|_| Error)?; + + if data { + self.rs.set_low().map_err(|_| Error)?; + } + Ok(()) + } +} diff --git a/sw/deps/hd44780-driver/src/bus/i2c.rs b/sw/deps/hd44780-driver/src/bus/i2c.rs new file mode 100644 index 0000000..160e205 --- /dev/null +++ b/sw/deps/hd44780-driver/src/bus/i2c.rs @@ -0,0 +1,71 @@ +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; +use embedded_hal::blocking::i2c::Write; + +use crate::{bus::DataBus, error::Result}; + +/// This module supports I2C backpacks with a PCF8574 IC. +/// Connections as follows: +/// +/// <table> +/// <tr><th>PCF8574 pin</th><th>name</th><th>LCD pin</th></tr> +/// <tr><td>P0</td><td>RS</td><td>4</td></tr> +/// <tr><td>P1</td><td>RW</td><td>5</td></tr> +/// <tr><td>P2</td><td>E</td><td>6</td></tr> +/// <tr><td>P3</td><td>Backlight</td><td></td></tr> +/// <tr><td>P4</td><td>DB4</td><td>11</td></tr> +/// <tr><td>P5</td><td>DB5</td><td>12</td></tr> +/// <tr><td>P6</td><td>DB6</td><td>13</td></tr> +/// <tr><td>P7</td><td>DB7</td><td>14</td></tr> +/// </table> + +pub struct I2CBus<I2C: Write> { + i2c_bus: I2C, + address: u8, +} + +const BACKLIGHT: u8 = 0b0000_1000; +const ENABLE: u8 = 0b0000_0100; +// const READ_WRITE: u8 = 0b0000_0010; // Not used as no reading of the `HD44780` is done +const REGISTER_SELECT: u8 = 0b0000_0001; + +impl<I2C: Write> I2CBus<I2C> { + pub fn new(i2c_bus: I2C, address: u8) -> I2CBus<I2C> { + I2CBus { i2c_bus, address } + } + + /// Write a nibble to the lcd + /// The nibble should be in the upper part of the byte + fn write_nibble<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + nibble: u8, + data: bool, + delay: &mut D, + ) { + let rs = match data { + false => 0u8, + true => REGISTER_SELECT, + }; + let byte = nibble | rs | BACKLIGHT; + + let _ = self.i2c_bus.write(self.address, &[byte, byte | ENABLE]); + delay.delay_ms(2u8); + let _ = self.i2c_bus.write(self.address, &[byte]); + } +} + +impl<I2C: Write> DataBus for I2CBus<I2C> { + fn write<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + byte: u8, + data: bool, + delay: &mut D, + ) -> Result<()> { + let upper_nibble = byte & 0xF0; + self.write_nibble(upper_nibble, data, delay); + + let lower_nibble = (byte & 0x0F) << 4; + self.write_nibble(lower_nibble, data, delay); + + Ok(()) + } +} diff --git a/sw/deps/hd44780-driver/src/bus/i2c_mcp23008.rs b/sw/deps/hd44780-driver/src/bus/i2c_mcp23008.rs new file mode 100644 index 0000000..1ccb479 --- /dev/null +++ b/sw/deps/hd44780-driver/src/bus/i2c_mcp23008.rs @@ -0,0 +1,102 @@ +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; +use embedded_hal::blocking::i2c::Write; + +use crate::{bus::DataBus, error::Result}; + +/// This module supports I2C backpacks with a MCP23008 IC, like +/// the one from adafruit. +/// Connections as follows: +/// +/// <table> +/// <tr><th>MCP23008 pin</th><th>name</th><th>LCD pin</th></tr> +/// <tr><td>0</td><td>N/C</td><td></td></tr> +/// <tr><td>1</td><td>RS</td><td>4</td></tr> +/// <tr><td>2</td><td>E</td><td>6</td></tr> +/// <tr><td>3</td><td>DB4</td><td>11</td></tr> +/// <tr><td>4</td><td>DB5</td><td>12</td></tr> +/// <tr><td>5</td><td>DB6</td><td>13</td></tr> +/// <tr><td>6</td><td>DB7</td><td>14</td></tr> +/// <tr><td>7</td><td>Backlight</td><td></td></tr> +/// </table> + +pub struct I2CMCP23008Bus<I2C: Write> { + i2c_bus: I2C, + address: u8, +} + +const REG_IODIR : u8 = 0x00; +const REG_GPIO : u8 = 0x09; + +impl<I2C: Write> I2CMCP23008Bus<I2C> { + + /// Create a new instance of the MCP23008 I2C driver. The address of those + /// devices is 0b010_0xxx where x is configured by bootstrap pins. + pub fn new(i2c_bus: I2C, address: u8) -> Result<I2CMCP23008Bus<I2C>> { + let mut mcp23008 = I2CMCP23008Bus { i2c_bus, address }; + // Set to reset values according to datasheet + mcp23008.write_reg(REG_IODIR, 0b1111_1111)?; + for reg in 0x01usize..0x0A { + mcp23008.write_reg(reg as u8, 0)?; + } + // Configure pins 1..=7 as outputs, see pin mapping above + mcp23008.write_reg(REG_IODIR, 0b0000_0001)?; + Ok(mcp23008) + } + + fn write_reg(&mut self, reg: u8, value: u8) -> Result<()> { + let data = [reg, value]; + self.i2c_bus.write(self.address, &data) + .map_err(|_| crate::error::Error) + } + + fn set_pins(&mut self, pins: u8) -> Result<()> { + self.write_reg(REG_GPIO, pins) + } + +} + +impl<I2C: Write> DataBus for I2CMCP23008Bus<I2C> { + fn write<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + byte: u8, + data: bool, + delay: &mut D, + ) -> Result<()> { + let rs = if data { 0b10 } else { 0b00 }; + let en = 0b0000_0100; + let backlight = 0b1000_0000; // always enable backlight + + let upper_nibble = (byte & 0xF0) >> 4; + let lower_nibble = byte & 0x0F; + + // upper nibble: [d7 d6 d5 d4] + // Pulse EN + // lower nibble: [d3 d2 d1 d0] + // Pulse EN + + let pins = rs | backlight | (upper_nibble << 3); + self.set_pins(pins)?; + + delay.delay_ms(1); + + let pins = rs | en | backlight | (upper_nibble << 3); + self.set_pins(pins)?; + + delay.delay_ms(1); + + let pins = rs | backlight | (lower_nibble << 3); + self.set_pins(pins)?; + + delay.delay_ms(1); + + let pins = rs | en | backlight | (lower_nibble << 3); + self.set_pins(pins)?; + + delay.delay_ms(1); + + let pins = backlight | (lower_nibble << 3); + self.set_pins(pins)?; + + Ok(()) + } +} diff --git a/sw/deps/hd44780-driver/src/bus/mod.rs b/sw/deps/hd44780-driver/src/bus/mod.rs new file mode 100644 index 0000000..b141f7e --- /dev/null +++ b/sw/deps/hd44780-driver/src/bus/mod.rs @@ -0,0 +1,25 @@ +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; + +mod eightbit; +mod fourbit; +mod i2c; +mod i2c_mcp23008; + +pub use self::eightbit::EightBitBus; +pub use self::fourbit::FourBitBus; +pub use self::i2c::I2CBus; +pub use self::i2c_mcp23008::I2CMCP23008Bus; + +use crate::error::Result; + +pub trait DataBus { + fn write<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + byte: u8, + data: bool, + delay: &mut D, + ) -> Result<()>; + + // TODO + // fn read(...) +} diff --git a/sw/deps/hd44780-driver/src/display_mode.rs b/sw/deps/hd44780-driver/src/display_mode.rs new file mode 100644 index 0000000..8039f7b --- /dev/null +++ b/sw/deps/hd44780-driver/src/display_mode.rs @@ -0,0 +1,95 @@ +use crate::{Cursor, CursorBlink, Display}; + +pub struct DisplayMode { + pub cursor_visibility: Cursor, + pub cursor_blink: CursorBlink, + pub display: Display, +} + +impl Default for DisplayMode { + fn default() -> DisplayMode { + DisplayMode { + cursor_visibility: Cursor::Visible, + cursor_blink: CursorBlink::On, + display: Display::On, + } + } +} + +impl DisplayMode { + pub fn as_byte(&self) -> u8 { + let cursor_blink_bits = match self.cursor_blink { + CursorBlink::On => 0b0000_0001, + CursorBlink::Off => 0, + }; + + let cursor_visible_bits = match self.cursor_visibility { + Cursor::Visible => 0b0000_0010, + Cursor::Invisible => 0, + }; + + let display_bits = match self.display { + Display::On => 0b0000_0100, + Display::Off => 0, + }; + + 0b0000_1000 | cursor_visible_bits | cursor_blink_bits | display_bits + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn cursor_visible() { + let dm = DisplayMode { + cursor_visibility: Cursor::Visible, + ..Default::default() + }; + + assert!(dm.as_byte() & 0b0000_0010 != 0); + + let dm = DisplayMode { + cursor_visibility: Cursor::Invisible, + ..Default::default() + }; + + assert!(dm.as_byte() & 0b0000_0010 == 0); + } + + #[test] + fn cursor_blink() { + let dm = DisplayMode { + cursor_blink: CursorBlink::On, + ..Default::default() + }; + + assert!(dm.as_byte() & 0b0000_0001 != 0); + + let dm = DisplayMode { + cursor_blink: CursorBlink::Off, + ..Default::default() + }; + + assert!(dm.as_byte() & 0b0000_0001 == 0); + } + + #[test] + fn display_visible() { + let dm = DisplayMode { + display: Display::On, + ..Default::default() + }; + + assert!(dm.as_byte() & 0b0000_0100 != 0); + + let dm = DisplayMode { + display: Display::Off, + ..Default::default() + }; + + assert!(dm.as_byte() & 0b0000_0100 == 0); + } +} diff --git a/sw/deps/hd44780-driver/src/entry_mode.rs b/sw/deps/hd44780-driver/src/entry_mode.rs new file mode 100644 index 0000000..577c5d0 --- /dev/null +++ b/sw/deps/hd44780-driver/src/entry_mode.rs @@ -0,0 +1,97 @@ +/// Determines if the cursor should be incremented or decremented on write +#[derive(Debug, PartialEq, Eq)] +pub enum CursorMode { + Increment, + Decrement, +} + +impl Default for CursorMode { + fn default() -> CursorMode { + CursorMode::Increment + } +} + +/// Determines if the screen should be shifted on write +#[derive(Debug, PartialEq, Eq)] +pub enum ShiftMode { + Enabled, + Disabled, +} + +impl From<bool> for ShiftMode { + fn from(b: bool) -> ShiftMode { + if b { + ShiftMode::Enabled + } else { + ShiftMode::Disabled + } + } +} + +impl Default for ShiftMode { + fn default() -> ShiftMode { + ShiftMode::Disabled + } +} + +#[derive(Default)] +pub struct EntryMode { + pub cursor_mode: CursorMode, + pub shift_mode: ShiftMode, +} + +impl EntryMode { + pub fn as_byte(&self) -> u8 { + let cursor_bits = match self.cursor_mode { + CursorMode::Increment => 0b0000_0010, + CursorMode::Decrement => 0, + }; + + let shift_bits = match self.shift_mode { + ShiftMode::Enabled => 0b0000_0001, + ShiftMode::Disabled => 0, + }; + + 0b0000_0100 | cursor_bits | shift_bits + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn cursor_mode() { + let em = EntryMode { + cursor_mode: CursorMode::Increment, + shift_mode: Default::default(), + }; + + assert!(em.as_byte() & 0b0000_0010 != 0); + + let em = EntryMode { + cursor_mode: CursorMode::Decrement, + shift_mode: Default::default(), + }; + + assert!(em.as_byte() & 0b0000_0010 == 0); + } + + #[test] + fn shift_mode() { + let em = EntryMode { + cursor_mode: Default::default(), + shift_mode: ShiftMode::Enabled, + }; + + assert!(em.as_byte() & 0b0000_0001 != 0); + + let em = EntryMode { + cursor_mode: Default::default(), + shift_mode: ShiftMode::Disabled, + }; + + assert!(em.as_byte() & 0b0000_0001 == 0); + } +} diff --git a/sw/deps/hd44780-driver/src/error.rs b/sw/deps/hd44780-driver/src/error.rs new file mode 100644 index 0000000..2deaf85 --- /dev/null +++ b/sw/deps/hd44780-driver/src/error.rs @@ -0,0 +1,3 @@ +#[derive(Debug)] +pub struct Error; +pub type Result<T> = core::result::Result<T, Error>; diff --git a/sw/deps/hd44780-driver/src/lib.rs b/sw/deps/hd44780-driver/src/lib.rs new file mode 100644 index 0000000..5211346 --- /dev/null +++ b/sw/deps/hd44780-driver/src/lib.rs @@ -0,0 +1,552 @@ +#![no_std] + +//use core::fmt::Result; +//use core::fmt::Write; + +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; +use embedded_hal::blocking::i2c; +use embedded_hal::digital::v2::OutputPin; + +pub mod bus; +use bus::{DataBus, EightBitBus, FourBitBus, I2CBus, I2CMCP23008Bus}; + +pub mod error; +use error::Result; + +pub mod entry_mode; + +use entry_mode::{CursorMode, EntryMode}; + +pub mod display_mode; + +pub use display_mode::DisplayMode; + +pub struct HD44780<B: DataBus> { + bus: B, + entry_mode: EntryMode, + display_mode: DisplayMode, +} + +/// Used in the direction argument for shifting the cursor and the display +pub enum Direction { + Left, + Right, +} + +/// Used in set_display_mode to make the parameters more clear +pub enum Display { + On, + Off, +} + +pub enum Cursor { + Visible, + Invisible, +} + +pub enum CursorBlink { + On, + Off, +} + +impl< + RS: OutputPin, + EN: OutputPin, + D0: OutputPin, + D1: OutputPin, + D2: OutputPin, + D3: OutputPin, + D4: OutputPin, + D5: OutputPin, + D6: OutputPin, + D7: OutputPin, + > HD44780<EightBitBus<RS, EN, D0, D1, D2, D3, D4, D5, D6, D7>> +{ + /// Create an instance of a `HD44780` from 8 data pins, a register select + /// pin, an enable pin and a struct implementing the delay trait. + /// - The delay instance is used to sleep between commands to + /// ensure the `HD44780` has enough time to process commands. + /// - The eight db0..db7 pins are used to send and recieve with + /// the `HD44780`. + /// - The register select pin is used to tell the `HD44780` + /// if incoming data is a command or data. + /// - The enable pin is used to tell the `HD44780` that there + /// is data on the 8 data pins and that it should read them in. + /// + pub fn new_8bit<D: DelayUs<u16> + DelayMs<u8>>( + rs: RS, + en: EN, + d0: D0, + d1: D1, + d2: D2, + d3: D3, + d4: D4, + d5: D5, + d6: D6, + d7: D7, + delay: &mut D, + ) -> Result<HD44780<EightBitBus<RS, EN, D0, D1, D2, D3, D4, D5, D6, D7>>> { + let mut hd = HD44780 { + bus: EightBitBus::from_pins(rs, en, d0, d1, d2, d3, d4, d5, d6, d7), + entry_mode: EntryMode::default(), + display_mode: DisplayMode::default(), + }; + + hd.init_8bit(delay)?; + + return Ok(hd); + } +} + +impl<RS: OutputPin, EN: OutputPin, D4: OutputPin, D5: OutputPin, D6: OutputPin, D7: OutputPin> + HD44780<FourBitBus<RS, EN, D4, D5, D6, D7>> +{ + /// Create an instance of a `HD44780` from 4 data pins, a register select + /// pin, an enable pin and a struct implementing the delay trait. + /// - The delay instance is used to sleep between commands to + /// ensure the `HD44780` has enough time to process commands. + /// - The four db0..db3 pins are used to send and recieve with + /// the `HD44780`. + /// - The register select pin is used to tell the `HD44780` + /// if incoming data is a command or data. + /// - The enable pin is used to tell the `HD44780` that there + /// is data on the 4 data pins and that it should read them in. + /// + /// This mode operates differently than 8 bit mode by using 4 less + /// pins for data, which is nice on devices with less I/O although + /// the I/O takes a 'bit' longer + /// + /// Instead of commands being sent byte by byte each command is + /// broken up into it's upper and lower nibbles (4 bits) before + /// being sent over the data bus + /// + pub fn new_4bit<D: DelayUs<u16> + DelayMs<u8>>( + rs: RS, + en: EN, + d4: D4, + d5: D5, + d6: D6, + d7: D7, + delay: &mut D, + ) -> Result<HD44780<FourBitBus<RS, EN, D4, D5, D6, D7>>> { + let mut hd = HD44780 { + bus: FourBitBus::from_pins(rs, en, d4, d5, d6, d7), + entry_mode: EntryMode::default(), + display_mode: DisplayMode::default(), + }; + + hd.init_4bit(delay)?; + + return Ok(hd); + } +} + +impl<I2C: i2c::Write> HD44780<I2CBus<I2C>> { + /// Create an instance of a `HD44780` from an i2c write peripheral, + /// I2C address and a struct implementing the delay trait. + /// The `HD44780` is driven through a PCF8574 I2C port expander. + /// - The delay instance is used to sleep between commands to + /// ensure the `HD44780` has enough time to process commands. + /// - The i2c peripheral is used to send data to the `HD44780` and to set + /// its register select and enable pins. + /// + /// This mode operates on an I2C bus, using a PCF8574 I2C to port expander + /// The IC connections are described in `I2CBus` + /// + pub fn new_i2c<D: DelayUs<u16> + DelayMs<u8>>( + i2c_bus: I2C, + address: u8, + delay: &mut D, + ) -> Result<HD44780<I2CBus<I2C>>> { + let mut hd = HD44780 { + bus: I2CBus::new(i2c_bus, address), + entry_mode: EntryMode::default(), + display_mode: DisplayMode::default(), + }; + + hd.init_4bit(delay)?; + + return Ok(hd); + } +} + +impl<I2C: i2c::Write> HD44780<I2CMCP23008Bus<I2C>> { + /// Create an instance of a `HD44780` from an i2c write peripheral, + /// I2C address and a struct implementing the delay trait. + /// The `HD44780` is driven through a MCP23008 I2C port expander. + /// - The delay instance is used to sleep between commands to + /// ensure the `HD44780` has enough time to process commands. + /// - The i2c peripheral is used to send data to the `HD44780` and to set + /// its register select and enable pins. + /// + /// This mode operates on an I2C bus, using an I2C to parallel port expander based on MCP23008. + /// The IC connections are described in `I2CMCP23008Bus` + /// + pub fn new_i2c_mcp23008<D: DelayUs<u16> + DelayMs<u8>>( + i2c_bus: I2C, + address: u8, + delay: &mut D, + ) -> Result<HD44780<I2CMCP23008Bus<I2C>>> { + let mut hd = HD44780 { + bus: I2CMCP23008Bus::new(i2c_bus, address)?, + entry_mode: EntryMode::default(), + display_mode: DisplayMode::default(), + }; + + hd.init_4bit(delay)?; + + return Ok(hd); + } +} + +impl<B> HD44780<B> +where + B: DataBus, +{ + /// Unshifts the display and sets the cursor position to 0 + /// + /// ```rust,ignore + /// lcd.reset(); + /// ``` + pub fn reset<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> { + self.write_command(0b0000_0010, delay)?; + + Ok(()) + } + + /// Set if the display should be on, if the cursor should be + /// visible, and if the cursor should blink + /// + /// Note: This is equivilent to calling all of the other relavent + /// methods however this operation does it all in one go to the `HD44780` + pub fn set_display_mode<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + display_mode: DisplayMode, + delay: &mut D, + ) -> Result<()> { + self.display_mode = display_mode; + + let cmd_byte = self.display_mode.as_byte(); + + self.write_command(cmd_byte, delay)?; + + Ok(()) + } + + /// Clear the entire display + /// + /// ```rust,ignore + /// lcd.clear(); + /// ``` + pub fn clear<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> { + self.write_command(0b0000_0001, delay)?; + + Ok(()) + } + + /// If enabled, automatically scroll the display when a new + /// character is written to the display + /// + /// ```rust,ignore + /// lcd.set_autoscroll(true); + /// ``` + pub fn set_autoscroll<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + enabled: bool, + delay: &mut D, + ) -> Result<()> { + self.entry_mode.shift_mode = enabled.into(); + + let cmd = self.entry_mode.as_byte(); + + self.write_command(cmd, delay)?; + + Ok(()) + } + + /// Set if the cursor should be visible + pub fn set_cursor_visibility<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + visibility: Cursor, + delay: &mut D, + ) -> Result<()> { + self.display_mode.cursor_visibility = visibility; + + let cmd = self.display_mode.as_byte(); + + self.write_command(cmd, delay)?; + + Ok(()) + } + + /// Set if the characters on the display should be visible + pub fn set_display<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + display: Display, + delay: &mut D, + ) -> Result<()> { + self.display_mode.display = display; + + let cmd = self.display_mode.as_byte(); + + self.write_command(cmd, delay)?; + + Ok(()) + } + + /// Set if the cursor should blink + pub fn set_cursor_blink<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + blink: CursorBlink, + delay: &mut D, + ) -> Result<()> { + self.display_mode.cursor_blink = blink; + + let cmd = self.display_mode.as_byte(); + + self.write_command(cmd, delay)?; + + Ok(()) + } + + /// Set which way the cursor will move when a new character is written + /// + /// ```rust,ignore + /// // Move right (Default) when a new character is written + /// lcd.set_cursor_mode(CursorMode::Right) + /// + /// // Move left when a new character is written + /// lcd.set_cursor_mode(CursorMode::Left) + /// ``` + pub fn set_cursor_mode<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + mode: CursorMode, + delay: &mut D, + ) -> Result<()> { + self.entry_mode.cursor_mode = mode; + + let cmd = self.entry_mode.as_byte(); + + self.write_command(cmd, delay)?; + + Ok(()) + } + + /// Set the cursor position + /// + /// ```rust,ignore + /// // Move to line 2 + /// lcd.set_cursor_pos(40) + /// ``` + pub fn set_cursor_pos<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + position: u8, + delay: &mut D, + ) -> Result<()> { + let lower_7_bits = 0b0111_1111 & position; + + self.write_command(0b1000_0000 | lower_7_bits, delay)?; + + Ok(()) + } + + /// Shift just the cursor to the left or the right + /// + /// ```rust,ignore + /// lcd.shift_cursor(Direction::Left); + /// lcd.shift_cursor(Direction::Right); + /// ``` + pub fn shift_cursor<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + dir: Direction, + delay: &mut D, + ) -> Result<()> { + let bits = match dir { + Direction::Left => 0b0000_0000, + Direction::Right => 0b0000_0100, + }; + + self.write_command(0b0001_0000 | bits | bits, delay)?; + + Ok(()) + } + + /// Shift the entire display to the left or the right + /// + /// ```rust,ignore + /// lcd.shift_display(Direction::Left); + /// lcd.shift_display(Direction::Right); + /// ``` + pub fn shift_display<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + dir: Direction, + delay: &mut D, + ) -> Result<()> { + let bits = match dir { + Direction::Left => 0b0000_0000, + Direction::Right => 0b0000_0100, + }; + + self.write_command(0b0001_1000 | bits, delay)?; + + Ok(()) + } + + /// Write a single character to the `HD44780` + /// + /// ```rust,ignore + /// lcd.write_char('A'); + /// ``` + pub fn write_char<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + data: char, + delay: &mut D, + ) -> Result<()> { + self.bus.write(data as u8, true, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + Ok(()) + } + + fn write_command<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + cmd: u8, + delay: &mut D, + ) -> Result<()> { + self.bus.write(cmd, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + Ok(()) + } + + fn init_4bit<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> { + // Wait for the LCD to wakeup if it was off + delay.delay_ms(15u8); + + // Initialize Lcd in 4-bit mode + self.bus.write(0x33, false, delay)?; + + // Wait for the command to be processed + delay.delay_ms(5u8); + + // Sets 4-bit operation and enables 5x7 mode for chars + self.bus.write(0x32, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + self.bus.write(0x28, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + // Clear Display + self.bus.write(0x0E, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + // Move the cursor to beginning of first line + self.bus.write(0x01, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + // Set entry mode + self.bus.write(self.entry_mode.as_byte(), false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + self.bus.write(0x80, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + Ok(()) + } + + // Follow the 8-bit setup procedure as specified in the HD44780 datasheet + fn init_8bit<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> { + // Wait for the LCD to wakeup if it was off + delay.delay_ms(15u8); + + // Initialize Lcd in 8-bit mode + self.bus.write(0b0011_0000, false, delay)?; + + // Wait for the command to be processed + delay.delay_ms(5u8); + + // Sets 8-bit operation and enables 5x7 mode for chars + self.bus.write(0b0011_1000, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + self.bus.write(0b0000_1110, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + // Clear Display + self.bus.write(0b0000_0001, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + // Move the cursor to beginning of first line + self.bus.write(0b000_0111, false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + // Set entry mode + self.bus.write(self.entry_mode.as_byte(), false, delay)?; + + // Wait for the command to be processed + delay.delay_us(100); + + Ok(()) + } + + pub fn write_str<D: DelayUs<u16> + DelayMs<u8>>( + &mut self, + string: &str, + delay: &mut D, + ) -> Result<()> { + for c in string.chars() { + self.write_char(c, delay)?; + } + Ok(()) + } + + // Send a byte to the HD44780 by setting the data on the bus and + // also pulsing the enable pin + /*fn send_byte(&mut self, data: u8) { + // Pulse the enable pin + self.set_bus_bits(data); + self.pulse_enable(); + }*/ + + // Pulse the enable pin telling the HD44780 that we something for it + /*fn pulse_enable(&mut self) { + self.en.set_high(); + self.delay.delay_ms(15u8); + self.en.set_low(); + }*/ +} + +//impl<B> Write for HD44780<B> +//where +// B: DataBus, +//{ +// fn write_str(&mut self, string: &str) -> Result { +// for c in string.chars() { +// self.write_char(c, delay); +// } +// Ok(()) +// } +//} diff --git a/sw/deps/si5351/.gitignore b/sw/deps/si5351/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/sw/deps/si5351/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/sw/deps/si5351/.travis.yml b/sw/deps/si5351/.travis.yml new file mode 100644 index 0000000..2c773e0 --- /dev/null +++ b/sw/deps/si5351/.travis.yml @@ -0,0 +1,54 @@ +# Based on the "trust" template v0.1.2 +# https://github.com/japaric/trust/tree/v0.1.2 + +language: rust +services: docker + +matrix: + include: + # Linux + - env: TARGET=x86_64-unknown-linux-gnu + rust: nightly + + # Bare metal + - env: TARGET=thumbv6m-none-eabi + rust: nightly + - env: TARGET=thumbv7m-none-eabi + rust: nightly + - env: TARGET=thumbv7em-none-eabi + rust: nightly + - env: TARGET=thumbv7em-none-eabihf + rust: nightly + +before_install: + - set -e + - rustup self update + +install: + - sh ci/install.sh + - source ~/.cargo/env || true + +script: + - bash ci/script.sh + +after_script: set +e + +cache: + cargo: true + directories: + - $HOME/.xargo + +before_cache: + # Travis can't cache files that are not readable by "others" + - chmod -R a+r $HOME/.cargo + +branches: + only: + - /^v\d+\.\d+\.\d+.*$/ + - auto + - master + - try + +notifications: + email: +on_success: never diff --git a/sw/deps/si5351/Cargo.toml b/sw/deps/si5351/Cargo.toml new file mode 100644 index 0000000..147ae69 --- /dev/null +++ b/sw/deps/si5351/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "si5351" +version = "0.2.0" +keywords = ["embedded", "no-std", "embedded-hal-driver", "clock", "ham-radio"] +categories = ["embedded", "no-std"] +authors = ["Ilya Epifanov <elijah.epifanov@gmail.com>"] +description = "A platform agnostic driver for the Si5351 clock generator" +license = "MIT OR Apache-2.0" +exclude = [".travis.yml", ".gitignore"] +repository = "https://github.com/ilya-epifanov/si5351" +edition = "2018" + +[badges.travis-ci] +branch = "master" +repository = "jamwaffles/ssd1306" + +[dependencies] +embedded-hal = "0.2.2" +bitflags = "1.0" diff --git a/sw/deps/si5351/LICENSE-APACHE b/sw/deps/si5351/LICENSE-APACHE new file mode 100644 index 0000000..3a55053 --- /dev/null +++ b/sw/deps/si5351/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Ilya Epifanov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sw/deps/si5351/LICENSE-MIT b/sw/deps/si5351/LICENSE-MIT new file mode 100644 index 0000000..b8f5571 --- /dev/null +++ b/sw/deps/si5351/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Ilya Epifanov + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/sw/deps/si5351/README.md b/sw/deps/si5351/README.md new file mode 100644 index 0000000..e40eb02 --- /dev/null +++ b/sw/deps/si5351/README.md @@ -0,0 +1,24 @@ +# si5351 + +[![docs](https://docs.rs/si5351/badge.svg)](https://docs.rs/si5351) +[![crates.io](https://img.shields.io/crates/v/si5351.svg)](https://crates.io/crates/si5351) +[![ci](https://travis-ci.org/ilya-epifanov/si5351.svg)](https://travis-ci.org/ilya-epifanov/si5351) + +## Documentation + +On [docs.rs](https://docs.rs/si5351) + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) + * MIT license ([LICENSE-MIT](LICENSE-MIT)) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/sw/deps/si5351/ci/install.sh b/sw/deps/si5351/ci/install.sh new file mode 100644 index 0000000..4892d1a --- /dev/null +++ b/sw/deps/si5351/ci/install.sh @@ -0,0 +1,21 @@ +set -ex + +main() { + # This fetches latest stable release of Xargo + local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/xargo \ + | cut -d/ -f3 \ + | grep -E '^v[0.1.0-9.]+$' \ + | sort --version-sort \ + | tail -n1) + + curl -LSfs https://japaric.github.io/trust/install.sh | \ + sh -s -- \ + --force \ + --git japaric/xargo \ + --tag $tag \ + --target x86_64-unknown-linux-musl + + rustup component add rust-src +} + +main diff --git a/sw/deps/si5351/ci/script.sh b/sw/deps/si5351/ci/script.sh new file mode 100644 index 0000000..f6bca6f --- /dev/null +++ b/sw/deps/si5351/ci/script.sh @@ -0,0 +1,16 @@ +# This script takes care of testing your crate + +set -euxo pipefail + +main() { + case $TARGET in + x86_64-unknown-linux-gnu) + cargo check --target $TARGET + ;; + *) + xargo check --target $TARGET + ;; + esac +} + +main diff --git a/sw/deps/si5351/src/lib.rs b/sw/deps/si5351/src/lib.rs new file mode 100644 index 0000000..a23d4f9 --- /dev/null +++ b/sw/deps/si5351/src/lib.rs @@ -0,0 +1,633 @@ +/* + Copyright 2018 Ilya Epifanov + + Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or + http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or + http://opensource.org/licenses/MIT>, at your option. This file may not be + copied, modified, or distributed except according to those terms. +*/ +/*! +A platform agnostic Rust driver for the [Si5351], based on the +[`embedded-hal`] traits. + +## The Device + +The Silicon Labs [Si5351] is an any-frequency CMOS clock generator. + +The device has an I²C interface. + +## Usage + +Import this crate and an `embedded_hal` implementation: + +``` +extern crate stm32f103xx_hal as hal; +extern crate si5351; +``` + +Initialize I²C bus (differs between `embedded_hal` implementations): + +```no_run +# extern crate stm32f103xx_hal as hal; +use hal::i2c::I2c; +type I2C = ...; + +# fn main() { +let i2c: I2C = initialize_i2c(); +# } +``` + +Then instantiate the device: + +```no_run +# extern crate stm32f103xx_hal as hal; +# extern crate si5351; +use si5351; +use si5351::{Si5351, Si5351Device}; + +# fn main() { +let mut clock = Si5351Device<I2C>::new(i2c, false, 25_000_000); +clock.init(si5351::CrystalLoad::_10)?; +# } +``` + +Or, if you have an [Adafruit module], you can use shortcut functions to initializate it: +```no_run +# extern crate stm32f103xx_hal as hal; +# extern crate si5351; +use si5351; +use si5351::{Si5351, Si5351Device}; + +# fn main() { +let mut clock = Si5351Device<I2C>::new_adafruit_module(i2c); +clock.init_adafruit_module()?; +# } +``` + +And set frequency on one of the outputs: + +```no_run +use si5351; + +clock.set_frequency(si5351::PLL::A, si5351::ClockOutput::Clk0, 14_175_000)?; +``` + +[Si5351]: https://www.silabs.com/documents/public/data-sheets/Si5351-B.pdf +[`embedded-hal`]: https://github.com/japaric/embedded-hal +[Adafruit module]: https://www.adafruit.com/product/2045 +*/ +//#![deny(missing_docs)] +#![deny(warnings)] +#![no_std] + +#[macro_use] +extern crate bitflags; +use embedded_hal as hal; + +use crate::hal::blocking::i2c::{Write, WriteRead}; + +#[derive(Debug)] +pub enum Error { + CommunicationError, + InvalidParameter, +} + +#[derive(Debug, Copy, Clone)] +pub enum CrystalLoad { + _6, + _8, + _10, +} + +#[derive(Debug, Copy, Clone)] +pub enum PLL { + A, + B, +} + +#[derive(Debug, Copy, Clone)] +pub enum FeedbackMultisynth { + MSNA, + MSNB, +} + +#[derive(Debug, Copy, Clone)] +pub enum Multisynth { + MS0, + MS1, + MS2, + MS3, + MS4, + MS5, +} + +#[derive(Debug, Copy, Clone)] +pub enum SimpleMultisynth { + MS6, + MS7, +} + +#[derive(Debug, Copy, Clone)] +pub enum ClockOutput { + Clk0 = 0, + Clk1, + Clk2, + Clk3, + Clk4, + Clk5, + Clk6, + Clk7, +} + +#[derive(Debug, Copy, Clone)] +pub enum OutputDivider { + Div1 = 0, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +const ADDRESS: u8 = 0b0110_0000; + +impl PLL { + pub fn multisynth(&self) -> FeedbackMultisynth { + match *self { + PLL::A => FeedbackMultisynth::MSNA, + PLL::B => FeedbackMultisynth::MSNB, + } + } +} + +trait FractionalMultisynth { + fn base_addr(&self) -> u8; + fn ix(&self) -> u8; +} + +impl FractionalMultisynth for FeedbackMultisynth { + fn base_addr(&self) -> u8 { + match *self { + FeedbackMultisynth::MSNA => 26, + FeedbackMultisynth::MSNB => 34, + } + } + fn ix(&self) -> u8 { + match *self { + FeedbackMultisynth::MSNA => 6, + FeedbackMultisynth::MSNB => 7, + } + } +} + +impl FractionalMultisynth for Multisynth { + fn base_addr(&self) -> u8 { + match *self { + Multisynth::MS0 => 42, + Multisynth::MS1 => 50, + Multisynth::MS2 => 58, + Multisynth::MS3 => 66, + Multisynth::MS4 => 74, + Multisynth::MS5 => 82, + } + } + fn ix(&self) -> u8 { + match *self { + Multisynth::MS0 => 0, + Multisynth::MS1 => 1, + Multisynth::MS2 => 2, + Multisynth::MS3 => 3, + Multisynth::MS4 => 4, + Multisynth::MS5 => 5, + } + } +} + +impl SimpleMultisynth { + pub fn base_addr(&self) -> u8 { + match *self { + SimpleMultisynth::MS6 => 90, + SimpleMultisynth::MS7 => 91, + } + } +} + +#[derive(Debug, Copy, Clone)] +enum Register { + DeviceStatus = 0, + OutputEnable = 3, + Clk0 = 16, + Clk1 = 17, + Clk2 = 18, + Clk3 = 19, + Clk4 = 20, + Clk5 = 21, + Clk6 = 22, + Clk7 = 23, + PLLReset = 177, + CrystalLoad = 183, +} + +impl Register { + pub fn addr(&self) -> u8 { + *self as u8 + } +} + +bitflags! { + pub struct DeviceStatusBits: u8 { + const SYS_INIT = 0b1000_0000; + const LOL_B = 0b0100_0000; + const LOL_A = 0b0010_0000; + const LOS = 0b0001_0000; + } +} + +bitflags! { + struct CrystalLoadBits: u8 { + const RESERVED = 0b00_010010; + const CL_MASK = 0b11_000000; + const CL_6 = 0b01_000000; + const CL_8 = 0b10_000000; + const CL_10 = 0b11_000000; + } +} + +bitflags! { + struct ClockControlBits: u8 { + const CLK_PDN = 0b1000_0000; + const MS_INT = 0b0100_0000; + const MS_SRC = 0b0010_0000; + const CLK_INV = 0b0001_0000; + const CLK_SRC_MASK = 0b0000_1100; + const CLK_SRC_XTAL = 0b0000_0000; + const CLK_SRC_CLKIN = 0b0000_0100; + const CLK_SRC_MS_ALT = 0b0000_1000; + const CLK_SRC_MS = 0b0000_1100; + const CLK_DRV_MASK = 0b0000_0011; + const CLK_DRV_2 = 0b0000_0000; + const CLK_DRV_4 = 0b0000_0001; + const CLK_DRV_6 = 0b0000_0010; + const CLK_DRV_8 = 0b0000_0011; + } +} + +bitflags! { + struct PLLResetBits: u8 { + const PLLB_RST = 0b1000_0000; + const PLLA_RST = 0b0010_0000; + } +} + +impl ClockOutput { + fn register(self) -> Register { + match self { + ClockOutput::Clk0 => Register::Clk0, + ClockOutput::Clk1 => Register::Clk1, + ClockOutput::Clk2 => Register::Clk2, + ClockOutput::Clk3 => Register::Clk3, + ClockOutput::Clk4 => Register::Clk4, + ClockOutput::Clk5 => Register::Clk5, + ClockOutput::Clk6 => Register::Clk6, + ClockOutput::Clk7 => Register::Clk7, + } + } + + fn ix(&self) -> u8 { + *self as u8 + } +} + +impl OutputDivider { + fn bits(&self) -> u8 { + *self as u8 + } + + fn min_divider(desired_divider: u16) -> Result<OutputDivider, Error> { + match 16 - (desired_divider.max(1) - 1).leading_zeros() { + 0 => Ok(OutputDivider::Div1), + 1 => Ok(OutputDivider::Div2), + 2 => Ok(OutputDivider::Div4), + 3 => Ok(OutputDivider::Div8), + 4 => Ok(OutputDivider::Div16), + 5 => Ok(OutputDivider::Div32), + 6 => Ok(OutputDivider::Div64), + 7 => Ok(OutputDivider::Div128), + _ => Err(Error::InvalidParameter) + } + } + + fn denominator_u8(&self) -> u8 { + match *self { + OutputDivider::Div1 => 1, + OutputDivider::Div2 => 2, + OutputDivider::Div4 => 4, + OutputDivider::Div8 => 8, + OutputDivider::Div16 => 16, + OutputDivider::Div32 => 32, + OutputDivider::Div64 => 64, + OutputDivider::Div128 => 128, + } + } +} + +fn i2c_error<E>(_: E) -> Error { + Error::CommunicationError +} + +/// Si5351 driver +pub struct Si5351Device<I2C> { + i2c: I2C, + address: u8, + xtal_freq: u32, + clk_enabled_mask: u8, + ms_int_mode_mask: u8, + ms_src_mask: u8, +} + +pub trait Si5351 { + fn init_adafruit_module(&mut self) -> Result<(), Error>; + fn init(&mut self, xtal_load: CrystalLoad) -> Result<(), Error>; + fn read_device_status(&mut self) -> Result<DeviceStatusBits, Error>; + + fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error>; + fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error>; + + fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error>; + fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool); + + fn flush_output_enabled(&mut self) -> Result<(), Error>; + fn flush_clock_control(&mut self, clk: ClockOutput) -> Result<(), Error>; + + fn setup_pll_int(&mut self, pll: PLL, mult: u8) -> Result<(), Error>; + fn setup_pll(&mut self, pll: PLL, mult: u8, num: u32, denom: u32) -> Result<(), Error>; + fn setup_multisynth_int(&mut self, ms: Multisynth, mult: u16, r_div: OutputDivider) -> Result<(), Error>; + fn setup_multisynth(&mut self, ms: Multisynth, div: u16, num: u32, denom: u32, r_div: OutputDivider) -> Result<(), Error>; + fn select_clock_pll(&mut self, clocl: ClockOutput, pll: PLL); +} + +impl<I2C, E> Si5351Device<I2C> + where + I2C: WriteRead<Error=E> + Write<Error=E>, +{ + /// Creates a new driver from a I2C peripheral + pub fn new(i2c: I2C, address_bit: bool, xtal_freq: u32) -> Self { + let si5351 = Si5351Device { + i2c, + address: ADDRESS | if address_bit { 1 } else { 0 }, + xtal_freq, + clk_enabled_mask: 0, + ms_int_mode_mask: 0, + ms_src_mask: 0, + }; + + si5351 + } + + pub fn new_adafruit_module(i2c: I2C) -> Self { + Si5351Device::new(i2c, false, 25_000_000) + } + + fn write_ms_config<MS: FractionalMultisynth + Copy>(&mut self, ms: MS, int: u16, frac_num: u32, frac_denom: u32, r_div: OutputDivider) -> Result<(), Error> { + if frac_denom == 0 { + return Err(Error::InvalidParameter); + } + if frac_num > 0xfffff { + return Err(Error::InvalidParameter); + } + if frac_denom > 0xfffff { + return Err(Error::InvalidParameter); + } + + let p1: u32; + let p2: u32; + let p3: u32; + + if frac_num == 0 { + p1 = 128 * int as u32 - 512; + p2 = 0; + p3 = 1; + } else { + let ratio = (128u64 * (frac_num as u64) / (frac_denom as u64)) as u32; + + p1 = 128 * int as u32 + ratio - 512; + p2 = 128 * frac_num - frac_denom * ratio; + p3 = frac_denom; + } + + self.write_synth_registers(ms, [ + ((p3 & 0x0000FF00) >> 8) as u8, + p3 as u8, + ((p1 & 0x00030000) >> 16) as u8 | r_div.bits(), + ((p1 & 0x0000FF00) >> 8) as u8, + p1 as u8, + (((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16)) as u8, + ((p2 & 0x0000FF00) >> 8) as u8, + p2 as u8, + ])?; + + if frac_num == 0 { + self.ms_int_mode_mask |= ms.ix(); + } else { + self.ms_int_mode_mask &= !ms.ix(); + } + + Ok(()) + } + + pub fn reset_pll(&mut self, pll: PLL) -> Result<(), Error> { + self.write_register(Register::PLLReset, match pll { + PLL::A => PLLResetBits::PLLA_RST.bits(), + PLL::B => PLLResetBits::PLLB_RST.bits(), + })?; + + Ok(()) + } + + fn read_register(&mut self, reg: Register) -> Result<u8, Error> { + let mut buffer = [0u8; 1]; + self.i2c.write_read(self.address, &[reg.addr()], &mut buffer).map_err(i2c_error)?; + Ok(buffer[0]) + } + + fn write_register(&mut self, reg: Register, byte: u8) -> Result<(), Error> { + self.i2c.write(self.address, &[reg.addr(), byte]).map_err(i2c_error) + } + + fn write_synth_registers<MS: FractionalMultisynth>(&mut self, ms: MS, params: [u8; 8]) -> Result<(), Error> { + self.i2c.write(self.address, &[ms.base_addr(), + params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7] + ]).map_err(i2c_error) + } +} + +impl<I2C, E> Si5351 for Si5351Device<I2C> where + I2C: WriteRead<Error=E> + Write<Error=E> +{ + fn init_adafruit_module(&mut self) -> Result<(), Error> { + self.init(CrystalLoad::_10) + } + + fn init(&mut self, xtal_load: CrystalLoad) -> Result<(), Error> { + loop { + let device_status = self.read_device_status()?; + if !device_status.contains(DeviceStatusBits::SYS_INIT) { + break; + } + } + + self.flush_output_enabled()?; + const CLK_REGS: [Register; 8] = [Register::Clk0, Register::Clk1, + Register::Clk2, Register::Clk3, Register::Clk4, + Register::Clk5, Register::Clk6, Register::Clk7]; + for ® in CLK_REGS.iter() { + self.write_register(reg, ClockControlBits::CLK_PDN.bits())?; + } + + self.write_register(Register::CrystalLoad, + (CrystalLoadBits::RESERVED | match xtal_load { + CrystalLoad::_6 => CrystalLoadBits::CL_6, + CrystalLoad::_8 => CrystalLoadBits::CL_8, + CrystalLoad::_10 => CrystalLoadBits::CL_10, + }).bits())?; + + Ok(()) + } + + fn read_device_status(&mut self) -> Result<DeviceStatusBits, Error> { + Ok(DeviceStatusBits::from_bits_truncate(self.read_register(Register::DeviceStatus)?)) + } + + fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error> { + let total_divider = (max_pll_freq / freq) as u16; + + let r_div = OutputDivider::min_divider(total_divider / 900)?; + + let ms_div = (total_divider / (2 * r_div.denominator_u8() as u16) * 2).max(6); + if ms_div > 1800 { + return Err(Error::InvalidParameter); + } + + Ok((ms_div, r_div)) + } + + fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error> { + if denom == 0 || denom > 0xfffff { + return Err(Error::InvalidParameter); + } + + let pll_freq = freq * total_div; + + let mult = (pll_freq / self.xtal_freq) as u8; + let f = ((pll_freq % self.xtal_freq) as u64 * denom as u64 / self.xtal_freq as u64) as u32; + + Ok((mult, f)) + } + + fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error> { + let denom: u32 = 1048575; + + let (ms_divider, r_div) = self.find_int_dividers_for_max_pll_freq(900_000_000, freq)?; + let total_div = ms_divider as u32 * r_div.denominator_u8() as u32; + let (mult, num) = self.find_pll_coeffs_for_dividers(total_div, denom, freq)?; + + let ms = match clk { + ClockOutput::Clk0 => Multisynth::MS0, + ClockOutput::Clk1 => Multisynth::MS1, + ClockOutput::Clk2 => Multisynth::MS2, + ClockOutput::Clk3 => Multisynth::MS3, + ClockOutput::Clk4 => Multisynth::MS4, + ClockOutput::Clk5 => Multisynth::MS5, + _ => return Err(Error::InvalidParameter), + }; + + self.setup_pll(pll, mult, num, denom)?; + self.setup_multisynth_int(ms, ms_divider, r_div)?; + self.select_clock_pll(clk, pll); + self.set_clock_enabled(clk, true); + self.flush_clock_control(clk)?; + self.reset_pll(pll)?; + self.flush_output_enabled()?; + + Ok(()) + } + + fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool) { + let bit = 1u8 << clk.ix(); + if enabled { + self.clk_enabled_mask |= bit; + } else { + self.clk_enabled_mask &= !bit; + } + } + + fn flush_output_enabled(&mut self) -> Result<(), Error> { + let mask = self.clk_enabled_mask; + self.write_register(Register::OutputEnable, !mask) + } + + fn flush_clock_control(&mut self, clk: ClockOutput) -> Result<(), Error> { + let bit = 1u8 << clk.ix(); + let clk_control_pdn = if self.clk_enabled_mask & bit != 0 { + ClockControlBits::empty() + } else { + ClockControlBits::CLK_PDN + }; + + let ms_int_mode = if self.ms_int_mode_mask & bit == 0 { + ClockControlBits::empty() + } else { + ClockControlBits::MS_INT + }; + + let ms_src = if self.ms_src_mask & bit == 0 { + ClockControlBits::empty() + } else { + ClockControlBits::MS_SRC + }; + + let base = ClockControlBits::CLK_SRC_MS | ClockControlBits::CLK_DRV_8; + + self.write_register(clk.register(), (clk_control_pdn | ms_int_mode | ms_src | base).bits()) + } + + fn setup_pll_int(&mut self, pll: PLL, mult: u8) -> Result<(), Error> { + self.setup_pll(pll, mult, 0, 1) + } + + fn setup_pll(&mut self, pll: PLL, mult: u8, num: u32, denom: u32) -> Result<(), Error> { + if mult < 15 || mult > 90 { + return Err(Error::InvalidParameter); + } + + self.write_ms_config(pll.multisynth(), mult.into(), num, denom, OutputDivider::Div1)?; + + if mult % 2 == 0 && num == 0 {} else {} + + Ok(()) + } + + fn setup_multisynth_int(&mut self, ms: Multisynth, mult: u16, r_div: OutputDivider) -> Result<(), Error> { + self.setup_multisynth(ms, mult, 0, 1, r_div) + } + + fn setup_multisynth(&mut self, ms: Multisynth, div: u16, num: u32, denom: u32, r_div: OutputDivider) -> Result<(), Error> { + if div < 6 || div > 1800 { + return Err(Error::InvalidParameter); + } + + self.write_ms_config(ms, div, num, denom, r_div)?; + + Ok(()) + } + + fn select_clock_pll(&mut self, clock: ClockOutput, pll: PLL) { + let bit = 1u8 << clock.ix(); + match pll { + PLL::A => self.ms_src_mask &= !bit, + PLL::B => self.ms_src_mask |= bit, + } + } +} |