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}