added buzzer and rtc. rtc freeze the system, bevore interrupt can be initilized..

This commit is contained in:
Philipp_EndevourOS 2025-07-30 18:50:47 +02:00
parent c1b54920ff
commit 161ebf9bd2
9 changed files with 236 additions and 212 deletions

1
Cargo.lock generated
View File

@ -984,6 +984,7 @@ name = "fw-anwesenheit"
version = "0.1.0"
dependencies = [
"bleps",
"chrono",
"critical-section",
"ds3231",
"edge-dhcp",

View File

@ -60,9 +60,9 @@ edge-nal-embassy = { version = "0.6.0", features = ["log"] }
picoserve = { version = "0.16.0", features = ["embassy", "log"] }
embassy-sync = { version = "0.7.0", features = ["log"] }
ds3231 = { version = "0.3.0", features = ["async"] }
ds3231 = { version = "0.3.0", features = ["async", "temperature_f32"] }
ws2812-spi = "0.5.1"
chrono = { version = "0.4.41", default-features = false }
[profile.dev]
# Rust debug is too slow.

View File

@ -1,7 +1,31 @@
use std::env;
use std::path::Path;
use std::fs::File;
use std::io::Write;
fn main() {
linker_be_nice();
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
println!("cargo:rustc-link-arg=-Tlinkall.x");
save_build_time();
}
fn save_build_time() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("build_time.rs");
let system_time = std::time::SystemTime::now();
let unix_time = system_time
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
println!("cargo:rustc-env=BUILD_TIME={}", unix_time);
let content = format!(
"/// compile time as UNIX-Timestamp (seconds since 1970-01-01)
pub const BUILD_UNIX_TIME: u64 = {};",
unix_time
);
let mut f = File::create(dest_path).unwrap();
f.write_all(content.as_bytes()).unwrap();
}
fn linker_be_nice() {
@ -14,7 +38,9 @@ fn linker_be_nice() {
"undefined-symbol" => match what.as_str() {
"_defmt_timestamp" => {
eprintln!();
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
eprintln!(
"💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`"
);
eprintln!();
}
"_stack_start" => {

View File

@ -1,2 +1,3 @@
pub mod nfc_reader;
pub mod rtc;
pub mod rtc;
pub mod buzzer;

20
src/drivers/buzzer.rs Normal file
View File

@ -0,0 +1,20 @@
use embassy_time::{Duration, Timer};
use esp_hal::peripherals;
use log::{debug, error, info};
use crate::init;
#[embassy_executor::task]
pub async fn feedback_task(buzzer: peripherals::GPIO19<'static>) {
info!("Starting feedback task");
let mut buzzer = init::hardware::setup_buzzer(buzzer).await;
loop {
debug!("Buzzer feedback task running");
buzzer.set_high();
Timer::after(Duration::from_millis(100)).await;
buzzer.set_low();
Timer::after(Duration::from_millis(100)).await;
return ;
}
}

View File

@ -1,12 +1,58 @@
use ds3231::{Alarm1Config, DS3231Error, Seconds, DS3231};
use embassy_time::{Timer, Duration};
use esp_hal::{i2c::{self, master::I2c}, peripherals, Async};
use log::{debug, error, info};
use crate::{drivers, init, UTC_TIME};
const RTC_ADDRESS: u8 = 0x57;
#[embassy_executor::task]
async fn rtc_task(
i2c: i2c::master::I2c<'static, Async>,
sqw_pin: peripherals::GPIO21<'static>,
) {
debug!("init rtc interrupt");
let mut rtc_interrupt = init::hardware::setup_rtc_iterrupt(sqw_pin).await;
debug!("configuring rtc");
let mut rtc = drivers::rtc::rtc_config(i2c).await;
let mut utc_time = UTC_TIME.lock().await;
let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await;
*utc_time = timestamp_result.unwrap_or(0);
loop {
debug!("Waiting for RTC interrupt...");
rtc_interrupt.wait_for_falling_edge().await;
debug!("RTC interrupt triggered");
utc_time = UTC_TIME.lock().await;
let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await;
*utc_time = timestamp_result.unwrap_or(0);
Timer::after(Duration::from_secs(1)).await; // Debounce delay
}
}
pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> {
let mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS);
let daily_alarm = Alarm1Config::AtTime {
hours: 0, // set alarm every day 00:00:00 to sync time
minutes: 0,
seconds: 0,
seconds: 10,
is_pm: None, // 24-hour mode
};
// Replace 'main::UTC_TIME' with the correct path to UTC_TIME, for example 'crate::UTC_TIME'
let mut utc_time;
{
utc_time = crate::UTC_TIME.lock().await;
}
let naive_dt = chrono::NaiveDateTime::from_timestamp_opt(*utc_time as i64, 0)
.expect("Invalid timestamp for NaiveDateTime");
rtc.set_datetime(&naive_dt).await.unwrap_or_else(|e| {
error!("Failed to set RTC datetime: {:?}", e);
panic!();
});
if let Err(e) = rtc.set_alarm1(&daily_alarm).await {
error!("Failed to configure RTC: {:?}", e);
panic!();
@ -25,4 +71,19 @@ pub async fn read_rtc_time<'a>(rtc: &'a mut DS3231<I2c<'static, Async>>) -> Resu
Err(e)
}
}
}
}
// TODO Update time when device is connected other device over Wifi
/* pub async fn update_rtc_time<'a>(rtc: &'a mut DS3231<I2c<'static, Async>>, datetime: u64) -> Result<(), DS3231Error<esp_hal::i2c::master::Error>> {
match rtc.set_datetime(datetime).await {
info!("RTC datetime updated to: {}", datetime);
Ok(_) => Ok(()),
Err(e) => {
error!("Failed to update RTC datetime: {:?}", e);
Err(e)
}
}
}
*/

View File

@ -1,180 +1,98 @@
use anyhow::Result;
use log::error;
use rgb::RGB8;
use smart_leds::colors::{GREEN, RED};
use std::time::Duration;
use tokio::{join, time::sleep};
use crate::hardware::{Buzzer, StatusLed};
#[cfg(not(feature = "mock_pi"))]
use crate::{hardware::GPIOBuzzer, hardware::SpiLed};
/* pub async fn failure(&mut self) {
let buzzer_handle = Self::beep_nak(&mut self.buzzer);
let led_handle = Self::flash_led_for_duration(&mut self.led, RED, LED_BLINK_DURATION);
#[cfg(feature = "mock_pi")]
use crate::hardware::{MockBuzzer, MockLed};
let (buzzer_result, _) = join!(buzzer_handle, led_handle);
const LED_BLINK_DURATION: Duration = Duration::from_secs(1);
buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}");
});
pub enum DeviceStatus {
NotReady,
Ready,
HotspotEnabled,
let _ = self.led_to_status();
}
impl DeviceStatus {
pub fn color(&self) -> RGB8 {
match self {
Self::NotReady => RGB8::new(0, 0, 0),
Self::Ready => RGB8::new(0, 50, 0),
Self::HotspotEnabled => RGB8::new(0, 0, 50),
}
}
}
pub struct Feedback<B: Buzzer, L: StatusLed> {
device_status: DeviceStatus,
buzzer: B,
led: L,
pub async fn activate_error_state(&mut self) -> Result<()> {
self.led.turn_on(RED)?;
Self::beep_nak(&mut self.buzzer).await?;
Ok(())
}
impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
pub async fn success(&mut self) {
let buzzer_handle = Self::beep_ack(&mut self.buzzer);
let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, LED_BLINK_DURATION);
let (buzzer_result, _) = join!(buzzer_handle, led_handle);
pub async fn startup(&mut self){
self.device_status = DeviceStatus::Ready;
buzzer_result.unwrap_or_else(|err| {
error!("Failed to buzz: {err}");
});
let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, Duration::from_secs(1));
let buzzer_handle = Self::beep_startup(&mut self.buzzer);
let _ = self.led_to_status();
}
let (buzzer_result, led_result) = join!(buzzer_handle, led_handle);
pub async fn failure(&mut self) {
let buzzer_handle = Self::beep_nak(&mut self.buzzer);
let led_handle = Self::flash_led_for_duration(&mut self.led, RED, LED_BLINK_DURATION);
buzzer_result.unwrap_or_else(|err| {
error!("Failed to buzz: {err}");
});
let (buzzer_result, _) = join!(buzzer_handle, led_handle);
led_result.unwrap_or_else(|err| {
error!("Failed to blink led: {err}");
});
buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}");
});
let _ = self.led_to_status();
}
pub async fn activate_error_state(&mut self) -> Result<()> {
self.led.turn_on(RED)?;
Self::beep_nak(&mut self.buzzer).await?;
Ok(())
}
pub async fn startup(&mut self){
self.device_status = DeviceStatus::Ready;
let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, Duration::from_secs(1));
let buzzer_handle = Self::beep_startup(&mut self.buzzer);
let (buzzer_result, led_result) = join!(buzzer_handle, led_handle);
buzzer_result.unwrap_or_else(|err| {
error!("Failed to buzz: {err}");
});
led_result.unwrap_or_else(|err| {
error!("Failed to blink led: {err}");
});
let _ = self.led_to_status();
}
pub fn set_device_status(&mut self, status: DeviceStatus){
self.device_status = status;
let _ = self.led_to_status();
}
fn led_to_status(&mut self) -> Result<()> {
self.led.turn_on(self.device_status.color())
}
async fn flash_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> {
led.turn_on(color)?;
sleep(duration).await;
led.turn_off()?;
Ok(())
}
async fn beep_ack(buzzer: &mut B) -> Result<()> {
buzzer
.modulated_tone(1200.0, Duration::from_millis(100))
.await?;
sleep(Duration::from_millis(10)).await;
buzzer
.modulated_tone(2000.0, Duration::from_millis(50))
.await?;
Ok(())
}
async fn beep_nak(buzzer: &mut B) -> Result<()> {
buzzer
.modulated_tone(600.0, Duration::from_millis(150))
.await?;
sleep(Duration::from_millis(100)).await;
buzzer
.modulated_tone(600.0, Duration::from_millis(150))
.await?;
Ok(())
}
async fn beep_startup(buzzer: &mut B) -> Result<()> {
buzzer
.modulated_tone(523.0, Duration::from_millis(150))
.await?;
buzzer
.modulated_tone(659.0, Duration::from_millis(150))
.await?;
buzzer
.modulated_tone(784.0, Duration::from_millis(150))
.await?;
buzzer
.modulated_tone(1046.0, Duration::from_millis(200))
.await?;
sleep(Duration::from_millis(100)).await;
buzzer
.modulated_tone(784.0, Duration::from_millis(100))
.await?;
buzzer
.modulated_tone(880.0, Duration::from_millis(200))
.await?;
Ok(())
}
let _ = self.led_to_status();
}
#[cfg(feature = "mock_pi")]
pub type FeedbackImpl = Feedback<MockBuzzer, MockLed>;
#[cfg(not(feature = "mock_pi"))]
pub type FeedbackImpl = Feedback<GPIOBuzzer, SpiLed>;
impl FeedbackImpl {
pub fn new() -> Result<Self> {
#[cfg(feature = "mock_pi")]
{
Ok(Feedback {
device_status: DeviceStatus::NotReady,
buzzer: MockBuzzer {},
led: MockLed {},
})
}
#[cfg(not(feature = "mock_pi"))]
{
Ok(Feedback {
device_status: DeviceStatus::NotReady,
buzzer: GPIOBuzzer::new_default()?,
led: SpiLed::new()?,
})
}
}
async fn flash_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> {
led.turn_on(color)?;
sleep(duration).await;
led.turn_off()?;
Ok(())
}
async fn beep_ack(buzzer: &mut B) -> Result<()> {
buzzer
.modulated_tone(1200.0, Duration::from_millis(100))
.await?;
sleep(Duration::from_millis(10)).await;
buzzer
.modulated_tone(2000.0, Duration::from_millis(50))
.await?;
Ok(())
}
async fn beep_nak(buzzer: &mut B) -> Result<()> {
buzzer
.modulated_tone(600.0, Duration::from_millis(150))
.await?;
sleep(Duration::from_millis(100)).await;
buzzer
.modulated_tone(600.0, Duration::from_millis(150))
.await?;
Ok(())
}
async fn beep_startup(buzzer: &mut B) -> Result<()> {
buzzer
.modulated_tone(523.0, Duration::from_millis(150))
.await?;
buzzer
.modulated_tone(659.0, Duration::from_millis(150))
.await?;
buzzer
.modulated_tone(784.0, Duration::from_millis(150))
.await?;
buzzer
.modulated_tone(1046.0, Duration::from_millis(200))
.await?;
sleep(Duration::from_millis(100)).await;
buzzer
.modulated_tone(784.0, Duration::from_millis(100))
.await?;
buzzer
.modulated_tone(880.0, Duration::from_millis(200))
.await?;
Ok(())
}
*/

View File

@ -1,13 +1,10 @@
use core::slice::RChunks;
use ds3231::{Alarm1Config, DS3231Error, DS3231};
use embassy_executor::Spawner;
use embassy_net::{driver, Stack};
use embassy_sync::mutex::Mutex;
use esp_hal::config;
use esp_hal::gpio::{Input, Pull};
use esp_hal::i2c::master::Config;
use esp_hal::peripherals::{self, GPIO0, GPIO1, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO21, GPIO22, GPIO23, I2C0, UART1};
use esp_hal::peripherals::{self, GPIO0, GPIO1, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO19, GPIO21, GPIO22, GPIO23, I2C0, UART1};
use esp_hal::time::Rate;
use esp_hal::{
Async,
@ -18,7 +15,7 @@ use esp_hal::{
gpio::{Output, OutputConfig}
};
use esp_println::logger::init_logger;
use log::error;
use log::{debug, error};
use crate::init::wifi;
use crate::init::network;
@ -26,17 +23,15 @@ use crate::init::network;
/*************************************************
* GPIO Pinout Xiao Esp32c6
*
* D0 -> Level Shifter OE
* D1 -> Level Shifter A0 -> LED
* D3 -> SQW Interrupt RTC
* D4 -> SDA
* D5 -> SCL
* D7 -> Level Shifter A1 -> NFC Reader
* D8 -> Buzzer
* D0 -> GPIO0 -> Level Shifter OE
* D1 -> GPIO1 -> Level Shifter A0 -> LED
* D3 -> GPIO21 -> SQW Interrupt RTC
* D4 -> GPIO22 -> SDA
* D5 -> GPIO23 -> SCL
* D7 -> GPIO17 -> Level Shifter A1 -> NFC Reader
* D8 -> GPIO19 -> Buzzer
*
*/
const RTC_ADDRESS: u8 = 0x57;
*************************************************/
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
@ -45,7 +40,7 @@ fn panic(_: &core::panic::PanicInfo) -> ! {
esp_bootloader_esp_idf::esp_app_desc!();
pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stack<'static>, I2c<'static, Async>, GPIO21<'static>) {
pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stack<'static>, I2c<'static, Async>, GPIO21<'static>, GPIO19<'static>) {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
@ -73,9 +68,11 @@ pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stac
//RTC Interrupt pin
let sqw_pin = peripherals.GPIO21;
//TODO change to get I2C device back / maybe init for each protocol
let buzzer_gpio = peripherals.GPIO19;
(uart_device, stack, i2c_device, sqw_pin)
debug!("hardware init done");
(uart_device, stack, i2c_device, sqw_pin, buzzer_gpio)
}
// Initialize the level shifter for the NFC reader and LED (output-enable (OE) input is low, all outputs are placed in the high-impedance (Hi-Z) state)
@ -105,7 +102,7 @@ fn setup_i2c(
sda: GPIO22<'static>,
scl: GPIO23<'static>,
) -> I2c<'static, Async> {
debug!("init I2C");
let config = Config::default().with_frequency(Rate::from_khz(400));
let i2c = match I2c::new(i2c0, config) {
Ok(i2c) => i2c.with_sda(sda).with_scl(scl).into_async(),
@ -117,12 +114,20 @@ fn setup_i2c(
i2c
}
pub async fn rtc_init_iterrupt(sqw_pin: GPIO21<'static>) -> Input<'static> {
pub async fn setup_rtc_iterrupt(sqw_pin: GPIO21<'static>) -> Input<'static> {
debug!("init rtc interrupt");
let config = esp_hal::gpio::InputConfig::default().with_pull(Pull::Up); //Active low interrupt in rtc
let sqw_interrupt = Input::new(sqw_pin, config);
sqw_interrupt
}
pub async fn setup_buzzer(buzzer_gpio: GPIO19<'static>) -> Output<'static> {
let config = esp_hal::gpio::OutputConfig::default().with_drive_strength(esp_hal::gpio::DriveStrength::_40mA);
let buzzer = Output::new(buzzer_gpio, esp_hal::gpio::Level::Low, config);
buzzer
}
fn setup_spi_led() {

View File

@ -13,17 +13,20 @@ use embassy_sync::{
};
use embassy_time::{Duration, Timer};
use esp_alloc::psram_allocator;
use esp_hal::{i2c, peripherals, Async};
use esp_hal::{gpio::Output, i2c, peripherals, Async};
use esp_hal::uart::Uart;
use log::{debug, info};
use static_cell::make_static;
use crate::{store::TallyID, webserver::start_webserver};
include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
mod init;
mod drivers;
mod store;
mod webserver;
mod feedback;
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
@ -32,7 +35,13 @@ static UTC_TIME: Mutex<CriticalSectionRawMutex, u64> = Mutex::new(0);
#[esp_hal_embassy::main]
async fn main(mut spawner: Spawner) {
let (uart_device, stack, i2c, sqw_pin) = init::hardware::hardware_init(&mut spawner).await;
{
let mut utc_time = UTC_TIME.lock().await;
*utc_time = BUILD_UNIX_TIME;
info!("UTC Time initialized to: {}", *utc_time);
}
let (uart_device, stack, _i2c, sqw_pin, buzzer_gpio) = init::hardware::hardware_init(&mut spawner).await;
wait_for_stack_up(stack).await;
@ -42,8 +51,12 @@ async fn main(mut spawner: Spawner) {
let publisher = chan.publisher().unwrap();
debug!("spawing NFC reader task");
spawner.must_spawn(drivers::nfc_reader::rfid_reader_task(uart_device, publisher));
spawner.must_spawn(rtc_task(i2c, sqw_pin));
//debug!("spawing rtc task");
//spawner.must_spawn(rtc_task(_i2c, sqw_pin));
debug!("spawing feedback task");
spawner.must_spawn(drivers::buzzer::feedback_task(buzzer_gpio));
let mut sub = chan.subscriber().unwrap();
loop {
@ -66,25 +79,4 @@ async fn wait_for_stack_up(stack: Stack<'static>) {
}
Timer::after(Duration::from_millis(500)).await;
}
}
#[embassy_executor::task]
async fn rtc_task(
i2c: i2c::master::I2c<'static, Async>,
sqw_pin: peripherals::GPIO21<'static>,
) {
let mut rtc_interrupt = init::hardware::rtc_init_iterrupt(sqw_pin).await;
let mut rtc = drivers::rtc::rtc_config(i2c).await;
let mut utc_time = UTC_TIME.lock().await;
let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await;
*utc_time = timestamp_result.unwrap_or(0);
loop {
rtc_interrupt.wait_for_falling_edge().await;
debug!("RTC interrupt triggered");
utc_time = UTC_TIME.lock().await;
let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await;
*utc_time = timestamp_result.unwrap_or(0);
}
}