pybadge_high/
lib.rs

1#![no_std]
2#![allow(clippy::tabs_in_doc_comments)]
3#![warn(unreachable_pub)]
4#![cfg_attr(all(doc, nightly), feature(doc_auto_cfg))]
5#![allow(deprecated)]
6#![cfg_attr(
7	all(feature = "bluescreen-message-nightly", nightly),
8	feature(panic_info_message)
9)]
10
11//! Goal of this crate is to provide **high level hardware abstraction** layer for the pybade and the edgebadge.
12//! It should allow people with no/less knowledge of rust and embedded hardware, to program the boards mention before.
13//! If you try to do anything hardware-near or usinig additonal expensions,
14//! you should probably use the more hardware-near the [edgebadge](https://crates.io/crates/edgebadge) or [atsamd_hal](https://docs.rs/atsamd-hal/latest/atsamd_hal/) crate instead.
15//!
16//! # Setup
17//! #### Installation
18//! * Install rustup.
19//! I recommand to use the [package manger](https://repology.org/project/rustup/versions) of your operation system.
20//! Alternative you can install it from <https://www.rust-lang.org/tools/install>
21//! * install the rust thumbv7em-none-eabihf target. (the architecture of the micronctroller)
22//! ```bash
23//! rustup target install thumbv7em-none-eabihf
24//! ```
25//! * optional: install nightly toolchain for better doc (only relevant if you build the doc by yourself).
26//! ```
27//! rustup toolchain install nightly --target thumbv7em-none-eabihf
28//! ```
29//! * install the [hf2-cli](https://crates.io/crates/hf2-cli) flasher
30//!
31//! #### Create your Project
32//! * Create a new rust project.
33//! ```bash
34//! cargo new my-app
35//! ```
36//! * Add a `.cargo/config.toml` with the following content, to define target architecture and flasher
37//! ```toml
38//! [target.thumbv7em-none-eabihf]
39//! runner = "hf2 elf"
40//! #runner = 'probe-run --chip ATSAMD51J19A'
41//!
42//! [build]
43//! target = "thumbv7em-none-eabihf"
44//! rustflags = [
45//!
46//!   # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
47//!   # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
48//!   "-C", "link-arg=--nmagic",
49//!
50//!   "-C", "link-arg=-Tlink.x",
51//! ]
52//! ```
53//!
54//! * Add this crate as dependency
55//! ```bash
56//! cargo add pybadge-high
57//! ```
58//! * optional: add this to your `cargo.toml` for better optimizations
59//! ```toml
60//! [profile.release]
61//! codegen-units = 1 # better optimizations
62//! debug = true # symbols are nice and they don't increase the size on Flash
63//! lto = true # better optimizations
64//! ```
65//!
66//! * Addjust your `main.rs`
67//!
68//! You need to do some changes at your `main.rs`.
69//! First you must disable the rust standart libary by adding `#![no_std]`, because it is not supported by the pybadge.
70//! This does also mean you can not use the default main function any more and must disable it with `#![no_main]`.
71//! But because we still need a main function to enter the code we need to define our own main with `#[entry]`.
72//! This main function does never return (`!`).
73//! Otherwise the pybadge would do random stuff after the program has finish.
74//! So we need a endless loop.
75//! To get access to the peripherals of the pybadge, like display, buttons, leds etc you call [`PyBadge::take()`].
76//! This function can only called once at runtime otherwise it will return an Error.
77//! ```
78//! #![no_std]
79//! #![no_main]
80//!
81//! use pybadge_high::{prelude::*, PyBadge};
82//!
83//! #[entry]
84//! fn main() -> ! {
85//! 	let mut pybadge = PyBadge::take().unwrap();
86//! 	loop {}
87//! }
88//! ```
89//! When a program does panic, the red led at the back of the board starts flashing.
90//! If the `bluescreen`(default) feature is enable, the display does show the postion of the error.
91//! When the `beep_panic` feature is enable, the pybadge also beep for 3 seconds.
92//!
93//! #### Flashing:
94//! To flash you program, put your device in bootloader mode by hitting the reset button twice.
95//! After this excute
96//! ```bash
97//! cargo run --release
98//! ```
99//! The display does not work until you have press the reset button of the pybadge after flashing.
100//!
101//! # Feature-flags
102//! This crate has spilt functionallity in multiple feature flags.
103//! See the [rust book](https://doc.rust-lang.org/cargo/reference/features.html) for more information about features.
104//! Enabling only the feauters, which are needed, helps to keep the binary size small
105//! and reduce the number of needed dependencies.
106//!
107//! The following features are aviable:
108#![doc = document_features::document_features!()]
109
110#[cfg(feature = "bluescreen")]
111use core::fmt::Write;
112pub use cortex_m;
113#[cfg(feature = "neopixel")]
114use edgebadge::gpio::v2::PA15;
115use edgebadge::{
116	gpio,
117	gpio::{v2::PA23, *},
118	hal, pac,
119	prelude::*,
120	Pins
121};
122#[cfg(feature = "bluescreen")]
123use embedded_graphics::{
124	mono_font::{ascii::FONT_6X10, MonoTextStyle},
125	prelude::*,
126	text::Text
127};
128#[cfg(feature = "neopixel")]
129use embedded_hal::digital::v1_compat::OldOutputPin;
130#[cfg(feature = "neopixel")]
131use hal::timer::SpinTimer;
132use hal::{clock::GenericClockController, pwm::Pwm2, sercom::SPIMaster4};
133#[cfg(any(feature = "usb", feature = "time"))]
134use pac::interrupt;
135use pac::{CorePeripherals, Peripherals};
136#[cfg(feature = "neopixel")]
137use smart_leds_trait::SmartLedsWrite;
138use st7735_lcd::ST7735;
139#[cfg(feature = "neopixel")]
140use ws2812::Ws2812;
141#[cfg(feature = "neopixel")]
142use ws2812_timer_delay as ws2812;
143
144pub mod time;
145
146/// There are 8 buttons on the front: A, B, Select, Start and four arranged in a d-pad.
147pub mod buttons;
148use buttons::Buttons;
149
150pub mod prelude {
151	pub use cortex_m_rt::entry;
152	pub use edgebadge::prelude::{
153		_embedded_hal_blocking_delay_DelayMs, _embedded_hal_blocking_delay_DelayUs
154	};
155	#[cfg(feature = "neopixel")]
156	pub use smart_leds_trait::SmartLedsWrite;
157}
158
159#[cfg(feature = "usb")]
160pub mod usb;
161#[cfg(feature = "usb")]
162use usb::UsbBuilder;
163
164#[cfg(feature = "flash")]
165mod flash;
166#[doc(hidden)] //feature temporary disable
167#[cfg(feature = "flash")]
168pub use flash::Flash;
169
170#[cfg(feature = "pwm_sound")]
171mod sound;
172#[cfg(feature = "pwm_sound")]
173pub use sound::PwmSound;
174
175/// The [`Rgb565`](embedded_graphics::pixelcolor::Rgb565) Color type used by the display
176pub type Color = embedded_graphics::pixelcolor::Rgb565;
177/// Backlight of the display
178pub type Backlight = Pwm2<gpio::v2::PA01>;
179/// 1.8" TFT display - The front features a 160x128 pixel [`Rgb565`](embedded_graphics::pixelcolor::Rgb565) color display.
180///
181/// ![🖼️](https://cdn-learn.adafruit.com/assets/assets/000/075/105/original/adafruit_products_PyBadge_Top_Display.jpg)
182pub type Display = ST7735<
183	SPIMaster4<
184		hal::sercom::Sercom4Pad2<Pb14<PfC>>,
185		hal::sercom::Sercom4Pad3<Pb15<PfC>>,
186		hal::sercom::Sercom4Pad1<Pb13<PfC>>
187	>,
188	Pb5<Output<PushPull>>,
189	Pa0<Output<PushPull>>
190>;
191pub type Delay = edgebadge::delay::Delay;
192#[cfg(feature = "neopixel")]
193/// 5 individually addressable RGB NeoPixel LEDs
194/// located on the front of the board along the bottom middle.
195///
196/// If you have a PyBadgeLC there is only 1 NeoPixel in the center.
197/// ![🖼️](https://cdn-learn.adafruit.com/assets/assets/000/075/104/original/adafruit_products_PyBadge_Top_NeoPixels_and_Light_Sensor.jpg)
198pub type NeoPixel = Ws2812<
199	SpinTimer,
200	OldOutputPin<edgebadge::gpio::Pin<PA15, gpio::v2::Output<gpio::v2::PushPull>>>
201>;
202#[cfg(feature = "neopixel")]
203///Color type of the NeoPixel leds.
204pub type NeoPixelColor = <NeoPixel as SmartLedsWrite>::Color;
205
206#[cfg(all(feature = "bluescreen-message-nightly", not(feature = "bluescreen")))]
207core::compile_error!(
208	"bluescreen-message-nightly feature is enbaled but bluescreen feature is disable"
209);
210#[cfg(all(feature = "bluescreen-message-nightly", not(nightly)))]
211build_alert::yellow! {"
212WARNING:
213	bluescreen-message-nightly feature is enabled, but nigthly toolchain is not used!
214	This feature has no effect if stable toolchain is used
215"}
216
217///The red led at the back of the board.
218pub struct Led {
219	pin: Pin<PA23, Output<PushPull>>
220}
221
222impl Led {
223	pub fn off(&mut self) -> Result<(), ()> {
224		self.pin.set_low()
225	}
226
227	pub fn on(&mut self) -> Result<(), ()> {
228		self.pin.set_high()
229	}
230}
231
232///Allow acces to the peripherals, like display, buttons, flash etc.
233///
234///Can only called once at runtime otherwise it will return an Error.
235#[non_exhaustive]
236pub struct PyBadge {
237	pub backlight: Backlight,
238	pub display: Display,
239	pub buttons: Buttons,
240	pub red_led: Led,
241	pub delay: Delay,
242	#[cfg(feature = "neopixel")]
243	pub neopixel: NeoPixel,
244	#[doc(hidden)] //feature temporary disable
245	#[cfg(feature = "flash")]
246	pub flash: Flash,
247	#[cfg(feature = "pwm_sound")]
248	pub speaker: PwmSound,
249	#[cfg(feature = "usb")]
250	pub usb_builder: UsbBuilder
251}
252
253impl PyBadge {
254	/// Returns all the supported peripherals.
255	/// This function can only called once,
256	/// otherwise it does return Err.
257	pub fn take() -> Result<PyBadge, ()> {
258		let mut peripherals = Peripherals::take().ok_or(())?;
259		#[allow(unused_mut)] //only some feature flags need mut
260		let mut core = CorePeripherals::take().ok_or(())?;
261		let mut clocks = GenericClockController::with_internal_32kosc(
262			peripherals.GCLK,
263			&mut peripherals.MCLK,
264			&mut peripherals.OSC32KCTRL,
265			&mut peripherals.OSCCTRL,
266			&mut peripherals.NVMCTRL
267		);
268		let mut pins = Pins::new(peripherals.PORT).split();
269		let mut delay = hal::delay::Delay::new(core.SYST, &mut clocks);
270
271		//display
272		//move TC2
273		let (display, backlight) = pins.display.init(
274			&mut clocks,
275			peripherals.SERCOM4,
276			&mut peripherals.MCLK,
277			peripherals.TC2,
278			&mut delay,
279			&mut pins.port
280		)?;
281
282		//buttons
283		let buttons = {
284			let latch = pins.buttons.latch.into_push_pull_output(&mut pins.port);
285			let data_in = pins.buttons.data_in.into_floating_input(&mut pins.port);
286			let clock = pins.buttons.clock.into_push_pull_output(&mut pins.port);
287			Buttons {
288				current_state: 0,
289				last_state: 0,
290				latch,
291				data_in,
292				clock
293			}
294		};
295
296		//red led
297		let red_led = {
298			let mut led = Led {
299				pin: pins.led_pin.into_push_pull_output(&mut pins.port)
300			};
301			led.off()?;
302			led
303		};
304
305		//neopixel
306		#[cfg(feature = "neopixel")]
307		let neopixel = {
308			let timer = SpinTimer::new(4);
309			pins.neopixel.init(timer, &mut pins.port)
310		};
311
312		//flash
313		#[cfg(feature = "flash")]
314		let flash = flash::Flash::init(
315			pins.flash,
316			&mut peripherals.MCLK,
317			peripherals.QSPI,
318			&mut delay
319		);
320
321		//32kHz clock to be used for sound and time at TC4 and TC5
322		//move tc4_tc5
323		#[cfg(any(feature = "pwm_sound", feature = "time"))]
324		let tc4_tc5 = {
325			let gclk = clocks.gclk1();
326			clocks.tc4_tc5(&gclk).unwrap()
327		};
328
329		//speaker
330		//move Tc4
331		#[cfg(feature = "pwm_sound")]
332		let speaker = {
333			let enable_pin = pins.speaker.enable.into_push_pull_output(&mut pins.port);
334			let speaker_pin = pins.speaker.speaker.into_push_pull_output(&mut pins.port);
335			let counter = edgebadge::thumbv7em::timer::TimerCounter::tc4_(
336				&tc4_tc5,
337				peripherals.TC4,
338				&mut peripherals.MCLK
339			);
340			sound::PwmSound::init(enable_pin, speaker_pin, counter)
341		};
342
343		//time
344		//move TC5
345		#[cfg(feature = "time")]
346		{
347			let counter = edgebadge::thumbv7em::timer::TimerCounter::tc5_(
348				&tc4_tc5,
349				peripherals.TC5,
350				&mut peripherals.MCLK
351			);
352			time::init_counter(counter);
353			unsafe {
354				//set priority to highest, to make measurement more exactly
355				core.NVIC.set_priority(interrupt::TC5, 0);
356			}
357		};
358
359		//usb
360		#[cfg(feature = "usb")]
361		let usb_builder = {
362			unsafe {
363				core.NVIC.set_priority(interrupt::USB_OTHER, 1);
364				core.NVIC.set_priority(interrupt::USB_TRCPT0, 1);
365				core.NVIC.set_priority(interrupt::USB_TRCPT1, 1);
366			}
367			UsbBuilder {
368				usb_vid: 0x16c0,
369				usb_pid: 0x27dd,
370				manufacturer: "Fake company",
371				product: "Serial port",
372				serial_number: "Test",
373				pins: pins.usb,
374				peripherals: peripherals.USB,
375				clocks,
376				mclk: peripherals.MCLK
377			}
378		};
379
380		Ok(PyBadge {
381			backlight,
382			display,
383			buttons,
384			red_led,
385			#[cfg(feature = "neopixel")]
386			neopixel,
387			#[cfg(feature = "flash")]
388			flash,
389			#[cfg(feature = "pwm_sound")]
390			speaker,
391			#[cfg(feature = "usb")]
392			usb_builder,
393			delay
394		})
395	}
396}
397
398#[inline(never)]
399#[panic_handler]
400#[allow(unused_variables)] //panic_info is unused if bluescreen feature is disable
401fn panic(panic_info: &core::panic::PanicInfo) -> ! {
402	//simple turn red led on
403	let mut peripherals = unsafe { crate::pac::Peripherals::steal() };
404	let mut pins = Pins::new(peripherals.PORT).split();
405	let mut led = pins.led_pin.into_push_pull_output(&mut pins.port);
406	led.set_high().ok();
407
408	//enable blinking for led
409	let core = unsafe { CorePeripherals::steal() };
410	let mut clocks = GenericClockController::with_internal_32kosc(
411		peripherals.GCLK,
412		&mut peripherals.MCLK,
413		&mut peripherals.OSC32KCTRL,
414		&mut peripherals.OSCCTRL,
415		&mut peripherals.NVMCTRL
416	);
417
418	let mut delay = hal::delay::Delay::new(core.SYST, &mut clocks);
419	let mut speaker_enable = pins.speaker.enable.into_push_pull_output(&mut pins.port);
420	let mut speaker = pins.speaker.speaker.into_push_pull_output(&mut pins.port);
421
422	#[cfg(feature = "bluescreen")]
423	{
424		let dislpay = pins
425			.display
426			.init(
427				&mut clocks,
428				peripherals.SERCOM4,
429				&mut peripherals.MCLK,
430				peripherals.TC2,
431				&mut delay,
432				&mut pins.port
433			)
434			.ok()
435			.map(|(display, _backlight)| display);
436		if let Some(mut display) = dislpay {
437			display.clear(Color::BLUE).unwrap();
438			let mut output = heapless::String::<1024>::new();
439			writeln!(output, "program panicked at\n").ok();
440			if let Some(location) = panic_info.location() {
441				writeln!(
442					output,
443					"{}:{}:{}",
444					location.file(),
445					location.line(),
446					location.column()
447				)
448				.ok();
449			}
450			#[cfg(all(feature = "bluescreen-message-nightly", nightly))]
451			if let Some(message) = panic_info.message() {
452				writeln!(output, "{message}").ok();
453			}
454			//insert newline every x char (no auto line wrap)
455			let old_output = output;
456			let mut output = heapless::String::<1024>::new();
457			let mut i: usize = 0;
458			for char in old_output.chars() {
459				i += 1;
460				if char == '\n' {
461					i = 0;
462				}
463				if i >= 26 {
464					i = 0;
465					writeln!(output).ok();
466				}
467				write!(output, "{char}").ok();
468			}
469			let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
470			Text::new(&output, Point::new(5, 20), style)
471				.draw(&mut display)
472				.ok();
473		}
474	}
475
476	let mut i = 0_u8;
477	loop {
478		led.toggle();
479		//stop sound after 3 seconds (it is annoying)
480		if i <= 8 && cfg!(feature = "beep_panic") {
481			speaker_enable.toggle();
482			i += 1
483		} else {
484			speaker_enable.set_low().ok();
485		}
486		for _ in 0..100 {
487			delay.delay_ms(2_u8);
488			speaker.toggle();
489		}
490	}
491}