-
Notifications
You must be signed in to change notification settings - Fork 256
/
Copy pathpwm_irq_input.rs
223 lines (184 loc) · 8.32 KB
/
pwm_irq_input.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! # PWM IRQ Input Example
//!
//! Read a 5V 50Hz PWM servo input signal from gpio pin 1 and turn the LED on when
//! the input signal is high ( > 1600 us duty pulse width ) and off when low ( < 1400 us ).
//!
//! This signal is commonly used with radio control model systems and small servos.
//!
//! It may need to be adapted to your particular board layout and/or pin assignment.
//!
//! See the `Cargo.toml` file for Copyright and license details.
#![no_std]
#![no_main]
// Ensure we halt the program on panic (if we don't mention this crate it won't
// be linked)
use panic_halt as _;
// Alias for our HAL crate
use rp235x_hal as hal;
// Some things we need
use embedded_hal::digital::OutputPin;
// Our interrupt macro
use hal::pac::interrupt;
// Shorter alias for gpio and pwm modules
use hal::gpio;
use hal::pwm;
// Some short-cuts to useful types for sharing data with the interrupt handlers
use core::cell::RefCell;
use critical_section::Mutex;
/// Tell the Boot ROM about our application
#[link_section = ".start_block"]
#[used]
pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe();
/// 50 Hz PWM servo signals have a pulse width between 1000 us and 2000 us with
/// 1500 us as the centre point. us is the abbreviation for micro seconds.
///
/// The PWM threshold value for turning off the LED in us
const LOW_US: u16 = 1475;
/// The PWM threshold value for turning on the LED in us
const HIGH_US: u16 = 1525;
/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz.
/// Adjust if your board has a different frequency
const XTAL_FREQ_HZ: u32 = 12_000_000u32;
/// Pin types quickly become very long!
/// We'll create some type aliases using `type` to help with that
///
/// This pin will be our output - it will drive an LED if you run this on a Pico
type LedPin = gpio::Pin<gpio::bank0::Gpio25, gpio::FunctionSio<gpio::SioOutput>, gpio::PullNone>;
/// This pin will be our input for a 50 Hz servo PWM signal
type InputPwmPin = gpio::Pin<gpio::bank0::Gpio1, gpio::FunctionPwm, gpio::PullNone>;
/// This will be our PWM Slice - it will interpret the PWM signal from the pin
type PwmSlice = pwm::Slice<pwm::Pwm0, pwm::InputHighRunning>;
/// Since we're always accessing these pins together we'll store them in a tuple.
/// Giving this tuple a type alias means we won't need to use () when putting them
/// inside an Option. That will be easier to read.
type LedInputAndPwm = (LedPin, InputPwmPin, PwmSlice);
/// This how we transfer our LED pin, input pin and PWM slice into the Interrupt Handler.
/// We'll have the option hold both using the LedAndInput type.
/// This will make it a bit easier to unpack them later.
static GLOBAL_PINS: Mutex<RefCell<Option<LedInputAndPwm>>> = Mutex::new(RefCell::new(None));
/// Entry point to our bare-metal application.
///
/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables and the spinlock are initialised.
///
/// The function configures the rp235x peripherals, then fades the LED in an
/// infinite loop.
#[hal::entry]
fn main() -> ! {
// Grab our singleton objects
let mut pac = hal::pac::Peripherals::take().unwrap();
// Set up the watchdog driver - needed by the clock setup code
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
// Configure the clocks
//
// The default is to generate a 125 MHz system clock
hal::clocks::init_clocks_and_plls(
XTAL_FREQ_HZ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.unwrap();
// The single-cycle I/O block controls our GPIO pins
let sio = hal::Sio::new(pac.SIO);
// Set the pins up according to their function on this particular board
let pins = gpio::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
// Init PWMs
let pwm_slices = pwm::Slices::new(pac.PWM, &mut pac.RESETS);
// Configure PWM0 slice
// The PWM slice clock should only run when the input is high (InputHighRunning)
let mut pwm: pwm::Slice<_, pwm::InputHighRunning> = pwm_slices.pwm0.into_mode();
// Divide the 125 MHz system clock by 125 to give a 1 MHz PWM slice clock (1 us per tick)
pwm.set_div_int(125);
pwm.enable();
// Connect to GPI O1 as the input to channel B on PWM0
let input_pin = pins.gpio1.reconfigure();
let channel = &mut pwm.channel_b;
channel.set_enabled(true);
// Enable an interrupt whenever GPI O1 goes from high to low (the end of a pulse)
input_pin.set_interrupt_enabled(gpio::Interrupt::EdgeLow, true);
// Configure GPIO 25 as an output to drive our LED.
// we can use reconfigure() instead of into_pull_up_input()
// since the variable we're pushing it into has that type
let led = pins.gpio25.reconfigure();
// Give away our pins by moving them into the `GLOBAL_PINS` variable.
// We won't need to access them in the main thread again
critical_section::with(|cs| {
GLOBAL_PINS.borrow(cs).replace(Some((led, input_pin, pwm)));
});
// Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller
// will jump to the interrupt function when the interrupt occurs.
// We do this last so that the interrupt can't go off while
// it is in the middle of being configured
unsafe {
cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::IO_IRQ_BANK0);
}
loop {
// interrupts handle everything else in this example.
hal::arch::wfi();
}
}
#[allow(static_mut_refs)] // See https://github.com/rust-embedded/cortex-m/pull/561
#[interrupt]
fn IO_IRQ_BANK0() {
// The `#[interrupt]` attribute covertly converts this to `&'static mut Option<LedAndInput>`
static mut LED_INPUT_AND_PWM: Option<LedInputAndPwm> = None;
// This is one-time lazy initialisation. We steal the variables given to us
// via `GLOBAL_PINS`.
if LED_INPUT_AND_PWM.is_none() {
critical_section::with(|cs| {
*LED_INPUT_AND_PWM = GLOBAL_PINS.borrow(cs).take();
});
}
// Need to check if our Option<LedInputAndPwm> contains our pins and pwm slice
// borrow led, input and pwm by *destructuring* the tuple
// these will be of type `&mut LedPin`, `&mut InputPwmPin` and `&mut PwmSlice`, so we
// don't have to move them back into the static after we use them
if let Some((led, input, pwm)) = LED_INPUT_AND_PWM {
// Check if the interrupt source is from the input pin going from high-to-low.
// Note: this will always be true in this example, as that is the only enabled GPIO interrupt source
if input.interrupt_status(gpio::Interrupt::EdgeLow) {
// Read the width of the last pulse from the PWM Slice counter
let pulse_width_us = pwm.get_counter();
// if the PWM signal indicates low, turn off the LED
if pulse_width_us < LOW_US {
// set_low can't fail, but the embedded-hal traits always allow for it
// we can discard the Result
let _ = led.set_low();
}
// if the PWM signal indicates high, turn on the LED
else if pulse_width_us > HIGH_US {
// set_high can't fail, but the embedded-hal traits always allow for it
// we can discard the Result
let _ = led.set_high();
}
// If the PWM signal was in the dead-zone between LOW and HIGH, don't change the LED's
// state. The dead-zone avoids the LED flickering rapidly when receiving a signal close
// to the mid-point, 1500 us in this case.
// Reset the pwm counter back to 0, ready for the next pulse
pwm.set_counter(0);
// Our interrupt doesn't clear itself.
// Do that now so we don't immediately jump back to this interrupt handler.
input.clear_interrupt(gpio::Interrupt::EdgeLow);
}
}
}
/// Program metadata for `picotool info`
#[link_section = ".bi_entries"]
#[used]
pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [
hal::binary_info::rp_cargo_bin_name!(),
hal::binary_info::rp_cargo_version!(),
hal::binary_info::rp_program_description!(c"PWM IRQ Input Example"),
hal::binary_info::rp_cargo_homepage_url!(),
hal::binary_info::rp_program_build_attribute!(),
];
// End of file