This commit is contained in:
Vitalii 2026-02-11 23:12:53 +02:00
parent cc9d55f160
commit 99b36997ba
Signed by: SymbX
GPG Key ID: FF51F4E4BCE459EE
2 changed files with 170 additions and 17 deletions

View File

@ -1,5 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000 + 156K, LENGTH = 1024K - 156K
RAM : ORIGIN = 0x20000000 + 31K, LENGTH = 256K - 31K
RAM : ORIGIN = 0x20007b08, LENGTH = 256K - 31496
}

View File

@ -5,6 +5,7 @@ mod fmt;
use core::mem;
use core::ops::{AddAssign, SubAssign};
use core::pin::Pin;
use defmt::{error, Format};
#[cfg(not(feature = "defmt"))]
use panic_halt as _;
@ -30,7 +31,7 @@ use futures::pin_mut;
use nrf_softdevice::{raw, Flash, Softdevice};
use nrf_softdevice::ble::advertisement_builder::{Flag, LegacyAdvertisementBuilder, LegacyAdvertisementPayload, ServiceList, ServiceUuid16};
use nrf_softdevice::ble::{peripheral, gatt_server, Connection};
use embedded_storage_async::nor_flash::ReadNorFlash;
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
#[derive(Copy, Clone, Eq, PartialEq, Format)]
enum SysAction {
@ -46,6 +47,27 @@ enum TextLightType {
Hue
}
impl From<u8> for TextLightType {
fn from(value: u8) -> Self {
match value {
0 => TextLightType::Hue,
1 => TextLightType::ConstantColor,
2 => TextLightType::White,
_ => TextLightType::Hue,
}
}
}
impl Into<u8> for TextLightType {
fn into(self) -> u8 {
match self {
TextLightType::White => 2,
TextLightType::ConstantColor => 1,
TextLightType::Hue => 0,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Format)]
enum HeartLightType {
ConstantColor,
@ -53,6 +75,27 @@ enum HeartLightType {
HeartBeat
}
impl From<u8> for HeartLightType {
fn from(value: u8) -> Self {
match value {
0 => HeartLightType::HeartBeat,
1 => HeartLightType::Hue,
2 => HeartLightType::ConstantColor,
_ => HeartLightType::HeartBeat,
}
}
}
impl Into<u8> for HeartLightType {
fn into(self) -> u8 {
match self {
HeartLightType::ConstantColor => 2,
HeartLightType::Hue => 1,
HeartLightType::HeartBeat => 0,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Format)]
pub enum TouchAction {
Tap,
@ -68,13 +111,22 @@ pub enum AnimationSignal<T> {
SetColor(u16),
}
#[derive(Copy, Clone, Eq, PartialEq, Format)]
pub enum StoreType {
TextLight(TextLightType),
HeartLight(HeartLightType),
TextColor(u16),
HeartColor(u16),
Text(TextLightType, u16),
Heart(HeartLightType, u16),
}
static TOUCH_SIGNAL: Signal<ThreadModeRawMutex, TouchAction> = Signal::new();
static RADAR_SIGNAL: Signal<ThreadModeRawMutex, u8> = Signal::new();
static TEXT_ANIMATION_SIGNAL: Signal<ThreadModeRawMutex, AnimationSignal<TextLightType>> = Signal::new();
static HEART_ANIMATION_SIGNAL: Signal<ThreadModeRawMutex, AnimationSignal<HeartLightType>> = Signal::new();
static SYS_ACTION: Mutex<ThreadModeRawMutex, SysAction> = Mutex::new(SysAction::PresentenceOn);
static TEXT_ANIMATION: Mutex<ThreadModeRawMutex, TextLightType> = Mutex::new(TextLightType::Hue);
static HEART_ANIMATION: Mutex<ThreadModeRawMutex, HeartLightType> = Mutex::new(HeartLightType::HeartBeat);
static SAVE_SIGNAL: Signal<ThreadModeRawMutex, StoreType> = Signal::new();
#[nrf_softdevice::gatt_service(uuid = "9e7312e0-2354-11eb-9f10-fbc30a62cf38")]
struct AnimationService {
@ -105,6 +157,8 @@ bind_interrupts!(struct AdcIrqs {
SAADC => saadc::InterruptHandler;
});
const DATA_POINT: u32 = 0x000F9800 - 4096;
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let mut config = embassy_nrf::config::Config::default();
@ -153,13 +207,19 @@ async fn main(spawner: Spawner) {
channel_config.reference = Reference::INTERNAL;
let saadc = Saadc::new(p.SAADC, AdcIrqs, saadc::Config::default(), [channel_config]);
saadc.calibrate().await;
let flash = Flash::take(sd);
pin_mut!(flash);
let mut flash = Flash::take(sd);
let mut data = [0u8; 16];
if let Err(e) = flash.as_mut().read(0x000F9800 - 4096, &mut data).await {
if let Err(e) = flash.read(DATA_POINT, &mut data).await {
error!("Error reading from flash: {}", e);
}
info!("Data in flash: {:?}", data);
let initial_data = if data[0] == 0xFF && data[1] == 0xFF {
(TextLightType::Hue, 0u16, HeartLightType::HeartBeat, 0u16)
} else {
let text_color = u16::from_le_bytes([data[4], data[5]]);
let heart_color = u16::from_le_bytes([data[6], data[7]]);
(TextLightType::from(data[2]), text_color, HeartLightType::from(data[3]), heart_color)
};
let pwm_text = SimplePwm::new_3ch(p.PWM0, p.P1_00, p.P1_04, p.P1_06, &Default::default());
let pwm_heart = SimplePwm::new_3ch(p.PWM2, p.P0_17, p.P0_20, p.P0_22, &Default::default());
@ -167,11 +227,82 @@ async fn main(spawner: Spawner) {
spawner.spawn(touch_button(p.P0_08.into())).expect("failed to spawn touch task");
spawner.spawn(actions_task()).expect("failed to spawn actions task");
spawner.spawn(softdevice_task(sd)).expect("failed to spawn softdevice task");
// spawner.spawn(battery_task(saadc)).expect("failed to spawn softdevice task");
spawner.spawn(storage_task(flash, initial_data.0, initial_data.1, initial_data.2, initial_data.3)).expect("failed to spawn softdevice task");
join4(gatt_task(server, saadc, sd), moving_radar(p.P0_06.into()), heartbeat_task(pwm_heart), light_task(pwm_text)).await;
join4(gatt_task(server, saadc, sd), moving_radar(p.P0_06.into()), heartbeat_task(pwm_heart, initial_data.2, initial_data.3), light_task(pwm_text, initial_data.0, initial_data.1)).await;
}
#[embassy_executor::task]
async fn storage_task(flash: Flash, initial_text_type: TextLightType, initial_text_color: u16, initial_heart_type: HeartLightType, initial_heart_color: u16) {
pin_mut!(flash);
let mut text_type = initial_text_type;
let mut text_color = initial_text_color;
let mut heart_type = initial_heart_type;
let mut heart_color = initial_heart_color;
let mut changes = false;
loop {
match with_timeout(Duration::from_secs(60), SAVE_SIGNAL.wait()).await {
Ok(sig) => {
info!("Received signal with {:?}", sig);
match sig {
StoreType::TextLight(val) => {
if text_type != val {
text_type = val;
changes = true;
}
}
StoreType::HeartLight(val) => {
if heart_type != val {
heart_type = val;
changes = true;
}
}
StoreType::TextColor(val) => {
if text_color != val {
text_color = val % 1536;
changes = true;
}
}
StoreType::HeartColor(val) => {
if heart_color != val {
heart_color = val % 1536;
changes = true;
}
}
StoreType::Text(t, c) => {
if text_color != c || text_type != t {
text_color = c;
text_type = t;
changes = true;
}
}
StoreType::Heart(t, c) => {
if heart_color != c || heart_type != t {
heart_color = c;
heart_type = t;
changes = true;
}
}
}
},
Err(e) => {
info!("Error fetching from storage: {}", e);
if changes {
changes = false;
let t_color: [u8; 2] = text_color.to_le_bytes();
let h_color: [u8; 2] = heart_color.to_le_bytes();
let data = [0b01010101u8, 0b10101010u8, text_type.into(), heart_type.into(), t_color[0], t_color[1], h_color[0], h_color[1], 0xFF, 0xFF, 0xFF, 0xFF];
info!("Saving data: {:?}", data);
if let Err(e) = flash.as_mut().write(DATA_POINT, &data).await {
error!("Error writing to flash: {}", e);
}
} else {
info!("Storage tick");
}
}
}
}
}
async fn gatt_task(server: Server, mut saadc: Saadc<'static, 1>, sd: &'static Softdevice) {
static ADV_DATA: LegacyAdvertisementPayload = LegacyAdvertisementBuilder::new()
@ -471,7 +602,7 @@ async fn moving_radar(pin: Peri<'static, AnyPin>) {
}
}
async fn light_task(mut pwm: SimplePwm<'static>) {
async fn light_task(mut pwm: SimplePwm<'static>, light_type: TextLightType, initial_color: u16) {
info!("Starting light task");
const MAX_DUTY: u16 = 32767;
@ -483,8 +614,8 @@ async fn light_task(mut pwm: SimplePwm<'static>) {
pwm.set_all_duties([DutyCycle::normal(0), DutyCycle::normal(0), DutyCycle::normal(0), DutyCycle::normal(0)]);
let mut intensity = 0i32;
let mut hue: u16 = 0;
let mut animation_type = TextLightType::Hue;
let mut hue: u16 = initial_color;
let mut animation_type = light_type;
loop {
let action = {
@ -519,12 +650,23 @@ async fn light_task(mut pwm: SimplePwm<'static>) {
TextLightType::Hue
}
};
if animation_type == TextLightType::ConstantColor {
SAVE_SIGNAL.signal(StoreType::Text(TextLightType::ConstantColor, hue));
} else {
SAVE_SIGNAL.signal(StoreType::TextLight(animation_type));
}
}
AnimationSignal::SetMode(val) => {
animation_type = val;
if animation_type == TextLightType::ConstantColor {
SAVE_SIGNAL.signal(StoreType::Text(TextLightType::ConstantColor, hue));
} else {
SAVE_SIGNAL.signal(StoreType::TextLight(animation_type));
}
}
AnimationSignal::SetColor(val) => {
hue = val;
SAVE_SIGNAL.signal(StoreType::TextColor(hue));
}
}
}
@ -585,15 +727,15 @@ fn hue_to_rgb(hue: u16, max: u16) -> (u16, u16, u16) {
(r as u16, g as u16, b as u16)
}
async fn heartbeat_task(mut pwm: SimplePwm<'static>) {
async fn heartbeat_task(mut pwm: SimplePwm<'static>, light_type: HeartLightType, initial_color: u16) {
const MAX: u16 = 16384;
pwm.set_max_duty(MAX);
pwm.set_prescaler(Prescaler::Div1);
let mut intensity = 0i32;
let mut animation_type = HeartLightType::HeartBeat;
let mut hue: u16 = 0;
let mut animation_type = light_type;
let mut hue: u16 = initial_color;
let mut heart_beat = 0;
loop {
@ -604,11 +746,11 @@ async fn heartbeat_task(mut pwm: SimplePwm<'static>) {
match action {
SysAction::PresentenceOn if intensity < 100 => {
intensity.add_assign(1);
info!("Increase intensity to {}", intensity);
// info!("Increase intensity to {}", intensity);
},
SysAction::PresentenceOff if intensity > 0 => {
intensity.sub_assign(1);
info!("Decrease intensity to {}", intensity);
// info!("Decrease intensity to {}", intensity);
},
_ => {
// ignore
@ -629,12 +771,23 @@ async fn heartbeat_task(mut pwm: SimplePwm<'static>) {
HeartLightType::HeartBeat
}
};
if animation_type == HeartLightType::ConstantColor {
SAVE_SIGNAL.signal(StoreType::Heart(HeartLightType::ConstantColor, hue));
} else {
SAVE_SIGNAL.signal(StoreType::HeartLight(animation_type));
}
}
AnimationSignal::SetMode(val) => {
animation_type = val;
if animation_type == HeartLightType::ConstantColor {
SAVE_SIGNAL.signal(StoreType::Heart(HeartLightType::ConstantColor, hue));
} else {
SAVE_SIGNAL.signal(StoreType::HeartLight(animation_type));
}
}
AnimationSignal::SetColor(val) => {
hue = val;
SAVE_SIGNAL.signal(StoreType::HeartColor(hue));
}
}
}