#![no_std] #![no_main] #![feature(type_alias_impl_trait)] use core::sync::atomic::*; use defmt::*; use embassy_executor::Spawner; use embassy_executor::_export::StaticCell; //use embassy_futures::join::join; //use embassy_futures::select::*; use embassy_rp::adc::{self, Adc}; use embassy_rp::peripherals::{*, self}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer, Instant}; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::i2c; use embassy_rp::interrupt; //use embedded_hal_async::i2c::I2c; use embassy_embedded_hal::SetConfig; use embassy_rp::uart::{self, Parity}; use embassy_rp::pwm::{self, Pwm}; use {defmt_rtt as _, panic_probe as _}; use heapless::String; use heizung::i2c_scan::{self, ScanResultForBus}; use heizung::modbus_server::{ModbusServer, ModbusRegisters, ModbusErrorCode, ModbusAdressMatch, U16Pusher}; use heizung::program_info::{ProgramInfoBuilder, PinFunction, ConstString}; use heizung::rs485::RS485; use heizung::uf2updater::UF2UpdateHandler; use heizung::watchdog::WatchdogFixed; macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell<T> = StaticCell::new(); let (x,) = STATIC_CELL.init(($val,)); x }}; } #[embassy_executor::task] async fn i2c_task(mut i2c_peri: embassy_rp::peripherals::I2C0, mut scl: embassy_rp::peripherals::PIN_13, mut sda: embassy_rp::peripherals::PIN_12, mut i2c_irq: embassy_rp::interrupt::I2C0_IRQ, i2c_config: i2c::Config) { let mut prev_scan_result = ScanResultForBus::new(); loop { if true { let _i2c = i2c::I2c::new_async(&mut i2c_peri, &mut scl, &mut sda, &mut i2c_irq, i2c_config); } let scan_result = ScanResultForBus::scan(&mut scl, &mut sda).await; if prev_scan_result != scan_result { prev_scan_result = scan_result; const MSG_LEN : usize = i2c_scan::SCAN_RESULT_STRING_LENGTH; let mut msg = String::<MSG_LEN>::new(); match scan_result.format(&mut msg) { Ok(()) => info!("I2C scan result:\n{}", msg), Err(()) => info!("I2C scan result: too long for our buffer!"), } } Timer::after(Duration::from_secs(1)).await; } } #[embassy_executor::task] async fn beeper_task(pwm_channel: embassy_rp::peripherals::PWM_CH3, beeper_pin: embassy_rp::peripherals::PIN_6) { let mut c: pwm::Config = Default::default(); c.top = 0x8000; c.compare_a = 8; c.divider = fixed::FixedU16::from_num(20); let mut pwm = Pwm::new_output_a(pwm_channel, beeper_pin, c.clone()); let mut toggle = false; loop { info!("PWM: duty={}/32768, divider={}", c.compare_a, c.divider.to_bits()); Timer::after(Duration::from_millis(200)).await; //c.compare_a = c.compare_a.rotate_left(4); c.compare_a = if toggle { c.top/2 } else { 0 }; //toggle = !toggle; c.divider -= fixed::FixedU16::from_num(0.1); if c.divider < 1 { c.divider = fixed::FixedU16::from_num(20); } toggle = true; pwm.set_config(&c); } } #[embassy_executor::task] async fn uart_task(this: RS485<ModbusServer<ModBusRegs<'static>>>) { this.run_task().await; } // Zero offset for current measurement will be exponential smoothing with alpha=1/ZERO_OFFSET_FACTOR. // The value in zero_offset_raw will be the smoothed offset times ZERO_OFFSET_FACTOR. This factor // should be a power of two because we will divide by it. const ZERO_OFFSET_FACTOR : u32 = 32; struct AdcCurrentData { // Value measured while both MOSFETs are off // (multiplied by ZERO_OFFSET_FACTOR) zero_offset_raw: AtomicU32, // Current measured with 2k resistor (raw value and 1/10 mA) current_2k_raw: AtomicU16, current_2k_dezi_milliamps: AtomicU16, // Current measured while main MOSFET is/was on (raw value and 1/10 mA and when this was measured) current_on_raw: AtomicU16, current_on_dezi_milliamps: AtomicU16, current_on_time: Mutex<CriticalSectionRawMutex, Instant>, } impl AdcCurrentData { const fn const_default() -> AdcCurrentData { AdcCurrentData { zero_offset_raw: AtomicU32::new(0), current_2k_raw: AtomicU16::new(0), current_2k_dezi_milliamps: AtomicU16::new(0), current_on_raw: AtomicU16::new(0), current_on_dezi_milliamps: AtomicU16::new(0), current_on_time: Mutex::new(Instant::from_ticks(0)), } } } struct AdcData { analog_in1_raw: AtomicU16, analog_in1_millivolt: AtomicU16, vcc_raw: AtomicU16, vcc: AtomicU16, temp_raw: AtomicU16, temp_centicelsius: AtomicU16, currents: [AdcCurrentData; 2], } impl AdcData { const fn const_default() -> AdcData { AdcData { analog_in1_raw: AtomicU16::new(0), analog_in1_millivolt: AtomicU16::new(0), vcc_raw: AtomicU16::new(0), vcc: AtomicU16::new(0), temp_raw: AtomicU16::new(0), temp_centicelsius: AtomicU16::new(0), currents: [AdcCurrentData::const_default(), AdcCurrentData::const_default()], } } } #[embassy_executor::task] async fn adc_task(shared_data: &'static AdcData, adc: ADC, mut en_measure_current: Output<'static, PIN_3>, mut analog_in1: PIN_26, mut current2: PIN_27, mut current1: PIN_28, mut measure_vcc: PIN_29, outputs_active: &'static AtomicU8) { let irq = interrupt::take!(ADC_IRQ_FIFO); let mut adc = Adc::new(adc, irq, adc::Config::default()); let debug = false; const MEASURE_CYCLES_BEFORE_TOGGLE: u32 = 16; let mut en_measure_current_toggle = 0; loop { if true { let level = adc.read(&mut analog_in1).await; // 3.05 V for 4096 counts, voltage divider with (10k + 5.1k) over 5.1k, result in mV: // level * 3.05/4096. * (10+5.1+5.1)/5.1 * 1000 // -> level * 305 * (100+51+51) * 1000 / 100 / 4096 / 51 // -> level * 305 * ((100+51+51)/2) * (1000/100) / 2048 / 51 --> fits in 32 bits for level=4096 (but barely) let value = level as u32 * 305 * ((100+51+51)/2) * (1000/100); let value = (value + 2048*51/2) / 2048 / 51; let value = if value > u16::MAX as u32 { u16::MAX } else { value as u16 }; shared_data.analog_in1_raw.store(level, Ordering::Relaxed); shared_data.analog_in1_millivolt.store(value, Ordering::Relaxed); if debug { let value2 = 3.05*level as f32/4096. * (10.+2.*5.1)/5.1; info!("ADC analog_in1: {} cnt -> {} V ({} mV)", level, value2, value); } } if true { let level = adc.read(&mut measure_vcc).await; // 3.05V for 4096 counts, voltage divider with 100k over 10, result in mV: // level * 3.05/4096. * (100 + 10)/10 * 1000 // -> level * 305 * 11 * 1000 / 100 / 4096 let value = (level as u32 * 305 * 11 * 1000 / 100 + 2048) / 4096; let value = if value > u16::MAX as u32 { u16::MAX } else { value as u16 }; shared_data.vcc_raw.store(level, Ordering::Relaxed); shared_data.vcc.store(value, Ordering::Relaxed); if debug { let vcc = 3.05 * level as f32 / 4096. * (100. + 10.)/10.; info!("ADC measure_vcc: {} cnt -> {} V ({} mV)", level, vcc, value); } } let outputs_were_active = outputs_active.load(Ordering::SeqCst); let mut measure_currents = true; if outputs_were_active != 0 && false { // output is active but also en_measure_current -> turn it off // -> Actually, don't do that because there is no harm and it can be useful for the other channel. if en_measure_current.is_set_high() { en_measure_current.set_low(); measure_currents = false; } en_measure_current_toggle = 0; } else { match en_measure_current_toggle { 0 | MEASURE_CYCLES_BEFORE_TOGGLE => { en_measure_current.set_level(if en_measure_current_toggle >= MEASURE_CYCLES_BEFORE_TOGGLE { Level::High } else { Level::Low }); // skip in this loop to give it time to settle measure_currents = false; } _ => {} } en_measure_current_toggle = (en_measure_current_toggle + 1) % (2*MEASURE_CYCLES_BEFORE_TOGGLE); } // read another value between looking at output_active and measuring the currents if true { let level = adc.read_temperature().await; // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet let adc_vcc = 3.0522; // from CJ431 let celsius = 27.0 - (level as f32 * adc_vcc / 4096.0 - 0.706) / 0.001721; let value = celsius * 100.; let value = if value > u16::MAX as f32 { u16::MAX } else { value as u16 }; shared_data.temp_raw.store(level, Ordering::Relaxed); shared_data.temp_centicelsius.store(value, Ordering::Relaxed); if debug { info!("Temp: {} -> {} deg C", level, celsius); } } if measure_currents { let levels = [ adc.read(&mut current1).await, adc.read(&mut current2).await, ]; let outputs_are_active = outputs_active.load(Ordering::SeqCst); //let en_measure_current_is_active = en_measure_current.is_set_high(); //FIXME I think is_set_high() has a bug. let en_measure_current_is_active = en_measure_current_toggle >= MEASURE_CYCLES_BEFORE_TOGGLE; for i in 0..2 { // 3.05 V for 4096 counts, shunt resistor is 0.1 ohms (i.e. 0.1 V/A), amplifier gain is 56, result should be mA/10 // (level - offset) * 3.05/4096 / 0.1 * 1/56 * 1e4 // -> (level - offset) * 305 * 10 * 1e4 / 56 / 4096 / 100 // -> (level - offset) * 305 * 1000 / 56 / 4096 // -> (level - offset) * 305 * (1000/8) / 4096 / 7 let level = levels[i]; let offset = (shared_data.currents[i].zero_offset_raw.load(Ordering::Relaxed)/ZERO_OFFSET_FACTOR) as u16; let level2 = if level > offset { level - offset } else { 0 }; //info!("CUR{}: raw: {}, {}, zero={}", i, level, level2, shared_data.currents[i].zero_offset_raw.load(Ordering::Relaxed)); let value = (level2 as u32 * 305 * (1000/8) + 7*4096/2) / 4096 / 7; let value = if value > u16::MAX as u32 { u16::MAX } else { value as u16 }; let output_was_active = (outputs_were_active>>i) & 1 != 0; let output_is_active = (outputs_are_active>>i) & 1 != 0; #[allow(unused_variables, unused_assignments)] let mode : char; match (output_was_active, output_is_active, en_measure_current_is_active) { (true, true, false) => { // output was consistently (hopefully) turned on during our measurement // and en_measure_current is off -> good measurement for current in "on" state shared_data.currents[i].current_on_raw.store(levels[i], Ordering::Relaxed); shared_data.currents[i].current_on_dezi_milliamps.store(value, Ordering::Relaxed); *shared_data.currents[i].current_on_time.lock().await = Instant::now(); mode = 'O'; } (false, false, false) => { // output and en_measure_current are off -> good measurement for zero level // fetch_update is exactly what we want but it doesn't seem to be available... //shared_data.currents[i].zero_offset_raw.fetch_update(Ordering::Relaxed, Ordering::Relaxed, // |x| { x*(ZERO_OFFSET_FACTOR-1)/ZERO_OFFSET_FACTOR + levels[i] }); //SAFETY: We are the only writers for zero_offset_raw. let x = shared_data.currents[i].zero_offset_raw.load(Ordering::Relaxed); let x2 = x*(ZERO_OFFSET_FACTOR-1)/ZERO_OFFSET_FACTOR + level as u32; shared_data.currents[i].zero_offset_raw.store(x2, Ordering::Relaxed); mode = 'z'; } (false, false, true) => { // output is off and en_measure_current is on -> good measurement through 2k resistor shared_data.currents[i].current_2k_raw.store(levels[i], Ordering::Relaxed); shared_data.currents[i].current_2k_dezi_milliamps.store(value, Ordering::Relaxed); mode = 'k'; } _ => { // inconsistent state of MOSFETs -> ignore this measurement mode = '!'; } } if debug { info!("ADC current{}: {}: {} -> {} [10*mA]", i, mode, level, value); } } if false { info!("Currents: 2k={}, {}, {}, zero={}, {} -> {}, {} -> {}, {}", en_measure_current_is_active, levels[0], levels[1], shared_data.currents[0].zero_offset_raw.load(Ordering::Relaxed), shared_data.currents[1].zero_offset_raw.load(Ordering::Relaxed), shared_data.currents[0].current_2k_raw.load(Ordering::Relaxed), shared_data.currents[1].current_2k_raw.load(Ordering::Relaxed), shared_data.currents[0].current_2k_dezi_milliamps.load(Ordering::Relaxed), shared_data.currents[1].current_2k_dezi_milliamps.load(Ordering::Relaxed), ); } } if debug { Timer::after(Duration::from_secs(1)).await; } else { Timer::after(Duration::from_millis(1)).await; } } } #[embassy_executor::task] async fn reset_soon(delay: Duration) { Timer::after(delay).await; cortex_m::peripheral::SCB::sys_reset(); } const FLASH_SIZE: usize = 2 * 1024 * 1024; #[derive(Clone, Copy, PartialEq, Eq)] enum ReadType { HoldingRegister, InputRegister, } const DEVICE_STATE_RESET: u16 = 1; const DEVICE_STATE_RESPOND_DETECT: u16 = 2; const DEVICE_STATE_PROCESS_BROADCAST_PROGRAM: u16 = 4; struct ModBusRegs<'a> { led_g: Output<'a, PIN_5>, button_boot2: Input<'a, PIN_11>, reed3: Input<'a, PIN_19>, reed4: Input<'a, PIN_20>, reed2: Input<'a, PIN_21>, reed1: Input<'a, PIN_22>, adc_data: &'a AdcData, device_state: u16, scratch: u16, page_offset_for_flash_read: u16, uf2updater: UF2UpdateHandler<FLASH_SIZE>, consistent_read_type: ReadType, consistent_read_addr: u16, consistent_read_length: u16, consistent_read_data: [u16; 4], spawner: Spawner, } impl<'a> ModBusRegs<'a> { fn read_reg(self: &mut Self, read_type: ReadType, base_addr: u16, addr: u16, value: &[u16]) -> Result<u16, ModbusErrorCode> { defmt::assert!(addr >= base_addr); defmt::assert!(addr < base_addr + value.len() as u16); defmt::assert!(value.len() <= self.consistent_read_data.len()); self.consistent_read_type = read_type; self.consistent_read_addr = base_addr; self.consistent_read_length = value.len() as u16; self.consistent_read_data[0..value.len()].copy_from_slice(value); Ok(self.consistent_read_data[(addr - base_addr) as usize]) } fn read_reg_2x_u32(self: &mut Self, read_type: ReadType, base_addr: u16, addr: u16, value: [u32; 2]) -> Result<u16, ModbusErrorCode> { self.read_reg(read_type, base_addr, addr, &[ ((value[0] >> 16) & 0xffff) as u16, ((value[0] >> 0) & 0xffff) as u16, ((value[1] >> 16) & 0xffff) as u16, ((value[1] >> 0) & 0xffff) as u16, ]) } fn read_reg_u64(self: &mut Self, read_type: ReadType, base_addr: u16, addr: u16, value: u64) -> Result<u16, ModbusErrorCode> { self.read_reg(read_type, base_addr, addr, &[ ((value >> 48) & 0xffff) as u16, ((value >> 32) & 0xffff) as u16, ((value >> 16) & 0xffff) as u16, ((value >> 0) & 0xffff) as u16, ]) } } impl<'a> ModbusRegisters for ModBusRegs<'a> { fn is_address_match(self: &mut Self, device_addr: u8) -> ModbusAdressMatch { use ModbusAdressMatch::*; if device_addr == 0x01 { OurAddress } else if device_addr == 0x55 { BroadcastNoReply } else { NotOurAddress } } fn read_discrete_input(self: &mut Self, _device_addr: u8, addr: u16) -> Result<bool, ModbusErrorCode> { match addr { 0 => Ok(self.reed1.is_high()), 1 => Ok(self.reed2.is_high()), 2 => Ok(self.reed3.is_high()), 3 => Ok(self.reed4.is_high()), 4 => Ok(self.button_boot2.is_low()), _ => Err(ModbusErrorCode::IllegalDataAddress), } } fn read_input_register(self: &mut Self, _device_addr: u8, addr: u16) -> Result<u16, ModbusErrorCode> { if self.consistent_read_type == ReadType::InputRegister && addr >= self.consistent_read_addr && addr < self.consistent_read_addr + self.consistent_read_length { return Ok(self.consistent_read_data[(addr - self.consistent_read_addr) as usize]); } match addr { 0 => Ok(42), 1 => Ok(self.adc_data.analog_in1_raw.load(Ordering::Relaxed)), 2 => Ok(self.adc_data.analog_in1_millivolt.load(Ordering::Relaxed)), 3 => Ok(self.adc_data.vcc_raw.load(Ordering::Relaxed)), 4 => Ok(self.adc_data.vcc.load(Ordering::Relaxed)), 5 => Ok(self.adc_data.temp_raw.load(Ordering::Relaxed)), 6 => Ok(self.adc_data.temp_centicelsius.load(Ordering::Relaxed)), 7 => Ok((self.adc_data.currents[0].zero_offset_raw.load(Ordering::Relaxed) / ZERO_OFFSET_FACTOR) as u16), 8 => Ok(self.adc_data.currents[0].current_2k_raw.load(Ordering::Relaxed)), 9 => Ok(self.adc_data.currents[0].current_2k_dezi_milliamps.load(Ordering::Relaxed)), 10 => Ok(self.adc_data.currents[0].current_on_raw.load(Ordering::Relaxed)), 11 => Ok(self.adc_data.currents[0].current_on_dezi_milliamps.load(Ordering::Relaxed)), 12 | 13 | 14 | 15 => self.read_reg_u64(ReadType::InputRegister, 12, addr, self.adc_data.currents[0].current_on_time.try_lock().map( |x| x.as_micros()).unwrap_or(0)), 16 => Ok((self.adc_data.currents[1].zero_offset_raw.load(Ordering::Relaxed) / ZERO_OFFSET_FACTOR) as u16), 17 => Ok(self.adc_data.currents[1].current_2k_raw.load(Ordering::Relaxed)), 18 => Ok(self.adc_data.currents[1].current_2k_dezi_milliamps.load(Ordering::Relaxed)), 19 => Ok(self.adc_data.currents[1].current_on_raw.load(Ordering::Relaxed)), 20 => Ok(self.adc_data.currents[1].current_on_dezi_milliamps.load(Ordering::Relaxed)), 21 | 22 | 23 | 24 => self.read_reg_u64(ReadType::InputRegister, 21, addr, self.adc_data.currents[1].current_on_time.try_lock().map( |x| x.as_micros()).unwrap_or(0)), 25 | 26 | 27 | 28 => { let (from, to) = self.uf2updater.get_missing_block_info(); self.read_reg_2x_u32(ReadType::InputRegister, 25, addr, [from, to]) }, _ => Err(ModbusErrorCode::IllegalDataAddress) } } fn write_coil(self: &mut Self, _device_addr: u8, addr: u16, value: bool) -> Result<(), ModbusErrorCode> { match addr { 0 => { self.led_g.set_level(Level::from(value)); Ok(()) }, _ => Err(ModbusErrorCode::IllegalDataAddress), } } fn read_holding_register(self: &mut Self, _device_addr: u8, addr: u16) -> Result<u16, ModbusErrorCode> { if self.consistent_read_type == ReadType::HoldingRegister && addr >= self.consistent_read_addr && addr < self.consistent_read_addr + self.consistent_read_length { return Ok(self.consistent_read_data[(addr - self.consistent_read_addr) as usize]); } match addr { 0 => Ok(self.device_state), 1 => Ok(42), 2 => Ok(self.scratch), 3 => Ok(self.page_offset_for_flash_read), _ => Err(ModbusErrorCode::IllegalDataAddress), } } fn write_register(self: &mut Self, _device_addr: u8, addr: u16, value: u16) -> Result<u16, ModbusErrorCode> { match addr { 0 => { self.device_state = value; Ok(self.device_state) }, 1 => { const fn be(s: &str) -> u16 { core::assert!(s.len() == 2); core::assert!(s.as_bytes().len() == 2); let x = s.as_bytes()[0]; let y = s.as_bytes()[1]; u16::from_be_bytes([x, y]) } const OK: u16 = be("OK"); // mark booted const UP: u16 = be("UP"); // mark updated -> swap to DFU on next reset const PI: u16 = be("PI"); // ping / no-op const RE: u16 = be("RE"); // reset match value { OK => { self.uf2updater.mark_booted().map(|_| value) }, UP if self.uf2updater.successfully_programmed() => { self.uf2updater.mark_updated().map(|_| value) }, UP => { Ok(0) }, PI => { Ok(value) }, RE => { match self.spawner.spawn(reset_soon(Duration::from_millis(100))) { Ok(()) => Ok(value), Err(err) => { error!("Couldn't spawn reset_soon: {:?}", err); Err(ModbusErrorCode::ServerDeviceFailure) } } }, _ => Err(ModbusErrorCode::IllegalDataValue), } }, 2 => { self.scratch = value; Ok(value) }, 3 => { self.page_offset_for_flash_read = value; Ok(value) }, _ => Err(ModbusErrorCode::IllegalDataAddress), } } fn read_file_record(self: &mut Self, _device_addr: u8, ref_type: u8, file_number: u16, record_number: u16, record_length: u16, mut pusher: U16Pusher<'_, 256>) -> Result<(), ModbusErrorCode> { match (ref_type, file_number) { (6, 0) => { let value = "Hello, World!!".as_bytes(); if record_number as usize >= value.len()/2 { return Ok(()) // or should that be an error? } let start = 2 * record_number as usize; let end = start + 2 * record_length as usize; let end = core::cmp::min(end, value.len()); // or should too large length be an error? pusher.push_bytes(&value[start .. end])?; Ok(()) }, (6, 1) => { pusher.push_fn(record_length as usize * 2, |buf| { let flash_addr = self.page_offset_for_flash_read as u32 * 512 + record_number as u32 * 2; self.uf2updater.flash.read(flash_addr, buf) .map_err(|err| { info!("Error reading flash: {:?}", err); ModbusErrorCode::ServerDeviceFailure }) }) }, _ => { Err(ModbusErrorCode::IllegalDataAddress) } } } fn write_file_record(self: &mut Self, device_addr: u8, ref_type: u8, file_number: u16, record_number: u16, data: &[u8]) -> Result<(), ModbusErrorCode> { match (ref_type, file_number) { (6, 0) => { Ok(()) }, (6, 1) => { if device_addr != 0x55 || (self.device_state & DEVICE_STATE_PROCESS_BROADCAST_PROGRAM) != 0 { //NOTE This will use blocking flash operations and we won't be able to respond to Modbus // during that time. There isn't much that we can do about this because the flash won't // be in XIP mode so we cannot continue running our program from flash. self.uf2updater.write(record_number as u32 * 2, data) } else { Ok(()) } }, _ => Err(ModbusErrorCode::IllegalDataAddress), } } } #[embassy_executor::task] async fn dogfeeder(watchdog: peripherals::WATCHDOG) { WatchdogFixed::new(watchdog).start_and_feed_continuously().await; } // use 19200 baud in 8E1 mode - not great but it's the Modbus default const DEFAULT_MODBUS_CONFIG_BAUDRATE: u32 = 19200; //const DEFAULT_MODBUS_CONFIG_BAUD = 115200*2; const DEFAULT_MODBUS_CONFIG_PARITY: Parity = Parity::ParityEven; const DEFAULT_MODBUS_CONFIG_STRING: ConstString<30> = { let mut x = ConstString::new(); x = x.append_u32(DEFAULT_MODBUS_CONFIG_BAUDRATE); x = x.push_str(" baud, "); x = x.push_str("8"); x = x.push(match DEFAULT_MODBUS_CONFIG_PARITY { Parity::ParityEven => 'E', Parity::ParityOdd => 'O', Parity::ParityNone => 'N', } as u8); x = x.push_str("1"); x }; fn default_modbus_config() -> uart::Config { let mut uart_config = uart::Config::default(); uart_config.baudrate = DEFAULT_MODBUS_CONFIG_BAUDRATE; //uart_config.baudrate = 115200*2; uart_config.parity = DEFAULT_MODBUS_CONFIG_PARITY; uart_config } //#[embassy_executor::main] async fn main2(spawner: Spawner) { let p = embassy_rp::init(Default::default()); info!("starting"); // pinout: let mut _drive2 = Output::new(p.PIN_0, Level::Low); let mut _drive1 = Output::new(p.PIN_1, Level::Low); //let LED_W = p.PIN_2; let _ws2812_display = p.PIN_2; //let LED_Y = p.PIN_3; let en_measure_current = Output::new(p.PIN_3, Level::Low); let mut led_b = Output::new(p.PIN_4, Level::Low); let led_g = Output::new(p.PIN_5, Level::Low); let led_r = p.PIN_6; let _matrix_in1 = Input::new(p.PIN_7, Pull::Up); let _sbu1 = p.PIN_8; let _sbu2 = p.PIN_9; let _vbus_det = p.PIN_10; let button_boot2 = Input::new(p.PIN_11, Pull::Up); let sda = p.PIN_12; let scl = p.PIN_13; let _ws2811_red_in = Input::new(p.PIN_14, Pull::None); let tx_en = p.PIN_15; let tx = p.PIN_16; let rx = p.PIN_17; let _matrix_in2 = Input::new(p.PIN_18, Pull::Up); let reed3 = Input::new(p.PIN_19, Pull::Up); let reed4 = Input::new(p.PIN_20, Pull::Up); let reed2 = Input::new(p.PIN_21, Pull::Up); let reed1 = Input::new(p.PIN_22, Pull::Up); let _matrix_in3 = Input::new(p.PIN_23, Pull::Up); let _digout1 = p.PIN_24; let _digout2 = p.PIN_25; let analog_in1 = p.PIN_26; let current2 = p.PIN_27; let current1 = p.PIN_28; let measure_vcc = p.PIN_29; let i2c_irq = interrupt::take!(I2C0_IRQ); let mut i2c_config = i2c::Config::default(); i2c_config.frequency = 400_000; unwrap!(spawner.spawn(i2c_task(p.I2C0, scl, sda, i2c_irq, i2c_config))); static ADC_DATA: AdcData = AdcData::const_default(); static OUTPUTS_ACTIVE: AtomicU8 = AtomicU8::new(0); unwrap!(spawner.spawn(adc_task(&ADC_DATA, p.ADC, en_measure_current, analog_in1, current2, current1, measure_vcc, &OUTPUTS_ACTIVE))); let rx_buf = singleton!([0u8; 256]); let rs485 = RS485::new( p.UART0, rx, tx, tx_en, interrupt::take!(UART0_IRQ), p.DMA_CH1, default_modbus_config(), rx_buf, p.PIO0, p.DMA_CH0, p.DMA_CH2, ModbusServer::new(ModBusRegs { led_g, button_boot2, reed1, reed2, reed3, reed4, adc_data: &ADC_DATA, device_state: DEVICE_STATE_RESET | DEVICE_STATE_RESPOND_DETECT, scratch: 0, page_offset_for_flash_read: 0, uf2updater: UF2UpdateHandler::new(p.FLASH), consistent_read_type: ReadType::InputRegister, consistent_read_addr: u16::default(), consistent_read_length: u16::default(), consistent_read_data: [0; 4], spawner: spawner.clone(), }), ); unwrap!(spawner.spawn(uart_task(rs485))); if false { unwrap!(spawner.spawn(beeper_task(p.PWM_CH3, led_r))); } unwrap!(spawner.spawn(dogfeeder(p.WATCHDOG))); loop { led_b.set_high(); Timer::after(Duration::from_secs(1)).await; led_b.set_low(); Timer::after(Duration::from_secs(1)).await; } } /* #[embassy_executor::main] async fn main(spawner: Spawner) { main2(spawner).await; } */ #[::embassy_executor::task()] async fn __embassy_main(spawner: Spawner) { main2(spawner).await; } unsafe fn __make_static<T>(t: &mut T) -> &'static mut T { ::core::mem::transmute(t) } #[cortex_m_rt::entry] fn main() -> ! { init_early(); let mut executor = ::embassy_executor::Executor::new(); let executor = unsafe { __make_static(&mut executor) }; executor.run(|spawner| { spawner.must_spawn(__embassy_main(spawner)); }) } fn init_early() { use embassy_rp::pac; // release spinlock 31 because we sometimes block on this in the init code unsafe { pac::SIO.spinlock(31).write_value(1); } } // Add program info. // Show with: picotool info heizung-release.uf2 -a // or `picotool info` if actual hardware is connected in bootloader mode #[used] #[link_section = ".program_info"] pub static PROGRAM_INFO: [u8; 4096] = { // all of these must match the values in the bootloader const PROGRAM_INFO_FLASH_OFFSET: u32 = 0x10087000 - PROGRAM_INFO_TOTAL_SIZE; // last 4k of active partition const PROGRAM_INFO_TOTAL_SIZE: u32 = 4096; const PROGRAM_INFO_TABLE_SIZE: u32 = 128; // one pointer (4 bytes) for each piece of data, all must be valid pointers const PROGRAM_INFO_COPY_TABLE_SIZE: u32 = 128; // 3*u32 for each entry, picotool stops after 10 entries #[cfg(debug_assertions)] let build_type = "Debug"; #[cfg(not(debug_assertions))] let build_type = "Release"; // We can fill in some values from info that is provided by Cargo. // see https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates let mut info = ProgramInfoBuilder::new(PROGRAM_INFO_FLASH_OFFSET, PROGRAM_INFO_TABLE_SIZE, PROGRAM_INFO_COPY_TABLE_SIZE) .program_name("subraum-heizung") .program_description(env!("CARGO_PKG_DESCRIPTION")) .program_version_string(env!("CARGO_PKG_VERSION")) .program_url(env!("CARGO_PKG_HOMEPAGE")) .board("subraum-heizung base v1.0") .sdk_version("Embassy") .program_build_attribute(build_type); //FIXME should be based on what is selected for embassy-rp crate info = info.boot2_name("boot2_w25q080"); let build_timestamp = build_time::build_time_utc!("%s"); if build_timestamp.len() == 1 && build_timestamp.as_bytes()[0] == '0' as u8 { // We are building under NixOS or some other system that doesn't let us get the real time so omit it. } else { let local_build_time = build_time::build_time_local!("%Y-%m-%d %H:%M:%S %:z"); info = info.program_build_date_string(local_build_time); } info = info .pins_with_func(PinFunction::UART, &[16, 17]) .pins_with_func(PinFunction::USB, &[10]) .pins_with_func(PinFunction::I2C, &[12, 13]) .pins_with_names(&[ (0, "DRIVE2"), (1, "DRIVE1"), (2, "WS2812 Ring around Display, white LED"), (3, "Enable current measurement, yellow LED"), (4, "blue LED"), (5, "green LED"), (6, "buzzer, red LED"), (7, "MATRIX_IN1"), (8, "USB type-c connector, SBU1"), (9, "USB type-c connector, SBU2"), (11, "Button ADDR"), (14, "Red channel of WS2811"), (15, "RS485 TX Enable"), ]) // picotool doesn't support too many pins at once so add a break here .pins_with_names(&[ (18, "MATRIX_IN2"), (19, "REED Input 3"), (20, "REED Input 4"), (21, "REED Input 2"), (22, "REED Input 1"), (23, "MATRIX_IN3"), (24, "DIGOUT1"), (25, "DIGOUT2"), (26, "Analog IN 1"), (27, "Current Measurement Channel 2"), (28, "Current Measurement Channel 1"), (29, "VCC Measurement"), ]) .program_feature_group(['C', '3'], 0x27a7c59b, "Modbus") .feature_utf8(&DEFAULT_MODBUS_CONFIG_STRING.to_slice::<{DEFAULT_MODBUS_CONFIG_STRING.len()}>()) .group_done(); info.build() };