Skip to main content

avr_port/
lib.rs

1//! no_std GPIO port abstraction for AVR (ATmega328P).
2//!
3//! Use Port B, C, and D via DDR/PORT/PIN registers:
4//! - `PORTx`: data register (read/write)
5//! - `DDRx`: data direction register (read/write)
6//! - `PINx`: input pins (read only)
7//!
8//! # Example
9//! ```no_run
10//! use avr_port::{portb, Pin};
11//!
12//! let mut port_b = portb::init();
13//! port_b.set_pin_mode(Pin::P5, true);
14//! port_b.set_pin_state(Pin::P5, true);
15//! let _level = port_b.read_pin(Pin::P5);
16//! ```
17
18#![no_std]
19
20use core::ptr::{read_volatile, write_volatile};
21
22pub enum Pin {
23    P0,
24    P1,
25    P2,
26    P3,
27    P4,
28    P5,
29    P6,
30    P7,
31}
32
33impl Pin {
34    /// Returns the bit index for this pin (0..=7).
35    pub const fn index(self) -> u8 {
36        match self {
37            Pin::P0 => 0,
38            Pin::P1 => 1,
39            Pin::P2 => 2,
40            Pin::P3 => 3,
41            Pin::P4 => 4,
42            Pin::P5 => 5,
43            Pin::P6 => 6,
44            Pin::P7 => 7,
45        }
46    }
47
48    pub const fn from_u8(value: u8) -> Option<Self> {
49        match value {
50            0 => Some(Pin::P0),
51            1 => Some(Pin::P1),
52            2 => Some(Pin::P2),
53            3 => Some(Pin::P3),
54            4 => Some(Pin::P4),
55            5 => Some(Pin::P5),
56            6 => Some(Pin::P6),
57            7 => Some(Pin::P7),
58            _ => None,
59        }
60    }
61}
62
63pub struct Port {
64    ddr: *mut u8,
65    port: *mut u8,
66    pin: *const u8,
67}
68
69impl Port {
70    /// Creates a port from register addresses.
71    ///
72    /// # Safety
73    /// The register addresses must match the target MCU's memory map. For ATmega328P,
74    /// see the datasheet's "I/O Ports" section.
75    pub unsafe fn new(ddr_addr: u16, port_addr: u16, pin_addr: u16) -> Self {
76        Port {
77            ddr: ddr_addr as *mut u8,
78            port: port_addr as *mut u8,
79            pin: pin_addr as *const u8,
80        }
81    }
82
83    /// Sets the direction of a pin (input or output).
84    ///
85    /// Note: this performs a read-modify-write of `DDRx`, which is not atomic.
86    /// Interrupts can modify the same register
87    pub fn set_pin_mode(&mut self, pin: Pin, is_output: bool) {
88        unsafe {
89            let current = read_volatile(self.ddr);
90            let new = if is_output {
91                set_bit(current, pin)
92            } else {
93                clear_bit(current, pin)
94            };
95            write_volatile(self.ddr, new);
96        }
97    }
98
99    /// Sets the output state of a pin (high or low).
100    ///
101    /// Note: this performs a read-modify-write of `PORTx`, which is not atomic.
102    /// Interrupts can modify the same register
103    pub fn set_pin_state(&mut self, pin: Pin, is_high: bool) {
104        unsafe {
105            let current = read_volatile(self.port);
106            let new = if is_high {
107                set_bit(current, pin)
108            } else {
109                clear_bit(current, pin)
110            };
111            write_volatile(self.port, new);
112        }
113    }
114
115    /// Reads the input state of a pin.
116    pub fn read_pin(&self, pin: Pin) -> bool {
117        unsafe {
118            let value = read_volatile(self.pin);
119            read_bit(value, pin)
120        }
121    }
122
123    /// Reads the raw DDR register value.
124    pub fn read_ddr(&self) -> u8 {
125        unsafe { read_volatile(self.ddr) }
126    }
127
128    /// Reads the raw PORT register value.
129    pub fn read_port(&self) -> u8 {
130        unsafe { read_volatile(self.port) }
131    }
132
133    /// Reads the raw PIN register value.
134    pub fn read_pin_reg(&self) -> u8 {
135        unsafe { read_volatile(self.pin) }
136    }
137}
138
139#[inline]
140fn set_bit(value: u8, pin: Pin) -> u8 {
141    value | (1 << pin.index())
142}
143
144#[inline]
145fn clear_bit(value: u8, pin: Pin) -> u8 {
146    value & !(1 << pin.index())
147}
148
149#[inline]
150fn read_bit(value: u8, pin: Pin) -> bool {
151    (value & (1 << pin.index())) != 0
152}
153
154pub mod portb {
155    use super::Port;
156
157    const DDRB: u16 = 0x24;
158    const PORTB: u16 = 0x25;
159    const PINB: u16 = 0x23;
160
161    pub fn init() -> Port {
162        unsafe { Port::new(DDRB, PORTB, PINB) }
163    }
164}
165
166pub mod portc {
167    use super::Port;
168
169    const DDRC: u16 = 0x27;
170    const PORTC: u16 = 0x28;
171    const PINC: u16 = 0x26;
172
173    pub fn init() -> Port {
174        unsafe { Port::new(DDRC, PORTC, PINC) }
175    }
176}
177
178pub mod portd {
179    use super::Port;
180
181    const DDRD: u16 = 0x2a;
182    const PORTD: u16 = 0x2b;
183    const PIND: u16 = 0x29;
184
185    pub fn init() -> Port {
186        unsafe { Port::new(DDRD, PORTD, PIND) }
187    }
188}