No description
Find a file
2025-10-16 16:58:33 +02:00
canisterse-fw Protocol changes 2025-10-16 15:15:26 +02:00
canisterse-kicad minor movement for cleanup 2025-10-15 12:01:20 +02:00
common Protocol changes 2025-10-16 15:15:26 +02:00
dispenser-fw motor calibration and task 2025-10-16 16:58:33 +02:00
dispenser-kicad minor movement for cleanup 2025-10-15 12:01:20 +02:00
hostbus-fw Some minor mantaince on dispenser-fw 2025-10-16 14:48:34 +02:00
hostbus-fw-test After move from Monosol 2025-10-02 14:31:13 +02:00
hostbus-kicad minor movement for cleanup 2025-10-15 12:01:20 +02:00
vendors update dependencies 2025-10-16 14:59:51 +02:00
.gitignore Last fixes 2025-10-15 09:54:43 +02:00
.gitmodules Some minor mantaince on dispenser-fw 2025-10-16 14:48:34 +02:00
Cargo.lock motor calibration and task 2025-10-16 16:58:33 +02:00
Cargo.toml Protocol changes 2025-10-16 15:15:26 +02:00
diagram.d2 After move from Monosol 2025-10-02 14:31:13 +02:00
diagram.svg After move from Monosol 2025-10-02 14:31:13 +02:00
Notes.md After move from Monosol 2025-10-02 14:31:13 +02:00
pnet.pcapng After move from Monosol 2025-10-02 14:31:13 +02:00
qrgen.sh Update profinet parts 2025-10-15 09:25:46 +02:00
README.md After move from Monosol 2025-10-02 14:31:13 +02:00

Functional Requirements

  • The NextDispenser requires that up to 500 devices are dispensed at effectively the same time. This makes the round-trip-time (RTT) 2ms max for all devices.
  • Communication the super-system (likely PLC) needs to support the the same speed
  • Pills need to be dispensed within ~100ms of being requested, the amount dispensed needs to be checked against the request amount to notify of any discrepancy.
  • Canisters put onto a NextDispenser need to be uniquely identified to the main system.
  • Pills are different sizes/hardness and require different motor settings.

Proposal

NOTE: all specifications are defined for to peak speed (all 500 devices being hit at the same time), likely real-world speed requirements can be significantly lower.

The NextDispenser requires that up to 500 devices are dispensed at effectively the same time. This makes the RTT 2ms max for all devices.

With a synchronous protocol this may mean bauds from 2MHz-16Mhz to have them all done within a second. However to ensure we have lowest latency we propose to use an asynchronous protocol which means you need only the 2Mhz to do it within the 2ms. Internally we'll utilize LVDS/M-LVDS to achieve a speed ceiling of ~100MHz using a common Half-Duplex configuration.

Additionally we will split the bus into multiple buses of ~70 devices meaning that the internal communication has significant headroom.

Communication the super-system (likely PLC) needs to support the the same speed

The host bus controller (which splits the buses) will communicate with the super-system using RS485(to be confirmed?), to ensure we have a consistent RTT, it's efficient bundle requests to multiple nodes into one (which ensures higher throughput): requires >=4Mhz baud.

Pills need to be dispensed within ~100ms of being requested, the amount dispensed needs to be checked against the request amount to notify of any discrepancy.

With the asynchronous protocol the communication overhead at the start is minimal, and so we expect to be able to start within ~1μs. Then it's up to the motor control cycle to get the speed for dispensing.

Canisters put onto a NextDispenser need to be uniquely identified to the main system.

Canisters are planned to identifiabe using a 1-Wire EEPROM such as a DS28E05 (128 bytes, 1k write cycles) or DS28EC20 (2560 bytes, 200k write cycles), with pogo-pins (I/O & GND). Alternatively regular flash/eeprom chips may be used however these require more connectivity (e.g. SCLK, MISO, MOSI, VCC, GND).

Pills are different sizes/hardness and require different motor settings.

These settings are expected to be provided by the main system upon canister identification; if these settings are not provided before attempting to dispense the NextDispenser should alarm.

Technical specifications

  • MCUs: RP2040
  • Communication: SN65MLVD203B, SP3485
  • Motor ICs: DRV8428, INAx181
  • Dispensor sensor: VEMT2523SLX01, VSMB2943SLX01

Power Consumption

Measured at 48VDC.

Hostbus:

Idle: 1W

Dispenser:

Idle (Flair-Low + Sensor): 1.3W

Network

PSU: 48V PSU (<=12A)

Switch: GbE Auto-MDI-X Switch\nManaged if possible

PLC: PLC Controller

Unit: {
	 Hostbus: Hostbus\n(Profinet to 48VDC+LVDS)
	 DispenserBus: {
		Dispenser1: Dispenser1 {}
		Dispenser2: Dispenser2 {}
		DispenserX: ... {
		    style.multiple: true
		    style.stroke-dash: 10
		    style.stroke: black
		    style.animated: 1
		}
		DispenserN: DispenserN {}
	}
}

PSU -> Unit.Hostbus {
	style: {
		stroke: red
	}
}

Unit.Hostbus <-> Unit.DispenserBus.Dispenser1 {
	style: {
		stroke: red
	}
}

Unit.Hostbus -> Unit.DispenserBus.Dispenser1 {
	style: {
		stroke: green
		animated: true
	}
}


Unit.DispenserBus.Dispenser1 <-> Unit.DispenserBus.Dispenser2 {
	style: {
		stroke: red
	}
}

Unit.DispenserBus.Dispenser1 -> Unit.DispenserBus.Dispenser2 {
	style: {
		stroke: green
		animated: true
	}
}

Unit.DispenserBus.Dispenser2 <-> Unit.DispenserBus.DispenserX {
	style: {
		stroke: red
	}
}

Unit.DispenserBus.Dispenser2 -> Unit.DispenserBus.DispenserX {
	style: {
		stroke: green
		animated: true
	}
}


Unit.DispenserBus.DispenserX <-> Unit.DispenserBus.DispenserN {
	style: {
		stroke: red
	}
}

Unit.DispenserBus.DispenserX -> Unit.DispenserBus.DispenserN {
	style: {
		stroke: green
		animated: true
	}
}

Unit.DispenserBus.DispenserN -> Unit.Hostbus {
	style: {
		stroke: green
		animated: true
	}
}

Unit.DispenserBus.DispenserN <-> Unit.Hostbus {
	style: {
		stroke: red
	}
}

Switch -> Unit.Hostbus {
	style: {
		stroke: green
	}
}

PLC -> Switch {
	style: {
		stroke: green
	}
}

flowchart TD
	subgraph Network1
	  subgraph DispensorUnit
	      MCU[RP2040]
	      MLVDS[Transciever]
	      STEPPER-DRV[Motor Driver + Current sense]
	      LED[SK6812]
	      
		  MLVDS <-- UART --> MCU
		  MCU <---> IR-Detector
		  MCU <---> LED
		  STEPPER-DRV <---> MCU
	  end
  end
  subgraph HostController
	  direction TB
	  HostBusA <-- MLVDS/SLAACR --> MLVDS
	  HostMCU[RP2040?]
	  HostBusA <--> HostMCU
	  HostBusB <--> HostMCU
  end
  HostMCU <-- RS485(?)/Modbus --> PLC
  HostBusB <-- MLVDS --> Network2[Network2]

Pre-design ides

RS485 address conflict resolution

SLAACR

(IPv6 SLAAC-inspired) Each node starts with a random address based on their MAC.

Periodically the host will request for each address that they respond (ping or some other command), whenever a node is responding they will delay by a random amount (<0.5ms) and during this time listen for other nodes on the network responding to their address. If they receive a slave response which contains their address they will pick a new address from a list of unseen addresses; if there are no unseen addresses then the unseen table is reset and they will pick a random one.

If for k req-response cycles they haven't had a conflict they will lock in their address until a power-cycle (maybe not actually that useful?).

Due to the strict timing requirements, responses are deemed to be available at all times (similar to modbus implementations) and the response function should be poll-able after a RX within 0.1ms.

// `decode` mutates addr_table if it contains a response
if let Some(req) = self.decode(rx_ch) && req.addr = self.addr {
	// At 5Mhz each bit takes 200ns, so our miss rate would be 1/62.5 (8 bits) @ 0.1ms
	// We can increase the marginal probability by changes the wait time (e.g. 0.2 gives 1/125)
	await sleep_us(rand()*100);
	if rx_ch.has_data() {
		if rx.read() == self.addr {
			// Detected conflict, pick new address
			self.addr = pick_free(addr_table);
		} else {
			// Someone else responded, what??
		}
	}
}

Using modbus the first by is the address so we can actually verify that the respondent is in conflict with our address, although it's fairly safe to assume it is whenever it's this close to the master request.

Timing based ordering

In order to get a decent ordering of nodes across the transmission line we can initialize SLAAC with a settle on time-based deterministic addresses.

Once regular SLAAC is in a stable state (ping all nodes a few times); then the master start a timer and sets up an interrupt on the RX pin. The master sends a "prep" command to ensure each node is polling the TX/RX continuously, then it pings each node individually once the first RX bit hits it notes the time it took from command till response (the Round-trip-time), this can be repeated multiple times to improve the estimate; once all nodes are done a large packet is broadcasted with all the changes in addresses (e.g. 10->200, 5->31, 3->5, etc), so that all the addresses are changed at the same time and there are no conflicts. Now all of the nodes should be ordered by the relative position in the transmission line.

The resolution of a single measurement is only (SpeedOfLight × 0.98) / (133 megahertz) ≈ 2.208997059 m assuming perfect accuracy and PIO, however the Pico can be overclock (temporarily) such that it gets 270MHz clock which would give a resolution of ~1m. If we then take multiple measurements per online slave we can take a statistical distance (using kalman filter or similar) which has a higher accuracy, still error in measurement may persist.

Functional

Slave:

  • Stepper driver with stuck detection
  • Light sensor for pill dispenser
  • RS485 driver for remote control (with SLAAC)
  • NFC/RFID reader for canister
  • LEDs for maintenance and operation info

Host:

  • Double RS485/LVDS driver
  • Connection with Host? USB, I2C slave? (RS485 | CANBUS)
flowchart TD
	subgraph Network1
	  subgraph DispensorUnit
	      MCU[RP2040]
	      RS485-BUS[Transciever: ISO308x]
	      STEPPER-DRV[Motor: DRV8846?]
	      NFC-IC[NFC: ST25R3916B]
	      LED[SK6812]
	      
		  RS485-BUS <-- UART --> MCU
		  MCU <-- SPI --> NFC-IC
		  MCU <---> IR-Detector
		  MCU <---> LED
		  STEPPER-DRV <---> MCU
	  end
  end
  subgraph HostController
	  direction TB
	  HostBusA <-- RS485/MODBUS/SLAACR --> RS485-BUS
	  HostMCU[RP2040?]
	  HostBusA <--> HostMCU
	  HostBusB <--> HostMCU
  end
  HostMCU <-- (Protocol?) --> PC
  HostBusB <-- RS485 --> Network2[Network2]