 my nice new brother WP1 word processor. I could connect the Raspberry Pi up to the UART. I am actually going to take the time to write a whole new terminal emulator. So I think that is actually ready to test. So I'm gonna stop on a cliffhanger and get on to the testing next time. Okay, moment of truth time. Let's try it and see if it works. Now I am also trying a new technique for recording the video from the brother device. This is using a webcam, which does at least have manual focus. The picture quality is still not brilliant. And as I mentioned before, a lot of that is the fault of this cable, which is screwing up the video somewhat. I should really have used a more shielded one. Anyway, let's try this and see what happens. And the answer is nothing, which means my boot loader is broken. The Z180 MMU is an absolute pain to configure. Okay. Well, better. Okay, so I'm apparently clearing the screen to the wrong value, which is zero rather than the space. And I am not configuring the attribute memory, which is why some of it's shown up in reverse. And while it's printed, the boot message, it hasn't actually, you know, started booting. So after not overwriting the current program, let's try this. Well, we're clearing the screen. It's not loading anything off disk. That's probably because, because, because the memory map it currently in use is not the same as the original boot sector and the temporary buffer used to load stuff off disks is in the wrong place. Okay, let's try this one. Interesting. It's not really what I expected, but at least you can see on the screen here. I actually am not sure it's coming out. I didn't set this thing up to record inverse video, but I am at least getting superscript. Yeah, I think this is wrong. Fun though. So boot it and nothing happens. We are at least clearing the screen, which is nice. Well, I managed to fix the bugs in the bootloader, mainly by throwing it away and starting again with the CPM-ish bootloader. And as a result, our application runs sort of. So that's interesting. The snow is clearly due to video memory refresh. So I think the application's actually running and is completely failing to update the screen properly. I'm not quite sure where the changing characters are, which you see if just stopped. So I suspect that some workspace is in video memory somehow. That means register corruption. The fact that it hasn't removed the boot go message, which is produced by the bootloader, I'll fire it up again, is interesting because that means we failed to clear the screen. So I think the first place to look is the screen refresh code. Here's the routine to clear the screen. And I do notice this. CPA comma D is a regrettable typo, which is going to compare A against D. The A is implicit because in CPA the A is implicit because you can only compare A with things. So this is going to be comparing whatever happens to be an A, which is whatever garbage this has left behind with what's in D. This should be an LD. So I think that this has been, this is spinning forever, writing to the screen. Now where those garbage values were coming from? Well, if you pass in a line number out of range then the display code is going to look that up against back buffer address table. So now actually that doesn't make any sense because it will be clearing random bits of garbage. It'll be reading the address of a non-existent line and then trying to clear it. Where's our clear routine here? Also it's trying to write zeros, the character zero to the screen rather than a literal constant zero. That's not going to work. Okay, well those are at least two bugs. So let's give this a try and it boots. And that's interesting, we get exactly the same thing. So I wonder the garbage on the screen not the snow but the strange characters. This is clearly counting down. You see it just stops there and the snow changes. I wonder if we've never actually got to the display flush code at all. And in fact it's hung somewhere else or rather it's clearly not hung because the snow indicates it's writing to the video memory. So if it's spinning doing something really stupid and not reach the flush code, then that explains why the screen hasn't cleared because the first thing the program does is try to clear the screen. Maybe it's the clear screen code that's gone wrong. It is after all, it does look like it's counting. So we've got something going on here, some garbage and then this looks like two bytes counting down and there's a flashing quotation mark just to the right of it. Yes, the easiest way to debug this kind of thing is to simply insert calls to jump to yourself that is infinite loop throughout the startup code. It becomes very easy to tell how far things have gotten. Well, the problem actually turns out to be really straightforward. So in our flush code we are copying from the back buffer to the video RAM starting a video RAM base. However, the video RAM actually consists of two 4K chunks, one for the attributes and one for the character data. What I didn't cover in the previous video is changing everything to update the character RAM rather than the video RAM and it looks like I forgot to change this. So we just need to do that and hopefully it should now be writing to the right place. So let's write this back. Something should have changed. So after inserting one of these right at the very beginning of our program we still get garbage. This means that none of the program is actually running. So this suggests it's a bootloader issue. Well, we can see the go message and then what we do is we do some memory fiddling and jump to the beginning of the program. So there shouldn't really be anything out of the ordinary there. I do need to check to make sure that all the places where the program starts, which are coded in, agree. So there's one here and there's also the one in the make file, which is here. This tells the linker where things go and then there's this LD80 in this mode returns a 64K memory image. And so we need to remove the bit we're not using. Well, that does actually seem to be the right value. So what could be happening? Well, the first thing to do is to make sure everything is assembling to the code we think it is. So let's do that. That causes listings to be produced by when compiling SIM files, which is a command images. The same as a com file as far as LD80 is concerned. And that gives us a listing for the bootloader. So this shows the op codes that the assembler is actually generating. So there are we. Here is the go code. So here are the five instructions to rework the, to reconfigure the video, but to reconfigure the memory manager. Yeah, I'll just talk about the memory manager a bit. So there's this big long comment up here that describes it, but basically the Z180 memory manager gives you three banks of memory. The first one is called base and always starts at logical address zero and is always mapped to physical address zero. The second one is called bank and you can change both the mapping and the division point. And the third one is called common. And again, you can change the mapping and the division point. So they always have to appear in this order. Common is always above bank, which is always above base. And we need base to be that's wrong. Base always points at the brother OS kernel. It allows us to do system calls. Bank is the code the application actually lives in and it is always mapped to this physical address and this logical address. Common starts at EOOO and it either points at the video memory which lives at the dis-address range in physical RAM or the disc buffers which we use when loading the program which are dis-addressed in physical RAM. So when we load the program we actually have to keep switching between having the disc buffers available so that we can call the system call to read sectors and having the video RAM available so that we can write stuff to the screen. Well, once the program is loaded we no longer need the disc buffers. So we switch permanently into video RAM mode before calling the application start point. Now, are we loading the program to the wrong place? Because the configuration we've got here is actually slightly different from the one that CPM uses. The way the load works is we read sectors we always put them into a temporary buffer. This is why we need a disc buffers block. We then use the DMA engine to copy out of the temporary buffer and into user RAM. We use the DMA engine both because it's fast and because the DMA engine always works with physical memory which makes life easier when loading CPM-ish. However, copying 12 sectors is not exactly time-intensive and for our particular use case we always have the target memory mapped. So I think we should actually be able to get rid of this. We can replace this with a simple LDIR which would simplify things. So I'm finally beginning to remember which way round LDIR goes. So HL is the source address and this is always the disc buffer. LD is the destination address and we keep the destination page here in D. So E is always correct. We just have to, D is always correct. We just have to put a zero into the bottom byte. BC is always 100 because it's one sector. Okay, it's possible that this configuration is wrong, you see. So advance the next sector, next address, reduce the sector count by one and loop. Yes, let's leave that. And then we turn the motor off and go. So that is now a simpler bootstrap code which is here. I see reasonable looking values. So we'll try that and see if there makes any difference. Boot, no, no it doesn't. Well, this isn't helping. This is setting the stack pointer for our program. Putting it here means that we're using the system stack in the common workspace with the disc buffer. And of course, every time you switch to the video memory, the stack becomes inaccessible. So if we leave it in video memory mode, then the stack goes into video memory. So possibly that is what's writing to the screen. Now the obvious thing is to just move the stack somewhere else but we can't make system calls unless the stack is here. But of course, there's no reason why we can't simply switch for different stack down here. So let's try this. So that means our program lives at app start and goes up and our stack starts app start and goes down. That will, program goes here. So that means the stack starts at the high part of, actually let's put that somewhere else. Because of course, we don't know how much of that memory block is being used. Let's put that here. So the program goes from 7000 up. We know this is ordinary RAM. So that should be fine at this point. The odd thing is I still don't know where all the noise is coming from. So I think that I am going to just try and write out what byte is at app start before we do anything else. This will mean copying the hex print code which I got rid of but we can steal a copy from CPM-ish. That's, now hang on. We can see it's in our common utils print.lib. This, so let's just copy this, stick that here. And this wants to be put byte. So we're just gonna do load whatever's app start into A and print it. Okay, just check to make sure that our boot loader is not too big. This is our program. Here is our boot loader. And we're close. We have about 23 bytes remaining but that will fit. So what I would expect to get a zero, zero because that is the first byte of the program. That's, hang on, that's not right. Zero, zero, that is a knob. I never told it to put a knob there. That's our JR. His value was wrong. Off by one. That's better. Well, that won't help but it shouldn't have caused that mess. So where is the noise coming from? Well, normally the memory that lives at, lives in this block is the kernel workspace. It's the brother operating system but we've just swapped it out in favor of the video memory. This is safe because we've turned interrupts off. So I wonder whether one of these system calls we're making has turned interrupts back on. Now the difference between this and CPM-ish is this routine here that's on the motor off. So let's do this because it's possible that this is in fact working and this is executing our infinite loop but there's an interrupt routine which is updating variables in kernel memory. Of course, now the kernel memory is being replaced by the video memory therefore it's updating the video memory. CPM-ish has this call in it. So the problem can only be this one. Just check to make sure. No, we're not calling EI anywhere. So let's try this. And it works. We have a clean screen. So yes, that was clearly the problem. This means that we can now go and turn back on bits of our program and see what comes out. So how does it look? Boot. Good, good. The eyes are wrong but the cursor is flashing which means it's initialized video display. It's trying to clear the screen. So, well the only thing to do now is hit the return key and see if anything comes out. No, of course it doesn't. Okay, I will see if I can... Let me just... I have the Raspberry Pi keyboard here too. So let me just poke something to the serial terminals to see if it works in that direction. Cat2dev-dtby-ama0. No. Okay, so we can fix the screen clearing and you'll notice that it's nice and crisp. So it's either hung or our video refresh code is working. Well, this is weird. Sometimes when I boot it, I get this keyboard error message. This is coming from the brother operating system and I don't know where that could be coming from. My entire program is running with interrupts disabled and I'm never calling into the brother operating system. I'm doing everything myself and talking to the hardware myself. So this suggests that something is being corrupted somewhere probably on the stack resulting in a hyperspace jump when things go wrong. And I'm now wondering about that stack position. Is there actually memory there? Well, I know there is because CPM-ish uses it. I will try relocating it and seeing what happens. Well, look what I found. Current state used to be a byte. Now it's a pointer and it's a pointer to the state routine that we're calling. So by initializing this to, well, a zero in the low byte, it's going to cause all kinds of weird things. So yes, we're getting our hyperspace jump when the first time we go through TTYput to see it's calling whatever address is in there. So there's a zero in the low byte. There's probably a zero in the high byte so it's probably gonna try and reset the system. But we're not doing it right because we're just calling zero with all the wrong memory. So bad things happen. So we initialize the state instead of state waiting and hopefully that will work better. So I now have some code in place that will attempt to write lots of dots to the screen. Of course, we have a handy TTYput to see routine for writing text, which is useful for tracing. Let me show you what happens when I try it. That was the machine crashing and rebooting. So we are printing some stuff onto the screen. It's kind of not working. The fact that it seems to work for a bit even though it's printing the wrong characters before rebooting suggests that it's the terminal state is going wrong. For example, scrolling off the bottom of the screen, if the cursor Y position goes out of bounds then we've got this lookup table to look up each line and the back buffer so we may pick up an invalid address from there and just start writing gibberish all over memory. But that's definitely progress. Well, here's some stuff that's wrong. When we call print A to print the character on entry A is whatever's left over after we've subtracted lots of values here and C contains the real value. However, we seem to have defined it to take the char in A. So A is garbage. So we change that to that and copy C into A. Then we should at least get correct output text. So there's more stuff going on. So we get the address of cursor X. We increment HL. If it is not screen width, we go straight to update cursor. If it is the screen width, we reset X to zero and go to TTY line feed, which is here. If A is on, if we are on the last line of the screen, scroll up, otherwise move the cursor down one. So scroll up is straightforward. All we're going to do is delete line zero on the screen and leave the cursor where it is. We have, though I do think we have print printable, we have actually moved the cursor and TTY line feed may not actually call. Wow, unexpected phone call. I've forgotten that people actually called people on phones. It's been a while. Yes, I was looking at the cursor motion. I was wondering if there was a code path where the cursor would be moved, but update cursor wasn't being called. But to be honest, it's sounding like a crash is happening when we hit scroll up, which is course calls delete line, which is in the display routine, which of course is doing this stuff. And this is very likely to be wrong. If we're doing a LDR with the wrong parameters, then we're gonna just like copy garbage around in memory and be very likely to corrupt things. If we mangle the stack, then return parameters will be incorrect. And the system's gonna crash. So let's just disable this. And yeah, let's just see if it crashes still. Well, it's not crashing, but we're still getting the eyes. I'm not sure if it's coming through on the camera, but those are actually double barred eyes for some reason. It seems to be the character the thing prints if you give it a invalid screen code. The snow is interesting because we don't seem to get that when we're not printing to the screen. And it should take the same amount of work whenever you flush the screen. So, oh, I know what's going on. And that is going to be a pig to fix. Yeah, I'll come back to that later, I think. But we are definitely making progress. So let's try and fix delete line then. Well, this ain't gonna help. Yeah, the back buffer address table was all wrong. I have also added a new file. This print.z80, let's put that down here where it belongs. This again is stolen from CPM-ish. It contains all the number printing and string printing routines. So we're gonna go to Dell here, but instead of actually doing the copy, we are going to not do anything because there's a non-zero chance that now I fix the back buffer table, this should work. Do you think it worked? Of course it didn't work, but it did fail an interesting way. So the, you can see the row of dots down the middle of the screen. It should have filled up the entire screen. So this suggests that the cursor is not being placed correctly. There's snow, we expect to see snow. The cursor itself is flickering around. I would only expect to see the cursor on the last row of the screen. And the right hand border has got our barred eye in it. So that's not being cleared properly. Are we printing the characters in the wrong place? Well, on entry to DPUI-pronay, we have DE is Y comma X. Y is in D, it's the high byte. X is in E, the low byte. So we call a calc back buffer address. D has not been touched anywhere. So D is the Y byte. And yeah, that's not gonna work. Each address is two bytes. So in fact, here we want to do a shift logical left by shift logical left A, or just do an add A. So that will double A, so we're now properly indexing into the back buffer table. And yeah, we're doing exactly the same thing here. Luckily, we're calling calc back buffer address everywhere. So we're not actually directly referring to the tables anywhere. Okay, with luck that has placed our text in the right place. Right, del L, the thing that blanks the, you see, if we're on the last line, yeah, then just go to blank last line. Otherwise, do the scroll. I think the scroll is at least doing something. So blanking the last line looks like it's not working. Honestly, this looks fairly straightforward. HL is source, D is destination. So the destination is, the source is the beginning of the last line. The destination is one byte further on. Count is width minus one because the extra byte is done by the priming byte here. That looks okay actually. So calculate the address of the line in DE. That puts the, excuse me, that puts the address in HL. Copy that to DE, increment DE, prime, set the size, fill. And we know this works. It's possible that I've got this subtraction wrong. Different CPUs have, well, what SBC here is a subtract with carry. So it will subtract DE from HL but also take into account the carry being passed in. This allows you to chain together multiple subtractions for arbitrary width stuff. The problem is different CPUs interpret the carry flag differently. And I do not recall whether the Z80 is a borrow flag or a carry flag system because if you don't want a carry passed in, you have to either set or reset the bit appropriately. It is indeed CCF. So the subtract is DE minus HL minus the carry which because we've cleared it is zero. Okay, well, with luck, we should now be drawing text in the right place but I'm not sure about the recipe. I'm gonna just try this and see what happens. So boot and it works. We are printing dots correctly. So it was just the back buffer table that was wrong. Good. So we should now be able to, well, we seem to be able to print stuff or at least print dots. So let's try and get more of the terminal working. I also got myself some tea as I'm yawning a lot. So let's call keyboard push to try and get a value out of the ring buffer and then called keyboard pull to pull a value out of the ring buffer and then TTY put C to print it to the screen. So we should now have a system where we type and keys show up on the screen. Assuming everything works, which almost certainly won't. Let's just have a quick skim through the code. So keyboard push. Read to keyboard scan code. We copied all this from CPMH so it should be relatively reliable. So convert scan code is doing the conversion. Keyboard type is our ring buffer. Adds to the ring buffer keyboard pull, pulls it out again. This is the state machine that runs the keyboard, keeping track of whether it's seen like shift key down and up, caps lock, et cetera. All right, boot. Okay, now we press a key. Oh, it may not be showing up on the camera, but that was a Y. We're getting text. We are getting text. The cursor is entirely the wrong place. Let's get the cursor down to where things are a bit more stable. There we go. Interesting that everything has suddenly gone just up a case. What happens if I press the shift key to try and toggle it back to lower? No, it doesn't work. Caps lock. Okay, maybe I bumped the caps lock key. Space doesn't work. If I remember correctly, space is a scan code of zeros. So maybe we're getting that wrong. The cursor position could be more correct or, you know, at all correct. How about the code which you can't see me to press because it's off the screen? Code A, B, C, D, yep. And this is generating garbage. Oh, code J, of course, has a line feed. Excellent. Yeah, the code plus letters produce garbage because we don't support printing characters below 32. But code J a few times, scrolls. This looks like at least this bit of our terminal emulator works. Now, can I remember any, oh yeah, I haven't tried shift. Yep, that works, number keys. Yep, about the cursor keys. They should actually do nothing because they will have extended values but I haven't edited the key map yet. So they are still producing ADM3 cursor control codes. Let's try, trying to remember any of the, let's try escape shift A, no, escape shift E. That's cursor down. Does at least seem to be putting the cursor in the right place. It's a square bracket. So escape square bracket. Was J clear screen? Well, it cleared something, but I don't think that was right. Escape square bracket J, no, escape square bracket J. I can't remember what that actually did. But yes, this does actually seem to be relatively, that one was delete, relatively decent looking. So let's take a look at space and cursor key, cursor position. Okay, well, here's our conversion. Let's take a look at the key map generator, look key tab, let's see. So the scan codes are here on the left. Right hand side contains the ASCII codes. So where is space here? That should have worked. Scan code 0x20. So I wonder if, right, this is wrong. Instead of ret z, we want ret c. Ret z was doing, is the key a space? If so, return now and do nothing. What we wanted was, is the key a lower code than a space? So that should be a c. Good. What was the other issue? Cursor position. Well, the cursor is set with this. So we have, no, wait, I thought we'd miss the doubling. D is the Y position. So we take, we get the address of our table, multiplied by two, add on to get the address of a position in the table, right, we need to, you've done this in the wrong order. We need to load the value out of the table before we add on the X position. So that should do that bit. So it looks like we have lots of stuff working. Well, the next thing to do is to let's start pulling stuff out of the interface. In fact, let's just turn it all on because I don't think it will do any harm. Okay, let's boot it. Has not cleared the screen. Okay, well, let's press the return key. No, I think that's wedged. Let's try doing the thing in the Raspberry Pi to admit text. Yeah, that's doing nothing. Cursors. You see here where we compare the state against RS readable and then return if the state is RS readable when we should be returning if the state is not RS readable. Yeah, I think that's probably going to make a bit of a difference. I have, for the time being, disabled all the keyboard stuff. What was that doing there? That shouldn't be there because it did seem to cause things to lock up and I'm not sure why. So let's just see whether this side of things works. We should be able to cat stuff from the Raspberry Pi to our terminal and see things appear. So let's use the Raspberry Pi to dump some text and what do you know? It works. Cool. And now let's press the return key and it works. So I will just move that keyboard out of the way, move this one off camera and log in. Yikes, why did that happen? You're not supposed to get text out at this point. Oh, it timed out. All right, I typed my password into the login prompt. Newbie mistake there, I think. Okay, let's do this properly. Brilliant. Okay. So our terminal type is VT220. Let's set that to VT102. We're going to use, we have 14 rows and 91 columns. Okay. Let's start VI, cross fingers, touch whatever superstitious material you like. Wow. Okay, well that's not worked. At least something's not working. I'd be... Hang on, how do I work this? Yeah, something's wrong. Yeah, okay, cursors got confused about the screen layout. Something is not right, but we got into uppercase, which is a little surprising, but a lot of that is working. So let's try a man page. We don't get any trailing Ms. Something's not right in the bottom row. Okay, that's bad. Let's just restart the terminal. States got confused. What was the next thing we've got to try? Oh yeah, word grinder. So this is a chunky cursors program that has also got the terminal state confused. So the terminals crashed. And we actually want to... Can we exit word grinder? Escape? No, I'm gonna have to kill this from the Raspberry Pi. Yep, there we go. Okay, well that wasn't great. But a lot of things work. Now there's a... I'm pressing the tab key and it's not doing the right thing. I'm pressing the delete key and it's not doing the right thing. Well, there is a program called VT test, not spelled like that, which is supposed to test VT100 terminals. So let's just run the first test. That's not supposed to happen. Back to the other keyboard. Kill all VT test. And I think I'm gonna have to restart terminal. So this is a fair chunk of work to do. What's going on here? Oh, it's still running the program. What does modified test parameters do? Don't think there's anything useful there. But yeah, a lot of stuff works. I'll try terminal reports. That should... Okay, device status report. This is the testing the type back feature. Yep, it says report okay. Wait a minute. Test of device status, what six report curse position? Report ism. Really, ism? Square brackets 005, semicolon 001R. Unknown response, it doesn't like that. I suspect it doesn't like the leading zeros. I'll try that one again. I think it doesn't like the leading zeros. Let's try testing screen features. That's two. There should be three identical lines of stars completely filling the top of the screen. Test of wrap around mode setting. Yeah, these aren't gonna work. We're not setting 132 column mode because this thing is limited to 91. So this one's irrelevant. We don't have a scroll up region. Yeah, none of this stuff is relevant to our program. Origin mode we haven't done. Attributes we haven't done. Save restore cursor feature. We have implemented and it does look like it works. Although a lot of this is garbage. Let's try nano. That has not displayed properly. I don't know where the eights come from. We can type. Right, scrolling down is del line. Inserting, a scrolling up is ints line. But I do not know why that's not done anything useful. Ah, I know why it keeps switching to capital letters. I normally use this as a control key. So I have to press a different key on this. No, I don't want to save the modified buffer. Okay, so I'm just trying to get a handle on what might be broken. Does do we, are we still in the right? We see argument two, then it hangs. Really? Reboot the terminal. Interesting. And now it's doing the very slow text thing. Okay, so, so you run script to log output. STTY rows, missing argument two, and it hangs. Reboot the terminal exit to exit the script. We now have a log in TypeScript, which we can look at using, yeah, that I need to fix that delete key that's maddening. You can edit, we can look at with TypeScript. So down here, I think my finger is showing up on the camera, you can see me typing STTY. I don't think I can scroll down without working cursor keys. I can go to a position, so let's go to CO, go to 80, is that gonna force it to, nope, go to DO, should force it to scroll. Only a little. So, yes, that's not luckily, there we go. Here at DO, you can see STTY, missing argument two, and then E28098 rows, that's a Unicode carrier. That's what it is. E28098, so that should be a, ha. That's gonna be an open smart quote, and that's why it's just hung there, because you can see from the context, it's probably just about to print a smart quote. So, that gives us a place to look. So we need to fix delete, because otherwise I will go insane, cursor keys, and UTF-8.