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 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
use embassy_executor::Executor;
use embassy_time::{Duration, Timer};
use esp_println::println;
use hal::{
adc::{self, AdcCalCurve, AdcConfig, AdcPin, Attenuation, ADC, ADC1},
clock::ClockControl,
embassy,
gpio::{Analog, Gpio3, Gpio8, Output, PushPull},
interrupt,
peripherals::{Interrupt, Peripherals, UART1},
prelude::*,
system::SystemParts,
timer::TimerGroup,
uart::TxRxPins,
Priority, Rtc, Uart, IO,
};
use core::{fmt::Write, sync::atomic::AtomicU8, writeln};
/// # Exercise: Send Battery percentage over UART to the `onboard-computer`
///
/// Use an [`AtomicU8`] to store the percentage of the battery and later send it over UART.
///
/// `pub static BATTERY: ... = todo!("Use AtomicU8");`
pub static BATTERY: AtomicU8 = AtomicU8::new(0);
/// # Exercise: Flashing Onboard LED
///
/// Read docs: <https://docs.rs/esp32-hal/latest/esp32_hal/gpio/index.html>
///
/// Check out the Blinky example: <https://github.com/esp-rs/esp-hal/blob/main/esp32c3-hal/examples/blinky.rs>
///
/// Check out Olimex schematic, for pins and other board features: <https://raw.githubusercontent.com/OLIMEX/ESP32-C3-DevKit-Lipo/main/HARDWARE/ESP32-C3-DevKit-Lipo_Rev_B/ESP32-C3-DevKit-Lipo_Rev_B.pdf>
///
/// We want to create an Output of GPIO 8 which is the onboard led (check schematics above).
///
/// GPIO 8 docs: <https://docs.rs/esp32c3-hal/latest/esp32c3_hal/gpio/type.Gpio8.html>
///
///
///
/// `pub type OnboardLed = todo!("Onboard Led type - Push/Pull Output pin on GPIO 8");`
pub type OnboardLed = Gpio8<Output<PushPull>>;
/// # Exercise: Battery measurement with ADC
///
/// - GPIO 3 docs: <https://docs.rs/esp32c3-hal/latest/esp32c3_hal/gpio/type.Gpio3.html>
/// - ADC example: <https://github.com/esp-rs/esp-hal/blob/main/esp32c3-hal/examples/adc.rs>
///
/// pub type BatteryMeasurementPin = todo!();
pub type BatteryMeasurementPin = AdcPin<Gpio3<Analog>, ADC1, AdcCalCurve<ADC1>>;
pub struct Application {
// TODO: Uncomment when you create an `ADC` instance of the `ADC1` peripheral
adc: ADC<'static, ADC1>,
// TODO: Uncomment when you create a `Uart` instance of the `UART1` peripheral
uart: Uart<'static, UART1>,
// TODO: Uncomment when you create the `OnboardLed` instance
onboard_led: OnboardLed,
// TODO: Uncomment when you create the `AdcPin` instance
battery_measurement_pin: BatteryMeasurementPin,
}
impl Application {
/// Initialises all the peripherals which the [`Application`] will use.
pub fn init(peripherals: Peripherals) -> Self {
let system: SystemParts = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let mut peripheral_clock_control = system.peripheral_clock_control;
let timer_group0 =
TimerGroup::new(peripherals.TIMG0, &clocks, &mut peripheral_clock_control);
let mut wdt0 = timer_group0.wdt;
let timer_group1 =
TimerGroup::new(peripherals.TIMG1, &clocks, &mut peripheral_clock_control);
let mut wdt1 = timer_group1.wdt;
// Disable watchdog timers
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
#[cfg(feature = "embassy-time-systick")]
embassy::init(&clocks, system_timer);
#[cfg(feature = "embassy-time-timg0")]
embassy::init(&clocks, timer_group0.timer0);
// Setup IO peripherals for application
//
// TODO: Uncomment line and implement the `todo!()` for all exercises requiring GPIO.
// let io = todo!("Setup the GPIO peripherals");
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// Exercise: Battery measurement
//
// Set GPIO 3 as a battery measurement pin but keep it mind that it's not mandatory to make an external voltage divider.
// instead you can solder 3 jumpers on the board and use the integrated voltage divider of the Olimex board at GPIO 3.
// GPIO 4 can be used to sense the power flowing into the board at +5V when e.g. the USB is connected to the board.
// This corresponds to a "Charging" or "Not charging" state of the battery.
// TODO: Can we use an `Input` GPIO instead of ADC measurement?
//
// Olimex ESP32-C3 schematics: https://raw.githubusercontent.com/OLIMEX/ESP32-C3-DevKit-Lipo/main/HARDWARE/ESP32-C3-DevKit-Lipo_Rev_B/ESP32-C3-DevKit-Lipo_Rev_B.pdf
//
// Create an ADC instances
// HAL example: https://github.com/esp-rs/esp-hal/blob/main/esp32c3-hal/examples/adc.rs
//
// 1. Get the ADC peripherals
// TODO: Uncomment line and implement the `todo!()` for all exercises requiring ADC.
// let analog = todo!("You need to split() the `APB_SARADC` peripheral from `peripherals`");
let analog = peripherals.APB_SARADC.split();
// 2. Create a configuration for ADC1
//
// TODO: Uncomment line and implement the `todo!()` for all exercises requiring ADC1.
// let adc1_config = todo!("Create an AdcConfig instance for ADC1");
let mut adc1_config = AdcConfig::new();
// 3. Create the battery measurement pin by enabling it as an Analog pin
//
// Attenuation 11db should be used to get measurements between 0 and 2500 mV (or 2.5 V).
// Given that we use a voltage divider and the maximum (the 100% of the battery) is 4.2 V,
// this gives a range of `4.2V / 2 = 2.1V`
//
// TODO: Uncomment line and implement the `todo!()` for all exercises requiring ADC1.
// let battery_measurement_pin = todo!("Enable GPIO 3 as analog and Attenuation - 11dB for measuring the battery voltage");
type AdcCal = adc::AdcCalCurve<ADC1>;
let battery_measurement_pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(
io.pins.gpio3.into_analog(),
Attenuation::Attenuation11dB,
);
// 4. Initialise ADC1 peripheral
let adc1 = ADC::<ADC1>::adc(&mut peripheral_clock_control, analog.adc1, adc1_config)
.expect("Failed to init ADC1");
// TODO: Upcoming future exercise - power sensing:
// adc1_config.enable_pin(io.pins.gpio4.into_analog(), Attenuation::Attenuation11dB);
// Exercise: Flashing Onboard LED
//
// Olimex ESP32-C3 schematics: https://raw.githubusercontent.com/OLIMEX/ESP32-C3-DevKit-Lipo/main/HARDWARE/ESP32-C3-DevKit-Lipo_Rev_B/ESP32-C3-DevKit-Lipo_Rev_B.pdf
// Configure GPIO and set GPIO 8 (LED pin) as an Push/Pull output pin
// TODO: Uncomment line and implement the `todo!()` for the exercise
// let onboard_led = todo!("Onboard Led setup - Push/Pull Output pin")
let onboard_led = io.pins.gpio8.into_push_pull_output();
// Exercise: Send battery percentage to `onboard-computer` over UART.
//
// The Olimex ESP32-C3 board has debug UART on pins 21 (TX) and 20 (RX)
// but you should use GPIO pins 0 (TX) and 1 (RX) for sending and receiving data respectively
// to/from the `onboard-computer` board.
//
// 1. Setup a new Uart instance using the UART1 peripheral and set the pins in the configuration
//
// TODO: Uncomment line and implement the `todo!()` for the exercise
// let uart1 = todo!("Configure UART 1 at pins 0 (TX) and 1 (RX) with `None` or default for the `Config`");
let uart1 = Uart::new_with_config(
peripherals.UART1,
None,
Some(TxRxPins::new_tx_rx(
io.pins.gpio0.into_floating_input(),
io.pins.gpio1.into_push_pull_output(),
)),
&clocks,
&mut peripheral_clock_control,
);
// 2. Enable the UART1 interrupt using Priority1
// HAL docs: https://docs.rs/esp32c3-hal/latest/esp32c3_hal/interrupt/fn.enable.html
//
// TODO: Uncomment line, import the `interrupt` module and enable UART1 with Priority1
// Caveat: Do not forget to handle the result!
//
// interrupt::enable(...)
interrupt::enable(Interrupt::UART1, Priority::Priority1).unwrap();
Self {
adc: adc1,
uart: uart1,
onboard_led,
battery_measurement_pin,
}
}
/// Runs the application by spawning each of the [`Application`]'s tasks
pub fn run(self, executor: &'static mut Executor) -> ! {
executor.run(|spawner| {
spawner.must_spawn(run_battery_measurement_adc(
self.adc,
self.battery_measurement_pin,
));
spawner.must_spawn(run_blinky(self.onboard_led));
spawner.must_spawn(run_uart(self.uart));
})
}
}
/// # Exercise: Send Battery percentage over UART to the `onboard-computer`
#[embassy_executor::task]
async fn run_uart(mut uart: Uart<'static, UART1>) {
// This communication task will be executed every 1 second
// First the battery percentage will be sent to the power system
// Second, the task will enter blocking state until new GNSS message is received from power system
loop {
// Transmit Operations
// First we send the battery percentage value to the Power system
uart.write_str("Hello world!").ok();
// Receive Operations
// Second we poll UART receiver to check if any messages are received
// This code will receive NMEA messages from the power board
// Code will NOT? block until message is received
// On board computer needs to send the GNSS messages more frequently so that this task does not block for long
// (Can look into option of using async hal/interrupts but not sure if supported)
Timer::after(Duration::from_millis(50)).await;
}
}
/// # Exercise: Battery measurement with ADC
#[embassy_executor::task]
async fn run_battery_measurement_adc(
mut adc_1: ADC<'static, ADC1>,
mut battery_measurement_pin: BatteryMeasurementPin,
) {
loop {
// Take an ADC Reading
// ADC is 12 bit resolution (2^12 = 4096) with attenuation 11 db (from 0 to 2.6V)
// ADC will allow values from 0 to 4096 for voltages between 0 and 3.3V.
//
// ADC docs for attenuation: https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32c3/api-reference/peripherals/adc.html#_CPPv415ADC_ATTEN_DB_11
//
// Using 11 db we can measure from 0 to 2600 mV (or 2.6V) and we'll take into account the Voltage divider
// and a battery with voltage specs of:
// - 4.2V - 100% charge
// - 3.7V (nominal)
// - 3.0V cut-off
// We will use 3.3V for 0% because of the board's buck converter which is lowering the voltage only.
//
// Precision factor ( Vref / ADC resolution 2^12): f(p) = 3.3V / 4096
// R1 - resistor connected on the positive side (+) of the battery
// R2 - resistor connected on GND (-) of the battery
// scale = R2 / (R1 + R2) =~ 0.5
// Formula for calculating the voltage: ADC reading * Precision factor / scale
// = ADC reading * 3.3 / 4096.0 / 0.5
//
// Formula Percentage: (voltage - 3.3) / (4.2 - 3.3) * 100
// We use 3.3V as the lower
// 470k Ohms resistor with 1% tolerance can varie between:
// `465300` (465.3 k Ohms) - `474700` (474.7 k Ohms)
// let scale = todo!();
let reading_result: Result<u16, _> = nb::block!(adc_1.read(&mut battery_measurement_pin));
match reading_result {
Ok(reading) => {
let pin_value_mv = reading as u32 * Attenuation::Attenuation11dB.ref_mv() as u32 / 4096;
println!("PIN2 ADC reading = {reading} ({pin_value_mv} mV)");
let precision = 3.3 / 4096.0;
let scale = 0.5;
let voltage = reading as f32 * precision / scale;
let percentage = (voltage - 3.3) / (4.2 - 3.3) * 100.0;
println!("(Debug) ADC reading: {reading} / 4096");
println!("Battery (V = {voltage}) {percentage} %");
}
Err(_) => {
println!("Failed to read ADC 1 value")
}
};
Timer::after(Duration::from_millis(30)).await;
}
}
/// # Exercise: Flashing Onboard LED
#[embassy_executor::task]
async fn run_blinky(mut led: OnboardLed) {
// LED Blinking Code goes here
// Make an infinite loop
loop {
// Turn on the LED
led.set_high().unwrap();
// Delay 500 ms
Timer::after(Duration::from_millis(500)).await;
// Turn off the LED
led.set_low().unwrap();
// Delay 500 ms
Timer::after(Duration::from_millis(500)).await;
}
}