mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-12 16:16:28 +00:00
Phase 1 continued: serial, input devices + README indexes
10_devices: - serial.md: expanded with DB-25 pinout, custom chip UART registers, baud rate calculation table, open/configure/read/write patterns, serial debugging (KPrintF/Sushi) - input.md: expanded with handler chain architecture diagram, InputEvent struct, event classes table, qualifier bits, custom handler with key remapping, event consumption/blocking, synthetic event injection, Commodities Exchange Updated indexes: - 07_dos/README.md — enriched descriptions for all files - 10_devices/README.md — enriched descriptions for all files
This commit is contained in:
parent
da9e7d3b63
commit
0ded078134
4 changed files with 326 additions and 73 deletions
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [dos_base.md](dos_base.md) | DosLibrary structure and global state |
|
||||
| [file_io.md](file_io.md) | Open, Close, Read, Write, Seek |
|
||||
| [locks_examine.md](locks_examine.md) | Lock, UnLock, Examine, ExNext, ExAll |
|
||||
| [pattern_matching.md](pattern_matching.md) | ParsePattern, MatchPattern, wildcards |
|
||||
| [dos_base.md](dos_base.md) | DosLibrary structure, RootNode, BCPL heritage |
|
||||
| [file_io.md](file_io.md) | Open/Close/Read/Write/Seek, buffered I/O (FRead/SetVBuf), FileInfoBlock, practical patterns |
|
||||
| [locks_examine.md](locks_examine.md) | Lock semantics (shared/exclusive), handler discovery, Examine/ExNext/ExAll, antipatterns |
|
||||
| [pattern_matching.md](pattern_matching.md) | ParsePattern, MatchPattern, AmigaDOS wildcard syntax |
|
||||
| [process_management.md](process_management.md) | CreateNewProc, SystemTagList, Execute |
|
||||
| [packet_system.md](packet_system.md) | DosPacket, ACTION_* codes, handler protocol |
|
||||
| [filesystem.md](filesystem.md) | FFS/OFS layout, block structure |
|
||||
| [environment.md](environment.md) | GetVar/SetVar, local/global env variables |
|
||||
| [error_handling.md](error_handling.md) | IoErr, Fault, PrintFault, error codes |
|
||||
| [cli_shell.md](cli_shell.md) | CLI/Shell: pipes, redirection, scripts, ReadArgs |
|
||||
| [packet_system.md](packet_system.md) | DosPacket wire format, ACTION_* codes, handler protocol, BSTR encoding |
|
||||
| [filesystem.md](filesystem.md) | FFS/OFS block layout, root/file/data blocks, bitmap, hash function, ADF reader |
|
||||
| [environment.md](environment.md) | GetVar/SetVar, local/global/persistent env variables |
|
||||
| [error_handling.md](error_handling.md) | IoErr, Fault, PrintFault, complete error code table |
|
||||
| [cli_shell.md](cli_shell.md) | CLI/Shell: pipes, redirection, scripts, ReadArgs template parsing |
|
||||
|
|
|
|||
|
|
@ -2,17 +2,19 @@
|
|||
|
||||
# Devices — Overview
|
||||
|
||||
Amiga devices are shared libraries with an exec I/O request interface. They provide standardised access to hardware peripherals via `OpenDevice`/`CloseDevice` and `DoIO`/`SendIO` patterns.
|
||||
|
||||
## Section Index
|
||||
|
||||
| File | Description |
|
||||
|---|---|
|
||||
| [trackdisk.md](trackdisk.md) | trackdisk.device — floppy I/O |
|
||||
| [trackdisk.md](trackdisk.md) | Floppy disk DMA: MFM encoding, track format, disk geometry, track caching, direct HW access |
|
||||
| [scsi.md](scsi.md) | scsi.device / 2nd.scsi.device — hard disk I/O |
|
||||
| [serial.md](serial.md) | serial.device — RS-232 |
|
||||
| [parallel.md](parallel.md) | parallel.device — Centronics |
|
||||
| [timer.md](timer.md) | timer.device — timing and delays |
|
||||
| [audio.md](audio.md) | audio.device — DMA audio channels |
|
||||
| [keyboard.md](keyboard.md) | keyboard.device — keyboard events |
|
||||
| [gameport.md](gameport.md) | gameport.device — joystick/mouse |
|
||||
| [input.md](input.md) | input.device — event merging |
|
||||
| [serial.md](serial.md) | UART/RS-232: CIA registers, baud rate calculation, serial debugging (KPrintF) |
|
||||
| [parallel.md](parallel.md) | parallel.device — Centronics parallel port |
|
||||
| [timer.md](timer.md) | CIA timers, E-clock, VBlank: delays, ReadEClock, periodic patterns, signal-based waiting |
|
||||
| [audio.md](audio.md) | 4-channel DMA audio: hardware registers, priority allocation, double-buffering, modulation |
|
||||
| [keyboard.md](keyboard.md) | CIA-A serial handshake, raw key codes, key matrix, reset sequence, FPGA protocol notes |
|
||||
| [gameport.md](gameport.md) | gameport.device — joystick/mouse port reading |
|
||||
| [input.md](input.md) | Input handler chain: priority dispatch, event classes, key remapping, Commodities Exchange |
|
||||
| [console.md](console.md) | console.device — text terminal I/O |
|
||||
|
|
|
|||
|
|
@ -1,41 +1,216 @@
|
|||
[← Home](../README.md) · [Devices](README.md)
|
||||
|
||||
# input.device — Event Stream Merging
|
||||
# input.device — Input Event Handler Chain
|
||||
|
||||
## Overview
|
||||
|
||||
`input.device` merges events from keyboard, mouse, gameport, and timer into a single input stream that feeds Intuition. Input handlers can be installed at various priorities to filter, modify, or consume events.
|
||||
`input.device` is the central dispatcher for all input events on AmigaOS. It collects raw events from keyboard, mouse, joystick, and other sources, then distributes them through a **priority-ordered handler chain**. Intuition sits at priority 50 in this chain — custom input handlers can intercept events before or after Intuition.
|
||||
|
||||
---
|
||||
|
||||
## Handler Priority Levels
|
||||
## Handler Chain Architecture
|
||||
|
||||
| Priority | Consumer |
|
||||
|---|---|
|
||||
| 100 | System reserved |
|
||||
| 51+ | Custom high-priority handlers |
|
||||
| 50 | Intuition |
|
||||
| 20 | Console.device |
|
||||
| 0 | Default |
|
||||
```mermaid
|
||||
flowchart TD
|
||||
KB["keyboard.device"] --> ID["input.device"]
|
||||
GP["gameport.device"] --> ID
|
||||
OTHER["Other sources"] --> ID
|
||||
|
||||
ID --> H1["Handler Pri 100<br/>Commodities Exchange"]
|
||||
H1 --> H2["Handler Pri 51<br/>Custom 'hot key' handler"]
|
||||
H2 --> H3["Handler Pri 50<br/>Intuition"]
|
||||
H3 --> H4["Handler Pri 0<br/>Console.device"]
|
||||
H4 --> H5["Handler Pri -50<br/>Application handler"]
|
||||
|
||||
style H3 fill:#e8f4fd,stroke:#2196f3,color:#333
|
||||
```
|
||||
|
||||
Events flow from highest to lowest priority. Each handler can:
|
||||
- **Pass** the event through (return the event list unchanged)
|
||||
- **Modify** the event (e.g., remap keys)
|
||||
- **Consume** the event (remove from chain — lower handlers never see it)
|
||||
- **Insert** new synthetic events
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
## InputEvent Structure
|
||||
|
||||
| Code | Constant | Description |
|
||||
```c
|
||||
/* devices/inputevent.h — NDK39 */
|
||||
struct InputEvent {
|
||||
struct InputEvent *ie_NextEvent; /* linked list */
|
||||
UBYTE ie_Class; /* event type (IECLASS_*) */
|
||||
UBYTE ie_SubClass; /* subclass */
|
||||
UWORD ie_Code; /* key code, button, qualifier */
|
||||
UWORD ie_Qualifier; /* modifier keys state */
|
||||
union {
|
||||
struct {
|
||||
WORD ie_x; /* mouse X delta or absolute */
|
||||
WORD ie_y; /* mouse Y delta or absolute */
|
||||
} ie_xy;
|
||||
APTR ie_addr; /* pointer data */
|
||||
} ie_position;
|
||||
struct timeval ie_TimeStamp; /* when event occurred */
|
||||
};
|
||||
```
|
||||
|
||||
### Event Classes
|
||||
|
||||
| Class | Constant | Source |
|
||||
|---|---|---|
|
||||
| 9 | `IND_ADDHANDLER` | Add input handler |
|
||||
| 10 | `IND_REMHANDLER` | Remove input handler |
|
||||
| 11 | `IND_WRITEEVENT` | Inject an InputEvent into the stream |
|
||||
| 12 | `IND_SETTHRESH` | Set double-click threshold |
|
||||
| 13 | `IND_SETPERIOD` | Set key repeat period |
|
||||
| 14 | `IND_SETMPORT` | Set mouse port type |
|
||||
| 15 | `IND_SETMTRIG` | Set mouse trigger |
|
||||
| 16 | `IND_SETMTYPE` | Set mouse type |
|
||||
| $01 | `IECLASS_RAWKEY` | Raw key press/release |
|
||||
| $02 | `IECLASS_RAWMOUSE` | Mouse movement + buttons |
|
||||
| $04 | `IECLASS_TIMER` | Timer tick event |
|
||||
| $07 | `IECLASS_NEWPOINTERPOS` | Absolute pointer position |
|
||||
| $09 | `IECLASS_DISKINSERTED` | Disk inserted |
|
||||
| $0A | `IECLASS_DISKREMOVED` | Disk removed |
|
||||
| $0D | `IECLASS_NEWPREFS` | Preferences changed |
|
||||
|
||||
### Qualifier Bits
|
||||
|
||||
| Bit | Constant | Key |
|
||||
|---|---|---|
|
||||
| 0 | `IEQUALIFIER_LSHIFT` | Left Shift |
|
||||
| 1 | `IEQUALIFIER_RSHIFT` | Right Shift |
|
||||
| 2 | `IEQUALIFIER_CAPSLOCK` | Caps Lock |
|
||||
| 3 | `IEQUALIFIER_CONTROL` | Ctrl |
|
||||
| 4 | `IEQUALIFIER_LALT` | Left Alt |
|
||||
| 5 | `IEQUALIFIER_RALT` | Right Alt |
|
||||
| 6 | `IEQUALIFIER_LCOMMAND` | Left Amiga |
|
||||
| 7 | `IEQUALIFIER_RCOMMAND` | Right Amiga |
|
||||
| 8 | `IEQUALIFIER_NUMERICPAD` | Key is on numeric pad |
|
||||
| 9 | `IEQUALIFIER_REPEAT` | Key repeat (auto-repeat) |
|
||||
| 10 | `IEQUALIFIER_INTERRUPT` | Event from interrupt |
|
||||
| 11 | `IEQUALIFIER_MULTIBROADCAST` | Broadcast to all handlers |
|
||||
| 12 | `IEQUALIFIER_MIDBUTTON` | Middle mouse button |
|
||||
| 13 | `IEQUALIFIER_RBUTTON` | Right mouse button |
|
||||
| 14 | `IEQUALIFIER_LEFTBUTTON` | Left mouse button |
|
||||
| 15 | `IEQUALIFIER_RELATIVEMOUSE` | Mouse values are deltas |
|
||||
|
||||
---
|
||||
|
||||
## Installing a Custom Input Handler
|
||||
|
||||
```c
|
||||
#include <devices/input.h>
|
||||
#include <devices/inputevent.h>
|
||||
|
||||
/* Handler function — called from input.device context: */
|
||||
struct InputEvent * __saveds __asm MyHandler(
|
||||
register __a0 struct InputEvent *events,
|
||||
register __a1 APTR handlerData)
|
||||
{
|
||||
struct InputEvent *ev;
|
||||
for (ev = events; ev; ev = ev->ie_NextEvent)
|
||||
{
|
||||
if (ev->ie_Class == IECLASS_RAWKEY)
|
||||
{
|
||||
UWORD key = ev->ie_Code & 0x7F;
|
||||
BOOL up = ev->ie_Code & 0x80;
|
||||
|
||||
/* Example: remap F10 ($59) to Escape ($45): */
|
||||
if (key == 0x59)
|
||||
ev->ie_Code = (up ? 0x80 : 0x00) | 0x45;
|
||||
}
|
||||
}
|
||||
return events; /* pass all events to next handler */
|
||||
}
|
||||
|
||||
/* Install the handler: */
|
||||
struct Interrupt handlerInt;
|
||||
handlerInt.is_Node.ln_Type = NT_INTERRUPT;
|
||||
handlerInt.is_Node.ln_Pri = 51; /* just above Intuition (50) */
|
||||
handlerInt.is_Node.ln_Name = "MyKeyMapper";
|
||||
handlerInt.is_Data = myData;
|
||||
handlerInt.is_Code = (APTR)MyHandler;
|
||||
|
||||
struct MsgPort *inputPort = CreateMsgPort();
|
||||
struct IOStdReq *inputReq = (struct IOStdReq *)
|
||||
CreateIORequest(inputPort, sizeof(struct IOStdReq));
|
||||
OpenDevice("input.device", 0, (struct IORequest *)inputReq, 0);
|
||||
|
||||
inputReq->io_Command = IND_ADDHANDLER;
|
||||
inputReq->io_Data = (APTR)&handlerInt;
|
||||
DoIO((struct IORequest *)inputReq);
|
||||
|
||||
/* ... handler is now active ... */
|
||||
|
||||
/* Remove handler before exit: */
|
||||
inputReq->io_Command = IND_REMHANDLER;
|
||||
inputReq->io_Data = (APTR)&handlerInt;
|
||||
DoIO((struct IORequest *)inputReq);
|
||||
```
|
||||
|
||||
### Consuming Events (Blocking)
|
||||
|
||||
```c
|
||||
/* To consume an event (prevent it from reaching Intuition): */
|
||||
struct InputEvent * __saveds __asm BlockEscape(
|
||||
register __a0 struct InputEvent *events,
|
||||
register __a1 APTR data)
|
||||
{
|
||||
struct InputEvent *ev, *prev = NULL;
|
||||
for (ev = events; ev; )
|
||||
{
|
||||
if (ev->ie_Class == IECLASS_RAWKEY &&
|
||||
(ev->ie_Code & 0x7F) == 0x45) /* Escape */
|
||||
{
|
||||
/* Remove from chain: */
|
||||
if (prev)
|
||||
prev->ie_NextEvent = ev->ie_NextEvent;
|
||||
else
|
||||
events = ev->ie_NextEvent;
|
||||
ev = ev->ie_NextEvent;
|
||||
continue;
|
||||
}
|
||||
prev = ev;
|
||||
ev = ev->ie_NextEvent;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Injecting Synthetic Events
|
||||
|
||||
```c
|
||||
/* Send a fake key press: */
|
||||
struct InputEvent fake;
|
||||
fake.ie_Class = IECLASS_RAWKEY;
|
||||
fake.ie_Code = 0x45; /* Escape, key down */
|
||||
fake.ie_Qualifier = 0;
|
||||
|
||||
inputReq->io_Command = IND_WRITEEVENT;
|
||||
inputReq->io_Data = (APTR)&fake;
|
||||
inputReq->io_Length = sizeof(struct InputEvent);
|
||||
DoIO((struct IORequest *)inputReq);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commodities Exchange
|
||||
|
||||
The Commodities Exchange (`commodities.library`) provides a high-level framework for input handlers:
|
||||
|
||||
```c
|
||||
/* Define a hot key: */
|
||||
CxObj *broker = CxBroker(&newBroker, NULL);
|
||||
CxObj *filter = CxFilter("rawkey control esc"); /* Ctrl+Esc */
|
||||
CxObj *sender = CxSender(brokerPort, EVT_HOTKEY);
|
||||
AttachCxObj(filter, sender);
|
||||
AttachCxObj(broker, filter);
|
||||
ActivateCxObj(broker, TRUE);
|
||||
```
|
||||
|
||||
This is the preferred method for applications — avoids writing raw input handlers.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `devices/input.h`
|
||||
- [input_events.md](../09_intuition/input_events.md) — handler installation example
|
||||
- NDK39: `devices/input.h`, `devices/inputevent.h`
|
||||
- ADCD 2.1: input.device autodocs
|
||||
- See also: [keyboard.md](keyboard.md) — raw key code map
|
||||
- See also: [idcmp.md](../09_intuition/idcmp.md) — Intuition input handling
|
||||
- See also: [input_events.md](../09_intuition/input_events.md) — event flow through Intuition
|
||||
|
|
|
|||
|
|
@ -1,54 +1,130 @@
|
|||
[← Home](../README.md) · [Devices](README.md)
|
||||
|
||||
# serial.device — RS-232 Communication
|
||||
# serial.device — UART and RS-232 Communication
|
||||
|
||||
## Overview
|
||||
|
||||
`serial.device` provides buffered RS-232 serial I/O through the Amiga's built-in 8520 CIA UART (active-high active sense).
|
||||
`serial.device` provides access to the Amiga's built-in UART (Universal Asynchronous Receiver/Transmitter) for RS-232 serial communication. The hardware UART is implemented using **CIA-B** shift registers and custom chip DMA. Serial I/O is fundamental for debugging (serial console), modem communication, MIDI, and inter-machine networking.
|
||||
|
||||
---
|
||||
|
||||
## Opening
|
||||
## Hardware
|
||||
|
||||
```c
|
||||
struct IOExtSer *ser = (struct IOExtSer *)
|
||||
CreateIORequest(port, sizeof(struct IOExtSer));
|
||||
OpenDevice("serial.device", 0, (struct IORequest *)ser, 0);
|
||||
```
|
||||
### Serial Port Pins (DB-25)
|
||||
|
||||
---
|
||||
| Pin | Signal | Direction | Description |
|
||||
|---|---|---|---|
|
||||
| 2 | TxD | Output | Transmit data |
|
||||
| 3 | RxD | Input | Receive data |
|
||||
| 4 | RTS | Output | Request to send (active low) |
|
||||
| 5 | CTS | Input | Clear to send (active low) |
|
||||
| 6 | DSR | Input | Data set ready |
|
||||
| 7 | GND | — | Signal ground |
|
||||
| 8 | CD | Input | Carrier detect |
|
||||
| 20 | DTR | Output | Data terminal ready |
|
||||
|
||||
## Setting Parameters
|
||||
### Custom Chip Registers
|
||||
|
||||
```c
|
||||
ser->io_CtlChar = SER_DEFAULT_CTLCHAR;
|
||||
ser->io_RBufLen = 4096; /* read buffer size */
|
||||
ser->io_Baud = 9600;
|
||||
ser->io_ReadLen = 8; /* data bits */
|
||||
ser->io_WriteLen = 8;
|
||||
ser->io_StopBits = 1;
|
||||
ser->io_SerFlags = SERF_XDISABLED; /* no XON/XOFF */
|
||||
ser->IOSer.io_Command = SDCMD_SETPARAMS;
|
||||
DoIO((struct IORequest *)ser);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
| Code | Constant | Description |
|
||||
| Register | Address | Description |
|
||||
|---|---|---|
|
||||
| 2 | `CMD_READ` | Read bytes |
|
||||
| 3 | `CMD_WRITE` | Write bytes |
|
||||
| 5 | `CMD_CLEAR` | Clear buffers |
|
||||
| 8 | `CMD_FLUSH` | Abort pending requests |
|
||||
| 9 | `SDCMD_QUERY` | Get status (bytes in buffer, errors) |
|
||||
| 10 | `SDCMD_BREAK` | Send break signal |
|
||||
| 11 | `SDCMD_SETPARAMS` | Set baud/bits/parity/stop |
|
||||
| `SERDAT` | `$DFF030` | Serial data write (transmit) — 9 bits: stop bit + 8 data |
|
||||
| `SERDATR` | `$DFF018` | Serial data read (receive) — status + received byte |
|
||||
| `SERPER` | `$DFF032` | Serial period (baud rate divider) |
|
||||
| `ADKCON` | `$DFF09E` | UARTBRK bit for break signal |
|
||||
|
||||
### Baud Rate Calculation
|
||||
|
||||
```c
|
||||
/* SERPER value = (system_clock / baud_rate) - 1 */
|
||||
/* PAL system clock for serial = 3,546,895 Hz */
|
||||
/* NTSC = 3,579,545 Hz */
|
||||
|
||||
#define SERIAL_CLOCK_PAL 3546895
|
||||
#define SERIAL_CLOCK_NTSC 3579545
|
||||
|
||||
UWORD serper_from_baud(ULONG baud, BOOL isPAL) {
|
||||
return (isPAL ? SERIAL_CLOCK_PAL : SERIAL_CLOCK_NTSC) / baud - 1;
|
||||
}
|
||||
```
|
||||
|
||||
| Baud Rate | SERPER (PAL) | SERPER (NTSC) |
|
||||
|---|---|---|
|
||||
| 1200 | 2955 | 2982 |
|
||||
| 2400 | 1477 | 1491 |
|
||||
| 9600 | 368 | 372 |
|
||||
| 19200 | 184 | 185 |
|
||||
| 31250 (MIDI) | 112 | 113 |
|
||||
| 57600 | 60 | 61 |
|
||||
| 115200 | 29 | 30 |
|
||||
|
||||
---
|
||||
|
||||
## Using serial.device
|
||||
|
||||
### Open and Configure
|
||||
|
||||
```c
|
||||
struct MsgPort *serialPort = CreateMsgPort();
|
||||
struct IOExtSer *serialReq = (struct IOExtSer *)
|
||||
CreateIORequest(serialPort, sizeof(struct IOExtSer));
|
||||
|
||||
/* Open serial.device unit 0: */
|
||||
BYTE err = OpenDevice("serial.device", 0,
|
||||
(struct IORequest *)serialReq, 0);
|
||||
|
||||
/* Configure: */
|
||||
serialReq->io_SerFlags = SERF_SHARED | SERF_XDISABLED;
|
||||
serialReq->io_Baud = 9600;
|
||||
serialReq->io_ReadLen = 8; /* 8 data bits */
|
||||
serialReq->io_WriteLen = 8;
|
||||
serialReq->io_StopBits = 1;
|
||||
serialReq->io_RBufLen = 4096; /* receive buffer size */
|
||||
serialReq->IOSer.io_Command = SDCMD_SETPARAMS;
|
||||
DoIO((struct IORequest *)serialReq);
|
||||
```
|
||||
|
||||
### Write Data
|
||||
|
||||
```c
|
||||
serialReq->IOSer.io_Command = CMD_WRITE;
|
||||
serialReq->IOSer.io_Data = "Hello Serial\r\n";
|
||||
serialReq->IOSer.io_Length = 14;
|
||||
DoIO((struct IORequest *)serialReq);
|
||||
```
|
||||
|
||||
### Read Data
|
||||
|
||||
```c
|
||||
/* Check how many bytes are waiting: */
|
||||
serialReq->IOSer.io_Command = SDCMD_QUERY;
|
||||
DoIO((struct IORequest *)serialReq);
|
||||
ULONG waiting = serialReq->IOSer.io_Actual;
|
||||
|
||||
if (waiting > 0) {
|
||||
serialReq->IOSer.io_Command = CMD_READ;
|
||||
serialReq->IOSer.io_Data = buffer;
|
||||
serialReq->IOSer.io_Length = waiting;
|
||||
DoIO((struct IORequest *)serialReq);
|
||||
/* buffer now contains received data */
|
||||
}
|
||||
```
|
||||
|
||||
### Serial Debugging
|
||||
|
||||
Many developers use a null-modem cable to a terminal for kernel debugging:
|
||||
|
||||
```c
|
||||
/* Kprintf — ROM debug output via serial (ROMTags/exec): */
|
||||
/* This bypasses serial.device entirely — writes directly to SERDAT */
|
||||
void KPrintF(const char *fmt, ...); /* available in debug ROMs */
|
||||
|
||||
/* Sushi/Sashimi — capture serial debug output on another Amiga */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `devices/serial.h`
|
||||
- HRM: *Amiga Hardware Reference Manual* — Serial Port chapter
|
||||
- ADCD 2.1: serial.device autodocs
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue