Hello
I'm looking for information on the way the HP48 handle its screen,
and though I have found some by collecting documentation on the topic,
it's still relatively fuzzy.
Do you know if a doc could help ?
Here is below a copy/paste from the question list (in markdown format) :
?
Questions on HP48
================
Screen
---------
Questions related to grey level support
#### What's the exact refresh rate of HP48 screen ?
All docs seem to mention 64 Hz,
but that sounds too "round".
Could it be 64.1 , or 63.9 ?
Where does the refresh rate is controlled ?
Is refresh rate tied to cpu speed ?
#### What is the exact refresh policy ?
Are there 64 refreshes per second, meaning the whole screen is refreshed in a single scan ?
Or is there one refresh per line, meaning there are actually 64x64=4096 refresh events ?
#### _What_ is pushing pixel data onto screen ?
Is it the cpu directly ? or another component accessing RAM ?
If cpu : I assume there is some kind of "interrupt", and the cpu capability is "reserved" during the refresh. But in such case, it's supposed to send data to some kind of bus, with some kind of address ? What would be this address ?
If dedicated component : I assume the RAM access is likely reserved by the component during the refresh, blocking the CPU.
But could the CPU still work using its own registers during that period ?
#### Pixel latency
My own tests let me believe that interleaving up to 3 screens is fine.
I've read people saying that the cycle can be more lengthy, but I wasn't convinced by the results.
Is there any study on pixel fading latency ?
#### Contrast control
It's relatively straightforward to change the contrast of the screen, through a shared value in RAM.
Is it a good idea to play with this setting to generate more nuanced grey levels ? Or is there a crippling latency involved ?
Interruptselse in between.
--------------
All docs I could find provide a list of interrupts like below which does not include anything regarding screen :
- Character received by the UART
- Character placed in the UART transmit holding register
- Keyboard button down/repeat
- Timer expiry
- Low battery condition
- IR emission/receipt
- ON-key press
- VLBI (very low battery interrupt)
Now, I understand that it's possible to know which line has been (or will be) drawn thanks to RAM value stored at `LINECOUNT` address, but checking that requires some "busy loop", which is bad for battery consumption, and does not allow doing something
Is there a way to catch the `$LINECOUNT == 0` event as an interrupt ? or something equivalent ?
I suspect this topic has been solved by arcade games using 3-grey shade graphics.
There is an internal counter, running
off of the clock signal from crystal oscillator, that refreshes the screen at a
constant rate after a certain amount of clock cycles have elapsed. So, assuming
that the oscillator is perfectly stable, the nominal refresh rate is 64Hz. From
the display controller's POV, the refresh rate is always 64Hz.
The display controller drives each row at a time. The CPU is halted for a certain amount of time for each row of display memory, so that the display controller can access RAM and drive a row. So, since there are 64 rows, you could, I guess, say that there are "64x64=4096" "refresh events" per second. But, these "refresh events" only start every 1/64th of a second and don't last
very long*.
there is no interrupt generated by the display controller when LINECOUNT
is equal to 0. One method to mostly get around this is to configure a user interrupt service routine which sets TIMER2 to generate an interrupt *just before* LINECOUNT is equal to zero. After the interrupt is generated, the CPU would have to perform some busy waiting, but it wouldn't last long.
Thanks for the detailed answer !
It's way more accurate than anything I could find so far, and helps a lot !
I do have a few follow-up questions :
There is an internal counter, running
off of the clock signal from crystal oscillator, that refreshes the screen at a
constant rate after a certain amount of clock cycles have elapsed. So, assuming
that the oscillator is perfectly stable, the nominal refresh rate is 64Hz. From
the display controller's POV, the refresh rate is always 64Hz.
I read that as : the CPU and the Display are both driven by the same crystal oscillator,
and therefore, ignoring potential oscillator variations, the amount of CPU cycles between 2 refresh is simply hard-wired.
It should be constant, no matter what (except obviously when display is turned off).
Is this number of cycles known / documented somewhere ?
The display controller drives each row at a time. The CPU is halted for a
certain amount of time for each row of display memory, so that the display >> controller can access RAM and drive a row. So, since there are 64 rows, you >> could, I guess, say that there are "64x64=4096" "refresh events" per second. >> But, these "refresh events" only start every 1/64th of a second and don't last
very long*.
Thanks to your explanation on the display controller, I now get that the CPU gets HALTed when a line is refreshed. It lasts "a number of cycles", and I'm not sure how much. Then it can resume.
In my initial message, I feared that the CPU was actually being interrupted, meaning that it has to stop doing its stuff, save some essential registers and context information, proceed on driving the refresh operation, then restore initial state, andresume. All this context switching would have costed "something", and it would have made a difference wether it was happening 64 or 4096 times per second.
The difference between 4096 short HALT of 64 longer HALT is moot if they equal the same number of HALTed cycles. Actually, 4096 would be better, as it would spread the load more evenly.
there is no interrupt generated by the display controller when LINECOUNT
is equal to 0. One method to mostly get around this is to configure a user >> interrupt service routine which sets TIMER2 to generate an interrupt *just >> before* LINECOUNT is equal to zero. After the interrupt is generated, the CPU
would have to perform some busy waiting, but it wouldn't last long.
Well, thanks. I've been trying exactly that strategy.
But the results were disappointing, to say the least.
Even though I'm sure I selected TIMER2 value with a _very_ conservative wait amount, basically guaranteeing that the CPU would be busy-waiting, it was still resulting in large tearing.
An important thing though : since my real HP48SX stopped working recently (I suspect a RAM corruption problem though can't be sure), I've been relying on emulator (emu48) to continue development. emu48 is said to be cycle-exact, so I had high hopes thatit would be representative of a real hardware. But I guess that at that level of details, the emulation may reach its limits.
It could be that the level of tearing observed was in fact emulator-related, I could not confirm it on my real calculator.
On Tue, 2 Apr 2019 14:18:16 -0700 (PDT), Cyan wrote:
Thanks for the detailed answer !
It's way more accurate than anything I could find so far, and helps a lot !
I do have a few follow-up questions :
There is an internal counter, running
off of the clock signal from crystal oscillator, that refreshes the screen at a
constant rate after a certain amount of clock cycles have elapsed. So, assuming
that the oscillator is perfectly stable, the nominal refresh rate is 64Hz. From
the display controller's POV, the refresh rate is always 64Hz.
I read that as : the CPU and the Display are both driven by the same crystal oscillator,
Well, there is an important distinction : The display and the timers are *directly* driven by the crystal oscillator whereas the Saturn CPU's clock signal is derived from a PLL which generates a high frequency multiple of the crystal oscillator's frequency.
and therefore, ignoring potential oscillator variations, the amount of CPU cycles between 2 refresh is simply hard-wired.
Well, because of the Saturn CPU's PLL's small fluctuations ( there may be some
"spread spectrum" functionality, but I'm not sure ) with respect to the crystal
oscillator, the number of clock cycles is not going to be exactly the same for
each refresh. But, you're correct in that the refresh frequency is "hard-wired".
It should be constant, no matter what (except obviously when display is turned off).
Is this number of cycles known / documented somewhere ?
It's not known because it's not exactly the same in-between each display refresh
due to the display and timer parts of the Clarke / Yorke SoC being driven directly by the crystal oscillator and the CPU clock, while being derived from
the same oscillator, is generated by a PLL. So, one cannot calculate an *exact*
cycle time, but only an average. Depending on one's HP48's clock frequency, the
number of cycles in 1/64th of a second can vary as the CPU clock varies between
as low as 3.69 MHz to 3.9 MHz on an HP48-G/GX ( Personally, I've never seen a clock speed that is 4MHz or greater ).
The display controller drives each row at a time. The CPU is halted for a >> certain amount of time for each row of display memory, so that the display >> controller can access RAM and drive a row. So, since there are 64 rows, you
could, I guess, say that there are "64x64=4096" "refresh events" per second.
But, these "refresh events" only start every 1/64th of a second and don't last
very long*.
Thanks to your explanation on the display controller, I now get that the CPU gets HALTed when a line is refreshed. It lasts "a number of cycles", and I'm not sure how much. Then it can resume.
The halt lasts 22 to 23 microseconds.resume. All this context switching would have costed "something", and it would have made a difference wether it was happening 64 or 4096 times per second.
In my initial message, I feared that the CPU was actually being interrupted, meaning that it has to stop doing its stuff, save some essential registers and context information, proceed on driving the refresh operation, then restore initial state, and
The difference between 4096 short HALT of 64 longer HALT is moot if they equal the same number of HALTed cycles. Actually, 4096 would be better, as it would spread the load more evenly.
Conservatively, the number of halted cycles, for each 1/64th of a second refresh
period, should be around 3.9e6 Hz * (22.5e-6 s * 64) = 5616 cycles, for a G/GX
at least. This is about a 10% slowdown.
there is no interrupt generated by the display controller when LINECOUNT >> is equal to 0. One method to mostly get around this is to configure a user >> interrupt service routine which sets TIMER2 to generate an interrupt *just >> before* LINECOUNT is equal to zero. After the interrupt is generated, the CPU
would have to perform some busy waiting, but it wouldn't last long.
Well, thanks. I've been trying exactly that strategy.
But the results were disappointing, to say the least.
Even though I'm sure I selected TIMER2 value with a _very_ conservative wait amount, basically guaranteeing that the CPU would be busy-waiting, it was still resulting in large tearing.
That's interesting, as I've used the same method, but without any tearing, and
so have many greyscale action games on the 48. Note that I always use double-buffering where the new display data is written to display memory after
the refresh cycle ends.
that it would be representative of a real hardware. But I guess that at that level of details, the emulation may reach its limits.An important thing though : since my real HP48SX stopped working recently (I suspect a RAM corruption problem though can't be sure), I've been relying on emulator (emu48) to continue development. emu48 is said to be cycle-exact, so I had high hopes
It could be that the level of tearing observed was in fact emulator-related, I could not confirm it on my real calculator.
My guess is that it's emulator related.
Regards,
Hic quondam locutus sum
--------------------------------------------------------------------------------
Send e-mails to "NOhp48 Sat Palephnaught Adot Morg"
( Remove the capitalized letters spelling "NOSPAM" )
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 296 |
Nodes: | 16 (2 / 14) |
Uptime: | 56:26:50 |
Calls: | 6,652 |
Calls today: | 4 |
Files: | 12,200 |
Messages: | 5,330,869 |