6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat May 11, 2024 2:56 pm

All times are UTC




Post new topic Reply to topic  [ 34 posts ]  Go to page 1, 2, 3  Next
Author Message
PostPosted: Wed Feb 08, 2023 11:51 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Intro

My interest in the 6502 (and in retrocomputing generally) tends towards systems programming.

Typical system programs include the operating system and firmware, programming tools such as compilers, assemblers, I/O routines, interpreters, scheduler, loaders and linkers as well as the runtime libraries of the computer programming languages.

I know a lot of folks come to retrocomputing because of games. Sure, I like games. I still get out my NES and play Zelda from time to time, and I still love it! But it doesn't make me want to take Zelda apart and find out how it ticks, or inspire me to write my own neo-retro game, the way it does many people. Give me an operating system, though, and look out!

The thing is, though, there's not too much introductory instructional material about how to write this type of software, at least not that is 6502-specific. There's not a lot of "how to port DOS DEBUG to the 6502" or similar. The traditional method seems to boil down to studying code: either WOZMON (amazingly slick hacker code, but obscure) or some variation of Jim Butterfield's SUPERMON (pretty complex).

You can see why when I first came across Ken Skier's "Beyond Games: Systems Software for your 6502 Personal Computer" my interest was immediately piqued. This book can be found on Archive.org as a pretty nice scan. But, I hate reading online, especially if I'm trying to work on a project board at the same time. Unfortunately this is one of those retro books that (like Levinthal's "Assembly Language Subroutines") tends to be Really Expensive (TM) to get in print. I've been stalking the used book scene for a while, and finally found a used copy that was sort of affordable. It's in pretty good shape, but I did end up spending more on it than I really should have. However, I'm glad I did, because it's exactly what I wanted.

The first few chapters are a general beginner's introduction to 6502 assembly language written in a kind of (honestly, annoying) conversational style. After that it dives right into building a machine language monitor called "VISIMON." It has a modular, structured design that makes it easy to comprehend in detail, and it has a generalized I/O interface that makes it easy to port. (It's designed to be run on a variety of period computers, including the PET 2001, the Apple II, and the Atari 800. The author developed it on an Ohio Scientific Challenger 1P.) If, for example, you have been following along with Ben Eater's videos and have a working 6502 with keyboard input and LCD screen output, VISIMON is just about the perfect level of complexity for a next step.

The COVID fairy came to visit our house again this month, so that's kept me off the breadboards for a week or two. Instead, while I've been sitting around, I've been porting VISIMON over to Peanutbutter-1 and modifying it to incorporate some of the features of my own half-finished system monitor, PAGMON. I thought it might be a good idea to start a thread to document the process for other folks who might be out there googling things like "HOW TO WRITE A 6502 MACHINE LANGUAGE MONITOR DEBUGGER" without having much luck.

Part 1 - The Host System

Peanutbutter-1 is a fairly typical homebrew 6502SBC. Currently the MPU is a CMD G65SC02 running at 4MHz, but the board can be jumpered to accept a WDC 65C02. It has 32K of very fast (12ns) SRAM, and 32K of ROM. The ROM socket can be jumpered to support either 27C256 (EPROM) or 28C256 (EEPROM). I'm currently using a 150ns Atmel EEPROM, but I plan to switch it out for something faster at a later date. The I/O is handled by a 65C22 VIA, which has an LCD (in 4-bit mode) connected to PORTA and a PS/2 keyboard connected to PORTB.

The LCD deserves a little more attention. The typical Ben Eater system has one of those 2 x 16 LCD modules driven by an HD44780U LCD controller. Peanutbutter-1 was no different. As you can imagine, it's tough to get much of a user interface for an interactive program out of 32 visible characters. However, fortunately for me (in a way) at some point over the last few months I accidentally smashed my 2 x 16 display. As I was shopping around online to replace it, I noticed that the 4 x 20 LCD modules are not very much more expensive. Even better, they use exactly the same interface. You can unplug your 2 x 16 LCD and replace it with a 4 x 20 LCD and it will immediately just work. (There are a few weird things with the order of display addresses, but they are not too hard to manage!)

80 visible characters is a lot more than 32, but it's still not a lot of space for a user-interface. We're not going to be able to get something like Chad's fullscreen debugger. Luckily, VISIMON is "field" based. Its display looks like this:
Code:
               A  X  Y  P
0300 41 A     53 FE 3C 31
        †

The fields are the address field (field 0), the byte field (field 1), the character field (field 2), and the register fields (fields 3 - 6). You use some navigation keys ('<,' and '>' by default) to select fields, which changes which field the "arrow" points to. You can edit the currently selected field by typing into it, so, for example, the current address can changed by selecting it with the "arrow," and simply typing in a new one. The address can also be stepped forward or backwards by another set of navigation keys ('SPACE,' and 'RETURN' by default). The byte field displays the contents of the current address in hex, and the character field displays the same thing as an ASCII character. If the byte field is selected, you can type in hex digits to edit the contents of the current address (unless it's an address in ROM) and if the character field is selected, you can type whatever you want and it will be stored in memory at the current address as an ASCII character. The register fields contain garbage by default, but if you run a program from the monitor (by selecting the address of its first instruction and typing SHIFT+G, capital 'G' for Go) when it returns the register fields will be updated to contain their contents at the point that the called program returned to the monitor. This is useful if you want to pass a result back to the monitor in the .A register, for example.

VISIMON only needs 3 lines of display, which is one less than we have on our LCD. Unfortunately it's designed for 25 - 40 column text displays, which is still too big for our 4 x 20 LCD to hold. So PAGMON's display will have to be slightly altered:

Code:
      .A  .X  .Y  .P
      53  FE  3C  31

 0300  41 :A


Instead of an "arrow," a colon shows which field is selected. (In the example, the character field is selected.) Line 3 is empty for now, but later after adding some break routines I thought it might be useful to put a PC display there for single stepping.

This is not exactly a powerful development environment, but it does have the minimum requirements. You can look at memory, edit memory, and run code. Although it would be tedious and time consuming, it would be at least *possible* to hand load arbitrarily lengthy and complex programs and to run them.

Well, I had more to say in Part 1, but it's time to go make dinner and I don't want to lose what I've written so far, so I'll leave this here for now. Be back soon for Part 2!

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 3:08 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8433
Location: Southern California
About the display size:  Common bigger ones are 2x24, 2x40, and 4x40.  The 4x40 is interfaced like two 2x40's, with all the bits common except the E (enable), of which there are two, one for the two upper lines, and one for the two lower lines.  There used to be 1x80 and 2x80 made for the tail end of the typewriter market, so you could type a line and preview and make corrections before actually making it go on the paper; but that market is long gone and so are those LCDs.

About arrows and custom characters:  You can have up to eight custom characters at a time (16 if it's a 4x40, 8 in the upper two lines, and, eight more in the lower two lines).  I've made all kinds of custom characters for them.

My HP-71 computer shown here
Attachment:
HP71.jpg
HP71.jpg [ 99.35 KiB | Viewed 6801 times ]
has a 1x22 LCD.  I have the 80-column video monitor for it, but for portable use, when I was using it a lot, I wrote a rather full-featured text editor for use with only the built-in display, that allowed me to nimbly move around a large file.  It may seem like you're looking at your work as if through an old-fashioned keyhole; but if you make it so you can move that keyhole around your work nimbly, it works surprisingly well.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 6:27 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8178
Location: Midwestern USA
Paganini wrote:
Intro

My interest in the 6502 (and in retrocomputing generally) tends towards systems programming.

That’s two of us. :D

Quote:
Typical system programs include the operating system and firmware, programming tools such as compilers, assemblers, I/O routines, interpreters, scheduler, loaders and linkers as well as the runtime libraries of the computer programming languages.

In referring to “systems” programs, I wouldn’t include software development tools. “Systems” implies that having to do with the fundamental operation of the computer. A computer doesn’t need a development package in order to run and be of use. In my sphere, “systems” software is a bootstrap loader, kernel, BIOS and device drivers. “User” software is anything that isn’t required to bring a computer to a running state. Subsystems such as a scheduler are usually a component of the kernel. The BIOS may or may not be part of the kernel; MS-DOS is an example in which the BIOS was not part of the kernel.

Quote:
The thing is, though, there's not too much introductory instructional material about how to write this type of software, at least not that is 6502-specific. There's not a lot of "how to port DOS DEBUG to the 6502" or similar. The traditional method seems to boil down to studying code: either WOZMON (amazingly slick hacker code, but obscure) or some variation of Jim Butterfield's SUPERMON (pretty complex).

Let’s back up here for a minute. A debugger is not part of an operating system; it’s a user program. Like an assembler or a compiler, it is a software development tool that isn’t needed for the system to run. Incidentally, SuperMon is “complex” because it does more than WozMon and has a friendlier human interface. To me, WozMon is an outlier in the 6502 world from a programming perspective—SuperMon and its derivatives was the most widely-used M/L monitor in the heyday of 6502-based computers.

Quote:
You can see why when I first came across Ken Skier's "Beyond Games: Systems Software for your 6502 Personal Computer" my interest was immediately piqued. This book can be found on Archive.org as a pretty nice scan. But, I hate reading online, especially if I'm trying to work on a project board at the same time. Unfortunately this is one of those retro books that (like Levinthal's "Assembly Language Subroutines") tends to be Really Expensive (TM) to get in print.

That site has the book in downloadable PDF form, which could be printed on your Very Own Printer :D, as opposed to reading online. :D

Disclaimer: I purchased this very book in 1983, same time as when I bought my Commodore 64. At the time, I already nearly seven years experience in writing 6502 assembly language programs. I thought I would learn something about operating system design from this book. After reading it cover-to-cover, I gave it away. I still have my two Leventhal books, one of which I purchased in the late 1970s.

Quote:
The first few chapters are a general beginner's introduction to 6502 assembly language written in a kind of (honestly, annoying) conversational style. After that it dives right into building a machine language monitor called "VISIMON."...The author developed it on an Ohio Scientific Challenger 1P....

Both the OSC computer and VisiMon are 6502 outliers as well. Guys I knew who had an OSC would refer to VisiMon as “NannyMon” because of its annoying design. I was aware of at least one port of SuperMon to the OSC...for good reason!

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 6:42 am 
Offline

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 684
Location: Potsdam, DE
I've found the DIYMORE 2.42" OLED displays very good indeed. They're a seven pin SPI connection so easy to interface but as they're a pixel-addressable 128 x 64 graphics display (1309 controller) they're a bit harder work to drive. For text you'll need to include a font (vertical, just to confuse you!) and of course it takes a few more writes per character... but they are usable at eight lines of text though prettier at four lines (in both cases, of twenty characters).

They're available all over the bay; the last ones I bought were about thirteen quid each though single units are often offered at around twenty.

Somewhere I have a datasheet for the part but I can't immediately locate it :)

Neil


Attachments:
030526LV_1_991_1024x1024.jpg
030526LV_1_991_1024x1024.jpg [ 192.36 KiB | Viewed 6781 times ]
Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 11:41 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
Nice project and write-up Paganini! Just one comment: to include S in your status display, if you can find room for it! (Oh, another comment, might be even more useful, again if you can find the space: break out P into NV-BDIZC)


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 11:55 am 
Offline

Joined: Fri Dec 21, 2018 1:05 am
Posts: 1076
Location: Albuquerque NM USA
barnacle wrote:
I've found the DIYMORE 2.42" OLED displays very good indeed.

2.42" OLED display, nice! I've worked with 0.96" 128x64 I2C, even able to display animated video, but 2.42" would be very nice. I'm ordering one to try it out.
Bill


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 1:21 pm 
Offline

Joined: Sat Oct 09, 2021 11:21 am
Posts: 704
Location: Texas
Stumbled on this topic, very cool! And thanks for the mention to my full-page monitor. As of now, I have recreated something very similar to WozMon for the Apple II, and it is much easier to manage than that full-screen thingy I made (here viewtopic.php?f=4&t=7407&start=15#p97175).

I'm super interested in this topic because I want to eventually implement one on my own board. It would be a 'backup' style, where you can run most programs (including BASIC??) on an LCD so that the whole system can be run on battery power. I would also want to *try* to get it onto a 16x2 just because they are so common.

That said, can I ask some questions?

1) Why have the registers on display all the time? I'm sure they are useful, but I don't even have that feature in my own monitor and have never even thought of using it honestly. IDK.

2) So the "character" field is the ASCII equivalent of the "byte" field? I think using the colon as a selector is a super awesome idea. Very neat and compact. BTW, I also like your idea about 'hotkeys', like Shift+G for "Go" without having to type that directly. Interesting.

3) Is the plan to always just show one byte at a time? I'm sure you can fit at least 4 bytes on a row (with the address). That was my plan, something like:

Code:
C000 A9FF8D00 ____


ASCII characters would be in the _ space. I have found while using the monitor that displaying multiple bytes at a time was helpful for me at least.

My first vision as to have the top line as "display" and the bottom line as "command". So it would function very similar to my current monitor, but only display one line at a time.

This is neat, thanks for sharing, and yes I look forward to Part 2!

Chad


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 4:53 pm 
Offline

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 684
Location: Potsdam, DE
I found the technical PDF for the display; attached.

The initialisation code I use (In C, but obvious and linear) - ssd1309_write_command(xx) does exactly what you'd expect and sends the byte to the display via SPI
Code:
void ssd1309_init (void)
{
   // initialise the display for further use
   ssd1309_CS_HI                  // deselect oled
   ssd1309_RES_LO                  // reset
   HAL_Delay (10);                  // hold
   ssd1309_RES_HI
   HAL_Delay (100);               // wait for screen to boot
   ssd1309_write_command (0xae);      // display off
   ssd1309_write_command (0x20);       // Set Memory Addressing Mode
   ssd1309_write_command (0x02);       // 00b,Horizontal Addressing Mode; 01b,Vertical Addressing Mode;
                                      // 10b,Page Addressing Mode (RESET); 11b,Invalid
   ssd1309_write_command (0xB0);       // Set Page Start Address for Page Addressing Mode,0-7
   ssd1309_write_command (0xC8);       // Set COM Output Scan Direction
   //ssd1309_write_command (0x2e);      // disable horizontal scroll
   ssd1309_write_command (0xa0);
   ssd1309_write_command (0x02);       // ---set low column address
   ssd1309_write_command (0x10);       // ---set high column address (low nibbles so 0x00)
   ssd1309_write_command (0x40);       // --set start line address - CHECK
   ssd1309_write_command (0x81);
   ssd1309_write_command (0xff);      // contrast max
   ssd1309_write_command (0xA1);       //--set segment re-map 0 to 127 - CHECK
   ssd1309_write_command (0xA6);       //--set normal color
   ssd1309_write_command (0xA8);       //--set multiplex ratio(1 to 64) - CHECK
   ssd1309_write_command (0x3F);       //
   ssd1309_write_command (0xA4);       // 0xa4,Output follows RAM content;0xa5,Output ignores RAM content
   ssd1309_write_command (0xD3);       // -set display offset - CHECK
   ssd1309_write_command (0x00);       // -not offset
   ssd1309_write_command (0xD5);       // --set display clock divide ratio/oscillator frequency
   ssd1309_write_command (0xF0);       // --set divide ratio
   ssd1309_write_command (0xD9);       // --set pre-charge period
   ssd1309_write_command (0x22);       //
   ssd1309_write_command (0xDA);       // --set com pins hardware configuration - CHECK
   ssd1309_write_command (0x12);
   ssd1309_write_command (0xDB);       // --set vcomh
   ssd1309_write_command (0x20);       // 0x20,0.77xVcc
   ssd1309_write_command (0x8D);       // --set DC-DC enable
   ssd1309_write_command (0x14);       //
   ssd1309_write_command (0xaf);      // and turn display on
}


And a font using an 5 by 12 character in a 6 by 16 cell. Individual memory cells in the 1309 are vertical eight bits, so these font data match that. I use it to fill an area of memory to match the memory in the 1309 and then dump the lot in one hit to avoid video artifacts.

Code:
// each character occupies six wide by 16 deep cell, including space around it
// there are expected to be 20 characters across (the original display device
// was 120 dots wide; this display is 128 so we inset 3 bytes for some elbow
// room). Note that characters 0x00-0x1f are not included

const uint8_t fonts[960] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // ' '
   0x00, 0x00, 0x00, 0x00, 0x0B, 0xF8, 0x00, 0x00, 0x00, 0x00,    // '!'
   0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00,    // '"'
   0x02, 0x20, 0x0F, 0xF8, 0x02, 0x20, 0x0F, 0xF8, 0x02, 0x20,    // '#'
   0x04, 0x60, 0x08, 0x90, 0x1F, 0xF8, 0x09, 0x10, 0x06, 0x20,    // '$'
   0x06, 0x10, 0x01, 0x28, 0x04, 0x90, 0x0A, 0x40, 0x04, 0x30,    // '%'
   0x07, 0x30, 0x08, 0xC8, 0x09, 0x30, 0x06, 0x00, 0x09, 0x00,    // '&'
   0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00,    // '''
   0x00, 0x00, 0x00, 0x00, 0x0F, 0xF8, 0x10, 0x04, 0x00, 0x00,    // '('
   0x00, 0x00, 0x00, 0x00, 0x10, 0x04, 0x0F, 0xF8, 0x00, 0x00,    // ')'
   0x01, 0x10, 0x00, 0xA0, 0x03, 0xF8, 0x00, 0xA0, 0x01, 0x10,    // '*'
   0x01, 0x00, 0x01, 0x00, 0x07, 0xC0, 0x01, 0x00, 0x01, 0x00,    // '+'
   0x00, 0x00, 0x2C, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00,    // ','
   0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,    // '-'
   0x0C, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // '.'
   0x0C, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x38, 0x00, 0x00,    // '/'
   0x07, 0xF0, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0xF0,    // '0'
   0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x0F, 0xF8, 0x00, 0x00,    // '1'
   0x0C, 0x10, 0x0A, 0x08, 0x09, 0x08, 0x08, 0x88, 0x08, 0x70,    // '2'
   0x04, 0x10, 0x08, 0x08, 0x08, 0x88, 0x08, 0x88, 0x07, 0x70,    // '3'
   0x03, 0x00, 0x02, 0xC0, 0x02, 0x30, 0x0F, 0xF8, 0x02, 0x00,    // '4'
   0x04, 0xF8, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x07, 0x88,    // '5'
   0x07, 0xF0, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x07, 0x10,    // '6'
   0x00, 0x08, 0x0E, 0x08, 0x01, 0x88, 0x00, 0x68, 0x00, 0x18,    // '7'
   0x07, 0x70, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x07, 0x70,    // '8'
   0x04, 0x70, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x07, 0xF0,    // '9'
   0x0C, 0xC0, 0x0C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // ':'
   0x00, 0x00, 0x2C, 0xC0, 0x1C, 0xC0, 0x00, 0x00, 0x00, 0x00,    // ';'
   0x01, 0x00, 0x02, 0x80, 0x04, 0x40, 0x08, 0x20, 0x00, 0x00,    // '<'
   0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80,    // '='
   0x08, 0x20, 0x04, 0x40, 0x02, 0x80, 0x01, 0x00, 0x00, 0x00,    // '>'
   0x00, 0x10, 0x00, 0x08, 0x0B, 0x08, 0x00, 0x88, 0x00, 0x70,    // '?'
   0x01, 0xE0, 0x02, 0x10, 0x04, 0xC8, 0x05, 0x28, 0x02, 0xF0,    // '@'
   0x0F, 0x00, 0x02, 0xE0, 0x02, 0x18, 0x02, 0xE0, 0x0F, 0x00,    // 'A'
   0x0F, 0xF8, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x07, 0x70,    // 'B'
   0x07, 0xF0, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x04, 0x10,    // 'C'
   0x0F, 0xF8, 0x08, 0x08, 0x08, 0x08, 0x04, 0x10, 0x03, 0xE0,    // 'D'
   0x0F, 0xF8, 0x08, 0x88, 0x08, 0x88, 0x08, 0x08, 0x00, 0x00,    // 'E'
   0x0F, 0xF8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x08, 0x00, 0x00,    // 'F'
   0x07, 0xF0, 0x08, 0x08, 0x08, 0x88, 0x04, 0x88, 0x0F, 0x90,    // 'G'
   0x0F, 0xF8, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x0F, 0xF8,    // 'H'
   0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // 'I'
   0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0xF8, 0x00, 0x00,    // 'J'
   0x0F, 0xF8, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x10, 0x0C, 0x08,    // 'K'
   0x0F, 0xF8, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00,    // 'L'
   0x0F, 0xF8, 0x00, 0x70, 0x01, 0x80, 0x00, 0x70, 0x0F, 0xF8,    // 'M'
   0x0F, 0xF8, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0F, 0xF8,    // 'N'
   0x07, 0xF0, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0xF0,    // 'O'
   0x0F, 0xF8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70,    // 'P'
   0x07, 0xF0, 0x08, 0x08, 0x0A, 0x08, 0x0C, 0x08, 0x17, 0xF0,    // 'Q'
   0x0F, 0xF8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x0F, 0x70,    // 'R'
   0x04, 0x70, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x07, 0x10,    // 'S'
   0x00, 0x08, 0x00, 0x08, 0x0F, 0xF8, 0x00, 0x08, 0x00, 0x08,    // 'T'
   0x07, 0xF8, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0xF8,    // 'U'
   0x00, 0x38, 0x03, 0xC0, 0x0C, 0x00, 0x03, 0xC0, 0x00, 0x38,    // 'V'
   0x00, 0xF8, 0x0F, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0xF8,    // 'W'
   0x0E, 0x38, 0x01, 0x40, 0x00, 0x80, 0x01, 0x40, 0x0E, 0x38,    // 'X'
   0x00, 0x38, 0x00, 0x40, 0x0F, 0x80, 0x00, 0x40, 0x00, 0x38,    // 'Y'
   0x0C, 0x08, 0x0B, 0x08, 0x08, 0x88, 0x08, 0x68, 0x08, 0x18,    // 'Z'
   0x00, 0x00, 0x1F, 0xFC, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00,    // '['
   0x00, 0x00, 0x00, 0x38, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,    // '\'
   0x00, 0x00, 0x10, 0x04, 0x1F, 0xFC, 0x00, 0x00, 0x00, 0x00,    // ']'
   0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10,    // '^'
   0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,    // '_'
   0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    // '`'
   0x06, 0x00, 0x09, 0x40, 0x09, 0x40, 0x09, 0x40, 0x0F, 0x80,    // 'a'
   0x0F, 0xF8, 0x08, 0x40, 0x08, 0x40, 0x08, 0x40, 0x07, 0x80,    // 'b'
   0x07, 0x80, 0x08, 0x40, 0x08, 0x40, 0x08, 0x40, 0x04, 0x80,    // 'c'
   0x07, 0x80, 0x08, 0x40, 0x08, 0x40, 0x08, 0x40, 0x0F, 0xF8,    // 'd'
   0x07, 0x80, 0x09, 0x40, 0x09, 0x40, 0x09, 0x40, 0x05, 0x80,    // 'e'
   0x0F, 0xF0, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x10,    // 'f'
   0x27, 0x80, 0x28, 0x40, 0x28, 0x40, 0x28, 0x40, 0x1F, 0xC0,    // 'g'
   0x0F, 0xF8, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x0F, 0x80,    // 'h'
   0x00, 0x00, 0x00, 0x00, 0x0F, 0xC8, 0x00, 0x00, 0x00, 0x00,    // 'i'
   0x20, 0x00, 0x20, 0x00, 0x1F, 0xC8, 0x00, 0x00, 0x00, 0x00,    // 'j'
   0x0F, 0xF8, 0x01, 0x00, 0x02, 0x80, 0x04, 0x40, 0x08, 0x00,    // 'k'
   0x00, 0x00, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00,    // 'l'
   0x0F, 0xC0, 0x00, 0x40, 0x0F, 0x80, 0x00, 0x40, 0x0F, 0x80,    // 'm'
   0x0F, 0xC0, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x0F, 0x80,    // 'n'
   0x07, 0x80, 0x08, 0x40, 0x08, 0x40, 0x08, 0x40, 0x07, 0x80,    // 'o'
   0x3F, 0xC0, 0x08, 0x40, 0x08, 0x40, 0x08, 0x40, 0x07, 0x80,    // 'p'
   0x07, 0x80, 0x08, 0x40, 0x08, 0x40, 0x08, 0x40, 0x3F, 0xC0,    // 'q'
   0x0F, 0xC0, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00,    // 'r'
   0x04, 0x80, 0x09, 0x40, 0x0A, 0x40, 0x04, 0x80, 0x00, 0x00,    // 's'
   0x07, 0xF0, 0x08, 0x40, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00,    // 't'
   0x07, 0xC0, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0F, 0xC0,    // 'u'
   0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x03, 0x00, 0x00, 0xC0,    // 'v'
   0x01, 0xC0, 0x0E, 0x00, 0x01, 0xC0, 0x0E, 0x00, 0x01, 0xC0,    // 'w'
   0x0C, 0xC0, 0x03, 0x00, 0x03, 0x00, 0x0C, 0xC0, 0x00, 0x00,    // 'x'
   0x20, 0x00, 0x23, 0xC0, 0x1C, 0x00, 0x04, 0x00, 0x03, 0xC0,    // 'y'
   0x0C, 0x40, 0x0A, 0x40, 0x09, 0x40, 0x08, 0xC0, 0x00, 0x00,    // 'z'
   0x00, 0x00, 0x00, 0x80, 0x0F, 0x78, 0x10, 0x04, 0x00, 0x00,    // '{'
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00,    // '|'
   0x00, 0x00, 0x10, 0x04, 0x0F, 0x78, 0x00, 0x80, 0x00, 0x00,    // '}'
   0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10,    // '~'
   0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38,    // 'degree'
   };


Neil


Attachments:
DM-OLED24-630 Datasheet.pdf [507.29 KiB]
Downloaded 38 times
Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 5:16 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
BigDumbDinosaur wrote:
That site has the book in downloadable PDF form, which could be printed on your Very Own Printer™ , as opposed to reading online.

I do do that sometimes, but it's more expensive than you might think. And, as you say:
Quote:
Disclaimer: I purchased this very book in 1983, same time as when I bought my Commodore 64. At the time, I already nearly seven years experience in writing 6502 assembly language programs. I thought I would learn something about operating system design from this book. After reading it cover-to-cover, I gave it away. I still have my two Leventhal books, one of which I purchased in the late 1970s.

In case I didn't make this clear up above: VISIMON is not a wildly sexy program. "Beyond Games" is not a book you're going to want to live with the way you do with Leventhal. It is good for this project for two reasons:

1. The code is compact, modular, and straightforward, making it easier to understand than SUPERMON for someone who does *not* already have 7 years of experience developing for the 6502.

2. The UI is easy to adapt to a small display.

Can we improve it? You bet!

This might be a god point to bring up my own 6502 assembly language expertise. I would characterize myself as "early intermediate." My goal for this thread is to produce the kind of guide that I wanted a few months ago when I was still an "advanced beginner." My code will be honest, and it will work; but it won't be slick. When I share, for example, a lengthy case-selection block, I fully expect an expert to chime in with a description how to get the same effect more efficiently with a jump table. That's good! That will give the audience (and me!) paths for future expansion.
BigEd wrote:
Just one comment: to include S in your status display, if you can find room for it! (Oh, another comment, might be even more useful, again if you can find the space: break out P into NV-BDIZC)
sburrow wrote:
3) Is the plan to always just show one byte at a time? I'm sure you can fit at least 4 bytes on a row (with the address).

Both good suggestions! Some of this functionality is already "on the bench", because it was already in PAGMON, and integrating them into VISIMON isn't too hard. I think that may .S could go where .P is in the default UI. .PC could go in the upper left, and then line 3 could be the status display. Things start getting a little cramped, but I think it might be doable.

sburrow wrote:
1) Why have the registers on display all the time? I'm sure they are useful, but I don't even have that feature in my own monitor and have never even thought of using it honestly. IDK.

That's how it is in the reference design. Since VISIMON has no single step mode, they are useless except for passing results from programs back to the monitor. In fact, the register images are not initialized, and they hold random garbage unless and until you call and return from a program. This seems like an obvious and immediate area for improvement. :)

Quote:
2) So the "character" field is the ASCII equivalent of the "byte" field? I think using the colon as a selector is a super awesome idea. Very neat and compact. BTW, I also like your idea about 'hotkeys', like Shift+G for "Go" without having to type that directly. Interesting.

Thanks! I also think the colon is a nice and tidy solution to that problem. SHIFT+G is in the original design. My first thought was "why not just 'g'?" Then I left it as it was. SHIFT+G makes it less likely that you will jump to a bad address by accidentally bumping the 'g' key.

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Last edited by Paganini on Thu Feb 09, 2023 8:13 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 09, 2023 8:05 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Part 1: part 2!

The software environment

The monitor needs to call some external routines for handling I/O. VISIMON uses a couple of generic input and output functions. These are hooked to some fairly complex machine-specific I/O routines by means of software vectors. These machine specific code blocks are included in the appendices of the book, but there isn't one for "4 x 20 LCD and PS/2 keyboard." :wink:

PAGMON uses one input routine, and six output routines. Here they are:
Code:
_KB_poll

_LCD_instruction
_LCD_clear_screen
_LCD_set_cursor
_LCD_strout
_LCD_chrout
_LCD_hexout
My keyboard driver uses page 2 of RAM as a scan-code buffer. It's a 256 byte circular FIFO (queue). Keyboard input is interrupt driven and managed by the VIA. Whenever there's a keyboard interrupt the ISR transfers the scan-code to the buffer, updates the pointers, and returns. This should be pretty familiar to anyone who follows along with Ben Eater. The difference here is that the ISR doesn't do any processing of the scan-code. That's what _KB_poll is for.

_KB_poll checks if there's a scan-code in the buffer waiting to be processed. If not, it returns NULL (NULL = $00). If there is a scan-code that needs processing, _KB_poll calls _KB_dispatch. _KB_dispatch converts the scan-code into a key-code and returns it to _KB_poll. A key-code is an ASCII character code or control code, as much as possible. For things that don't have ASCII control codes (such as the cursor keys) I use bespoke codes borrowed from unused areas of the keymap. _KB_dispatch also handles things like key release codes, extended character codes. There are some scan-codes the driver is set to ignore; _KB_poll returns NULL for those codes too.

Your keyboard driver might be different. To make PAGMON portable, what we need is an interface routine.
Code:
; Returns the next keycode from the keyboard driver (or NULL if no keycode waiting) in .A
_PM_GetChar:
    jsr         _KB_poll        ; replace this with system-specific code
    rts
As long as _PM_GetChar returns a keycode that PAGMON understands, PAGMON will work; it doesn't need to know how the keycode was produced.

We could use this same kind of abstraction for the output routines.
Code:
; Writes the character in .A to the display
_PM_PutChar:
    jsr         _LCD_chrout     ; replace this with system-specific code
    rts
We could then use _PM_PutChar to make a whole set of display routines for PAGMON, like _PM_PutHex that takes a byte and displays it as two hexadecimal digits. In fact, maybe we should. I didn't do that, though. Since I've been working on my LCD library for a year or so it is already fairly robust. All the display routines needed by PAGMON already exist there, and so I just called them directly. This makes PAGMON less portable, but also easier to get working. Some of the routines in my LCD library are from when I was just learning 6502 assembly language. I would write them differently now but they're well tested and I trust them to work, which makes writing and debugging new code easier and faster.

Another reason for using my existing LCD routines is that it gives me the chance to talk about how I modified them to work with the 4 x 20 LCD. Internally, the 4 x 20 LCD is a little bit strange. A single HD44780U can only display one or two 8-character lines. That means to get an 80 character display the manufacturers of 4 x 20 LCDs had to combine at least 5 of them. The way this works out in practice is that display line 3 is actually the second half of display line 1, and display line 4 is actually the second half of display line 2. This means that the display lines are interlaced. If you write off the end of line 1 you will appear not on line 2, but on line 3. Going off the end of line 3 brings you back to line 2, and from line 2 you go to line 4. Yikes!

Furthermore, the addresses of the screen locations are not quite what you might expect. Since line 1 and line 3 are actually one long (40 character) line, you might anticipate that line 3 would comprise addresses 20 through 39. This is, in fact, the case. It would also make sense for line 2 to start at address 40. And it does, kind of. But not at decimal 40. It starts at *hex* 40, which is to say decimal 64. This caused me to make a fair few brain-twisty errors as I accidentally switched bases while counting. Eventually, I got fed up with making arithmetic errors while doing bounds checks and I came up with a way to brute force the problem: I made a little state machine.

LCD_cursor is a variable in Zero Page. It ranges in value from 0 to 79 (decimal). Whenever it is updated there's a bounds check, so if it's ever greater than 79 ($4F) it's reset to 0 and if it's ever less than 0 it's set to 79. This lets me think of the display as an array of consecutive bytes. To translate this virtual cursor position into an actual LCD address I use it as an index into this big lookup table:
Code:
;   Lookup table for translating cursor positions into LCD addresses

LCD_address:
LCD_line1:   .byte   $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0A,$0B,$0C,$0D,$0E,$0F,$10,$11,$12,$13   
LCD_line2:   .byte   $40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4A,$4B,$4C,$4D,$4E,$4F,$50,$51,$52,$53   
LCD_line3:   .byte   $14,$15,$16,$17,$18,$19,$1A,$1B,$1C,$1D,$1E,$1F,$20,$21,$22,$23,$24,$25,$26,$27   
LCD_line4:   .byte   $54,$55,$56,$57,$58,$59,$5A,$5B,$5C,$5D,$5E,$5F,$60,$61,$62,$63,$64,$65,$66,$67
Here's what a typical use looks like:
Code:
    ldy     #Cursor_Line3   ; A handy constant (In this case, $28 -> .Y)
    sty     LCD_cursor      ; Update cursor state
    lda     LCD_address,y   ; Translate cursor position into LCD address (in this case, $14 -> .A)
    jsr     _LCD_set_cursor
_LCD_set_cursor simply takes the address and uses _LCD_instruction to tell the LCD to reposition its internal cursor to that location. The instruction set for the HD44780 can be found in this data sheet:
Attachment:
HD44780.pdf [322.07 KiB]
Downloaded 42 times
This big lookup table illustrates one of the main choices you have to make when programming in assembly language. Usually for any particular problem there are at least two ways to solve it. Solution one is with a lookup table. Lookup tables are fast, obvious, and easy to implement. The downside is that they take up space. My solution devotes 80 bytes of ROM and a zero page variable to tracking the cursor position. Solution two is algorithmic. Whenever I need to move the cursor I could calculate the new position based on the old one. I actually did this in a routine called _LCD_get_line that I wrote back before I made the Big Table O' Addresses. I did a bit test of the cursor address to see if it was in an odd or even line, and then a magnitude test to see if it was in the front half or the back half. I'm still using that routine - because it works I haven't updated it to use the new system. Calculating things is slower than lookup tables but more compact. Sometimes there's a third solution that is both fast and compact. They are usually very hard to understand if you're reading one written by someone else. They also take a long time to discover, unless you're a 1337 hacker. They make you feel really good when you figure them out, though! :mrgreen:

It's been a few weeks since I made the big LCD address lookup table. While using it I thought of yet another way to manage the display that I think might be even better. That is to use an actual frame buffer. I could have an 80-byte array somewhere in RAM that corresponds to the 80 screen locations on the LCD. All of PAGMON's output would be writing ASCII characters to the frame buffer. Updating the display would simply involve dumping the frame-buffer to the LCD, one character at a time. There would be no need for meticulously checking boundaries, and all the fussy address translation could be done in this one routine. This would also make PAGMON more portable. The only downside I see is that the LCD is kind of slow and might not be able to keep up with having its entire screen redrawn rapidly and constantly. I really like this idea, though, so I might put it together this evening and see how it works! :)

That about covers the hardware and software that PAGMON will need to interface. Part 2 will be about the over-all structure of the program, and have some actual source code!

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 10, 2023 12:50 am 
Offline
User avatar

Joined: Wed Feb 13, 2013 1:38 pm
Posts: 586
Location: Michigan, USA
Paganini wrote:
... I thought of yet another way to manage the display that I think might be even better. That is to use an actual frame buffer. I could have an 80-byte array somewhere in RAM that corresponds to the 80 screen locations on the LCD. All of PAGMON's output would be writing ASCII characters to the frame buffer. Updating the display would simply involve dumping the frame-buffer to the LCD, one character at a time. There would be no need for meticulously checking boundaries, and all the fussy address translation could be done in this one routine. This would also make PAGMON more portable. The only downside I see is that the LCD is kind of slow and might not be able to keep up with having its entire screen redrawn rapidly and constantly. I really like this idea, though, so I might put it together this evening and see how it works! :)

If it may be of any help... I tested an LCD "frame buffer" method awhile back on a PIC uC and it worked quite well but I refreshed the display from the buffer one character at a time via an interrupt driver. In my main program I found it easy to set VTAB and HTAB position and to write LCD data using the buffer (no LCD inter-character write delays). Here's an excerpt of the interrupt driver (C) which I hope you may find useful.

Regards, Mike

Code:
  /******************************************************************
   *                                                                *
   ******************************************************************/

   void interrupt()             // 250 usec (500 cycle) interrupts
   { static char line = 0xC0;   // sync buffer index & DDRAM address
     static char bndx = 0;      //  "

     pir1.TMR2IF = 0;           // clear TMR2 interrupt flag

    /*                                                              *
     *  write 32 data characters and two "DDRAM address" commands,  *
     *  one write per 250-usec interrupt, to refresh the buffered   *
     *  display once every 8.5-msecs (~117-Hz).                     *
     *                                                              */

     if(line & 0x80)            // if "new line"
       PutCMD(line ^= 0x40);    // set DDRAM address (0x80 or 0xC0)
     else                       // not "new line" so
       PutDAT(lcd[bndx++]);     // refresh display, bump index
  // if(bndx == 80)             // for 4x20 display
  //   bndx = 0;                //
  // if(bndx % 40 == 0)         //
  //   line ^= 0x80             // toggle b7 pseudo "new line" flag
     bndx &= 31;                // pseudo %32, 0..31 inclusive
     if((bndx & 15) == 0)       // if new line (0 or 16)
       line ^= 0x80;            // toggle b7 pseudo "new line" flag
   }

It was also kind of neat to "flash" individual LCD characters as in this example; Buffered LCD Display Video

Code:
    /****************************************************************
     *  main loop                                                   *
     ****************************************************************/

     while(1)                       //
     {                              //
       delay_ms(250);               //
       delay_ms(250);               //

       lcd[line1+10] ^= (':'^' ');  // flash ':' characters
       lcd[line1+13] ^= (':'^' ');  //  "

       delay_ms(250);               //

       count++;                     //
       huns = count/100;            //
       tens = (count/10)%10;        //
       ones = count%10;             //
      /*
       *  updating the display takes 9 cycles (4.5 us)
       */
       lcd[line1+2] = huns|'0';     //
       lcd[line1+3] = tens|'0';     //
       lcd[line1+4] = ones|'0';     //

       delay_ms(240);               //

       lcd[line1+10] ^= (':'^' ');  // flash ':' characters
       lcd[line1+13] ^= (':'^' ');  //  "
     }                              //
   }


Attachments:
DDRAM.png
DDRAM.png [ 24.89 KiB | Viewed 6657 times ]
Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 10, 2023 2:37 am 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Michael wrote:
If it may be of any help... I tested an LCD "frame buffer" method awhile back on a PIC uC and it worked quite well but I refreshed the display from the buffer one character at a time via an interrupt driver. In my main program I found it easy to set VTAB and HTAB position and to write LCD data using the buffer (without LCD inter-character write delays). Here's an excerpt of the interrupt driver (C) which I hope you may find useful.
Very cool! I like your memory map. That's a really good way to visualize the LCD address space.

I actually went ahead and implemented a frame-buffer this evening. It only took a few hours, mostly removing LCD cruft and renaming some things. I like it a lot! It has made the code cleaner and more modular. The monitor only needs two wrapper routines to handle I/O now; everything else goes to the framebuffer. Mine isn't as fancy as yours though; I treated the framebuffer like an 80 character array and used a single pointer to index it. The frame refresh routine just gets called whenever something changes. The LCD doesn't seem to have any trouble keeping up though. It's not flickering, or anything.

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 10, 2023 3:54 am 
Offline
User avatar

Joined: Wed Feb 13, 2013 1:38 pm
Posts: 586
Location: Michigan, USA
Paganini wrote:
Michael wrote:
If it may be of any help... I tested an LCD "frame buffer" method awhile back on a PIC uC and it worked quite well but I refreshed the display from the buffer one character at a time via an interrupt driver. In my main program I found it easy to set VTAB and HTAB position and to write LCD data using the buffer (without LCD inter-character write delays). Here's an excerpt of the interrupt driver (C) which I hope you may find useful.

Very cool! I like your memory map. That's a really good way to visualize the LCD address space.

At the risk of going off-topic... I think it's interesting how you might take advantage of the HD44780 controller protocol and put those "unused" locations in DDRAM address space to use. For example, I prototyped a microcontroller based Serial LCD/LED Backpack that could drive either an LCD display or a 4-digit 7-segment LED display. Basically, each Backpack could be set to display four of the "unused" DDRAM memory locations if you plugged in a 4-digit display instead of an LCD display. Another "unused" location was used for brightness control. While I didn't take the concept further, I couldn't help wondering if other "unused" locations might be used for 8-bit output ports, a real-time clock, 8-bit input ports, a serial pass-through with circular buffers to a Terminal, etc. I've attached a picture of a prototype of 3 backpacks below. Two driving LED displays and one driving an LCD display.

Cheerful regards...


Attachments:
K8LH Ser LCD-LED Mockup.JPG
K8LH Ser LCD-LED Mockup.JPG [ 1.29 MiB | Viewed 6638 times ]
Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 11, 2023 7:13 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Part 2

Program structure

The main loop of VISIMON looks like this
Code:
VISIMON         PHP
LOOP            JSR     DISPLAY
                JSR     UPDATE
                CLC
                BCC     LOOP
Ken Skier wrote:
It places the monitor display on the screen. It then updates the information in that display by getting a keystroke from the user and performing an action based on that keystroke. It does this over and over.
A routine that continually gets input and then does something in response to the input is usually called a dispatcher. Every interactive program must have at least one dispatcher to respond to the user's input. VISIMON's UPDATE subroutine is a (somewhat convoluted) dispatcher.

Upthread, Chad asked if VISIMON could display more than one byte of memory at a time. The answer is a qualified 'yes.' VISIMON is accompanied by an external HEXDUMP utility. It can be assigned a hotkey in VISIMON's dispatcher so that it can be called from within VISIMON. VISIMON was designed for use on a TV Typewriter style display with at least 20 lines. Since the display routine only uses the top 3 lines, the rest of the screen was available to hold a canonical hex dump. This is not the case with a 4 x 20 LCD, so PAGIMON will have two display modes: REGISTER MODE (which is the modified VISIMON display from earlier) and HEX MODE, which will display 16 bytes of memory (4 per line) along with an ASCII interpretation of each byte. This means that user input will have to be interpreted in two different contexts: for example, in register mode the cursor keys will change which field is selected and advance (or retreat) the currently selected memory address. In hex mode the cursor keys will scan forward or backward through memory one byte, or sixteen bytes at a time.

Therefore, PAGIMON will have two dispatchers and a way to keep track of what mode it's in. To track the display mode I'll use a memory variable called PM_UI_mode. Since my keyboard scan-code buffer takes up all of page 2, I'll put PAGIMON's data in page 3. Here's the outline:
Code:
PM_REG_MODE     = %00000000
PM_HEX_MODE     = %00000001

PM_UI_mode      = $0359

_PM_main:
.loop:
    lda     PM_UI_mode
    cmp     #PM_REG_MODE
    beq     .regmode
    cmp     #PM_HEX_MODE
    beq     .hexmode

;   If PM_UI_mode gets corrupted somehow, fall through (default) to register mode

.regmode:
    jsr     _PM_regMode_display
    jsr     _PM_regMode_update
    clc
    bcc     .loop

.hexmode:
    jsr     _PM_hexMode_display
    jsr     _PM_hexMode_update
    clc
    bcc     .loop
The author of VISIMON tried to use relative branches as much as possible to make the resulting machine code more easy to relocate. I didn't see any reason not to do that, so I carried that practice over into PAGIMON. The labels that begin with dots, such as .loop, are local labels in my assembler (vasm). They are valid only between global labels (ones that don't begin with dots). If you need to refer to a local label from somewhere outside of its scope you can access it with a dot notation like structs in C: "_PM_main.hexmode." The manual suggests that, while this is allowed, it is not recommended. :lol:

Side note:
I could make the mode test more concise:
Code:
    lda     PM_UI_mode
    beq     .regmode
    bne     .hexmode
However, using the cmp instruction makes it easier to add new modes later on (a "dissasembly" mode for example) and it lets the code that tests the mode flags be agnostic about their values so that I can adjust the mode flags in the future without having to adjust the code.

VISIMON immediately pushes the caller's status flags onto the stack (php), but doesn't do any other initialization. PAGIMON can save the status flags too. Also, I want to make sure that PAGIMON always starts up in register mode. There are a few other initialization steps I want PAGIMON to take, but I will come back to those later. For now, I'll add:
Code:
_PM_main:
    php
    lda     #PM_REG_MODE
    sta     PM_UI_mode

.loop:
.
.
.etc.


That just leaves four subroutines to write. Part 3 will cover the hex-mode dispatcher.

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 11, 2023 9:39 pm 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 443
Part 3

A simple dispatcher

PAGIMON's hex mode screen looks like this:
Code:
+--------------------
|$8000:30313233  0123
|$8004:34353637  4567
|$8008:38394142  89AB
|$800C:43444546  CDEF
In VISIMON the variable that tracks the current memory address is called SELECT. Since memory addresses are 16 bits, two bytes are needed to hold this variable. In VISIMON the high order byte is referred to as SELECT+1. My custom is to handle two-byte variables in this style:
Code:
PM_UI_address   = $035A
PM_UI_addressL  = $035A
PM_UI_addressH  = $035B
When we switch to hex mode, the contents of PM_UI_address will determine where in memory our "window" is positioned.

To get back to register mode we'll press ESC. To move the "window" around in memory we'll use the cursor (arrow) keys. The up and down arrows will move the entire "window" forwards and backwards (in other words, the start address will be incremented or decremented by 16). The left and right arrows will shift the "window" forwards or backwards by just one byte (in other words, the start address will be incremented or decremented by 1.) All other key-presses will be ignored. What should happen when we ignore a keypress?
Ken Skier wrote:
Since the video display need not be refreshed (redisplayed within a given time) by the processor, the UPDATE routine need not return within a given amount of time. The UPDATE routine, therefore, can wait indefinitely for a new character from the keyboard, and then take appropriate action.
I disagree with this. If we just circle waiting for a keypress without updating the display we won't see any changes that may have taken place since the last keypress - if we're examining a VIA input or output port, for example, we'll miss any activity. So PAGIMON will always execute the main loop and refresh the display, even if no key has been pressed. If you remember from part 1, PAGIMON's input interface is a little wrapper called _PM_GetChar that returns a keycode, or a NULL if no input is waiting.
Code:
_PM_hexMode_update:
    jsr     _PM_GetChar     ; Don't dispatch NULLs, but do return.
    cmp     #NULL
    bne     .case1
    rts     ; _PM_hexMode_update

.case1:
    cmp     #ESC            ; Switch back to register mode
    bne     .case2
    lda     #PM_REG_MODE
    sta     PM_UI_mode
    rts     ; _PM_hexMode_update

.case2:
    cmp     #UP_ARROW       ; Move the "window" "up" - i.e., to a lower memory address.
    bne     .case3
    sec
    lda     PM_UI_addressL
    sbc     #16
    sta     PM_UI_addressL
    bcs     .skipHB2
    dec     PM_UI_addressH  ; Subtract 8-bit constant from 16-bit variable
.skipHB2:
    rts     ; _PM_hexMode_update

.case3:
    cmp     #DOWN_ARROW     ; Move the "window" "down" - i.e., to a higher memory address.
    bne     .case4
    clc
    lda     PM_UI_addressL
    adc     #16
    sta     PM_UI_addressL
    bcc     .skipHB3
    inc     PM_UI_addressH  ; Add 8-bit constant to 16-bit variable
.skipHB3:
    rts     ; _PM_hexMode_update

.case4:
    cmp     #LEFT_ARROW     ; Go back one byte
    bne     .case5:
    lda     PM_UI_addressL
    bne     .skipHB4
    dec     PM_UI_addressH
.skipHB4:
    dec     PM_UI_addressL  ; 16-bit dec
    rts     ; _PM_hexMode_update

.case5:
    cmp     #RIGHT_ARROW    ; Go forward one byte
    bne     .case6
    inc     PM_UI_addressL
    bne     .skipHB5
    inc     PM_UI_addressH  ; 16-bit inc
.skipHB5:
    rts     ; _PM_hexMode_update

.case6:     ; DEFAULT
    rts     ; _PM_hexMode_update
The dispatcher can be extended by chaining more cases on to the bottom. For example, at the moment you have to ESC out of hex mode before you can SHIFT+q to exit out of the monitor. Case 6 could be a handler for 'Q' that would let you exit PAGIMON with SHIFT+q regardless of the current mode; then case 7 would be the new default case. In fact, this seems like a good idea and I think I will go add it right now!

Part 4 will describe PAGIMON's shiny new framebuffer output interface, drawing the hex mode display, and make an excursion into data stacks!

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 34 posts ]  Go to page 1, 2, 3  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron