Here again, is the code just discussed for reading the serial port and then displaying the data on the status LEDs, but now it has been split into an initialization section and a main loop. Obviously, we should also want to do something with the data as it is input like storing it in a buffer, but this is a very useful piece of code that can be used for testing purposes.
INIT1: LDA 0x1f ; reset the UART OUT 0x0a ; configure for eight-bits + odd parity + one stop bit LDA 0x1c ; at 1X clock rate OUT 0x0a RET ; or HALT? LOOP: IN 0x0a ; get port status byte AND 0x01 ; mask for Rx status bit JZ LOOP IN 0x0b ; read the data OUT 0xff ; display front panel LEDs JMP LOOP NOP NOP
It would also be nice if this code were interrupt driven. Changing the infinite loop to a return at the end gives us a callable function. Then we need to call INIT1 to initialize the port, and then we could call GETCH, whenever we want to get a character. This works for polled I/O, that is if we don’t mind having our application hung while waiting for input, when in fact we want if to be able to do other things, like maintaining time code, streaming MIDI, and so on. Of course, if we are going to be working in a high-level language such as Pascal, C, or even C++, then we will probably want some functions that we could call, and that have names like PEEK, GETC, READ, etc.
INIT1: LDA 0x1f ; reset the UART OUT 0x0a ; LDA 0x9c ; for PS2 kbd configure for 8-bits + odd parity + 1 stop bit ' at 1X clock rate, and generate interrupts on RxByte ; FOR MIDI data - there should be NO PARITY - and the ; clock rate is 16X - so change this to LDA 0x95 OUT 0x0a ; RET PEEK: IN 0x0a ; get port status byte AND 0x01 ; mmask for Rx status bit JNZ GETC LDA 0xff ; return EOF if port not ready RET GETC: IN 0x0b ; read the data OUT 0xff ; display on front panel LEDs RET
Now let’s rewrite what we have so far to read data from the PS2 keyboard using interrupts. According to the 6850 UART data sheet, setting bit 7 enables the receive port interrupt, and on the MITS 88-2SIO serial board, the IRQ level is set by a jumper wire on the board so as to point to an octal address in the zero page, 0X0, giving us just eight bytes to store the interrupt service routine. Since we have 24 bytes so far, it should be obvious that the ISRs will have to go somewhere, and that the zero-page locations will contain a simple dispatch function. The 88-VI-RTC board will also need to be configured to allow interrupts at the appropriate level, and so on.
At a certain point, we will also need to start counting the number of CPU cycles that are used in servicing the read request. MIDI messages frequently contain multiple bytes, which are generally sent consecutively, in 8-bit format, with one stop bit, and no parity. For example, according to Wikipedia, a full MDI time code message looks something like this:
F0 7F 7F 01 01 hh mm ss ff F7
This will most likely be sent by a remote device in burst mode if our device is receiving the time code. So, although receiving bytes from a keyboard might seem quite leisurely, with MIDI streaming at 31250 bps, we will need to be able to handle up to 3125 bytes per second, which gives us a “budget” of about 320 microseconds per byte received, which will allow for no more than 160 8080A instructions to be executed, given a 2 MHz clock, or else we risk a buffer overflow since the 6850 can only buffer one already received character, in addition to any that are presently being clocked in.
The manual for the MITS 88-VT-RTC includes some sample code for creating a simple clock that is interrupt driven, and that allows the current time to be read from BASIC. Using this code as a starting point, why not rewrite it to be able to SYNC to a remote time code source, as well as be able to free-run it as a MIDI time code streaming device? Then there is also the issue of the MIDI beat clock, which is a different animal altogether, but that is another clock source that generates twenty-four MIDI ticks per quarter note according to whatever the tempo is in the current musical passage.
Oh, and there is also the “running time code” which is a series of messages that are interspersed as four separate sub-messages per frame of regular time code, so these need to be decoded if we are receiving them, as well as being created and then streamed correctly if we are running a time code generator.
Floating point arithmetic would also be nice to have so that any “old-fashioned menu-driven console mode application” might possibly be able to be configured to generate appropriate MIDI clock pulses for tempos that have decimal values, like 123.14 BPM, or whatever if that is so desired. Fortunately, I have a set of floating-point routines that I wrote in C++ that could be made to work on “any architecture” for which there is a C or C++ compiler available, even hypothetical machines that might only have logical shift and bitwise NOR operations. So even if I digress, as a hint of things to come, here is one way to do floating point multiplication, which seems to work – but which hasn’t been rigorously tested:
real real::operator * (real arg)
{
short _sign;
real result;
short exp1 = this->exp()+arg.exp()-127;
unsigned int frac1, frac2, frac3, frac4, fracr;
unsigned short s1, s2;
unsigned char c1, c2;
_sign = this->s^arg.s;
frac1 = this->f;
frac2 = arg.f;
s1 = (frac1>>7);
s2 = (frac2>>7);
c1 = (frac1&0x7f);
c2 = (frac2&0x7f);
frac3 = (c1*s2+c2*s1)>>16;
frac4 = (s1*s2)>>9;
fracr = frac1+frac2+frac3+frac4;
if (fracr>0x007FFFFF)
{
fracr = ((fracr+(1<<23))>>1);
exp1++;
}
result.dw = (fracr&0x007FFFFF)|(exp1<<23)|(_sign<<31);
return result;
}
That of course assumes that you have 8/16/32 bit integer, addition, subtraction, multiplication, etc., and that is another bear altogether to implement
inline DWORD square(unsigned short r1)
{
DWORD t0,result;
DWORD t1, t2, t3;
unsigned char highbyte, lowbyte;
lowbyte = r1&0xff;
highbyte = (r1&0xff00)>>BYTESIZE;
t0 = (lut1[highbyte]<<WORDSIZE)|(lut1[lowbyte]);
t1 = mult8(highbyte,lowbyte);
t2 = t1<<(BYTESIZE+1);
t3 = add(t0,t2);
result = t3;
ASSERT(r1*r1==result);
return result;
}
inline unsigned short mult8 (unsigned char c1, unsigned char c2)
{
unsigned short s0, s1, s2, s3, s4;
if ((c1==0)|(c2==0))
return 0;
if (c1==1)
return c2;
if (c2==1)
return c1;
s0 = (unsigned short) add(c1,c2);
s1 = (unsigned short) sub(c1,c2);
bool m_odd = (s0&0x1?true:false);
bool m_neg = (s1&0x8000?true:false);
if (m_odd) {
s0 = (unsigned short) add(s0,1);
s1 = (unsigned short) add(s1,1);
}
s2 = (unsigned short)(m_neg?add(not(s1),1):s1);
s0>>=1;
s2>>=1;
s3 = (unsigned short) sub(lut1[s0],lut1[s2]);
if (m_odd)
s4 = (unsigned short) sub(s3,c2);
else
s4 = s3;
ASSERT(s4==c1*c2);
return s4;
}
Well, then suffice it to say that regular integer arithmetic as well as most of what is needed to do floating point is pretty much done, since C++ is available on the Arduino, this is either already built-in or is easy to add in, along with whatever functions I might want for calculating tick_rate = 24*beats_per_minute/60, or where the interval between ticks in milliseconds would be 1000.0/(24.0*BPM/60.0) which comes out to 2500.0/BPM milliseconds between ticks. So at 112 BPM, for example, this would work out to whenever we set the BPM we are going to want to calculate some kind of interval, just like this, and we will also need a running count that gets incremented every time that the system timer interrupt fires, Maybe the good news is that 22000 microseconds seems like practically forever, even in 2Mhz 8080A land.
Now on a Parallax Propeller P1 chip, there is a really elegant way of generating signals which can be used for things like arbitrary frequency generation, where since the P1 chip has 8 cores we can take advantage of special dedicated 32-bit registers that are associated with each core which can be dedicated to custom timing applications. With the 8080A and the Altiar 88VI-RTC, we need to be a bit more creative, if we are going to be able to isochronously interleave a plurality of tasks, as we would normally want to do across an echelon of processors, this is to say – on just one CPU. Yet the way that the Propeller does it will turn out to be useful to us anyway, even on an 8080A, and that is because the way that we generate a particular event rate, i.e, frequency, is that we precalculate a 32-bit value that gets put in one register, which acts is the increment value, and that value gets added to the value in another register, which acts like a counter which should reach a count of 2^32, and then roll over, once during each event interval.
Now the Propeller will therefore add the increment value to the counter for us automatically, once every cycle of the master clock, which I think is typically set at 80 Mhz. So what we want to do is to use the same technique to set up some registers that contain increment values, as well as count values, so that whenever the 88-VI-RTC fires off an interrupt, that is based on the real-time-clock we can increment a bunch of counters according to their associated, and pre-calculated - increment values. Now even though this will happen at a few hundred Hertz, instead of 80MHz, it should be good enough to do basic time-keeping, including time code generation, as well as for the generation of the tick, beat, and measure data with sufficient accuracy, i.e., to within a few milliseconds or better, that is for MIDI programming, controlling drum machines, light shows (even though that is usually done with a protocol known as DMX), and so on.
From the proceeding, it is easy to see that if MIDI tick rate is 24 pulses per quarter note, then we can calculate that the tick rate in Hertz is actually equal to 0.4 times the tempo when measured in beats per minute. So if we want to control a drum machine that is putting out a dance rhythm at 128.0 BPM, then this would imply that we would want a tick rate of 51.2 Hertz.
So what we want to do now is figure out how to get the VI-RTC to generate events at that rate or any other reasonable rate, and not just the built-in rate, that is, with minimum modifications. So let’s take the previous example, and assume that the user has entered 128.0 BPM as the desired tempo and that we have calculated that we really want a tick rate of 51.2 Hertz. Now we need to calculate the amount to add to a counter that we will maintain in the software (the increment value and its associated counter), which will convert the interrupt rate to the desired rate. As will be discussed elsewhere, my 88-VI-RTC has been modified to generate interrupts at 10kHz, 625 Hertz, 39.06 Hertz, or 2.44 Hertz, because some of the divider chips have been replaced with pin-compatible, but functionally different CMOS parts.
Thus, assuming that we have created a group of 32-bit registers in software, then what we need to do is take the desired event frequency and multiply that the 2^32, and then divide by the interrupt rate, which we might set at 625 Hertz, based on the modifications that have been mentioned. This is a little slower than the stock rates of 10 kHz, 1 kHz, 100 Hertz, and 10 Hertz, according to the original design of the 88-VI-RTC, but hopefully, this will buy us some breathing room on the number of events that we have to process with a 2 Mhz CPU. Performing the calculation, therefore, gives us an increment value of 351,843,721.0. which is the number that if it is added to another 32-bit register will cause the second register to generate a carry each time it counts past 4,294,967,295.
Put another way, if we divide 351,843,721 by 4,294,967,296 we get 0.08192000 exactly, so if we think of the number 351,843,721.0 as representing a rational fraction where the denominator is 2^32, then what we are doing is in effect the same as adding 0.08192 to a register every time the interrupt fires, and then generating a tick event every time that counting register counts past one. Note that this means that we are in effect using a virtual 32-bit integer in such a fashion as to simulate the use of full-floating point arithmetic, but without the massive overhead associated therewith, that is by treating a 32-bit value as if it were a fractional value between zero and one. Therefore, when we simply let it count past one, we can discard the whole part of the number, that is the carry, or else put it in yet another register, and then we keep just the fractional part of the number in our 32 bit-register, even though it rolled past one.
Perhaps writing a simple simulation of this in C or C++ would be in order. Yet why not set something up that services multiple timers off of one interrupt? That could be done by creating a timer list, and then when the interrupt fires, an increment value is added to its register, and then for each register that generates a carry, an event can be put into an event queue, then the list of pending events can be sorted, and then dispatched. Thus, it seems like a good approach to implement this in a high-level language, like C - or C++, and then use whatever compiler tools are available to generate the 8080A or Atmega assembly code.
Yet this also calls to mind the possibility of implementing a hybrid approach, where an Arduino might be running an Altair emulator, but the floating point routines, when they are called from 8080A code might take advantage of the Atmega's built-in 8 by 8 bit two clock cycle hardware multiplier, and so on. So we could "fake" a hardware multiplier on the 8080A side of things by using a "call gate" into native code SWEET16 style, which is what we will have to do for the UART stuff anyway. This is easy to do with an 8080A emulator, for example by using an unused RST instruction as a special jump code, so that in effect RST 6 or RST 7 might be repurposed as CSP XXX, where XXX would be a value that represents the index into a function table that is to be called, whenever the RST instruction is encountered. This is more efficient than having the emulator look up every JUMP that is encountered to see if it is in some special table that maintains a list of native calls.
Apparently, RST 0 and RST 7 are used by CPM, so I might end up putting my system clock interrupt on IRQ1 and my UARTs on IRQs 3 and 4, respectively. Then if I am running in emulation, I might therefore end up using RST 6 as the call gate for invoking native Atmega code.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.