Skip to content

Commit 2fb8574

Browse files
authored
Merge pull request #2516 from jepler/runcpm
code for the runcpm guide
2 parents f4213db + 4dea249 commit 2fb8574

20 files changed

+9657
-0
lines changed

runcpm-rp2040-dvi-usb/keyboard-copro/.feather_rp2040_tinyusb.test.only

Whitespace-only changes.
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
// pio-usb is required for rp2040 host
6+
#include "pio_usb.h"
7+
#include "Adafruit_TinyUSB.h"
8+
9+
// Pin D+ for host, D- = D+ + 1
10+
#ifndef PIN_USB_HOST_DP
11+
#define PIN_USB_HOST_DP 16
12+
#endif
13+
14+
// Pin for enabling Host VBUS. comment out if not used
15+
#ifndef PIN_5V_EN
16+
#define PIN_5V_EN 18
17+
#endif
18+
19+
#ifndef PIN_5V_EN_STATE
20+
#define PIN_5V_EN_STATE 1
21+
#endif
22+
23+
// USB Host object
24+
Adafruit_USBH_Host USBHost;
25+
26+
// Serial output for RunCPM
27+
SerialPIO pio_serial(1 /* RX of the sibling board */, SerialPIO::NOPIN);
28+
29+
void setup() {
30+
}
31+
32+
void loop() {
33+
}
34+
35+
void setup1() {
36+
37+
#if 0
38+
while ( !Serial ) delay(10); // wait for native usb
39+
Serial.println("Core1 setup to run TinyUSB host with pio-usb");
40+
#endif
41+
42+
// Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB
43+
uint32_t cpu_hz = clock_get_hz(clk_sys);
44+
if ( cpu_hz != 120000000UL && cpu_hz != 240000000UL ) {
45+
while ( !Serial ) delay(10); // wait for native usb
46+
Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz);
47+
Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n");
48+
while (1) delay(1);
49+
}
50+
51+
#ifdef PIN_5V_EN
52+
pinMode(PIN_5V_EN, OUTPUT);
53+
digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE);
54+
#endif
55+
56+
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
57+
pio_cfg.pin_dp = PIN_USB_HOST_DP;
58+
USBHost.configure_pio_usb(1, &pio_cfg);
59+
60+
// run host stack on controller (rhport) 1
61+
// Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the
62+
// host bit-banging processing works done in core1 to free up core0 for other works
63+
USBHost.begin(1);
64+
65+
// this `begin` is a void function, no way to check for failure!
66+
pio_serial.begin(115200);
67+
}
68+
69+
int old_ascii = -1;
70+
uint32_t repeat_timeout;
71+
const uint32_t repeat_time = 150;
72+
73+
void send_ascii(uint8_t code) {
74+
old_ascii = code;
75+
repeat_timeout = millis() + repeat_time;
76+
if (code > 32 && code < 127) {
77+
Serial.printf("'%c'\r\n", code);
78+
} else {
79+
Serial.printf("'\\x%02x'\r\n", code);
80+
}
81+
pio_serial.write(code);
82+
}
83+
84+
void loop1()
85+
{
86+
uint32_t now = millis();
87+
uint32_t deadline = repeat_timeout - now;
88+
if (old_ascii >= 0 && deadline > INT32_MAX) {
89+
repeat_timeout += repeat_time;
90+
deadline = repeat_timeout - now;
91+
send_ascii(old_ascii);
92+
} else if (old_ascii < 0) {
93+
deadline = UINT32_MAX;
94+
}
95+
tuh_task_ext(deadline, false);
96+
}
97+
98+
// Invoked when device with hid interface is mounted
99+
// Report descriptor is also available for use.
100+
// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough
101+
// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE,
102+
// it will be skipped therefore report_desc = NULL, desc_len = 0
103+
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {
104+
(void)desc_report;
105+
(void)desc_len;
106+
uint16_t vid, pid;
107+
tuh_vid_pid_get(dev_addr, &vid, &pid);
108+
109+
Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
110+
Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid);
111+
112+
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
113+
if (itf_protocol == HID_ITF_PROTOCOL_KEYBOARD) {
114+
Serial.printf("HID Keyboard\r\n");
115+
if (!tuh_hid_receive_report(dev_addr, instance)) {
116+
Serial.printf("Error: cannot request to receive report\r\n");
117+
}
118+
}
119+
}
120+
121+
// Invoked when device with hid interface is un-mounted
122+
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
123+
Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
124+
}
125+
126+
#define FLAG_ALPHABETIC (1)
127+
#define FLAG_SHIFT (2)
128+
#define FLAG_NUMLOCK (4)
129+
#define FLAG_CTRL (8)
130+
#define FLAG_LUT (16)
131+
132+
const char * const lut[] = {
133+
"!@#$%^&*()", /* 0 - shifted numeric keys */
134+
"\r\x1b\10\t -=[]\\#;'`,./", /* 1 - symbol keys */
135+
"\n\x1b\177\t _+{}|~:\"~<>?", /* 2 - shifted */
136+
"\3\4\2\1", /* 3 - arrow keys RLDU */
137+
"/*-+\n1234567890.", /* 4 - keypad w/numlock */
138+
"/*-+\n\xff\2\xff\4\xff\3\xff\1\xff\xff.", /* 5 - keypad w/o numlock */
139+
};
140+
141+
struct keycode_mapper {
142+
uint8_t first, last, code, flags;
143+
} keycode_to_ascii[] = {
144+
{ HID_KEY_A, HID_KEY_Z, 'a', FLAG_ALPHABETIC, },
145+
146+
{ HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT | FLAG_LUT, },
147+
{ HID_KEY_1, HID_KEY_9, '1', 0, },
148+
{ HID_KEY_0, HID_KEY_0, ')', FLAG_SHIFT, },
149+
{ HID_KEY_0, HID_KEY_0, '0', 0, },
150+
151+
{ HID_KEY_ENTER, HID_KEY_ENTER, '\n', FLAG_CTRL },
152+
{ HID_KEY_ENTER, HID_KEY_SLASH, 2, FLAG_SHIFT | FLAG_LUT, },
153+
{ HID_KEY_ENTER, HID_KEY_SLASH, 1, FLAG_LUT, },
154+
155+
{ HID_KEY_F1, HID_KEY_F1, 0x1e, 0, }, // help key on xerox 820 kbd
156+
157+
{ HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 3, FLAG_LUT },
158+
159+
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 4, FLAG_NUMLOCK | FLAG_LUT },
160+
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 5, FLAG_LUT },
161+
};
162+
163+
164+
bool report_contains(const hid_keyboard_report_t &report, uint8_t key) {
165+
for (int i = 0; i < 6; i++) {
166+
if (report.keycode[i] == key) return true;
167+
}
168+
return false;
169+
}
170+
171+
hid_keyboard_report_t old_report;
172+
173+
void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report_t &report) {
174+
bool alt = report.modifier & 0x44;
175+
bool shift = report.modifier & 0x22;
176+
bool ctrl = report.modifier & 0x11;
177+
bool caps = old_report.reserved & 1;
178+
bool num = old_report.reserved & 2;
179+
uint8_t code = 0;
180+
181+
if (report.keycode[0] == 1 && report.keycode[1] == 1) {
182+
// keyboard says it has exceeded max kro
183+
return;
184+
}
185+
186+
// something was pressed or release, so cancel any key repeat
187+
old_ascii = -1;
188+
189+
for (auto keycode : report.keycode) {
190+
if (keycode == 0) continue;
191+
if (report_contains(old_report, keycode)) continue;
192+
193+
/* key is newly pressed */
194+
if (keycode == HID_KEY_NUM_LOCK) {
195+
num = !num;
196+
} else if (keycode == HID_KEY_CAPS_LOCK) {
197+
caps = !caps;
198+
} else {
199+
for (const auto &mapper : keycode_to_ascii) {
200+
if (!(keycode >= mapper.first && keycode <= mapper.last))
201+
continue;
202+
if (mapper.flags & FLAG_SHIFT && !shift)
203+
continue;
204+
if (mapper.flags & FLAG_NUMLOCK && !num)
205+
continue;
206+
if (mapper.flags & FLAG_CTRL && !ctrl)
207+
continue;
208+
if (mapper.flags & FLAG_LUT) {
209+
code = lut[mapper.code][keycode - mapper.first];
210+
} else {
211+
code = keycode - mapper.first + mapper.code;
212+
}
213+
if (mapper.flags & FLAG_ALPHABETIC) {
214+
if (shift ^ caps) {
215+
code ^= ('a' ^ 'A');
216+
}
217+
}
218+
if (ctrl) code &= 0x1f;
219+
if (alt) code ^= 0x80;
220+
send_ascii(code);
221+
break;
222+
}
223+
}
224+
}
225+
226+
uint8_t leds = (caps | (num << 1));
227+
if (leds != old_report.reserved) {
228+
Serial.printf("Send LEDs report %d (dev:instance = %d:%d)\r\n", leds, dev_addr, instance);
229+
// no worky
230+
auto r = tuh_hid_set_report(dev_addr, instance/*idx*/, 0/*report_id*/, HID_REPORT_TYPE_OUTPUT/*report_type*/, &leds, sizeof(leds));
231+
Serial.printf("set_report() -> %d\n", (int)r);
232+
}
233+
old_report = report;
234+
old_report.reserved = leds;
235+
}
236+
237+
// Invoked when received report from device via interrupt endpoint
238+
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
239+
if ( len != sizeof(hid_keyboard_report_t) ) {
240+
Serial.printf("report len = %u NOT 8, probably something wrong !!\r\n", len);
241+
} else {
242+
process_event(dev_addr, instance, *(hid_keyboard_report_t*)report);
243+
}
244+
// continue to request to receive report
245+
if (!tuh_hid_receive_report(dev_addr, instance)) {
246+
Serial.printf("Error: cannot request to receive report\r\n");
247+
}
248+
}

runcpm-rp2040-dvi-usb/runcpm-pico/.feather_rp2040_tinyusb.test.only

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Mockba the Borg
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
This is a port of runcpm to the raspberry pi pico.
8+
9+
It is based on:
10+
* [RunCPM](https://github.com/MockbaTheBorg/RunCPM/)
11+
* [RunCPM_RPi_Pico](https://github.com/guidol70/RunCPM_RPi_Pico)
12+
13+
It works on a Raspberry Pi Pico (or Pico W). It uses the internal flash
14+
for storage, and can be mounted as USB storage.
15+
16+
If your Pico is placed on the Pico DV carrier board, you also get a 100x30
17+
character screen to enjoy your CP/M output on!
18+
19+
First, build for your device. You must
20+
* Use the Philhower Pico Core
21+
* In the Tools menu, select
22+
* A flash size option that includes at least 512kB for filesystem
23+
* USB Stack: Adafruit TinyUSB
24+
25+
After it boots the first time, you need to
26+
* Format the flash device on your host computer
27+
* Create the folder "<DEVICE>/A/0"
28+
* Put something useful in that folder, such as [Zork](http://www.retroarchive.org/cpm/games/zork123_80.zip)
29+
* Files must respect the "8.3" naming convention (8 letters filename + 3 letters extension) and be all uppercase
30+
* Safely eject the drive, then reset the emulator.
31+
* Now at the "A0>" prompt you can run "ZORK1".

0 commit comments

Comments
 (0)