forked from commanderx16/x16-smc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathx16-smc.ino
347 lines (296 loc) · 10.2 KB
/
x16-smc.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Commander X16 ATX Power Control, Reset / NMI, PS/2
//
// By: Kevin Williams - TexElec.com
// Michael Steil
// Joe Burks
//#define ENABLE_NMI_BUT
#include <OneButton.h>
#include <Wire.h>
#include "dbg_supp.h"
#include "smc_pins.h"
#include "ps2.h"
#include "mouse.h"
// If not ATtiny861, we expect ATMega328p
#if !defined(__AVR_ATmega328P__) && !defined(__AVR_ATtiny861__)
#error "X16 SMC only builds for ATtiny861 and ATmega328P"
#endif
#define PWR_ON_MIN_MS 100
#define PWR_ON_MAX_MS 500
// Hold PWR_ON low while computer is on. High while off.
// PWR_OK -> Should go high 100ms<->500ms after PWR_ON invoked.
// Any longer or shorter is considered a fault.
// http://www.ieca-inc.com/images/ATX12V_PSDG2.0_Ratified.pdf
//Activity (HDD) LED
#define ACT_LED_DEFAULT_LEVEL 0
#define ACT_LED_ON_LEVEL 255
//Reset & NMI Lines
#define RESB_HOLDTIME_MS 500
#define NMI_HOLDTIME_MS 300
//I2C Pins
#define I2C_ADDR 0x42 // I2C Device ID
OneButton POW_BUT(POWER_BUTTON_PIN, true, true);
OneButton RES_BUT(RESET_BUTTON_PIN, true, true);
#if defined(ENABLE_NMI_BUT)
OneButton NMI_BUT(NMI_BUTTON_PIN, true, true);
#endif
PS2Port<PS2_KBD_CLK, PS2_KBD_DAT, 16> Keyboard;
PS2Port<PS2_MSE_CLK, PS2_MSE_DAT, 8> Mouse;
void keyboardClockIrq() {
Keyboard.onFallingClock();
}
void mouseClockIrq() {
Mouse.onFallingClock();
}
void MouseInitTick();
bool SYSTEM_POWERED = 0; // default state - Powered off
int I2C_Data[3] = {0, 0, 0};
bool I2C_Active = false;
char echo_byte = 0;
void setup() {
#if defined(USE_SERIAL_DEBUG)
Serial.begin(SERIAL_BPS);
#endif
//Setup Timer 1 interrupt to run every 25 us >>>
cli();
#if defined(__AVR_ATtiny861__)
static_assert(TIMER_TO_USE_FOR_MILLIS != 1);
TCCR1A = 0;
TCCR1C = 0;
TCCR1D = 0;
TC1H = 0;
TCNT1 = 0;
PLLCSR = 0;
OCR1B = 0;
TCCR1B = 0b01000101; // Prescaler sel = clk / 16
OCR1A = 100; // 100 x 1.0 us = 100 us
TIMSK |= 1 << OCIE1A;
#endif
#if defined(__AVR_ATmega328P__)
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
TCCR1B |= 8; //Clear Timer on Compare Match (CTC) Mode
TCCR1B |= 2; //Prescale 8x => 1 tick = 0.5 us @ 16 MHz
OCR1A = 200; //200 x 0.5 us = 100 us
TIMSK1 = (1<<OCIE1A);
#endif
//Timer 1 interrupt setup is done, enable interrupts
sei();
DBG_PRINTLN("Commander X16 SMC Start");
//initialize i2C
Wire.begin(I2C_ADDR); // Initialize I2C - Device Mode
Wire.onReceive(I2C_Receive); // Used to Receive Data
Wire.onRequest(I2C_Send); // Used to Send Data, may never be used
POW_BUT.attachClick(Power_Button_Press); // Should the Power off be long, or short?
POW_BUT.attachDuringLongPress(Power_Button_Press); // Both for now
RES_BUT.attachClick(Reset_Button_Press); // Short Click = NMI, Long Press = Reset
RES_BUT.attachDuringLongPress(Reset_Button_Hold); // Actual Reset Call
#if defined(ENABLE_NMI_BUT)
NMI_BUT.attachClick(Reset_Button_Press); // NMI Call is the same as short Reset
NMI_BUT.attachClick(HardReboot); // strangely, this works fine via NMI push, but fails via I2C?
#endif
pinMode(PWR_OK, INPUT);
pinMode(PWR_ON, OUTPUT);
digitalWrite(PWR_ON, HIGH);
pinMode(ACT_LED, OUTPUT);
analogWrite(ACT_LED, 0);
pinMode(RESB_PIN,OUTPUT);
digitalWrite(RESB_PIN,LOW); // Hold Reset on statup
#if defined(ENABLE_NMI_BUT)
pinMode(NMIB_PIN,OUTPUT);
digitalWrite(NMIB_PIN,HIGH);
#endif
// PS/2 host init
Keyboard.begin(keyboardClockIrq);
Mouse.begin(mouseClockIrq);
}
void loop() {
POW_BUT.tick(); // Check Button Status
RES_BUT.tick();
#if defined(ENABLE_NMI_BUT)
NMI_BUT.tick();
#endif
MouseInitTick();
if ((SYSTEM_POWERED == 1) && (!digitalRead(PWR_OK)))
{
PowerOffSeq();
//kill power if PWR_OK dies, and system on
//error handling?
}
// DEBUG: turn activity LED on if there are keys in the keybuffer
delay(10); // Short Delay, required by OneButton if code is short
}
void Power_Button_Press() {
if (SYSTEM_POWERED == 0) { // If Off, turn on
PowerOnSeq();
}
else { // If On, turn off
PowerOffSeq();
}
}
void I2C_Receive(int) {
// We are resetting the data beforehand
I2C_Data[0] = 0;
I2C_Data[1] = 0;
I2C_Data[2] = 0;
int ct=0;
while (Wire.available()) {
if (ct<3) { // read first two bytes only
byte c = Wire.read();
I2C_Data[ct] = c;
ct++;
}
else {
Wire.read(); // eat extra data, should not be sent
}
}
if (ct == 2 || ct == 3) {
I2C_Process(); // process received cmd
}
}
//I2C Commands - Two Bytes per Command
//0x01 0x00 - Power Off
//0x01 0x01 - Hard Reboot (not working for some reason)
//0x02 0x00 - Reset Button Press
//0x03 0x00 - NMI Button press
//0x04 0x00-0xFF - Power LED Level (PWM) // need to remove, not enough lines
//0x05 0x00-0xFF - Activity/HDD LED Level (PWM)
void I2C_Process() {
if (I2C_Data[0] == 1) { // 1st Byte : Byte 1 - Power Events (Power off & Reboot)
switch (I2C_Data[1]) {
case 0:PowerOffSeq(); // 2nd Byte : 0 - Power Off
break;
case 1:HardReboot(); // 2nd Byte : 1 - Reboot (Physical Power off, wait 500ms, Power on)
break; // This command triggers, but the system does not power back on? Not sure why.
}
}
if (I2C_Data[0] == 2) { // 1st Byte : Byte 2 - Reset Event(s)
switch (I2C_Data[1]) {
case 0:Reset_Button_Hold(); // 2nd Byte : 0 - Reset button Press
break;
}
}
if (I2C_Data[0] == 3) { // 1st Byte : Byte 3 - NMI Event(s)
switch (I2C_Data[1]) {
case 0:Reset_Button_Press(); // 2nd Byte : 0 - NMI button Press
break;
}
}
// ["Byte 4 - Power LED Level" removed]
if (I2C_Data[0] == 5) { // 1st Byte : Byte 5 - Activity LED Level
analogWrite(ACT_LED, I2C_Data[1]); // 2nd Byte : Set Value directly
}
if (I2C_Data[0] == 7) { // 1st Byte : Byte 7 - Keyboard: read next keycode
// Nothing to do here, register offset 7 is read only
}
if (I2C_Data[0] == 8) {
echo_byte = I2C_Data[1];
}
if (I2C_Data[0] == 9) {
DBG_PRINT("DBG register 9 called. echo_byte: ");
DBG_PRINTLN((byte)(echo_byte), HEX);
}
if (I2C_Data[0] == 0x19){
//Send command to keyboard (one byte)
Keyboard.sendPS2Command(I2C_Data[1]);
}
if (I2C_Data[0] == 0x1a){
//Send command to keyboard (two bytes)
Keyboard.sendPS2Command(I2C_Data[1], I2C_Data[2]);
}
}
void I2C_Send() {
// DBG_PRINTLN("I2C_Send");
int nextKey = 0;
if (I2C_Data[0] == 0x7) { // 1st Byte : Byte 7 - Keyboard: read next keycode
if (Keyboard.available()) {
nextKey = Keyboard.next();
Wire.write(nextKey);
}
else {
Wire.write(0);
}
}
if (I2C_Data[0] == 8) {
Wire.write(echo_byte);
}
if (I2C_Data[0] == 0x18){
//Get keyboard command status
Wire.write(Keyboard.getCommandStatus());
}
if (I2C_Data[0] == 0x21){
//Get mouse packet
if (Mouse.count()>2){
uint8_t buf[3];
buf[0] = Mouse.next();
if ((buf[0] & 0xc8) == 0x08){
//Valid first byte - Send mouse data packet
buf[1] = Mouse.next();
buf[2] = Mouse.next();
Wire.write(buf,3);
}
else{
//Invalid first byte - Discard, and return a 0
Wire.write(0);
}
}
else{
Wire.write(0);
}
}
}
void Reset_Button_Hold() {
Keyboard.flush();
Mouse.reset();
if (SYSTEM_POWERED == 1) { // Ignore unless Powered On
digitalWrite(RESB_PIN,LOW); // Press RESET
delay(RESB_HOLDTIME_MS);
digitalWrite(RESB_PIN,HIGH);
analogWrite(ACT_LED, 0);
mouse_init_state = 0;
}
}
void Reset_Button_Press() {
if (SYSTEM_POWERED == 1) { // Ignore unless Powered On
digitalWrite(NMIB_PIN,LOW); // Press NMI
delay(NMI_HOLDTIME_MS);
digitalWrite(NMIB_PIN,HIGH);
}
}
void PowerOffSeq() {
digitalWrite(PWR_ON, HIGH); // Turn off supply
SYSTEM_POWERED=0; // Global Power state Off
digitalWrite(RESB_PIN,LOW);
delay(RESB_HOLDTIME_MS); // Mostly here to add some delay between presses
}
void PowerOnSeq() {
digitalWrite(PWR_ON, LOW); // turn on power supply
unsigned long TimeDelta = 0;
unsigned long StartTime = millis(); // get current time
while (!digitalRead(PWR_OK)) { // Time how long it takes
TimeDelta=millis() - StartTime; // for PWR_OK to go active.
}
if ((PWR_ON_MIN_MS > TimeDelta) || (PWR_ON_MAX_MS < TimeDelta)) {
PowerOffSeq(); // FAULT! Turn off supply
// insert error handler, flash activity light & Halt? IE, require hard power off before continue?
}
else {
SYSTEM_POWERED=1; // Global Power state On
delay(RESB_HOLDTIME_MS); // Allow system to stabilize
digitalWrite(RESB_PIN,HIGH); // Release Reset
}
}
void HardReboot() { // This never works via I2C... Why!!!
PowerOffSeq();
delay(1000);
PowerOnSeq();
}
ISR(TIMER1_COMPA_vect){
#if defined(__AVR_ATtiny861__)
// Reset counter since timer1 doesn't reset itself.
TC1H = 0;
TCNT1 = 0;
#endif
Keyboard.timerInterrupt();
Mouse.timerInterrupt();
}