🦀 Embedded Systems with TuskLang and Rust
Embedded Systems with TuskLang and Rust
🔧 Embedded Foundation
Embedded systems development with TuskLang and Rust provides memory safety, zero-cost abstractions, and direct hardware control. This guide covers bare metal programming, real-time systems, and hardware interface development.
🏗️ Embedded Architecture
System Architecture
[embedded_architecture]
bare_metal: true
no_std: true
real_time: true
hardware_abstraction: true[embedded_components]
microcontroller: "target_hardware"
peripherals: "gpio_uart_spi_i2c"
memory: "flash_ram"
interrupts: "real_time_events"
Target Configuration
[target_configuration]
target_triple: "thumbv7m-none-eabi"
linker_script: "memory.x"
panic_handler: "custom_panic"
allocator: "custom_allocator"
🔧 Bare Metal Programming
No-Std Environment
[no_std_environment]
core_library: true
custom_panic: true
custom_allocator: true[no_std_implementation]
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use core::alloc::{GlobalAlloc, Layout};
// Custom panic handler
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {
// Halt the processor
cortex_m::asm::bkpt();
}
}
// Custom allocator for embedded systems
pub struct EmbeddedAllocator;
unsafe impl GlobalAlloc for EmbeddedAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Simple bump allocator
static mut HEAP: [u8; 1024] = [0; 1024];
static mut NEXT: usize = 0;
let start = NEXT;
let end = start + layout.size();
if end <= HEAP.len() {
NEXT = end;
HEAP.as_mut_ptr().add(start)
} else {
core::ptr::null_mut()
}
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
// Simple allocator doesn't deallocate
}
}
#[global_allocator]
static ALLOCATOR: EmbeddedAllocator = EmbeddedAllocator;
// Entry point
#[no_mangle]
pub extern "C" fn _start() -> ! {
// Initialize hardware
init_hardware();
// Main application loop
loop {
// Application logic
process_events();
// Sleep to save power
cortex_m::asm::wfi();
}
}
fn init_hardware() {
// Initialize GPIO, UART, etc.
}
fn process_events() {
// Process system events
}
Memory Management
[memory_management]
static_allocation: true
stack_management: true
heap_limitations: true[memory_implementation]
// Static memory allocation
static mut BUFFER: [u8; 1024] = [0; 1024];
static mut COUNTER: u32 = 0;
// Stack-based data structures
pub struct StackBuffer<T, const N: usize> {
data: [T; N],
len: usize,
}
impl<T: Default + Copy, const N: usize> StackBuffer<T, N> {
pub fn new() -> Self {
Self {
data: [T::default(); N],
len: 0,
}
}
pub fn push(&mut self, item: T) -> Result<(), &'static str> {
if self.len < N {
self.data[self.len] = item;
self.len += 1;
Ok(())
} else {
Err("Buffer full")
}
}
pub fn pop(&mut self) -> Option<T> {
if self.len > 0 {
self.len -= 1;
Some(self.data[self.len])
} else {
None
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
// Memory pool for dynamic allocation
pub struct MemoryPool<const N: usize> {
memory: [u8; N],
used: [bool; N],
}
impl<const N: usize> MemoryPool<N> {
pub fn new() -> Self {
Self {
memory: [0; N],
used: [false; N],
}
}
pub fn allocate(&mut self, size: usize) -> Option<&mut [u8]> {
if size == 0 || size > N {
return None;
}
for i in 0..=N - size {
if !self.used[i..i + size].iter().any(|&used| used) {
for j in i..i + size {
self.used[j] = true;
}
return Some(&mut self.memory[i..i + size]);
}
}
None
}
pub fn deallocate(&mut self, slice: &mut [u8]) {
let start = slice.as_ptr() as usize - self.memory.as_ptr() as usize;
let end = start + slice.len();
for i in start..end {
if i < N {
self.used[i] = false;
}
}
}
}
🔌 Hardware Interfaces
GPIO Control
[gpio_control]
pin_modes: true
digital_io: true
interrupt_handling: true[gpio_implementation]
// GPIO pin abstraction
pub struct Pin {
port: u8,
pin: u8,
mode: PinMode,
}
#[derive(Clone, Copy)]
pub enum PinMode {
Input,
Output,
InputPullUp,
InputPullDown,
Analog,
}
impl Pin {
pub fn new(port: u8, pin: u8, mode: PinMode) -> Self {
let mut pin = Self { port, pin, mode };
pin.configure();
pin
}
fn configure(&mut self) {
// Configure pin mode based on hardware
match self.mode {
PinMode::Input => self.set_as_input(),
PinMode::Output => self.set_as_output(),
PinMode::InputPullUp => self.set_as_input_pullup(),
PinMode::InputPullDown => self.set_as_input_pulldown(),
PinMode::Analog => self.set_as_analog(),
}
}
pub fn set_high(&self) {
if matches!(self.mode, PinMode::Output) {
// Set pin high
unsafe {
// Hardware-specific implementation
self.write_pin(true);
}
}
}
pub fn set_low(&self) {
if matches!(self.mode, PinMode::Output) {
// Set pin low
unsafe {
// Hardware-specific implementation
self.write_pin(false);
}
}
}
pub fn read(&self) -> bool {
unsafe {
// Hardware-specific implementation
self.read_pin()
}
}
pub fn toggle(&mut self) {
if self.read() {
self.set_low();
} else {
self.set_high();
}
}
// Hardware-specific methods (to be implemented for target)
unsafe fn write_pin(&self, _value: bool) {
// Implementation depends on target hardware
}
unsafe fn read_pin(&self) -> bool {
// Implementation depends on target hardware
false
}
fn set_as_input(&self) {
// Implementation depends on target hardware
}
fn set_as_output(&self) {
// Implementation depends on target hardware
}
fn set_as_input_pullup(&self) {
// Implementation depends on target hardware
}
fn set_as_input_pulldown(&self) {
// Implementation depends on target hardware
}
fn set_as_analog(&self) {
// Implementation depends on target hardware
}
}
// GPIO interrupt handling
pub struct GpioInterrupt {
pin: Pin,
callback: Option<fn()>,
}
impl GpioInterrupt {
pub fn new(pin: Pin) -> Self {
Self {
pin,
callback: None,
}
}
pub fn set_callback(&mut self, callback: fn()) {
self.callback = Some(callback);
}
pub fn enable(&self) {
// Enable interrupt for the pin
unsafe {
// Hardware-specific implementation
self.enable_interrupt();
}
}
pub fn disable(&self) {
// Disable interrupt for the pin
unsafe {
// Hardware-specific implementation
self.disable_interrupt();
}
}
// Hardware-specific methods
unsafe fn enable_interrupt(&self) {
// Implementation depends on target hardware
}
unsafe fn disable_interrupt(&self) {
// Implementation depends on target hardware
}
}
UART Communication
[uart_communication]
serial_protocol: true
baud_rate: true
interrupt_driven: true[uart_implementation]
// UART configuration
pub struct UartConfig {
baud_rate: u32,
data_bits: DataBits,
parity: Parity,
stop_bits: StopBits,
}
#[derive(Clone, Copy)]
pub enum DataBits {
Bits8,
Bits9,
}
#[derive(Clone, Copy)]
pub enum Parity {
None,
Even,
Odd,
}
#[derive(Clone, Copy)]
pub enum StopBits {
Bits1,
Bits2,
}
// UART peripheral
pub struct Uart {
config: UartConfig,
tx_buffer: StackBuffer<u8, 256>,
rx_buffer: StackBuffer<u8, 256>,
}
impl Uart {
pub fn new(config: UartConfig) -> Self {
let mut uart = Self {
config,
tx_buffer: StackBuffer::new(),
rx_buffer: StackBuffer::new(),
};
uart.configure();
uart
}
fn configure(&mut self) {
// Configure UART hardware
unsafe {
// Hardware-specific implementation
self.set_baud_rate(self.config.baud_rate);
self.set_data_bits(self.config.data_bits);
self.set_parity(self.config.parity);
self.set_stop_bits(self.config.stop_bits);
}
}
pub fn write(&mut self, data: &[u8]) -> Result<usize, &'static str> {
let mut written = 0;
for &byte in data {
if self.tx_buffer.push(byte).is_ok() {
written += 1;
} else {
break;
}
}
// Start transmission if not already running
if written > 0 {
self.start_transmission();
}
Ok(written)
}
pub fn read(&mut self, buffer: &mut [u8]) -> usize {
let mut read = 0;
for byte in buffer.iter_mut() {
if let Some(data) = self.rx_buffer.pop() {
*byte = data;
read += 1;
} else {
break;
}
}
read
}
pub fn write_byte(&mut self, byte: u8) -> Result<(), &'static str> {
if self.tx_buffer.push(byte).is_ok() {
self.start_transmission();
Ok(())
} else {
Err("TX buffer full")
}
}
pub fn read_byte(&mut self) -> Option<u8> {
self.rx_buffer.pop()
}
pub fn is_tx_ready(&self) -> bool {
// Check if UART is ready to transmit
unsafe {
// Hardware-specific implementation
self.tx_ready()
}
}
pub fn is_rx_ready(&self) -> bool {
// Check if UART has received data
unsafe {
// Hardware-specific implementation
self.rx_ready()
}
}
fn start_transmission(&mut self) {
if self.is_tx_ready() {
if let Some(byte) = self.tx_buffer.pop() {
unsafe {
// Hardware-specific implementation
self.transmit_byte(byte);
}
}
}
}
// Hardware-specific methods
unsafe fn set_baud_rate(&self, _baud_rate: u32) {
// Implementation depends on target hardware
}
unsafe fn set_data_bits(&self, _data_bits: DataBits) {
// Implementation depends on target hardware
}
unsafe fn set_parity(&self, _parity: Parity) {
// Implementation depends on target hardware
}
unsafe fn set_stop_bits(&self, _stop_bits: StopBits) {
// Implementation depends on target hardware
}
unsafe fn transmit_byte(&self, _byte: u8) {
// Implementation depends on target hardware
}
unsafe fn tx_ready(&self) -> bool {
// Implementation depends on target hardware
false
}
unsafe fn rx_ready(&self) -> bool {
// Implementation depends on target hardware
false
}
}
SPI Communication
[spi_communication]
master_slave: true
clock_polarity: true
data_transfer: true[spi_implementation]
// SPI configuration
pub struct SpiConfig {
clock_speed: u32,
mode: SpiMode,
bit_order: BitOrder,
}
#[derive(Clone, Copy)]
pub enum SpiMode {
Mode0, // CPOL=0, CPHA=0
Mode1, // CPOL=0, CPHA=1
Mode2, // CPOL=1, CPHA=0
Mode3, // CPOL=1, CPHA=1
}
#[derive(Clone, Copy)]
pub enum BitOrder {
MsbFirst,
LsbFirst,
}
// SPI peripheral
pub struct Spi {
config: SpiConfig,
cs_pin: Option<Pin>,
}
impl Spi {
pub fn new(config: SpiConfig) -> Self {
let mut spi = Self {
config,
cs_pin: None,
};
spi.configure();
spi
}
pub fn set_cs_pin(&mut self, pin: Pin) {
self.cs_pin = Some(pin);
}
fn configure(&mut self) {
// Configure SPI hardware
unsafe {
// Hardware-specific implementation
self.set_clock_speed(self.config.clock_speed);
self.set_mode(self.config.mode);
self.set_bit_order(self.config.bit_order);
}
}
pub fn transfer(&mut self, data: &[u8], response: &mut [u8]) -> Result<usize, &'static str> {
if data.len() != response.len() {
return Err("Data and response lengths must match");
}
// Assert chip select
if let Some(ref mut cs) = self.cs_pin {
cs.set_low();
}
let mut transferred = 0;
for (i, &byte) in data.iter().enumerate() {
let received = unsafe {
// Hardware-specific implementation
self.transfer_byte(byte)
};
response[i] = received;
transferred += 1;
}
// Deassert chip select
if let Some(ref mut cs) = self.cs_pin {
cs.set_high();
}
Ok(transferred)
}
pub fn write(&mut self, data: &[u8]) -> Result<usize, &'static str> {
let mut response = [0u8; 256];
let len = data.len().min(256);
self.transfer(&data[..len], &mut response[..len])
}
pub fn read(&mut self, response: &mut [u8]) -> Result<usize, &'static str> {
let data = vec![0u8; response.len()];
self.transfer(&data, response)
}
// Hardware-specific methods
unsafe fn set_clock_speed(&self, _speed: u32) {
// Implementation depends on target hardware
}
unsafe fn set_mode(&self, _mode: SpiMode) {
// Implementation depends on target hardware
}
unsafe fn set_bit_order(&self, _order: BitOrder) {
// Implementation depends on target hardware
}
unsafe fn transfer_byte(&self, _byte: u8) -> u8 {
// Implementation depends on target hardware
0
}
}
⏰ Real-Time Systems
Interrupt Handling
[interrupt_handling]
interrupt_vectors: true
priority_management: true
context_switching: true[interrupt_implementation]
use cortex_m::interrupt;
// Interrupt vector table
#[link_section = ".vector_table.interrupts"]
#[no_mangle]
pub static INTERRUPTS: [Option<unsafe extern "C" fn()>; 240] = {
let mut handlers = [None; 240];
handlers[16] = Some(sys_tick_handler);
handlers[17] = Some(pend_sv_handler);
handlers[18] = Some(sv_call_handler);
handlers
};
// System tick handler
#[interrupt]
fn SysTick() {
// Increment system tick counter
static mut TICK_COUNT: u32 = 0;
unsafe {
TICK_COUNT += 1;
// Handle periodic tasks
if TICK_COUNT % 1000 == 0 {
// 1 second tasks
handle_second_tasks();
}
if TICK_COUNT % 100 == 0 {
// 100ms tasks
handle_100ms_tasks();
}
if TICK_COUNT % 10 == 0 {
// 10ms tasks
handle_10ms_tasks();
}
}
}
// Task scheduling
pub struct Task {
id: u32,
priority: u8,
period_ms: u32,
last_run: u32,
handler: fn(),
}
pub struct TaskScheduler {
tasks: StackBuffer<Task, 16>,
current_tick: u32,
}
impl TaskScheduler {
pub fn new() -> Self {
Self {
tasks: StackBuffer::new(),
current_tick: 0,
}
}
pub fn add_task(&mut self, task: Task) -> Result<(), &'static str> {
self.tasks.push(task)
}
pub fn run(&mut self) {
self.current_tick += 1;
// Check for tasks that need to run
for i in 0..self.tasks.len() {
if let Some(task) = self.tasks.get_mut(i) {
if self.current_tick - task.last_run >= task.period_ms {
task.handler();
task.last_run = self.current_tick;
}
}
}
}
}
// Task handlers
fn handle_10ms_tasks() {
// High-frequency tasks
update_sensors();
process_inputs();
}
fn handle_100ms_tasks() {
// Medium-frequency tasks
update_display();
check_alarms();
}
fn handle_second_tasks() {
// Low-frequency tasks
log_status();
check_battery();
}
fn update_sensors() {
// Read sensor data
}
fn process_inputs() {
// Process user inputs
}
fn update_display() {
// Update display
}
fn check_alarms() {
// Check alarm conditions
}
fn log_status() {
// Log system status
}
fn check_battery() {
// Check battery level
}
// Hardware-specific interrupt handlers
extern "C" fn sys_tick_handler() {
SysTick();
}
extern "C" fn pend_sv_handler() {
// PendSV handler for context switching
}
extern "C" fn sv_call_handler() {
// SVC handler for system calls
}
Real-Time Constraints
[real_time_constraints]
deadline_management: true
priority_scheduling: true
resource_sharing: true[real_time_implementation]
// Real-time task with deadline
pub struct RealTimeTask {
id: u32,
priority: u8,
deadline_ms: u32,
execution_time_ms: u32,
last_deadline: u32,
missed_deadlines: u32,
}
impl RealTimeTask {
pub fn new(id: u32, priority: u8, deadline_ms: u32, execution_time_ms: u32) -> Self {
Self {
id,
priority,
deadline_ms,
execution_time_ms,
last_deadline: 0,
missed_deadlines: 0,
}
}
pub fn check_deadline(&mut self, current_time: u32) -> bool {
if current_time - self.last_deadline >= self.deadline_ms {
self.last_deadline = current_time;
true
} else {
false
}
}
pub fn mark_deadline_missed(&mut self) {
self.missed_deadlines += 1;
}
pub fn get_utilization(&self) -> f32 {
self.execution_time_ms as f32 / self.deadline_ms as f32
}
}
// Priority-based scheduler
pub struct PriorityScheduler {
tasks: StackBuffer<RealTimeTask, 16>,
current_time: u32,
}
impl PriorityScheduler {
pub fn new() -> Self {
Self {
tasks: StackBuffer::new(),
current_time: 0,
}
}
pub fn add_task(&mut self, task: RealTimeTask) -> Result<(), &'static str> {
self.tasks.push(task)
}
pub fn schedule(&mut self) -> Option<&mut RealTimeTask> {
self.current_time += 1;
// Find highest priority task that needs to run
let mut highest_priority_task: Option<&mut RealTimeTask> = None;
let mut highest_priority = 0;
for i in 0..self.tasks.len() {
if let Some(task) = self.tasks.get_mut(i) {
if task.check_deadline(self.current_time) && task.priority > highest_priority {
highest_priority = task.priority;
highest_priority_task = Some(task);
}
}
}
highest_priority_task
}
pub fn run_task(&mut self, task: &mut RealTimeTask) {
// Simulate task execution
let start_time = self.current_time;
// Check if task can complete before deadline
if start_time + task.execution_time_ms <= start_time + task.deadline_ms {
// Task can complete on time
self.current_time += task.execution_time_ms;
} else {
// Task will miss deadline
task.mark_deadline_missed();
self.current_time += task.execution_time_ms;
}
}
}
// Resource sharing with priority inheritance
pub struct Resource {
id: u32,
owner: Option<u32>,
ceiling_priority: u8,
}
impl Resource {
pub fn new(id: u32, ceiling_priority: u8) -> Self {
Self {
id,
owner: None,
ceiling_priority,
}
}
pub fn acquire(&mut self, task_id: u32, task_priority: u8) -> bool {
if self.owner.is_none() {
self.owner = Some(task_id);
true
} else {
false
}
}
pub fn release(&mut self, task_id: u32) -> bool {
if self.owner == Some(task_id) {
self.owner = None;
true
} else {
false
}
}
pub fn is_available(&self) -> bool {
self.owner.is_none()
}
}
🔋 Power Management
Low Power Modes
[power_management]
sleep_modes: true
power_optimization: true
wakeup_sources: true[power_implementation]
// Power management states
#[derive(Clone, Copy)]
pub enum PowerMode {
Run,
Sleep,
Stop,
Standby,
}
// Power manager
pub struct PowerManager {
current_mode: PowerMode,
wakeup_sources: StackBuffer<WakeupSource, 8>,
}
impl PowerManager {
pub fn new() -> Self {
Self {
current_mode: PowerMode::Run,
wakeup_sources: StackBuffer::new(),
}
}
pub fn enter_sleep_mode(&mut self, mode: PowerMode) {
// Configure wakeup sources
self.configure_wakeup_sources();
// Enter sleep mode
match mode {
PowerMode::Sleep => self.enter_sleep(),
PowerMode::Stop => self.enter_stop(),
PowerMode::Standby => self.enter_standby(),
PowerMode::Run => return,
}
self.current_mode = mode;
}
pub fn add_wakeup_source(&mut self, source: WakeupSource) -> Result<(), &'static str> {
self.wakeup_sources.push(source)
}
fn configure_wakeup_sources(&self) {
// Configure hardware wakeup sources
for i in 0..self.wakeup_sources.len() {
if let Some(source) = self.wakeup_sources.get(i) {
source.enable();
}
}
}
fn enter_sleep(&self) {
// Enter sleep mode
unsafe {
// Hardware-specific implementation
self.set_sleep_mode();
cortex_m::asm::wfi();
}
}
fn enter_stop(&self) {
// Enter stop mode
unsafe {
// Hardware-specific implementation
self.set_stop_mode();
cortex_m::asm::wfi();
}
}
fn enter_standby(&self) {
// Enter standby mode
unsafe {
// Hardware-specific implementation
self.set_standby_mode();
cortex_m::asm::wfi();
}
}
// Hardware-specific methods
unsafe fn set_sleep_mode(&self) {
// Implementation depends on target hardware
}
unsafe fn set_stop_mode(&self) {
// Implementation depends on target hardware
}
unsafe fn set_standby_mode(&self) {
// Implementation depends on target hardware
}
}
// Wakeup source abstraction
pub struct WakeupSource {
source_type: WakeupSourceType,
enabled: bool,
}
#[derive(Clone, Copy)]
pub enum WakeupSourceType {
GpioInterrupt(u8, u8), // port, pin
TimerInterrupt,
UartInterrupt,
RtcAlarm,
}
impl WakeupSource {
pub fn new(source_type: WakeupSourceType) -> Self {
Self {
source_type,
enabled: false,
}
}
pub fn enable(&mut self) {
self.enabled = true;
// Enable hardware wakeup source
unsafe {
self.enable_hardware_source();
}
}
pub fn disable(&mut self) {
self.enabled = false;
// Disable hardware wakeup source
unsafe {
self.disable_hardware_source();
}
}
// Hardware-specific methods
unsafe fn enable_hardware_source(&self) {
// Implementation depends on target hardware
}
unsafe fn disable_hardware_source(&self) {
// Implementation depends on target hardware
}
}
🎯 Best Practices
1. Memory Management
- Use static allocation when possible - Minimize heap usage - Implement custom allocators - Monitor stack usage2. Real-Time Constraints
- Design for worst-case execution time - Use priority-based scheduling - Implement deadline monitoring - Handle resource contention3. Power Optimization
- Use appropriate sleep modes - Minimize active time - Optimize clock frequencies - Implement wakeup strategies4. Hardware Abstraction
- Create portable abstractions - Separate hardware-specific code - Use trait-based interfaces - Implement proper error handling5. Testing and Debugging
- Use hardware-in-the-loop testing - Implement logging mechanisms - Use debug interfaces - Test interrupt handlingEmbedded systems development with TuskLang and Rust provides the safety and performance needed for reliable real-time applications while maintaining the flexibility to work with various hardware platforms.