Skip to content

Support for the NEC V25 microcontroller#2436

Merged
ghaerr merged 5 commits intoghaerr:masterfrom
swausd:master
Nov 3, 2025
Merged

Support for the NEC V25 microcontroller#2436
ghaerr merged 5 commits intoghaerr:masterfrom
swausd:master

Conversation

@swausd
Copy link
Copy Markdown
Contributor

@swausd swausd commented Nov 3, 2025

This PR includes all files necessary to get a minimal system with the NEC V25 microcontroller up and running. Next to the V25 (which includes among other periphery 2 serial ports, 2 timers, one interrupt controller) only 512KB RAM and 512KB ROM are needed. The system boots from ROMFS.

Capture of boot screen:
ELKS-necv25-1

swausd added 4 commits November 3, 2025 14:18
These are the changed files to get a minimal port (ROMFS, 512KB RAM, 512KB ROM, 1 serial port, 1 timer, IRQ controller) for the NEC V25 microcontroller compiling.
These are the new files to get a minimal port (ROMFS, 512KB RAM, 512KB ROM, 1 serial port, 1 timer, IRQ controller) for the NEC V25 microcontroller compiling and running.
The clock application hangs in an endless loop if no propper clock hardware is present. This hangs the init process. This change outputs the text "Sorry, clock not supported on this system." and exits. The init process can continue.
…cation

This commit adds an empty host-necv25.c to get the basic application compiling and running on the NEC V25 port.
@ghaerr
Copy link
Copy Markdown
Owner

ghaerr commented Nov 3, 2025

Hello @swausd,

Wow!! What a nice job you've done on this, very impressive. Would you like me to fix the elkscmd/Makefile merge issue for you? I modified that file in my last burst of PRs late last night.

The single only issue I see is the duplicate definition of USER_FLAGS in config.h. I can fix that after commit.

Beautiful job, the inline assembly and use of ASM include files with C ASM is very nicely done.

Thank you!

@ghaerr
Copy link
Copy Markdown
Owner

ghaerr commented Nov 3, 2025

Thanks for including the boot screen, the whole port is very cool!

I notice it's taking 1.25 seconds to boot, on ROMFS. I see the timer is ticking at 10ms also, so it seems this CPU is running on par with the speed of 8088s or so? What's the clock rate you're running?

Is 512k flash and 512k ROM standard on the V25? This seems quite a capable single-chip system!

@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 3, 2025

Hi @ghaerr, please fix what is necessary - that makes it very easy for me. And sorry I missed the USER_FLAGS change. It sneaked in again while I was struggling with git :-(

RAM and ROM is external with extra chips. The CPU has no ROM and only 256 byte of RAM in top memory. This RAM is share with the switchable register banks.

I use a clock crystal with 14.74560 MHz. Divided by 2 in the CPU the system clock is 7.3728, which gives best fits for baud rate generation. The CPU was available for 5, 8 or 10 MHz system clock.

@ghaerr
Copy link
Copy Markdown
Owner

ghaerr commented Nov 3, 2025

Very nicely done @swausd, in particular the use of extension asm in some routines with super formatting and embedded include files. I'll remember that for some of my own work :)

It sneaked in again while I was struggling with git

It seems that struggling with git is a required barrier to entry, but this was pretty painless on my end.

I'll fix the small USER_FLAGS issue with a subsequent commit, thank you!

@ghaerr ghaerr merged commit 4fcdb4e into ghaerr:master Nov 3, 2025
@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 3, 2025

Hi @ghaerr, Thank you very much for your help and the motivating kind words! Feels good to see something I have done included in this fine project.

Again thanks for your time spend for the community!

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 3, 2025

Make a photo of the whole setup please! :)
And a photo of the cpu if possible.

I still think a PCB is a perfect continuation of this project. It will allow other people to build it and wait ... they have a cool compatible OS now!

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 3, 2025

There are several NEC 25 micro controllers uPD70320. Which one do you use exactly? The 84-pin plastic LCC?

@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 3, 2025

Hi @toncho11, on special request ;-) this is my setup. Please notice, that I changed the crystal from the 14.7456 MHz (CPU Clock = 7.3728 MHz) stated above to now 22.1184 MHz (CPU clock = 11.0592 MHz). The max clock for the used uPD70320-8 I am using is 16 MHz (CPU clock = 8 MHz). So the CPU is overclocked.
necv25-front
necv25-back

@ghaerr
Copy link
Copy Markdown
Owner

ghaerr commented Nov 3, 2025

I changed the crystal from the 14.7456 MHz (CPU Clock = 7.3728 MHz) stated above to now 22.1184 MHz (CPU clock = 11.0592 MHz)

Haha - so the 1.21 second boot was getting long, huh? What's the boot time reading now?

@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 3, 2025

It's a whooping 0.94 secs now and I think it could be sped up with setting the serial port to 230400 baud ;-)

@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 3, 2025

I had to go back to 14.7456 MHz (CPU Clock = 7.3728 MHz) again. Overclocked system did not run stable. OK, so I have a chance to win at ASCII Invaders again :-)

@ishotjr
Copy link
Copy Markdown

ishotjr commented Nov 3, 2025

curious if this might suggest the NEC V20-based HP 95LX might be a viable target? 🤔

@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 3, 2025

Hi @ishotjr, I don't think the V25 port is of any special use for your hardware. My V25 system is in no way PC compatible. There is no video card functionality and no BIOS, for example.
According to the linked Wiki article the HP 95LX is not "completely" PC-compatible. So there may be other, more useful ELKS ports to start with.
Perhaps @ghaerr could explain this better.

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 3, 2025

Looks very nice! Show a video of ascii invaders if it is not too much to ask!
It will be a pity not to have it documented in a github repository with schematics on how to build it. This is how people usually do it.
It could be used for some tasks that are usually reserved for Raspberry Pi. ELKS has network connectivity via SLIP and CSLIP on serial port, so it might be used for web/ftp server or something?

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 3, 2025

The Gravity 2.0″ IPS Color Serial Display (DFRobot DFR0997) accepts commands as ASCII text sent over UART.
https://www.dfrobot.com/product-2863.html
It is a serial port configured and controlled display. It also says graphics support.
It is also quite cheap.

Command-based interface

The display is graphical, but its firmware provides a text-based command protocol.

Commands are sent as ASCII strings (terminated with \r or \n depending on mode).

Example command categories:

Font & text

FONT 16,16      // Set font width=16px, height=16px
POS 0,0         // Set cursor position at pixel (0,0)
TEXT "Hello"    // Draw the text at the current position

Colors

COLOR 255,255,255 // Set text color to white (RGB)

Screen control

CLS // Clear the screen

Graphics / images (optional)

ICON 1,10,10 // Draw icon #1 at pixel (10,10)

Here is an example program:

/*
 * ELKS Console Driver for Gravity 2.0" IPS Color Serial Display
 * UART Interface
 *
 * Features:
 * - Configures font size at startup
 * - Sends text as full ASCII commands
 * - Handles cursor position and newlines
 * - Wraps lines when necessary
 * - Minimal memory usage
 */

#include <stdio.h>
#include <string.h>
#include <stdint.h>

/* UART base port (replace with ELKS COM port) */
#define UART_BASE 0x3F8   // COM1 typical

/* Font configuration (pixels) */
#define FONT_WIDTH  8
#define FONT_HEIGHT 8

/* Screen dimensions in pixels */
#define SCREEN_WIDTH  320
#define SCREEN_HEIGHT 240

/* Cursor position in pixels */
static uint16_t cursor_x = 0;
static uint16_t cursor_y = 0;

/* Forward declarations */
void uart_init(void);
void uart_putc(char c);
void uart_puts(const char *s);
void gravity_set_font(uint8_t width, uint8_t height);
void gravity_set_cursor(uint16_t x, uint16_t y);
void gravity_write_string(const char *s);

/* Initialize UART and display */
void gravity_console_init(void) {
    uart_init();
    gravity_set_font(FONT_WIDTH, FONT_HEIGHT);
    cursor_x = 0;
    cursor_y = 0;
    gravity_set_cursor(cursor_x, cursor_y);
}

/* UART initialization for ELKS (COM1 example) */
void uart_init(void) {
    outb(UART_BASE+1, 0x00); // Disable interrupts
    outb(UART_BASE+3, 0x80); // Enable DLAB
    outb(UART_BASE+0, 12);   // Divisor low byte for 9600 baud
    outb(UART_BASE+1, 0);    // Divisor high byte
    outb(UART_BASE+3, 0x03); // 8 bits, no parity, 1 stop
    outb(UART_BASE+2, 0xC7); // Enable FIFO
}

/* Send a single character over UART */
void uart_putc(char c) {
    while (!(inb(UART_BASE+5) & 0x20)) ; // Wait TX ready
    outb(UART_BASE, c);
}

/* Send a string over UART */
void uart_puts(const char *s) {
    while (*s) uart_putc(*s++);
}

/* Set font size (pixels) on Gravity display */
void gravity_set_font(uint8_t width, uint8_t height) {
    char cmd[32];
    sprintf(cmd, "FONT %u,%u\r\n", width, height);
    uart_puts(cmd);
}

/* Set cursor position in pixels */
void gravity_set_cursor(uint16_t x, uint16_t y) {
    char cmd[32];
    sprintf(cmd, "POS %u,%u\r\n", x, y);
    uart_puts(cmd);
    cursor_x = x;
    cursor_y = y;
}

/* Write a full string to display with wrapping and newlines */
void gravity_write_string(const char *s) {
    char buffer[256];
    size_t i, line_chars;

    while (*s) {
        /* Handle newline character */
        if (*s == '\n') {
            cursor_x = 0;
            cursor_y += FONT_HEIGHT;
            if (cursor_y >= SCREEN_HEIGHT)
                cursor_y = 0;
            gravity_set_cursor(cursor_x, cursor_y);
            s++;
            continue;
        }

        /* Compute max chars per line based on font */
        line_chars = (SCREEN_WIDTH - cursor_x) / FONT_WIDTH;
        if (line_chars > sizeof(buffer)-4) line_chars = sizeof(buffer)-4;

        /* Copy chunk of text to buffer until newline or line end */
        for (i=0; i<line_chars && s[i] && s[i]!='\n'; i++) {
            buffer[i] = s[i];
        }
        buffer[i] = 0;

        /* Send as a single TEXT command */
        char cmd[300];
        sprintf(cmd, "TEXT \"%s\"\r\n", buffer);
        uart_puts(cmd);

        /* Update cursor position */
        cursor_x += FONT_WIDTH * i;
        if (cursor_x >= SCREEN_WIDTH) {
            cursor_x = 0;
            cursor_y += FONT_HEIGHT;
            if (cursor_y >= SCREEN_HEIGHT) cursor_y = 0;
            gravity_set_cursor(cursor_x, cursor_y);
        }

        s += i;
    }
}

/* Example usage */
int main(void) {
    gravity_console_init();

    gravity_write_string("Hello ELKS!\nThis is a test of Gravity 2.0 display.\nLine 3 here.");

    while (1) {
        // Main loop
    }
}

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 3, 2025

Here is a potential drop-in replacement for standard ELKS console (targets Open Watcom) for NEC 25:

/*
 * ELKS Gravity 2.0" IPS Color Serial Display Console Driver
 *
 * Features:
 * - Routes all console output to Gravity display via UART
 * - Batch TEXT commands for efficiency
 * - Handles newline and line wrapping
 * - Configurable font size
 * - Minimal memory usage (<64 KB)
 * - Cursor tracking in pixels
 */

#include <kernel.h>
#include <tty.h>
#include <stdint.h>
#include <stdio.h>

/* UART port base (COM1) */
#define UART_BASE 0x3F8

/* Display configuration */
#define FONT_WIDTH  8
#define FONT_HEIGHT 8
#define SCREEN_WIDTH  320
#define SCREEN_HEIGHT 240

/* Cursor state */
static uint16_t cursor_x = 0;
static uint16_t cursor_y = 0;

/* --- UART basic I/O --- */
static void uart_putc(char c)
{
    while (!(inb(UART_BASE+5) & 0x20)) ;  // Wait TX ready
    outb(UART_BASE, c);
}

static void uart_puts(const char *s)
{
    while (*s) uart_putc(*s++);
}

/* --- Gravity display commands --- */
static void gravity_set_font(uint8_t width, uint8_t height)
{
    char cmd[32];
    sprintf(cmd, "FONT %u,%u\r\n", width, height);
    uart_puts(cmd);
}

static void gravity_set_cursor(uint16_t x, uint16_t y)
{
    char cmd[32];
    sprintf(cmd, "POS %u,%u\r\n", x, y);
    uart_puts(cmd);
    cursor_x = x;
    cursor_y = y;
}

/* --- Console output --- */
static void gravity_write_string(const char *s)
{
    char buffer[128]; // batch buffer for TEXT command
    int i = 0;

    while (*s) {
        if (*s == '\n') {
            cursor_x = 0;
            cursor_y += FONT_HEIGHT;
            if (cursor_y >= SCREEN_HEIGHT)
                cursor_y = 0;
            gravity_set_cursor(cursor_x, cursor_y);
            s++;
            continue;
        }

        buffer[i++] = *s++;
        // Send batch if buffer full, newline next, or end of string
        if (i >= sizeof(buffer)-2 || *s == '\n' || *s == 0) {
            buffer[i] = 0;
            char cmd[256];
            sprintf(cmd, "TEXT \"%s\"\r\n", buffer);
            uart_puts(cmd);

            cursor_x += FONT_WIDTH * i;
            if (cursor_x >= SCREEN_WIDTH) {
                cursor_x = 0;
                cursor_y += FONT_HEIGHT;
                if (cursor_y >= SCREEN_HEIGHT)
                    cursor_y = 0;
                gravity_set_cursor(cursor_x, cursor_y);
            }
            i = 0;
        }
    }
}

/* --- tty_ops implementation --- */
static void gravity_conout(dev_t dev, int ch)
{
    if (ch == '\n') gravity_write_string("\n");
    else {
        char buf[2] = {ch, 0};
        gravity_write_string(buf);
    }
}

static int gravity_ioctl(struct tty *tty, int cmd, char *arg)
{
    // Only baud change or simple settings
    switch (cmd) {
        case TCSETS:
        case TCSETSW:
        case TCSETSF:
            // Currently only supports 8N1, no termios baud change
            break;
        default:
            return -EINVAL;
    }
    return 0;
}

static int gravity_write(struct tty *tty)
{
    int cnt = 0;
    while (tty->outq.len > 0) {
        int ch = tty_outproc(tty);
        gravity_conout(0, ch);
        cnt++;
    }
    return cnt;
}

static int gravity_open(struct tty *tty)
{
    return ttystd_open(tty);
}

static void gravity_release(struct tty *tty)
{
    ttystd_release(tty);
}

struct tty_ops gravity_console_ops = {
    gravity_open,
    gravity_release,
    gravity_write,
    NULL,
    gravity_ioctl,
    gravity_conout
};

/* --- Console initialization --- */
void gravity_console_init(void)
{
    // Initialize UART (COM1)
    outb(UART_BASE+1, 0x00); // disable interrupts
    outb(UART_BASE+3, 0x80); // DLAB
    outb(UART_BASE+0, 12);   // 9600 baud divisor
    outb(UART_BASE+1, 0);
    outb(UART_BASE+3, 0x03); // 8N1
    outb(UART_BASE+2, 0xC7); // enable FIFO

    // Set font and cursor
    gravity_set_font(FONT_WIDTH, FONT_HEIGHT);
    gravity_set_cursor(0, 0);

    // Register driver as console
    register_console_driver(&gravity_console_ops);
}

@ghaerr
Copy link
Copy Markdown
Owner

ghaerr commented Nov 3, 2025

Here is a potential drop-in replacement for standard ELKS console (targets Open Watcom) for NEC 25

A couple quick comments about that:

  • Kernel requires ia16-elf-gcc compiler, won't link with Watcom code
  • No sprintf function in kernel
  • Can't declare stack variables like buffer[128] - will cause kernel stack overflow
  • Console still needs read function (e.g. keyboard)

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 3, 2025

Thank you @ghaerr
A general purpose Gravity 2.0″ IPS Color Serial Display (DFRobot DFR0997) driver (when properly completed) can be used on any vintage computer. It looks compatible with my Amstrad 1640.

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 3, 2025

Actually uPD70320-8 has GPIO ports. It is a microcontroller after all. I am starting to love it :)

I am trying to understand how to control the GPIO ports from ELKS.

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 4, 2025

Yes, the NEC 25 microcontroller GPIO ports are used for serial port, but can be used for a CF card reader, etc.

The alternative for the GPIO ports is to use Arduino Nano or ATtiny85 through the serial port where on the Arduino Nano or ATtiny85 a program is running that accepts text commands. This way you can send a command "PIN1 1" for example. So you control the GPIO ports of the Arduino Nano or ATtiny85.

@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 4, 2025

@toncho11:

Show a video of ascii invaders if it is not too much to ask!

I am not a (youtube-) video guy, so no video from me, sorry.

It could be used for some tasks that are usually reserved for Raspberry Pi.

I really like to tinker with old processors, espacialy when I can salvage them from scrap. They can be soldert with an iron without the use of a microscope. But if it comes to solve a real life problem, then it's time to use ESP32s, Raspberry Pis, Picos or Arduinos.

The Gravity 2.0″ IPS Color Serial Display (DFRobot DFR0997) accepts commands as ASCII text sent over UART. It is a serial port configured and controlled display. It also says graphics support. It is also quite cheap.

What do you want to achieve? With it's 2" size its only a quarter of my perfboard. An 8088 compatible CPU as the V25 is not a good mobile device, so why not use a display with proper size?

Actually uPD70320-8 has GPIO ports. It is a microcontroller after all. I am starting to love it :) I am trying to understand how to control the GPIO ports from ELKS.

If you have any specific questions, I am happy to help.

Next I am going to look into the ELKS SPI and SD drivers to connect a SD-Card as mass storage device to my little system. It's only 3 GPIO-pins away ;-)

@toncho11
Copy link
Copy Markdown
Contributor

toncho11 commented Nov 4, 2025

@swausd

I realize that you probably do not have the time to release the schematics on how it is built. This means that I can not reproduce your hardware setup, so ... I need to lower my excitement :).

@swausd
Copy link
Copy Markdown
Contributor Author

swausd commented Nov 4, 2025

Hi @toncho11, didn't realized you want to build this right now. Do you already have the CPU at hand? The schematics is no secret. See this link; https://forum.classic-computing.de/index.php?file-download/162383/ It is functional identical to my build. Except crystal and reset chip. If you have questions, please contact me. For such a straight forward design, there is nearly no deviation possible.

Sorry, I have seen that this file is not available for the public. I will ask and come back to you.

@ghaerr
Copy link
Copy Markdown
Owner

ghaerr commented Nov 4, 2025

Next I am going to look into the ELKS SPI and SD drivers to connect a SD-Card as mass storage device

@cocus's 8018X-based SD subdriver is ssd-sd.c, with the bit-banging SPI portion in ASM in spi-8018x.S. (The other ssd_asm.S is 25 year old code from Psion and not used at all). These lower-level "subdriver" files are used by the master SSD block device driver ssd.c, which interfaces only through a few ssddev_read/write/ioctl routines.

With any luck, you might be able to use some of the 8018X SPI code. If so, we might want to talk about how to separate that out so it can be further shared. Having or creating a defined API helps in these situations.

@anchorz
Copy link
Copy Markdown

anchorz commented Nov 5, 2025

Thanks also from my side for V25 target,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants