Peripheral Initialisation of GPIO Output with stm32f1xx_hal on bluepill development board - rust

I would like to initialize a basic output GPIO pin on my blue pill board. I am using Rust and the stm32f1xx_hal crate. I want to create a struct Peripherals which holds the handle to the output in the following way:
use cortex_m_rt;
use stm32f1xx_hal::{
pac,
prelude::*,
gpio,
afio,
serial::{Serial, Config},
};
use crate::pac::{USART1};
type GpioOutput = gpio::gpioc::PC13<gpio::Output<gpio::PushPull>>;
pub struct Peripherals{
led: Option<GpioOutput>
}
impl Peripherals {
fn init() -> Peripherals {
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// set clock frequency to internal 8mhz oscillator
let mut rcc = dp.RCC.constrain();
let mut flash = dp.FLASH.constrain();
let clocks = rcc.cfgr.sysclk(8.mhz()).freeze(&mut flash.acr);
// access PGIOC registers
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
return Peripherals{
led: Peripherals::init_led(&mut gpioc)
}
}
fn init_led(gpioc: &mut gpio::gpioc::Parts) -> Option<GpioOutput> {
let led = &gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
return Some(led);
}
}
This code does not work, since init_led returns Option<&GpioOutput>. Now I am wondering if it makes sense to use a lifetime parameter in the Peripherals struct and store a reference to the GpioOutput within the struct. Or is it more sensible to store the unreferenced value - and how would I implement either of these options?
The only solution which seems to work is moving the init_led code to the scope of the init function:
return Peripherals{
led: Some(gpioc.pc13.into_push_pull_output(&mut gpioc.crh))
}
But i would like to seperate that code within its own function. How can i do that?

Ok, i figured out a way in case someone else is having the same problem:
pub fn init() -> Peripherals {
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// set clock frequency to internal 8mhz oscillator
let rcc = dp.RCC.constrain();
let mut flash = dp.FLASH.constrain();
// access PGIOC and PGIOB registers and prepare the alternate function I/O registers
let mut apb2 = rcc.apb2;
let gpioc = dp.GPIOC.split(&mut apb2);
let clocks = rcc.cfgr.sysclk(8.mhz()).freeze(&mut flash.acr);
return Peripherals{
led: Peripherals::init_led(gpioc)
}
}
fn init_led(mut gpioc: stm32f1xx_hal::gpio::gpioc::Parts) -> Option<GpioOutput> {
let led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
return Some(led);
}
I am just wondering if this is the correct way to do it or will it create extra overhead, because i am passing gpioc by value instead of by reference in the init_led function?

Related

Reading the DS18B20 temperature sensor with this Rust function

sorry, i'm a complete newbie to Rust. I try to read the temp from the sensor mentioned above on a Raspberry Pi using the code provided on this site: https://github.com/fuchsnj/ds18b20
Actually, i want to call the function
get_temperature
but i have no idea how to declare the parameters, especially delay and one_wire_bus.
I was able to resolve all the 'namespaces' or name bindings (sorry, coming from C++) but got stuck with the parameters. Can someone give me an example how to call and use this function like this:
use ds18b20::{Resolution, Ds18b20};
use embedded_hal::blocking::delay::{DelayUs, DelayMs};
use embedded_hal::digital::v2::{OutputPin, InputPin};
use one_wire_bus::{self, OneWire, OneWireResult};
use core::fmt::Debug;
use std::io::Write;
fn main() {
let mut delay = ?????;
let mut one_wire_bus = ?????;
let mut tx = ?????; //&mut Vec::new();
let temp = get_temperature(delay, tx, one_wire_bus);
...
//do something whit the temp
...
}
This is the implementation of the function from the website
fn get_temperature<P, E>(
delay: &mut (impl DelayUs<u16> + DelayMs<u16>),
tx: &mut impl Write,
one_wire_bus: &mut OneWire<P>,
) -> OneWireResult<(), E>
where
P: OutputPin<Error=E> + InputPin<Error=E>,
E: Debug
{
// initiate a temperature measurement for all connected devices
ds18b20::start_simultaneous_temp_measurement(one_wire_bus, delay)?;
// wait until the measurement is done. This depends on the resolution you specified
// If you don't know the resolution, you can obtain it from reading the sensor data,
// or just wait the longest time, which is the 12-bit resolution (750ms)
Resolution::Bits12.delay_for_measurement_time(delay);
// iterate over all the devices, and report their temperature
let mut search_state = None;
loop {
if let Some((device_address, state)) = one_wire_bus.device_search(search_state.as_ref(), false, delay)? {
search_state = Some(state);
if device_address.family_code() != ds18b20::FAMILY_CODE {
// skip other devices
continue;
}
// You will generally create the sensor once, and save it for later
let sensor = Ds18b20::new(device_address)?;
// contains the read temperature, as well as config info such as the resolution used
let sensor_data = sensor.read_data(one_wire_bus, delay)?;
writeln!(tx, "Device at {:?} is {}°C", device_address, sensor_data.temperature);
} else {
break;
}
}
Ok(())
}

Jitter Problem on Sending Clock Signal over Serial on STM32F103 (Bluepill)

i would like to generate a Midi Clock signal with UART on a stm32f1 bluepill board. The Signal basicly just needs to send one byte (0xF8) at a maximum frequency of 128 hz to a 31250 bps serial interface. I created a minimal example using one of the STM32f1s Timers. The problem is, that the received signal on my midi gear does not seem to be very stable. It jumps between 319 and 321 bpm, whereas it should show a stable clock of 320bpm for a 128hz signal (the conversion formula: freq = bpm * 24 / 60). Do you have any idea why there is so much jitter? is it the serial implementation that creates that jitter or can it be a hardware problem? or is it the hal abstraction layer which introduces the jitter?
This is the time differences between the clock signals i measured for 124hz:
On the y axis is the time difference in useconds, on the x axis is the number of readings. 8000 us should be the correct time interval between signals. But in regular intervals there seems to be a signal fired with a time difference of only ~500. What could cause that? Maybe a Counter overflow?
After reducing the prescaler to 12mhz i got this pattern:
Here is the code that generates the clock signal
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use stm32f1xx_hal::{
pac,
pac::{interrupt, Interrupt, TIM4},
prelude::*,
gpio,
afio,
serial::{Serial, Config},
timer::{Event, Timer, CountDownTimer},
};
use core::mem::MaybeUninit;
use stm32f1xx_hal::pac::{USART2};
pub use embedded_hal::digital::v2::{OutputPin, InputPin};
pub type Usart2Serial = Serial<
USART2, (gpio::gpioa::PA2<gpio::Alternate<gpio::PushPull>>,
gpio::gpioa::PA3<gpio::Input<gpio::Floating>>)>;
// When a panic occurs, stop the microcontroller
#[allow(unused_imports)]
use panic_halt;
static mut G_TIM2: MaybeUninit<CountDownTimer<TIM4>> = MaybeUninit::uninit();
static mut G_SERIAL: MaybeUninit<Usart2Serial> = MaybeUninit::uninit();
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let rcc = dp.RCC.constrain();
let mut flash = dp.FLASH.constrain();
let clocks = rcc.cfgr
.use_hse(8.mhz()) // set clock frequency to external 8mhz oscillator
.sysclk(72.mhz()) // set sysclock
.pclk1(36.mhz()) // clock for apb1 prescaler -> TIM1
.pclk2(36.mhz()) // clock for apb2 prescaler -> TIM2,3,4
.adcclk(12.mhz()) // clock for analog digital converters
.freeze(&mut flash.acr);
let mut apb1 = rcc.apb1;
let mut apb2 = rcc.apb2;
let mut gpioa = dp.GPIOA.split(&mut apb2);
let mut afio = dp.AFIO.constrain(&mut apb2);
// init serial
let mut serial = init_usart2(dp.USART2, gpioa.pa2, gpioa.pa3, &mut gpioa.crl, &mut afio, &clocks, &mut apb1);
unsafe { G_SERIAL.write(serial) };
// init timer
let bpm = 320;
let frequency_in_hertz : u32 = (bpm as u32) * 24 / 60;
let mut timer = Timer::tim4(dp.TIM4, &clocks, &mut apb1).start_count_down((frequency_in_hertz).hz());
timer.listen(Event::Update);
// write to global static var
unsafe { G_TIM2.write(timer); }
cortex_m::peripheral::NVIC::unpend(Interrupt::TIM4);
unsafe {
cortex_m::peripheral::NVIC::unmask(Interrupt::TIM4);
}
loop {
// do nothing
}
}
fn init_usart2(
usart2: USART2,
pa2: gpio::gpioa::PA2<gpio::Input<gpio::Floating>>,
pa3: gpio::gpioa::PA3<gpio::Input<gpio::Floating>>,
crl: &mut gpio::gpioa::CRL,
afio: &mut afio::Parts,
clocks: &stm32f1xx_hal::rcc::Clocks,
apb1: &mut stm32f1xx_hal::rcc::APB1
) -> Usart2Serial {
let tx = pa2.into_alternate_push_pull(crl);
let rx = pa3;
return Serial::usart2(
usart2,
(tx, rx),
&mut afio.mapr,
Config::default().baudrate(31250.bps()),
*clocks,
apb1,
);
}
#[interrupt]
fn TIM4() {
let serial = unsafe { &mut *G_SERIAL.as_mut_ptr() };
serial.write(0xF8).ok();
let tim2 = unsafe { &mut *G_TIM2.as_mut_ptr() };
tim2.clear_update_interrupt_flag();
}
Ok, i found the solution by myself. It seems to be connected with the Timers counter. I guess it overflows and triggers the timer at a wrong interval.
adding the following line to the timer interrupt function resets the counter and removes the jitter:
#[interrupt]
fn TIM4() {
...
tim2.reset();
}
You need to confirm the priority of your interrupt to make sure there isn't other higher priority interrupts that delay the UART output.

How can i controll a 8x8 led-matrix display Max7219 with a raspberrypi in rust?

I want to manually control every single dot on 4 together chained 8*8 led-matrices controlled by the max7219 microcontroller via the SPI interface.
I already hooked up the clock, master-output/slave-input and ChipSelect signal to my osciloscope and everything seems to work the way it should be.
But I am only able to get the display kind of working by sending random data to it and I do not know how that data gets encoded.
Here is the code
use rand::Rng;
use std::io;
use std::io::prelude::*;
use spidev::{Spidev, SpidevOptions, SpidevTransfer, SpiModeFlags};
// Read the state of GPIO4 on a raspberry pi. /dev/gpiochip0
// maps to the driver for the SoC (builtin) GPIO controller.
fn main() -> Result<(), gpio_cdev::Error> {
let mut spi = create_spi().unwrap();
write_spi(&mut spi);
Ok(())
}
fn write_spi(spi: &mut Spidev) -> io::Result<()> {
let mut rng = rand::thread_rng();
loop {
let mut tx_buf = [0u8; 8];
for i in 0..8 {
tx_buf[i] = rng.gen_range(0..255);
}
spi.write(&tx_buf);
}
Ok(())
}
fn create_spi() -> io::Result<Spidev> {
let mut spi = Spidev::open("/dev/spidev0.0")?;
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(10_000)
.mode(SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options)?;
Ok(spi)
}
What data must be sent to get it working?

the trait `_embedded_hal_digital_InputPin` is not implemented for `PE2<Output<OpenDrain>>`

I am trying to use DHT11 Library for my STM32F303VC
I am getting error:
error[E0277]: the trait bound `PE2<Output<OpenDrain>>: _embedded_hal_digital_InputPin` is not satisfied
--> src/DHT11/auxiliary/src/lib.rs:51:32
|
51 | let mut dht11 = Dht11::new(pin);
| ^^^ the trait `_embedded_hal_digital_InputPin` is not implemented for `PE2<Output<OpenDrain>>`
|
= note: required because of the requirements on the impl of `embedded_hal::digital::v2::InputPin` for `PE2<Output<OpenDrain>>`
= note: required by `Dht11::<GPIO>::new`
My Error Image:
My code is in auxilary module is:
//! Initialization code
#![no_std]
#[allow(unused_extern_crates)] // bug rust-lang/rust#53964
extern crate panic_itm; // panic handler
pub use cortex_m::{asm::bkpt, iprint, iprintln};
pub use cortex_m_rt::entry;
pub use f3::hal::{delay::Delay, prelude, stm32f30x::i2c1};
pub use f3::led::{Direction, Leds};
pub use m::Float as _0;
pub use f3::hal::stm32f30x::{gpioc, rcc};
pub use dht11::{self,Measurement,Dht11};
pub use stm32f30x_hal::gpio;
use f3::hal::stm32f30x::{self, GPIOE, RCC};
pub use embedded_hal::digital::v2::OutputPin;
pub use embedded_hal::digital::v2::InputPin;
use cortex_m::peripheral::ITM;
use f3::{
hal::{
i2c::I2c,
prelude::*,
},
Lsm303dlhc,
};
pub fn init() -> (Delay, ITM, Leds, Dht11<GPIOE>) {
(stm32f30x::Peripherals::take().unwrap());
let cp = cortex_m::Peripherals::take().unwrap();
let dp = stm32f30x::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let gpioe = dp.GPIOE.split(&mut rcc.ahb);
let leds = Leds::new(gpioe);
let mut gpiob = dp.GPIOB.split(&mut rcc.ahb);
let scl = gpiob.pb6.into_af4(&mut gpiob.moder, &mut gpiob.afrl);
let sda = gpiob.pb7.into_af4(&mut gpiob.moder, &mut gpiob.afrl);
let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 400.khz(), clocks, &mut rcc.apb1);
let pin = gpioe.pe2.into_open_drain_output(&mut gpioe.moder,&mut gpioe.otyper);
let delay = Delay::new(cp.SYST, clocks);
let mut dht11 = Dht11::new(pin);
(delay, cp.ITM, leds, dht11)
}
my main.rs code is:
#![deny(unsafe_code)]
#![no_main]
#![no_std]
#[allow(unused_imports)]
use aux19::{entry, iprint, iprintln, prelude::*, Direction};
use aux19::{prelude::_embedded_hal_blocking_delay_DelayMs};
use m::Float;
// Slave address
const MAGNETOMETER: u8 = 0b001_1110;
#[entry]
fn main() -> ! {
let (mut delay, mut itm,mut leds,mut dth11) = aux18::init();
loop {
match dht11.perform_measurement(&mut delay) {
Ok(meas) => iprintln!(&mut itm.stim[0],"Temp: {} Hum: {}", meas.temperature, meas.humidity).unwrap(),
Err(e) => iprintln!(&mut itm.stim[0],"Error: {:?}", e).unwrap(),
};
delay.delay_ms(2_000_u16);
}
}
Dht11::new expects the pin to be an input pin, i.e. a type that implements embedded_hal::digital::v2::InputPin. In your auxiliary module, you configure the pin to be an output pin, which is the opposite of what you need to do:
let pin = gpioe.pe2.into_open_drain_output(&mut gpioe.moder,&mut gpioe.otyper);
The HAL library you are using has several methods to put a pin into input mode. into_floating_input might work for your use case. If you need a pull-up or pull-down resistor, there's also into_pull_down_input and into_pull_up_input. See reference documentation (for some reason, you need to expand the implementation blocks by clicking "+" to see the methods; this also prevents me from linking to them directly).
Using one of those should resolve this error.

Is it possible to compile a Vulkano shader at runtime?

I've been using Vulkano in order to get some simple 3D graphics going on. Generally, I like to write my GLSL shaders in text and restart my program, or even changing shaders while the program is running. The examples given in Vulkano appear to use a macro to convert the GLSL to some form of SPIR-V based shader with Rust functions attached, but the GLSL is actually compiled into the binary (even when using a path to a file).
I've managed to get the crate shaderc to build my SPIR-V on the fly:
let mut f = File::open("src/grafx/vert.glsl")
.expect("Can't find file src/bin/runtime-shader/vert.glsl
This example needs to be run from the root of the example crate.");
let mut source = String::new();
f.read_to_string(&mut source);
//let source = "#version 310 es\n void EP() {}";
let mut compiler = shaderc::Compiler::new().unwrap();
let mut options = shaderc::CompileOptions::new().unwrap();
options.add_macro_definition("EP", Some("main"));
let binary_result = compiler.compile_into_spirv(
&source, shaderc::ShaderKind::Vertex,
"shader.glsl", "main", Some(&options)).unwrap();
assert_eq!(Some(&0x07230203), binary_result.as_binary().first());
let text_result = compiler.compile_into_spirv_assembly(
&source, shaderc::ShaderKind::Vertex,
"shader.glsl", "main", Some(&options)).unwrap();
assert!(text_result.as_text().starts_with("; SPIR-V\n"));
//println!("Compiled Vertex Shader: {}", text_result.as_text());
let vert_spirv = {
unsafe { ShaderModule::new(device.clone(), binary_result.as_binary_u8()) }.unwrap()
};
vert_spirv
So far, so good, we have a ShaderModule which seems to be the first step. However, we we actually need is a GraphicsEntryPoint which we can then put into our GraphicsPipeline. Apparently, GraphicsPipeline is where we string together our shaders, triangles and depth maps and all that lovely stuff.
Trouble is, I've no idea what is going on with the code that performs this feat:
pub fn shade_vertex <'a, S> (vert_spirv: &'a Arc<ShaderModule>) ->
GraphicsEntryPoint<'a, S, VertInput, VertOutput, VertLayout> {
let tn = unsafe {
vert_spirv.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
VertInput,
VertOutput,
VertLayout(ShaderStages { vertex: true, ..ShaderStages::none() }),
GraphicsShaderType::Vertex
)
};
tn
}
Specifically, what is VertInput and VertOutput? I've copied them from the example.
This is the closest example I could find that deals with loading Shaders on the fly. It looks like Input and Output are looking for entry points into the SPIR-V or something but I've no idea what to do with that. I'm hoping there is a function somewhere in the existing macro that will just take care of this for me. I've gotten this far but I seem a little stuck.
Has anyone else tried loading shaders at runtime?
I'm using wgpu, I've made my device, render_pipeline multithreaded like this:
let rx = Arc::new(Mutex::new(rx));
let window = Arc::new(Mutex::new(window));
let fs = Arc::new(Mutex::new(fs));
let fs_module = Arc::new(Mutex::new(fs_module));
let render_pipeline = Arc::new(Mutex::new(render_pipeline));
let device = Arc::new(Mutex::new(device));
used notify to listen to change events:
notify = "4.0.15"
use notify::{RecommendedWatcher, Watcher, RecursiveMode};
//mainxx
let (tx, rx) = mpsc::channel();
let mut watcher: RecommendedWatcher =
Watcher::new(tx, Duration::from_millis(500)).unwrap();
log::info!("Starting watcher on {:?}", *FRAG_SHADER_PATH);
watcher.watch((*FRAG_SHADER_PATH).clone(), RecursiveMode::NonRecursive).unwrap();
Then spawn a thread that listens to changes:
thread::spawn(move || {
log::info!("Shader watcher thread spawned");
loop {
if let Ok(notify::DebouncedEvent::Write(..)) = rx.lock().unwrap().recv() {
log::info!("Write event in fragment shader");
window.lock().unwrap().set_title("Loading shader.frag...");
*fs.lock().unwrap() = load_fs().unwrap();
*fs_module.lock().unwrap() = load_fs_module(Arc::clone(&device), &Arc::clone(&fs).lock().unwrap());
*render_pipeline.lock().unwrap() = create_render_pipeline_multithreaded(Arc::clone(&device), Arc::clone(&fs_module));
render.lock().unwrap().deref_mut()();
window.lock().unwrap().set_title(TITLE);
};
}
});
where load_fs is a closure that uses glsl_to_spirv:
let load_fs = move || -> Result<Vec<u32>, std::io::Error> {
log::info!("Loading fragment shader");
let mut buffer = String::new();
let mut f = File::open(&*FRAG_SHADER_PATH)?;
f.read_to_string(&mut buffer)?;
// Load fragment shader
wgpu::read_spirv(
glsl_to_spirv::compile(
&buffer,
glsl_to_spirv::ShaderType::Fragment
).expect("Compilation failed")
)
};
There is an updated example for this in the vulkano repository.
I followed that and the example for shaderc-rs to get to this:
fn compile_to_spirv(src: &str, kind: shaderc::ShaderKind, entry_point_name: &str) -> Vec<u32> {
let mut f = File::open(src).unwrap_or_else(|_| panic!("Could not open file {}", src));
let mut glsl = String::new();
f.read_to_string(&mut glsl)
.unwrap_or_else(|_| panic!("Could not read file {} to string", src));
let compiler = shaderc::Compiler::new().unwrap();
let mut options = shaderc::CompileOptions::new().unwrap();
options.add_macro_definition("EP", Some(entry_point_name));
compiler
.compile_into_spirv(&glsl, kind, src, entry_point_name, Some(&options))
.expect("Could not compile glsl shader to spriv")
.as_binary()
.to_vec()
}
let vs = {
unsafe {
ShaderModule::from_words(
device.clone(),
&compile_to_spirv(
"shaders/triangle/vs.glsl",
shaderc::ShaderKind::Vertex,
"main",
),
)
}
.unwrap()
};
After this, vs can be used as in the example.

Resources