aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorRenĂ© van Dorst <[email protected]>2023-08-24 00:45:15 +0200
committerRenĂ© van Dorst <[email protected]>2023-08-24 00:52:06 +0200
commit2cf6a5911408ff0a666f915e32fac7bc6b9fb5d3 (patch)
tree0511d74721726c4525513c5030d74aaf06857704 /examples
parente19f7d9a76fa13012b6c606167a6a8f215875a4e (diff)
stm32l4: Add EVAL-ADIN1110EBZ example with basic http server
Page show the current temperature and auto refresh every 1s.
Diffstat (limited to 'examples')
-rw-r--r--examples/stm32l4/.cargo/config.toml2
-rw-r--r--examples/stm32l4/Cargo.toml12
-rw-r--r--examples/stm32l4/src/bin/spe_adin1110_http_server.rs438
3 files changed, 449 insertions, 3 deletions
diff --git a/examples/stm32l4/.cargo/config.toml b/examples/stm32l4/.cargo/config.toml
index 36e74e5a5..db3a7ceff 100644
--- a/examples/stm32l4/.cargo/config.toml
+++ b/examples/stm32l4/.cargo/config.toml
@@ -2,7 +2,7 @@
2# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` 2# replace STM32F429ZITx with your chip as listed in `probe-rs chip list`
3#runner = "probe-rs run --chip STM32L475VGT6" 3#runner = "probe-rs run --chip STM32L475VGT6"
4#runner = "probe-rs run --chip STM32L475VG" 4#runner = "probe-rs run --chip STM32L475VG"
5runner = "probe-rs run --chip STM32L4S5VI" 5runner = "probe-run --chip STM32L4S5QI"
6 6
7[build] 7[build]
8target = "thumbv7em-none-eabi" 8target = "thumbv7em-none-eabi"
diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml
index f552a6109..e5be94eda 100644
--- a/examples/stm32l4/Cargo.toml
+++ b/examples/stm32l4/Cargo.toml
@@ -6,12 +6,17 @@ license = "MIT OR Apache-2.0"
6 6
7[dependencies] 7[dependencies]
8# Change stm32l4s5vi to your chip name, if necessary. 8# Change stm32l4s5vi to your chip name, if necessary.
9embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "memory-x", "time-driver-any", "exti", "unstable-traits", "chrono"] } 9embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "unstable-traits", "chrono"] }
10embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } 10embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
11embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } 11embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
12embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } 12embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", "unstable-traits", "nightly"] }
13embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } 13embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
14embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } 14embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
15embassy-net-adin1110 = { version = "0.1.0", path = "../../embassy-net-adin1110", default-features = false }
16embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "udp", "tcp", "dhcpv4", "medium-ethernet"] }
17embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
18embedded-io-async = { version = "0.5.0", features = ["defmt-03"] }
19embedded-io = { version = "0.5.0", features = ["defmt-03"] }
15 20
16defmt = "0.3" 21defmt = "0.3"
17defmt-rtt = "0.4" 22defmt-rtt = "0.4"
@@ -21,10 +26,13 @@ cortex-m-rt = "0.7.0"
21embedded-hal = "0.2.6" 26embedded-hal = "0.2.6"
22embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } 27embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" }
23embedded-hal-async = { version = "=1.0.0-rc.1" } 28embedded-hal-async = { version = "=1.0.0-rc.1" }
29embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] }
24panic-probe = { version = "0.3", features = ["print-defmt"] } 30panic-probe = { version = "0.3", features = ["print-defmt"] }
25futures = { version = "0.3.17", default-features = false, features = ["async-await"] } 31futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
26heapless = { version = "0.7.5", default-features = false } 32heapless = { version = "0.7.5", default-features = false }
27chrono = { version = "^0.4", default-features = false } 33chrono = { version = "^0.4", default-features = false }
34rand = { version = "0.8.5", default-features = false }
35static_cell = {version = "1.1", features = ["nightly"]}
28 36
29micromath = "2.0.0" 37micromath = "2.0.0"
30 38
diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
new file mode 100644
index 000000000..148c58771
--- /dev/null
+++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
@@ -0,0 +1,438 @@
1#![deny(clippy::pedantic)]
2#![allow(clippy::doc_markdown)]
3#![no_main]
4#![no_std]
5// Needed unitl https://github.com/rust-lang/rust/issues/63063 is stablised.
6#![feature(type_alias_impl_trait)]
7#![feature(associated_type_bounds)]
8#![allow(clippy::missing_errors_doc)]
9
10// This example works on a ANALOG DEVICE EVAL-ADIN110EBZ board.
11// Settings switch S201 "HW CFG":
12// - Without SPI CRC: OFF-ON-OFF-OFF-OFF
13// - With SPI CRC: ON -ON-OFF-OFF-OFF
14// Settings switch S303 "uC CFG": CFG0: On = static ip, Off = Dhcp
15// The webserver shows the actual temperature of the onboard i2c temp sensor.
16
17use core::marker::PhantomData;
18use core::sync::atomic::{AtomicI32, Ordering};
19
20use defmt::{error, info, println, unwrap, Format};
21use defmt_rtt as _; // global logger
22use embassy_executor::Spawner;
23use embassy_futures::select::{select, Either};
24use embassy_futures::yield_now;
25use embassy_net::tcp::TcpSocket;
26use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4};
27use embassy_time::{Delay, Duration, Ticker, Timer};
28use embedded_hal_async::i2c::I2c as I2cBus;
29use embedded_io::Write as bWrite;
30use embedded_io_async::Write;
31use hal::gpio::{Input, Level, Output, Speed};
32use hal::i2c::{self, I2c};
33use hal::rcc::{self};
34use hal::rng::{self, Rng};
35use hal::{bind_interrupts, exti, pac, peripherals};
36use heapless::Vec;
37use rand::RngCore;
38use static_cell::make_static;
39use {embassy_stm32 as hal, panic_probe as _};
40
41bind_interrupts!(struct Irqs {
42 I2C3_EV => i2c::InterruptHandler<peripherals::I2C3>;
43 RNG => rng::InterruptHandler<peripherals::RNG>;
44});
45
46use embassy_net_adin1110::{self, Device, Runner, ADIN1110};
47use embedded_hal_bus::spi::ExclusiveDevice;
48use hal::gpio::Pull;
49use hal::i2c::Config as I2C_Config;
50use hal::rcc::{ClockSrc, PLLClkDiv, PLLMul, PLLSource, PLLSrcDiv};
51use hal::spi::{Config as SPI_Config, Spi};
52use hal::time::Hertz;
53
54// Basic settings
55// MAC-address used by the adin1110
56const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
57// Static IP settings
58const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address([192, 168, 1, 5]), 24);
59// Listen port for the webserver
60const HTTP_LISTEN_PORT: u16 = 80;
61
62pub type SpeSpi = Spi<'static, peripherals::SPI2, peripherals::DMA1_CH1, peripherals::DMA1_CH2>;
63pub type SpeSpiCs = ExclusiveDevice<SpeSpi, Output<'static, peripherals::PB12>, Delay>;
64pub type SpeInt = exti::ExtiInput<'static, peripherals::PB11>;
65pub type SpeRst = Output<'static, peripherals::PC7>;
66pub type Adin1110T = ADIN1110<SpeSpiCs>;
67pub type TempSensI2c = I2c<'static, peripherals::I2C3, peripherals::DMA1_CH6, peripherals::DMA1_CH7>;
68
69static TEMP: AtomicI32 = AtomicI32::new(0);
70
71#[embassy_executor::main]
72async fn main(spawner: Spawner) {
73 defmt::println!("Start main()");
74
75 let mut config = embassy_stm32::Config::default();
76
77 // 80Mhz clock (Source: 8 / SrcDiv: 1 * PLLMul 20 / ClkDiv 2)
78 // 80MHz highest frequency for flash 0 wait.
79 config.rcc.mux = ClockSrc::PLL(
80 PLLSource::HSE(Hertz(8_000_000)),
81 PLLClkDiv::Div2,
82 PLLSrcDiv::Div1,
83 PLLMul::Mul20,
84 None,
85 );
86 config.rcc.hsi48 = true; // needed for rng
87 config.rcc.rtc_mux = rcc::RtcClockSource::LSI32;
88
89 let dp = embassy_stm32::init(config);
90
91 // RM0432rev9, 5.1.2: Independent I/O supply rail
92 // After reset, the I/Os supplied by VDDIO2 are logically and electrically isolated and
93 // therefore are not available. The isolation must be removed before using any I/O from
94 // PG[15:2], by setting the IOSV bit in the PWR_CR2 register, once the VDDIO2 supply is present
95 pac::PWR.cr2().modify(|w| w.set_iosv(true));
96
97 let reset_status = pac::RCC.bdcr().read().0;
98 defmt::println!("bdcr before: 0x{:X}", reset_status);
99
100 defmt::println!("Setup IO pins");
101
102 // Setup LEDs
103 let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low);
104 let mut led_uc2_red = Output::new(dp.PE2, Level::High, Speed::Low);
105 let led_uc3_yellow = Output::new(dp.PE6, Level::High, Speed::Low);
106 let led_uc4_blue = Output::new(dp.PG15, Level::High, Speed::Low);
107
108 // Read the uc_cfg switches
109 let uc_cfg0 = Input::new(dp.PB2, Pull::None);
110 let _uc_cfg1 = Input::new(dp.PF11, Pull::None);
111 let _uc_cfg2 = Input::new(dp.PG6, Pull::None);
112 let _uc_cfg3 = Input::new(dp.PG11, Pull::None);
113
114 // Setup I2C pins
115 let temp_sens_i2c = I2c::new(
116 dp.I2C3,
117 dp.PG7,
118 dp.PG8,
119 Irqs,
120 dp.DMA1_CH6,
121 dp.DMA1_CH7,
122 Hertz(100_000),
123 I2C_Config::default(),
124 );
125
126 // Setup IO and SPI for the SPE chip
127 let spe_reset_n = Output::new(dp.PC7, Level::Low, Speed::Low);
128 let spe_cfg0 = Input::new(dp.PC8, Pull::None);
129 let spe_cfg1 = Input::new(dp.PC9, Pull::None);
130 let _spe_ts_capt = Output::new(dp.PC6, Level::Low, Speed::Low);
131
132 let spe_int = Input::new(dp.PB11, Pull::None);
133 let spe_int = exti::ExtiInput::new(spe_int, dp.EXTI11);
134
135 let spe_spi_cs_n = Output::new(dp.PB12, Level::High, Speed::High);
136 let spe_spi_sclk = dp.PB13;
137 let spe_spi_miso = dp.PB14;
138 let spe_spi_mosi = dp.PB15;
139
140 // Don't turn the clock to high, clock must fit within the system clock as we get a runtime panic.
141 let mut spi_config = SPI_Config::default();
142 spi_config.frequency = Hertz(25_000_000);
143
144 let spe_spi: SpeSpi = Spi::new(
145 dp.SPI2,
146 spe_spi_sclk,
147 spe_spi_mosi,
148 spe_spi_miso,
149 dp.DMA1_CH1,
150 dp.DMA1_CH2,
151 spi_config,
152 );
153 let spe_spi = SpeSpiCs::new(spe_spi, spe_spi_cs_n, Delay);
154
155 let cfg0_without_crc = spe_cfg0.is_high();
156 let cfg1_spi_mode = spe_cfg1.is_high();
157
158 defmt::println!(
159 "ADIN1110: CFG SPI-MODE 1-{}, CRC-bit 0-{}",
160 cfg1_spi_mode,
161 cfg0_without_crc
162 );
163
164 // Check the SPI mode selected with the "HW CFG" dip-switch
165 if !cfg1_spi_mode {
166 error!("Driver doesn´t support SPI Protolcol \"OPEN Alliance\".\nplease use the \"Generic SPI\"! Turn On \"HW CFG\": \"SPI_CFG1\"");
167 loop {
168 led_uc2_red.toggle();
169 Timer::after(Duration::from_hz(10)).await;
170 }
171 };
172
173 let state = make_static!(embassy_net_adin1110::State::<8, 8>::new());
174
175 let (device, runner) =
176 embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, !cfg0_without_crc).await;
177
178 // Start task blink_led
179 unwrap!(spawner.spawn(heartbeat_led(led_uc3_yellow)));
180 // Start task temperature measurement
181 unwrap!(spawner.spawn(temp_task(temp_sens_i2c, led_uc4_blue)));
182 // Start ethernet task
183 unwrap!(spawner.spawn(ethernet_task(runner)));
184
185 let mut rng = Rng::new(dp.RNG, Irqs);
186 // Generate random seed
187 let seed = rng.next_u64();
188
189 let ip_cfg = if uc_cfg0.is_low() {
190 println!("Waiting for DHCP...");
191 let dhcp4_config = embassy_net::DhcpConfig::default();
192 embassy_net::Config::dhcpv4(dhcp4_config)
193 } else {
194 embassy_net::Config::ipv4_static(StaticConfigV4 {
195 address: IP_ADDRESS,
196 gateway: None,
197 dns_servers: Vec::new(),
198 })
199 };
200
201 // Init network stack
202 let stack = &*make_static!(Stack::new(
203 device,
204 ip_cfg,
205 make_static!(StackResources::<2>::new()),
206 seed
207 ));
208
209 // Launch network task
210 unwrap!(spawner.spawn(net_task(stack)));
211
212 let cfg = wait_for_config(stack).await;
213 let local_addr = cfg.address.address();
214
215 // Then we can use it!
216 let mut rx_buffer = [0; 4096];
217 let mut tx_buffer = [0; 4096];
218 let mut mb_buf = [0; 4096];
219 loop {
220 let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
221 socket.set_timeout(Some(Duration::from_secs(1)));
222
223 info!("Listening on http://{}:{}...", local_addr, HTTP_LISTEN_PORT);
224 if let Err(e) = socket.accept(HTTP_LISTEN_PORT).await {
225 defmt::error!("accept error: {:?}", e);
226 continue;
227 }
228
229 loop {
230 let _n = match socket.read(&mut mb_buf).await {
231 Ok(0) => {
232 defmt::info!("read EOF");
233 break;
234 }
235 Ok(n) => n,
236 Err(e) => {
237 defmt::error!("{:?}", e);
238 break;
239 }
240 };
241 led_uc2_red.set_low();
242
243 let status_line = "HTTP/1.1 200 OK";
244 let contents = PAGE;
245 let length = contents.len();
246
247 let _ = write!(
248 &mut mb_buf[..],
249 "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}\r\n\0"
250 );
251 let loc = mb_buf.iter().position(|v| *v == b'#').unwrap();
252
253 let temp = TEMP.load(Ordering::Relaxed);
254 let cel = temp / 1000;
255 let mcel = temp % 1000;
256
257 info!("{}.{}", cel, mcel);
258
259 let _ = write!(&mut mb_buf[loc..loc + 7], "{cel}.{mcel}");
260
261 let n = mb_buf.iter().position(|v| *v == 0).unwrap();
262
263 if let Err(e) = socket.write_all(&mb_buf[..n]).await {
264 error!("write error: {:?}", e);
265 break;
266 }
267
268 led_uc2_red.set_high();
269 }
270 }
271}
272
273async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 {
274 loop {
275 if let Some(config) = stack.config_v4() {
276 return config;
277 }
278 yield_now().await;
279 }
280}
281
282#[embassy_executor::task]
283async fn heartbeat_led(mut led: Output<'static, peripherals::PE6>) {
284 let mut tmr = Ticker::every(Duration::from_hz(3));
285 loop {
286 led.toggle();
287 tmr.next().await;
288 }
289}
290
291// ADT7422
292#[embassy_executor::task]
293async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static, peripherals::PG15>) -> ! {
294 let mut tmr = Ticker::every(Duration::from_hz(1));
295 let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap();
296
297 loop {
298 led.set_low();
299 match select(temp_sens.read_temp(), Timer::after(Duration::from_millis(500))).await {
300 Either::First(i2c_ret) => match i2c_ret {
301 Ok(value) => {
302 led.set_high();
303 let temp = i32::from(value);
304 println!("TEMP: {:04x}, {}", temp, temp * 78 / 10);
305 TEMP.store(temp * 78 / 10, Ordering::Relaxed);
306 }
307 Err(e) => defmt::println!("ADT7422: {}", e),
308 },
309 Either::Second(_) => println!("Timeout"),
310 }
311
312 tmr.next().await;
313 }
314}
315
316#[embassy_executor::task]
317async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! {
318 runner.run().await
319}
320
321#[embassy_executor::task]
322async fn net_task(stack: &'static Stack<Device<'static>>) -> ! {
323 stack.run().await
324}
325
326// same panicking *behavior* as `panic-probe` but doesn't print a panic message
327// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
328#[defmt::panic_handler]
329fn panic() -> ! {
330 cortex_m::asm::udf()
331}
332
333#[allow(non_camel_case_types)]
334#[repr(C)]
335pub enum Registers {
336 Temp_MSB = 0x00,
337 Temp_LSB,
338 Status,
339 Cfg,
340 T_HIGH_MSB,
341 T_HIGH_LSB,
342 T_LOW_MSB,
343 T_LOW_LSB,
344 T_CRIT_MSB,
345 T_CRIT_LSB,
346 T_HYST,
347 ID,
348 SW_RESET = 0x2F,
349}
350
351pub struct ADT7422<'d, BUS: I2cBus> {
352 addr: u8,
353 phantom: PhantomData<&'d ()>,
354 bus: BUS,
355}
356
357#[derive(Debug, Format)]
358pub enum Error<I2cError: Format> {
359 I2c(I2cError),
360 Address,
361}
362
363impl<'d, BUS> ADT7422<'d, BUS>
364where
365 BUS: I2cBus,
366 BUS::Error: Format,
367{
368 pub fn new(bus: BUS, addr: u8) -> Result<Self, Error<BUS::Error>> {
369 if !(0x48..=0x4A).contains(&addr) {
370 return Err(Error::Address);
371 }
372
373 Ok(Self {
374 bus,
375 phantom: PhantomData,
376 addr,
377 })
378 }
379
380 pub async fn init(&mut self) -> Result<(), Error<BUS::Error>> {
381 let mut cfg = 0b000_0000;
382 // if self.int.is_some() {
383 // // Set 1 SPS mode
384 // cfg |= 0b10 << 5;
385 // } else {
386 // One shot mode
387 cfg |= 0b01 << 5;
388 // }
389
390 self.write_cfg(cfg).await
391 }
392
393 pub async fn read(&mut self, reg: Registers) -> Result<u8, Error<BUS::Error>> {
394 let mut buffer = [0u8; 1];
395 self.bus
396 .write_read(self.addr, &[reg as u8], &mut buffer)
397 .await
398 .map_err(Error::I2c)?;
399 Ok(buffer[0])
400 }
401
402 pub async fn write_cfg(&mut self, cfg: u8) -> Result<(), Error<BUS::Error>> {
403 let buf = [Registers::Cfg as u8, cfg];
404 self.bus.write(self.addr, &buf).await.map_err(Error::I2c)
405 }
406
407 pub async fn read_temp(&mut self) -> Result<i16, Error<BUS::Error>> {
408 let mut buffer = [0u8; 2];
409
410 // if let Some(int) = &mut self.int {
411 // // Wait for interrupt
412 // int.wait_for_low().await.unwrap();
413 // } else {
414 // Start: One shot
415 let cfg = 0b01 << 5;
416 self.write_cfg(cfg).await?;
417 Timer::after(Duration::from_millis(250)).await;
418 self.bus
419 .write_read(self.addr, &[Registers::Temp_MSB as u8], &mut buffer)
420 .await
421 .map_err(Error::I2c)?;
422 Ok(i16::from_be_bytes(buffer))
423 }
424}
425
426// Web page
427const PAGE: &str = r#"<!DOCTYPE html>
428<html lang="en">
429 <head>
430 <meta charset="utf-8">
431 <meta http-equiv="refresh" content="1" >
432 <title>ADIN1110 with Rust</title>
433 </head>
434 <body>
435 <p>EVAL-ADIN1110EBZ</p>
436 <table><td>Temp Sensor ADT7422:</td><td> #00.00 &deg;C</td></table>
437 </body>
438</html>"#;