mos_hardware/
cia.rs

1// copyright 2022 mikael lund aka wombat
2//
3// licensed under the apache license, version 2.0 (the "license");
4// you may not use this file except in compliance with the license.
5// you may obtain a copy of the license at
6//
7//     http://www.apache.org/licenses/license-2.0
8//
9// unless required by applicable law or agreed to in writing, software
10// distributed under the license is distributed on an "as is" basis,
11// without warranties or conditions of any kind, either express or implied.
12// see the license for the specific language governing permissions and
13// limitations under the license.
14
15//! Registers for the MOS Technology 6526/8520 Complex Interface Adapter (CIA)
16//!
17//! The CIA served as an I/O port controller for the 6502 family of microprocessors,
18//! providing for parallel and serial I/O capabilities as well as timers and a
19//! Time-of-Day (TOD) clock. The device's most prominent use was in the Commodore 64
20//! and Commodore 128(D), each of which included two CIA chips.
21
22use bitflags::bitflags;
23use core::mem::size_of;
24use static_assertions::const_assert;
25use volatile_register::RW;
26
27/// A real-time clock is incorporated in the CIA, providing a timekeeping device more
28/// conducive to human needs than the microsecond precision of the interval timers.
29/// Time is kept in the American 12-hour AM/PM format.
30/// The TOD clock consists of four read/write registers: hours (with bit 7 acting as
31/// the AM/PM flag), minutes, seconds and tenths of a second. All registers read out in
32/// [BCD format](https://en.wikipedia.org/wiki/Binary-coded_decimal), thus simplifying
33/// the encoding/decoding process.
34#[repr(C, packed)]
35pub struct TimeOfDay {
36    pub deci_seconds: RW<u8>, // 0x08
37    pub seconds: RW<u8>,      // 0x09
38    pub minutes: RW<u8>,      // 0x0a
39    pub hours: RW<u8>,        // 0x0b
40}
41
42const_assert!(size_of::<TimeOfDay>() == 4);
43
44#[repr(C, packed)]
45/// Registers for the MOS Tehnology Complex Interface Adapter 6526
46///
47/// The CIA served as an I/O port controller for the 6502 family of microprocessors,
48/// providing for parallel and serial I/O capabilities as well as timers and a
49/// Time-of-Day (TOD) clock. The device's most prominent use was in the Commodore 64
50/// and Commodore 128(D), each of which included two CIA chips.
51pub struct MOSComplexInterfaceAdapter6526 {
52    pub port_a: RW<GameController>,    // 0x00
53    pub port_b: RW<GameController>,    // 0x01
54    pub data_direction_port_a: RW<u8>, // 0x02
55    pub data_direction_port_b: RW<u8>, // 0x03
56    pub timer_a: RW<u16>,              // 0x04
57    pub timer_b: RW<u16>,              // 0x06
58    pub time_of_day: TimeOfDay,        // 0x08
59    pub serial_shift: RW<u8>,          // 0x0c
60    pub interrupt: RW<u8>,             // 0x0d
61    pub control_a: RW<u8>,             // 0x0e
62    pub control_b: RW<u8>,             // 0x0f
63}
64
65const_assert!(size_of::<MOSComplexInterfaceAdapter6526>() == 16);
66
67/// Enum for joystick positions
68pub enum JoystickPosition {
69    Up,
70    Down,
71    Left,
72    Right,
73    UpLeft,
74    DownLeft,
75    UpRight,
76    DownRight,
77    Middle,
78}
79
80/// Enum for joystick position (complexity: N enum values)
81///
82/// This is a convenience enum with some overhead in that
83/// the status is usually conditionally dermined twice:
84///
85/// 1. When creating the enum with `new()`
86/// 2. When matching the resulting enum in calling code
87///
88/// It is faster but more low-level to directly probe `GameController`
89/// (see example)
90impl JoystickPosition {
91    pub const fn new(value: GameController) -> JoystickPosition {
92        let complement = value.complement(); // complement since bit is UNSET if active
93        if complement.contains(GameController::UP_LEFT) {
94            JoystickPosition::UpLeft
95        } else if complement.contains(GameController::UP_RIGHT) {
96            JoystickPosition::UpRight
97        } else if complement.contains(GameController::DOWN_LEFT) {
98            JoystickPosition::DownLeft
99        } else if complement.contains(GameController::DOWN_RIGHT) {
100            JoystickPosition::DownRight
101        } else if complement.contains(GameController::UP) {
102            JoystickPosition::Up
103        } else if complement.contains(GameController::DOWN) {
104            JoystickPosition::Down
105        } else if complement.contains(GameController::LEFT) {
106            JoystickPosition::Left
107        } else if complement.contains(GameController::RIGHT) {
108            JoystickPosition::Right
109        } else {
110            JoystickPosition::Middle
111        }
112    }
113}
114
115bitflags! {
116    /// Bit mask for joystick controller (CIA1 port a or b)
117    ///
118    /// Note that bits are _unset_ when joystick actions are
119    /// triggered which is why we use `.complement()`.
120    ///
121    /// # Examples
122    /// ~~~
123    /// use cia::GameController::{UP, FIRE};
124    /// let joystick = c64::cia1().port_a.read().complement();
125    /// if joystick.contains(UP | FIRE) {
126    ///     // UP and FIRE pressed simultaneously...
127    /// }
128    /// ~~~
129    pub struct GameController: u8 {
130        const UP    = 0b0000_0001; // bit 0
131        const DOWN  = 0b0000_0010; // bit 1
132        const LEFT  = 0b0000_0100; // bit 2
133        const RIGHT = 0b0000_1000; // bit 3
134        const FIRE  = 0b0001_0000; // bit 4
135
136        const UP_FIRE    = Self::UP.bits | Self::FIRE.bits;
137        const DOWN_FIRE  = Self::DOWN.bits | Self::FIRE.bits;
138        const LEFT_FIRE  = Self::LEFT.bits | Self::FIRE.bits;
139        const RIGHT_FIRE = Self::RIGHT.bits | Self::FIRE.bits;
140
141        const UP_LEFT = Self::UP.bits | Self::LEFT.bits;
142        const UP_RIGHT = Self::UP.bits | Self::RIGHT.bits;
143        const DOWN_LEFT = Self::DOWN.bits | Self::LEFT.bits;
144        const DOWN_RIGHT = Self::DOWN.bits | Self::RIGHT.bits;
145
146        const UP_LEFT_FIRE = Self::UP.bits | Self::LEFT.bits | Self::FIRE.bits;
147        const UP_RIGHT_FIRE = Self::UP.bits | Self::RIGHT.bits | Self::FIRE.bits;
148        const DOWN_LEFT_FIRE = Self::DOWN.bits | Self::LEFT.bits | Self::FIRE.bits;
149        const DOWN_RIGHT_FIRE = Self::DOWN.bits | Self::RIGHT.bits | Self::FIRE.bits;
150    }
151}
152
153impl GameController {
154    /// Read joystick position and fire button status
155    ///
156    /// # Examples
157    ///
158    /// ~~~
159    /// let port_a = c64::cia1().port_a.read();
160    /// let (position, fire) = port_a.read_joystick();
161    /// ~~~
162    ///
163    /// # Note
164    ///
165    /// This is a convenience enum with some overhead in that
166    /// the status is usually conditionally dermined twice:
167    ///
168    /// 1. When creating the enum with `JoystickPosition::new()`
169    /// 2. When matching the resulting enum in calling code
170    ///
171    /// It is faster but more low-level to directly probe `GameController`
172    /// using bitflags.
173    pub const fn read_joystick(&self) -> (JoystickPosition, bool) {
174        let position = JoystickPosition::new(*self);
175        let fire = self.complement().contains(Self::FIRE);
176        (position, fire)
177    }
178}