Raspberry Pi Pico talks to Intel 80188 as I/O device

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Welcome back. If you were surprised by the last  video where instead of our Intel CPU I was talking   a lot about the Raspberry Pi Pico microcontroller,  then now it is time for me to explain my reasons.   But, before I start, let me first thank PCBWay  for helping me create this video. PCBWay is a   PCB prototyping manufacturer. Multiple PCBs are  their star products, ranging from standard PCB,   Flex PCB, Advanced PCB, PCB assembly service and  others. They also offer one-stop solution service:   apart from the inner PCBs, the outer enclosure  could be finished as well, such as 3D printing,   CNC machining, injection moulding and others,  covering different kinds of materials. With PCBWay   makers can realise their ideas into real projects.  Apart from that, their open-source community is   pretty popular among the creators, especially  the "Shared Projects" section. Many PCBWayers   have shared interesting electronic projects  there. If you are interested in any of them,   you can get the boards there directly. Check  them out at pcbway.com. Thank you PCBWay for   making this PCB for us. And... before we  start putting chips into the empty sockets,   let me tell you a few words about my future plans.  I decided to separate this series of building this   computer using 80C188 on the PCB into a separate  playlist and I'm gonna just carry on building and   programming this computer, which doesn't mean that  I forgot about the old computer - the 80C88 - here   on the breadboard. It's still here waiting for  more chips to be added to it, so in the original   series I will carry on with the breadboard  computer using 80C88 and I will add more   peripherals, more chips and explain how they work  and how to connect them to the computer. So OK,   now let's get back to the PCB. If you saw my  last video where I was talking about the PIO   block in Raspberry Pi Pico, it wouldn't come as  a surprise if I told you that that's exactly what   we're gonna put into this socket here. So let  me do this right now. That's Raspberry Pi Pico   and it's going here. There are two more sockets  here, which I will populate in a second. Just need   to mention what the Pico is doing here. I decided  to use it in the computer because the PIO block   is great for quickly reacting to what's happening  on the GPIO pins and sending data via those pins,   so I thought it would be perfect for using this  chip as a peripheral in our computer talking   to the main CPU using the data bus and the usual  signals: RD, WR and so on, but the problem is that   GPIO pins in Pico use 3.3V logic. They  are supplied with a 3.3V, not 5V like the   whole rest of the system and even worse the  GPIOs do not tolerate 5V on their inputs. So,   if I just put straight data bus to the GPIO  pins, they could be damaged - they are not   tolerant of 5V levels and that's exactly what  these two chips will do. These are usual 74245s   bi-directional transceivers, which can just  transfer 8 bits of data both ways and there   is a pin switching the direction of the data,  but they are a special version. It's another,   newer family of the 7400s called "LVC" and this  chips work in 3.3V logic level, but they are   tolerant of 5V. So you can put 5V to their inputs  and they will work correctly and recognise the   logic levels and at their outputs you will see  the usual, well usual, the newer 3.3 level, which   is acceptable for our Pico. So one of the chips  converts the data bus - 8 bits of the data bus -  and the chip here, which is identical, is used only really in one direction towards   the Pico and it just converts a few signals like  Chip Select, WR and RD and ALE, so... Address   Latch Enable. So let me explain why do I need the  ALE signal in the Pico. I'm gonna use the Pico as   a few different peripherals really, so I need  some address pins. And if I wanted to use the   separate GPIO pins for addresses and 8 additional  pins for data, I would run quickly out of the   GPIOs available. The standard Pico has 26 GPIOs  available. I want to use some of them for the   peripherals, which I'll talk through in a minute.  I just simply couldn't do do this - I couldn't   use separate GPIO pins for address lines and 8 GPIO pins for the data bus and then three pins   for the CS, WR and RD and also I need to talk to  that CPU the other way so I need 2 more pins for   the READY signal, because obviously, sometimes  we can't just serve the data instantly on the   data bus without Wait States. So I need the  READY signal and the Interrupt Request signal.   I will need to - like any other device really,  like the timer or any other device - sometimes   I need to request an interrupt in the CPU  to let it know, that it needs to read some   data coming in from our peripheral. So I would  run out of the GPIOs and therefore I thought:   well, we already have multiplexed data and address  signals in the data bus from the CPU, so even if   I've got those 573s here to latch the address and  using it in addressing all the other chips here,   I decided to use the multiplexed data and  latch the address again inside the Pico.   And that's why I need the ALE signal. So, in the  PIO programme which handles all the transmission   between the CPU and our Pico, which I will  discuss in a minute, you'll see how it's don.e   OK, so we know why we need to use the LVCs - they convert the 5V logic from the CPU to 3.3V   logic in the Pico. But what about the data bus,  when it's being read from the Pico and what about   the READY and Interrupt Request signals, which are  at 3.3V level and they are directly connected to   the CPU and they need to drive the CPUs inputs?  So, if you look at the datasheet of the 80C188,   you will see here in the 2nd row the V-ih symbol  which is a voltage allowed for the HIGH level on   any input and as you can see, the minimum voltage  is 2V, which is far less than 3.3, so I decided to   drive the inputs of the CPU straight from the  Pico, or - in case of the data bus - straight   from the LVC, which produces, of course, 3.3V  logic levels. OK, so what the Pico will be doing   here in our computer? You can see some pin headers  here and there. So, apart from obvious like serial   port, which will be implemented in the Pico and  then the CPU could talk to the Pico using the I/O   ports. There is also a pin header for an SD card  module - something like that - so we can slide it   in and you can use this SD card as a hard drive.  That's my plan. Also, the other pin header is to   be used with the RTC module, which works using I2C  bus, but I decided later on to not using this and   that's because I just realised that the original  RTC chip - the DS12885 - is still easily available   to buy. Also the 1287 version. Inside this big  case it contains the battery, which would keep   the memory and the RTC feature working even if  it's completely off the device. As you can see,   this one is produced in the 1988, so I'm sure the  battery is now completely dead after 35 years.   But, I will use this RTC in the new version  of this board instead of the I2C here,   but just for tinkering, there is still  I2C available in this pin header. So, most   importantly, hard drive using SD card. I'm gonna  implement a hard drive controller in this Pico,   a serial port - or UART. Also, because the Pico  may work as USB host, so you can connect devices   to the Pico and it can talk to them, I decided  to use it as the keyboard controller as well.   But more about these I will talk in the future  videos in this playlist, in this project and   I'm already designing a new version of this PCB,  because I unfortunately made a few mistakes on   it. The one which I already told you about the  Timer0In input, which was grounded - the pin   20 - I told you about this in the previous video,  but there are more. For example: I'm using the   S2 signal from the CPU to decode the Chip Select  for RAM and I used it straight away from the CPU,   but it's... it needs to be latched like the  address lines, because it's being removed too   early and may cause problems reading or writing  to memory. So I had to latch it. Here's the fix: I   just took the S2 and latched it using unused bits  of this 573. More fixes are visible on the back   side of this PCB. Unfortunately, I found that I  made a mistake in connecting the Pico and so I had   to just solder one "OR" gate - on the back of this  PCB. I added some decoupling capacitors as well.   So there are few things which need to be fixed  and I decided to change a few things as well,   but that's all is going to be done in the second  version of this PCB. There are some breadboards   on the top currently they are holding only our  text LCD, but I'm planning to add something to   this computer in one of the future videos. We'll  talk about it later. Here is the current schematic   of this PCB including all the fixes. I've already  told you roughly how our Pico is gonna communicate   with the main CPU, but now let's look at the  schematics. Here are the two LVC245s. One of them,   as I mentioned, is the transceiver for our data  bus - well, in fact it's not only data bus - it's   the multiplexed address and data 8-bit bus, that  is gonna go to ports 8-15 on the Pico. The PCS0 is   the chip select for the I/O straight from the CPU  and it enables the transceiver, so it talks on the   data bus only when the Pico is chosen by the CPU.  It's a chip select like any other: if it's HIGH -  if it's inactive - then the transceiver is in high  impedance state, so it doesn't talk: doesn't read,   doesn't write any data on the data bus. The  other LVC: I'm using only 4 bits out of 8,   so as you can see, the rest is just grounded  and those are the control signals, so: ALE, the   Address Latch Enable to demultiplex the address  from the data inside the Pico. Chip Select again,   because we still need to be monitoring it in our  PIO block and you'll see the programme doing that   in a second, and the WR and RD signals - they are  all going to the GPIO pins number 2, 3 and then   6 and 7. I didn't start from 0 and 1, because  these - as you can see - are reserved for UART,   or serial port and the pins 4 and 5 are reserved  for I2C controller - you've seen that 4-pin header   on the PCB. That's for future expansion. We  can add some I2C devices, some sensors to our   computer. I just decided to save it for later.  Pin 6 and 7, as I mentioned: WR , RD, then 8  bits of the data bus or multiplexed address and  data bus. Then some other pins of the GPIOs on   the Pico are reserved for SPI controller, which  will talk to our micro SD card and the other 2  are another I2C talking to the RTC - that's what's  on this schematic, but as I mentioned, I decided   to use the old original DS12885 RTC directly on  the CPU's data bus. I will talk about it in the   other series. We will pop it in our 80C88 data bus  on the breadboard the other time. OK, the other   LVC - the one which translates the logic levels of  the control signals - is kind of hardwired to talk   from the left hand side to the right hand side  only because these are input signals for our Pico,   always. The data bus though needs to be - of  course - switched between reading and writing and   I added this "OR" gate - which you saw on the back  of the PCB - because I forgot about it. Obviously,   the data bus needs to be read by the Pico in  two cases: whether the CPU wants to write to it   or whether the ALE signal is active and the Pico  needs to read the address... part of the address:   the 8 bits of the address put on the multiplexed  lines. So that's what this chip does. The output   pins from our Pico to the main CPU: pin 20  on the Pico - READY - and 21 - the Interrupt   Request - are output signals, which go  straight to our main CPU directly. OK,   let's finally have a look at our PIO programme for  our MultiIO peripheral. I called it "bus_ctrl" and   if you have seen my previous video about the PIO  you already can tell that in this programme I'm   using optional site-set to change the state of one  of the GPIOs. If you have a look here you can tell   that the site-set bit I'm setting is the READY  signal to the main CPU - the 80188. I also defined   other GPIO pins I use in this programme and I'm  doing this in the PIO section, not the C section   for a simple reason. Because those definitions I  can use both in the PIO, like here for example,   as well as in the C part of the programme. The definitions, the macros are prefixed with   the name of the programme and the underscore  and are defined for the C code as well,   so it's a good way to keep everything tidy and  you can easily change those definitions in one   place which is the PIO programme here. OK, these are comments helping me to remember   the pin mappings. We already looked  at the side-set - that's for READY   signal. The bits which are set to OUT, sometimes - not always, is the data bus the 8 bits of the data bus and the rest of the pins we  read, so they need to be mapped as the input pins.   So let me quickly scroll to the initialisation  function - C function, which looks very similarly   to the functions which I showed you in  the examples from the previous video,   but we have more pin mappings, which is  quite interesting to look at, because   in the previous video I used only site-set  and OUT, because we only used really 1 bit,   1GPIO pin to drive the LED modules, while in this  programme we're using 14 GPIO pins? I think it's   14 in total. We've got other mappings as well: the  IN mapping, which really tells the PIO which pin   is the starting pin to read the data from and  as you may notice in contrast to the OUT pins,   when you need to define how many pins will  be outputs - in our case it's only data bus,   so it's 8 pins - the IN pins function doesn't have  this parameter - the third parameter - telling how   many bits and that's because you can read all of  the pins in one go. The registers and the Input   Shift Register is 32-bit long, so it can read  all of the pins in one go. The function... only   thing the function does is to define which pin  is the least significant bit to read and if we,   for example, define it as 26, for example, and  read more than available pins on the RP2040 the   numbers will wrap up after 31. So it's... it will  go for example: from 26 next next bit would be 27,   28, 29, 30, 31 and then 0, 1, 2 and so on. So the  numbering wraps up around the 5-bit value: after   31 goes back to 0. In our case the lowest bit  we're gonna use as an input is the WR pin, which   you saw the mapping and the all the connections on  the schematic just a minute ago. So you may ask:   "oh, the ALE and CS pins have lower numbers, so  why don't you define the lower number for the ALE:   number 2?" And that will become obvious in a  second. The other interesting point I'll try to   make in this programme is that you may use Shift  Registers in different ways, really. Usually you   use them to transfer the data between the CPU -  the main CPU - and the GPIO pins, but I also used   Output Shift Register in a different way, which  will get to in a second. OK, let's go back to the   C initialisation code, because there is an other  interesting mapping which is not used that often,   which is the "jump pin". You can designate one  single pin to be checked and then conditionally   jumped, based on its state. So... and that's  how we really read the ALE pin - not via the   input functions, but just simply checking and  conditionally jumping if it's active or not. As   you can see, in this case, we jump if the pin is  active, which means if it has the state of 1. OK,   so let's analyse the programme line by line. This  is also an interesting line the "WAIT 0 gpio" and   the GPIO number. What it does is to wait until the  specified pin goes to the state which is defined   here, so effectively, at the beginning of our  programme, the first thing which we want to do is   to wait until the CS goes LOW - goes active - so  we're not doing anything really in this programme   until our peripheral device is selected by the  active Chip Select. Until then the PIO programme   stays here and waits doing effectively nothing,  waiting... just waiting for the Chip Select to go   LOW. If it does, which is the first kind of stage  of the bus cycle which involves our Pico as the   peripheral, it goes the line below which checks  and conditionally jump if the ALE is active,   it jumps down to the label called "ale_active" and  if not it just passes to the next instruction, but   we're gonna get to that in a second. First let's  analyse what happens in the programme when the ALE is active, which means that the CPU is serving us  the current address on the address and data bus.   Effectively, we're reading the 8 bits,  which are multiplexed with the data bus,   Which is more than enough because that really,  if you remember, the 80188 allows for each CS   to be mapped to 128 ports, I/O ports, which is  7 bits. We have the 8 bits, but we're kind of   gonna ignore the eighth bit of the address anyway.  But what happens here? This instruction: "MOV y,   pins" does two things, as you can see, it also  side-set the READY signal. If it does set it to   "1" it means that we are signalling "we're not  ready yet". We'll get the READY signal back to   "0" when we're ready reading or writing the  data. It's a signal for the 80188 to wait for   us until we're ready with the whole bus cycle.  "MOV y, pins" reads all of the pins as inputs   starting with our defined pin, which is the WR  - the lowest pin number 6 to the Y register and   then we are reading the Y register into the Input  Shift Register - specifically 10 bits of that. I   just need to explain two things now: why 10 bits?  That's because we read from the WR. We need to   read these 2 bits: WR, RD and then 8 bits of the  data bus which come straight after these pins. So   it's in total 8 + these 2: 10 bits. So that's why  we read 10 bits from the Y register into the ISR:   Input Shift Register. Now the question you may  ask is: "why not doing this in one step ? Why   do we need the Y register on the way?" Here is a  similar line which does the same thing in one go,   which reads pins straight into the Input  Shift Register. And that will become obvious   in a second, but we're using the Y register as a  temporary marker that the ALE was active and we've   got the address part from the bus already read.  So the only thing it does... the PIO programme   does in the "ale_active" is to read the 10 bits  from our inputs: WR, RD and the 8 bits of data,   storing in the Y register and Input Shift  Register and then jump up back to the "idle"   loop. And obviously, because the ALE is active for  around one whole clock of the 188, this programme   may then work in a loop here: between the "idle"  and "ale_active", but that's fine, because - so   it may read those bits many, multiple times - but that's fine, because the only thing we care   about is that the 10 least significant bits  of the Input Shift Register contain our input   signals... most current input signals - let's  say... So this goes in a loop until the ALE   becomes not active and therefore the jump wouldn't  be taken and the next step is to, as you can see,   check WR. So this part of the programme starts  looking into our input bits knowing that we   already passed the first stage of reading the  address from the address and data multiplexed   lines. So now we're looking whether the CPU, 80188  wants to read data from our peripheral or write   into it. First line explains why we had to use  the Y register. The only purpose of it is just   to mark that we've already passed the ALE signal  stage. "How?" - you may ask. It checks whether the   Y is 0 - "!y" means: (if it's) "jump if it's zero  - back to the idle". The Y register would contain   anything but 0 only when the ALE signal was  active and we read the input pins. "Why?" - you   may ask - "We could address the I/O port of 0 and  then Y would be 0?". Well, no, because we read not   only 8 bits of data in here, but we read 10 bits  together with the WR and RD. When the CPU reads   then the RD is active LOW - when it writes, then  the WR is active LOW, but they can't be LOW at   the same time. So one of those bits must be "1"  at any point in time. That's why we read into Y   all the pins and we know for a fact that if we've  gone through this part of the programme then the   Y register would not contain 0 for sure. And that  allows us to decide whether to carry on with the   programme or just go back to the "idle" state.  All right. So, first - as the label says - check   the WR state. How we do that... now this time we  read our pins into the Output Shift Register and   since the lowest number of the pin is WR, the  first bit in Output Shift Register - the least   significant bit - would be the WR. We are shifting  out from the Output Shift Register this one bit   to the X register. Then we're checking the state  of the X and conditionally jump to "check_rd"   if it's not active, because the "JMP x--" means:  "check if the X is not 0 and jump only if it's not   0 and post-decrement". You may ask: "why do I need  to post-decrement the X register?" - Well, I don't   have to, but that's the limited encoding of the  PIO instruction gets in the way. You can check if   the X or Y is 0 and then conditionally jump, like  we did here, but for the check... the opposite   check, where... "jump if the register is not 0",  the only encoding is with the post-decrement,   so we don't care about post-decrement - it's  just the way the PIO instructions are encoded.   It effectively means "jump if X is not 0". If the  WR is not active that means we need to check RD.   Maybe the CPU wants to read from us? And the label  is here. The next bit to shift out is the RD,   so we're shifting out to the X the register our RD  bit and doing the same check again so "if it's not   active ,whoops - something's wrong - we're going  back to the idle, because the CPU neither reads or   writes". But if it is active - the RD is active -  it just falls down here and since we already have   the address to be read from in our Input Shift  Register, then we simply push it to the FIFO   to be read by the the main CPU - the ARM core on  our RP2040. If I'm saying the CPU in the context   of PIO programmes I usually mean the ARM core  inside the RP2040 - not our, kind of, main CPU   in our computer: the Intel 80188, but I'll try to  explicitly tell which CPU I'm talking about. So,   we're pushing our address read previously from the  input pins here to the FIFO and then we raise an   interrupt - again, interrupt to the ARM core  within our Pico. That's interrupt number 1 - we are also using the other interrupt number 0 - that's for writing. We're gonna get to that in a   second, and straight after raising this interrupt,  we are changing the directions on the data bus,   because - by default - we're reading from  the perspective of the Pico - we are reading:   waiting and reading the address  bus and then the data bus,   if the CPU - the 80188 CPU - wants to write  to us, but if it wants to read from us,   that's the only case when we need  to change the direction of our GPIO pins as outputs to serve the data to the  80188 CPU. How do we do that? Well you   could do this straight away by "MOV pindirs",  so instead of doing this in 2 steps we could do   "MOV pindirs" and then immediate value,  but that value could be only maximum 5  bits. Unfortunately, we need to  change the direction of 8 bits:   the whole data bus, so we need to do  this via Output Shift Register and that's the case where we use Output Shift Register in  a different way - not to transfer any data via   GPIOs to or from the processor, but only to set  "pindirs" - pin direction - as outputs. And that's   because of the feature of the "!null", which, as  you may remember from the previous video, sets all   the bits to "1s", so effectively this function...  this instruction sets all 32 bits of the Output   Shift Register to "1". Well we need only 8 of them  but we can just shift 8 of them to the "pindirs",   which effectively changes the direction of the  data bus to outputs. Why does it not change the   WR and RD? Well, if you remember... here we've got  this comment and you can also see it here we are   setting the OUT pins with the lowest number... 8  pins... with the lowest number of the data bus,   which is pin number 8 - not 6 - like in other  cases. This changes the direction of our data   bus to outputs and gets it ready for the ARM core  and our PIO programme to serve the data on the   data bus to the 80188. In this moment while we...  after we have raised the interrupt, we hope that   ARM core will do its work in the interrupt handler  and at some point it will push through the FIFO the data to be served to the 80188 via the  data bus. And we're waiting for the data   from the FIFO here in the "PULL block". If  you remember, again from the previous video,   a "PULL block" waits - because it's a  blocking call - it waits until we've got some  data from the ARM core CPU by our FIFO sent to our  PIO programme. If the interrupt handler sends this   data, we can simply set the data bus pins to 8-bit  received from the ARM core and, as you can see,   in the same instruction, we side-set the READY  signal for the main CPU the 80188, letting it   know that the data is ready to be read and it can  finish its bus cycle. Then we simply wait in this   loop until the 80188 deactivates the RD signal. So  in this simple loop we again read the input pins   to the Output Shift Register. We need to throw the  WR bit which is in front of the Shift Register to   "null" - so it means just throw it away nowhere  and the next bit is RD, so we check its state   and if it's not zero... Sorry - if it's zero -  "!x" means "equal to 0 - jump if it is 0". So,   if it's still active - loop around and then we  fall down to the "reset_bus" which sets the Y   register to 0 - this is important exactly for  recognising the next bus cycle and whether the   ALE was active or not - like before - and  we're just for sure side-setting here to 0,   because the "reset_bus" is jumped from the  WR part of the code, which you will see in   a second. So it signals the CPU, the 80188 that we  already after writing to us. And also - again - in   the similar way to setting the direction of the  data bus to outputs, here we're doing the same,   but now this time we're populating the whole  Output Shift Register with "0s" - no "!null"   like here - "null" means set the  whole register to 0 and then "OUT"  changes the pin directions of the data bus back  to the inputs and it wraps around to the "idle"   state. So the only part of the code we haven't  really discussed yet is the WR part of the code.   We've already mentioned this, so if the WR is  active it falls under this "JMP" and this "NOP"   and the wait... delay I used it just to make sure  that the data bus is settled after initiating the   bus cycle and the WR goes active and then again  we read 10 bits from the pins. 10 - because,   again, we read the WR, RD and then 8 bits  of the data bus. So we read 10 pins of them   into the Input Shift Register remembering that  this part of the code already stored the address   from the CPU bus, so effectively we will have  in our Input Shift Register at this point 20   bits of data: the 10 bits - the address the WR  and RD which we're going to ignore in software   and then the data the CPU, the 80188 wants to  write to us together with, again, WR and RD,   but we're gonna ignore these as well. And what  we do here is: we push the Input Shift Register,   so the kind of address and the value being  written to us via the FIFO to the ARM core   and then we raise the interrupt. this time just  to easily tell between reading and writing we're   using the interrupt number 0 and we wait until  the interrupt status bit is 0 and that happens   when the ARM core acknowledges the interrupt  in software - we'll see the code in a second.   So the code waits until the ARM core receives the  interrupt and the data being pushed here. And then   we just go down to the "reset_bus" label, which  we've already seen. I just realised that I could   use the site-set and signal the readiness... the  end of the bus cycle for the 80188 here... "side   0", but since I've tested this programme in the  state which I'm showing you... and showed you   originally, I'm not gonna do this change now. The  only comment I need to make is that we cannot do   the "side 0" here. You may say: "oh, OK, we raised  the interrupt and then we wait so we can 'side 0'   marking that we we've done our thing on our side  and we're ready". Well, the thing is - and that's   quite important information. Despite the fact  that the "wait" and the PIO programme waits   on this instruction until the interrupt line  goes to 0, the side-set - and it's a comment on   any instruction in the PIO which may wait for  anything - the site-set is always executed at   the beginning, so even if the "wait" waits  here, the side-set would have been executed   at the beginning, when the PIO goes into executing  this wait instruction. So that would be premature   and we shouldn't do this here, but we might do  this here in the "JMP reset_bus" - it doesn't   really matter. We're doing this here, I tested  this programme and it looks like it works.   OK, let's finally see the C code of this Pico  programme and I will start from initialising   the PIO. That is the function which is called at  the very beginning and you already may recognise   some of the calls, like these two - this just  loads the programme and initialise the PIO to use   the programme. Instead of using the definitions  from the SDK straight into here, like "pio0", for   example and "state machine" could be 0, because  that's the only state machine we use on this PIO, I decided to separate all the definitions like  that into a separate file... header file here.   The other lines, which we haven't seen in the  previous video is to initialise the interrupt   requests. That line links the PIO interrupt line  number 0 and then we're just setting the function,   which will handle the interrupt, the priority and  enables the bit for the interrupt to be active   at all on the CPU side. That's the PIO side -  that's the CPU side. And that happens for the   interrupt line number 0, which is for writing,  hence the function name: "bus_write_handler"   and exactly similar thing for reading and the  interrupt number 1 - the PIO interrupt number 1   for reading... And that just runs... starts the  state machine running our programme. Let's go   now to the handlers: both write and read. These just effectively format the data being sent from   the PIO programme, so that's the function which  reads from the FIFO and really... I use the loop,   but effectively, we should be getting only one  data, one 32-bit data from the FIFO. It's just,   again, safe coding - just in case  something got lost on the way. So, we   read the data and then we just extract the bits  from the data, because in case of writing -  remember? - we've got the address in this 32-bit  data as well as the data from the 80188 sent via   the data bus. So we are extracting those  by just shifting out and, as you can see,   this limits the address to 7 bits - to "7F"  in hex, because we ignore the eighth bit of   the address line. And then the value and then we  call our "cpu_bus_write_io" function which just   gets the kind of extracted data ready to be acted  on. We'll see this function in a minute and then   we clear the flag - the interrupt flag - which  means that we are done in the handler. We've   noticed the interrupt in first place, we've done  our job and you can go on PIO and do its thing.   That's what the "WAIT 0 irq 0" does here. So once  we've called this "pio_interrupt_clear" function   this "WAIT" will just pass on to the next  instruction. The "bus_read_handler" is very   similar, but in this case we have only address  to be read from in the data sent from the PIO and we then call a twin function to the  "cpu_bus_write_io", the "cpu_bus_read_io" with   the extracted address and its result... because,  well CPU expects - the 80188 - expects some data   from us - it's reading from us - so this function  returns the data to be sent back to the 80188   during the bus cycle and the data returned from  this function we put to the FIFO, which then   is pulled here from the FIFO and sent to the  data bus of the 80188. And then we clear the   interrupt flag as well. Oh, yeah... One  more thing the "time_critical_func" is   quite... critical. It's a definition to tell  the compiler to store the function in the RAM,   so it will make the initialisation code to copy  the function with this definition from the flash   to the RAM. That's because the Pico module  contains a serial flash - the RP2040 doesn't   contain any flash for storing programmes  inside itself - it uses the serial flash,   which takes some time to read from. So  we just want to make sure that these   critical functions are being already  stored in the RAM and executed from RAM for the obvious reasons, because  we want to do our job as quickly as   possible without any delays waiting for  the flash. So that's the "cpu_write_io"  function which is called from the handler...  sorry... here... and the "cpu_bus_read_io",   which we'll start from is here. I - for the  test we're gonna make in a second - I defined   two separate I/O "devices". They are not very  useful really they just test our programme and   if the whole idea works at all. So, I defined two  16-bits port I/Os. One starts with the address   of "00" and "01" and that's because when you  read or write 16-bit I/O ports in the 80188,   let's say: you address the port number X - the  lowest, least significant byte is read or written   from the port number X, but the most significant  byte is read or written from the port number X+1.   So, if you use 16-bit "IN" or "OUT", it will  effectively read or write two I/O ports:   the X and X+1. So if we address port number 0  and use 16-bit transfer, it will effectively   read or write - in our case we're reading  here - port 0 and then 1. And that is, as you   might guess from the names of the variables, it generates Fibonacci sequence, which we,  remember, hopefully... from my first or second?  Second video on this channel, where we generated   Fibonacci sequence in the top 16 bytes of the  address space of the 8088, so I decided to   repeat this idea in this test. So if we address  port number 0 and you use 16-bit transfer, we can   get - on each read from that port - next Fibonacci  sequence number. And the other kind of I/O port at   address of 4 is even more boring - it just returns  constant data, so the lowest byte is "45" and the   highest byte is "23". So theoretically, if we  run a programme on 80188 and we do "IN AX, 4",   we should get number - 16-bit number - in hex:  "2345" - if everything works - we'll see it in   a second. And all the other ports, which  we didn't define here, just return "FF",   because we need to return something. But I...  just to test writing to the port - I also defined   that if you write to the port of address of  0, it will reset the Fibonacci generator,   because without it every time we read from the  port, it would generate consecutive numbers of   the Fibonacci sequence, but at some point we may  decide to read again from the first number: "2",   so if you want to do that, you just need  to "output" any value to address of 0,   because we ignore the value being written  to this. It's just to test writing to the   peripheral. And that's pretty much it in this  C code. So just to wrap it up before the test:   we have two I/O ports... 16-bit I/O ports.  At offset 4 we just return constant data,   writing doesn't do anything to this port and  the port at index 0, which reads consecutive   numbers from the Fibonacci sequence. If you  want to reset the generator you just write to   the port any value and that's it. Now, let's try  to execute everything and check if it works. OK,   let's finally test the whole idea and the PIO  programme in our Pico, using the step-by-step   mode. Before we actually turn on the the computer  let me just explain those wires to the...   to the text LCD. I decided to not using the direct  connection, because it was too slow. The text LCD   is too slow to talk to the CPU, so I decided to  use the text LCD only via the 8255 GPIO pins and   I also updated the code, the "driver" for our LCD.  You can find it on my website on GitHub as always.   I decided to use only 4 bits, because the text  LCDs can be driven using all 8 bits or 4 bits,   which obviously requires twice as many  transactions, kind of, via the pins,   but I still decided to do this. The code  is configurable, so you can switch it to   4 or 8 bits and you can also switch and use  different ports A, B or C and different pins   to drive all the signals to the text LCD. So  that's updated - you can check it on my GitHub   and I will be using for this test the new code  here. OK, I think it's time to test our idea   of the Pico as an I/O device. So, as you saw in  the code, I defined a few ports within the PIO and from the listing you can see that I'm stopping  our CPU using the "INT3", which is the debug   interrupt, to switch it into the step-by-step  mode. So after the whole initialisation it'll   stop at address... I guess... "6C". OK,  let's run it. Oh! Look at that... "006C"... so, as I said, we are now  paused before "XOR AX, AX",  which zeros the AX. So let's step through this.  Yeah... the AX now contains 0 and the whole idea   is to check the... if the reading from an I/O in  Pico works at all, so I defined, as you remember,   two ports with the offset 4 and offset  5: one returns... I think... "45" hex,   the other one returns "23" hex, so we are reading  now to the AL value from the port number 4.   If we step through, we can see that the  "45" is returned from the Pico. Again,   this instruction just read a value using I/O  address space from the Pico. The Pico returned   value "45". If we step through to read the, kind  of, older byte you - may say - if we treat these   as 16-bit value it would be "23", but still  in AL. Now the next test from the listing   is to read the whole value as a 16-bit integer.  So first, we zero out the AX again and the next   instruction, as you can see, is not "IN AL" - it's "IN AX", so it's a 16-bit read. It   will read port 4 and 5 - it will return the whole  16-bit value into the AX, hopefully. Let's read   that... And yes! We have the whole value: "2345" in the AX, currently. Next test is to use the   port at the offset 0, which is the test I kind of  borrowed from the one of the first videos on this   channel, which was to return consequent values  from the Fibonacci sequence. So we zero out   AX, but before we do that - sorry - the first  instruction loads 20 or "14" hex to the CX,   which is the counter register in the loop. We've  got "14", so we're gonna see 20 Fibonacci numbers   in the AX. All of them are 16-bit wide,  so we improved from the one of the first   videos - from 8-bit to 16 bit. Let's see the  first value in the AX... and there you go:   the first value is 2. Then we decrement CX and if  it's not 0, we go back in our loop to offset "79" and then we read another value  of the Fibonacci sequence which   is 3 and that's how the loop works 20  times. We were reading only in our test,   so I also defined, that if you  write anything to the same port 0,   it will reset the Fibonacci generator. So after  you've written anything to the I/O port at offset   0, it would reset and the subsequent reads will  read Fibonacci sequence from the beginning - from   the number 2. So, we're in the last part of our  loop. So we just zeroed out the AX and in the   next step, at offset "80", we're going to write it  to the same I/O port, to our Pico to let it know,   that we want to reset the Fibonacci sequence. No,  it's not very useful - I know - it's just a test   of whether the reading and writing, both work when  we talk to our Pico. So, we are now writing the   Pico should now see: "oh, they wrote something  to my port so I will now reset the sequence".   And now we're getting into the next, very similar,  loop with just 10 iterations this time, so "0A"   hex and we read the first - again - Fibonacci  number which is 2, then 3, then 5 and so on .  So that was just a test that the whole hardware  and our PIO programme inside the Pico works and   can effectively talk to our 80C188 like any  other peripheral. In the next video in this   series we will start maybe using - MAYBE - using SD card - we'll see - as the hard drive.   We'll make some tests as well, so I hope to  see you there. Take care and see you next time.
Info
Channel: Slador
Views: 2,074
Rating: undefined out of 5
Keywords:
Id: _zTlbCywZbY
Channel Id: undefined
Length: 60min 36sec (3636 seconds)
Published: Tue May 02 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.