So before I amble up to my Dad's for Christmas, I decided to continue to try and work out why the keypad isn't working as behaved.
Once the keypad is working, well, I have then made a real computer - input (keypad) and output (LCD).
The keypad itself is a hexdecimal (4 rows of 4 keys) numeric pad, for that is what I'll use for the Weather Station of Doom. (Unless the Sinclair Speccy I'm trying to repair tuns out to be not so repairable, then I'll use the Spectrum's keypad - the rubber keypad will be a lot more resilient to being in the glider club launch vehicle than a PC keyboard).
It is attached to port B of the PIO. For starters, what I did was put the PIO port into control mode, and make B7-B4 outputs, and B3-B0 inputs. The idea was to switch B7-B4 on, and tell the PIO to send us an interrupt when any of B3-B0 go active. This means a key somewhere on the keypad is being pressed. Then, the interrupt handler scans the keypad by sequentially turning on B7-B4, and stopping when one of of B3-B0 goes active. Then you know what row and column is making a circuit - and therefore the key that is down.
Unfortunately, the PIO is smarter than that. I knew already it looked for the Z80's RETI (return from interrupt) instruction to know when an interrupt service routine had completed - that way, the PIO knows to send no other interrupts, and to inhibit lower priority devices from sending an interrupt. But also, on reading the documentation more, it looks like it won't read owt else until the interrupt handler has exited. The idea is that if the data on the PIO's input changes while the interrupt is being handled, the interrupt handler will still get the old value - not the new one that's arrived halfway through the interrupt routine. Changing the output lines to scan for the row/colum just makes the PIO read zero for all its inputs - I think the behaviour is sort of undefined if you try and change outputs in the interrupt service routine. Even removing the interrupt vector and turning the interrupt generating stuff on the PIO didn't work.
So - the solution is to scan the rows continuously, and have the interrupt service routine simply read out the value on the PIO which will then have both the correct row and column. I will probably use a decade counter to scan the rows - but in the time I had, rather than making any new hardware, I just connected the already-present 555 timer to the Z80's NMI pin, and had the NMI service routine update the column being scanned via port B of the PIO.
With a continous scan of the rows, it makes the keypad interrupt handler routine vastly simpler of course, but it does either mean extra hardware in the form of a decade counter, or extra time in the form of a few more instructions in the NMI service routine. For good measure, I also added a 16 bit system counter to the NMI routine so that programs can get a sense of time.
So have a happy Chistmas and merry New Year.