Friday, June 5, 2020

Reset And Panic Buttons, Buttons Debouncing


Reset And Panic Buttons, Buttons Debouncing

The monitor program in my home brew computer has a nice debug feature. The NMI (Non-Maskable Interrupt) is used to service the so called 'panic' button. The role of a panic button in a microprocessor system is simple - if your program gets stuck or runs away, you can press the panic button connected to /NMI pin of the processor and the service routine assigned for NMI will store the current program status on the stack, including the return address, dump CPU registers and do whatever else you programmed it to do and then drop you back in the monitor program prompt thus giving you back control without the need to reset the system. You can then use monitor program functions to examine or alter memory of the faulty program and even continue the faulty program execution from the moment of NMI interrupt, if you think you corrected the cause of the problem.
I used that button often to recover from failed / hung up code or to return from 3-rd party applications I ran on my system (e.g.: Tiny Basic) to the monitor prompt. The continuation of the execution didn't work too well though simply because I didn't have the so called debouncing circuit implemented for the panic button.
What is debouncing and why do I need to debounce a button, you might ask?
When you deal with electronic circuits, which involve fast switching signals, you need to realize that in real life applications, things like buttons / switches, wires, connectors, resistors, capacitors, inductors and so on are not perfect. Wires have a resistance and capacitance. Resistors have a capacitance and inductance. Inductors have a resistance. If wires carry high frequency signal, there is interference. In case of mechanical switches the problem is that their contacts bounce off each other – literally and very fast. When you press a button, two metal surfaces come in contact with each other. These surfaces are not perfect. They may be not aligned perfectly. They are not perfectly flat. Over a very small amount of time there is a mechanical high frequency oscillation when these surfaces come in contact and detach from each other before they finally settle. This period of unstable / undefined connection between these surfaces is called bouncing.
To you this looks like a single button push. If you had a battery and light bulb circuit that such button closes, you would just see that the light comes on – and when you release button, the light comes off. To a micro controller or a microprocessor which are usually clocked with relatively high frequency and process the data much quicker than human perception allows to notice, the moment of you pressing the button appears like tens, hundreds or even thousands of separate signal switching events. However we want to register just one clean push. How to solve this dilemma?
There are two major ways – solve it with hardware or with software.
In our case software debouncing will not work, because NMI signal is Non-Maskable (can't be turned off) and is triggered by a low coming signal edge – button contacts bouncing results in a series of high-to-low edges of signals which will trigger a new NMI each time such edge is detected, assuming there was enough time between them to be detected by CPU. Therefore we cannot just mask interrupts at the entrance of NMI service and put a delay on it to ignore the oscillations.
We have to debounce the button in hardware.
One of the cheap and reliable methods to do this which I used in my circuit is to combine RC filter with hysteresis by the means of Schmitt trigger TTL gate, e.g.:  inverting gates found inside 74LS14 or 74HC14 TTL chip. (it has 6 of such gates)
The RC circuit at input to the gate provides low-pass filter to cut off high frequency oscillations resulting from bouncing and Schmitt trigger gate ensures that the on / off levels are separated by a dead zone – pretty much in the same manner that your A/C or heating system thermostat does.

Below is the result. Note that I also debounced the Reset button, however this is not as critical as NMI since /RES in MOS6502 is triggered (and held) by low level. You'll notice though that the capacitor in RC circuit for Reset button is of much higher value than for the Panic button. It is because there is unstable period when you turn on the power to microprocessor system when voltage and currents in the system oscillate before they reach stabilized levels. It is critical that after you turn on the power to the system, the initial reset pulse comes some time after power to all the system components has stabilized. It (/RES signal) also needs to come after any possible unstable oscillations from NMI debouncing circuit, otherwise the system may freeze at startup. It also needs to be at TTL high level when stable, since /RES is active low - thus two inverting gates are used, while it does not matter for NMI sigal which is triggered by the signal's edge, not by its level.


Now I can enjoy reliable panic button function to debug my software or temporarily switch to monitor program to load data, change memory contents etc.

Thanks for reading.

MK 6/5/2020

Credits:
Embed With Elliot: "DEBOUNCE YOUR NOISY BUTTONS, PART I"

References:
Schmitt Trigger Gate
DM74LS14: Hex Inverter with Schmitt Trigger Inputs


Monday, April 22, 2019

I/O interface card for MKHBC-8-Rx computer


(Updated on 6/5/2020, schematic diagram)
Finally work on the I/O interface card has begun. I finished the circuit design and completed some parts for a prototype. Of course first prototype will be built on a breadboard, however I am thinking ahead about a  more permanent working prototype. I want to build it as a small daugtherboard that will plug into a custom socket in the motherboard. The I/O chip used is WDC65C22S. D2 is supposed to be a low voltage (<0.5 V) diode since IRQB is a totem-pole output (unlike the classic MOS6522 with open drain output). I am thinking a Schottky diode, but haven't decided on the type yet. Two 16-pin ribbon sockets will be used as connectors for ports PA and PB.  20-pin single row DIP sized connector will be a plug-in to the motherboard to connect the VIA chip to all necessary microprocessor signals.





MK 4/23/2019

Monday, February 12, 2018

MKHBC-8-Rx, refactoring, development.

Hello 8-bit computing enthusiasts!

In recent weeks I concentrate on the software part of my project. First I cleaned up my code and put in on github as mentioned in previous blog update. Since then I added KbHit implementation and detection of RTC and Banked RAM to the firmware. I enhanced existing functions in enhshell.c to use added functionality. I also added small programs that provide one or few related functions in smaller package than the enhshell.c. Since I am still using serial port to load programs to the computer, I wanted to save on loading times.
I plan to create smaller standalone images of all functions currently implemented in enhshell.c.
I also plan to add few new tools that will help me to turn the computer into fully self hosting platform: text editor and on-line symbolic machine code assembler / disassembler.
Then I will move to further expand the hardware of my computer - build generic I/O card, control panel, local console (keyboard and CRT controller) and mass storage interface.

Updated kernel jump table and system internals documentation:

Programming API / Kernal Jump Table:

Function
Address (hex)
Parameters
Return
Description
CallDS1685Init
FFD2
RegB, RegA, RegXB, RegXA
RegC in Acc
Initialize RTC chip.
CallDS1685ReadClock
FFD5
n/a
Data is returned via hardware stack. Calling subroutine is responsible for allocating 8 bytes on stack before calling this function. Clock data are stored in following order below the subroutine return address: seconds, minutes, hours, dayofweek, date, month, year, century. Valid return data on stack only if Acc > 0 (Acc = # of bytes on stack). Calling subroutine still should de-allocate stack space by calling PLA x 8 after reading or discarding returned data.
Read RTC clock data.
CallDS1685SetClock
FFD8
Parameters are passed via hardware stack: seconds, minutes, hours, day of week, day of month, month, year, century. Calling subroutine is responsible for allocating 8 bytes on stack and filling the space with valid input data before calling this function. Calling subroutine is also responsible for freeing stack space (PLA x 8).
n/a
Set date/time of RTC chip.
CallDS1685SetTime
FFDB
Parameters are passed via hardware stack: seconds, minutes, hour. Calling subroutine is responsible for allocating space on stack and filling it with valid input data before calling this function.  Calling subroutine is also responsible for freeing stack space (PLA x 3).
n/a
Set time of RTC chip.
CallDS1685StoreRam
FFDE
BankNum, RamAddr, RamVal
n/a
Store a value in non-volatile RTC memory bank.
CallDS1685ReadRam
FFE1
BankNum, RamAddr
value in Acc
Read value from non-volatile RTC memory bank.
CallReadMem
FFE4
PromptLine (contains hexadecimal address range)
n/a (output)
Machine code monitor function - read memory.
CallWriteMem
FFE7
PromptLine (contains hexadecimal address and values)
n/a (memory is modified)
Machine code monitor function - write memory.
CallExecute
FFEA
PromptLine (contains hexadecimal address)
n/a (code is executed)
Machine code monitor function - execute code in memory.
CallGetCh
FFED
n/a
Character code in Acc
Standard I/O function - get character.
CallPutCh
FFF0
Character code in Acc.
n/a (output)
Standard I/O function - put/print character.
CallGets
FFF3
n/a (input)
PromptLine, PromptLen
Standard I/O function - get string.
CallPuts
FFF6
StrPtr
n/a (output)
Standard I/O function - put/print string.
CallBankRamSel
FFCF
Banked RAM bank # in Acc. (0..7)
n/a (selects RAM bank, updates shadow register in RAM)
Banked RAM bank selection.
CallKbHit
FFCC
n/a
Character in Acc or 0 if buffer empty.
Check if there is character in RX buffer (equivalent of check if key was pressed since this is UART I/O).

WARNING:
Disable interrupts before calling any RTC function:
SEI
<call to RTC API>
CLI

Registers, buffers, memory:

RTC RAM shadow:
               RegB        = $F6
               RegA        = $F7
               RegXB       = $F8
               RegXA       = $F9
               RegC        = $FA
               Temp        = $FB
               BankNum     = $FC
               RamAddr     = $FD
               RamVal      = $FE

UART Pointers

UartRxInPt  = $F2           ; Rx head pointer, for chars placed in buf
UartRxOutPt = $F3           ; Rx tail pointer, for chars taken from buf

Uart Queues (after stack)
               UartTxQue   = $200          ; 256 byte output queue
               UartRxQue   = $300          ; 256 byte input queue

          MOS Prompt variables
               PromptLine  = $80           ; Prompt line (entered by user)
               PromptMax   = $50           ; An 80-character line is permitted
                                           ; ($80 to $CF)
               PromptLen   = $D0           ; Location of length variable

          MOS I/O Function variables
               StrPtr      = $E0           ; String pointer for I/O functions

 Other variables:
 Timer64Hz   = $E2            ; 4-byte (32-bit) counter
                              ; incremented 64 times / sec
   ; $E2,$E3,$E4,$E5 (unsigned long,
   ; little endian)

 RamBankNum  = $E6            ; Current Banked RAM bank#.
 DetectedDevices = $E7        ; Flags indicating devices detected  
                              ; by system during startup.

Detected devices flags:
DEVPRESENT_RTC     
 %10000000
DEVPRESENT_NORTC   
%01111111
DEVPRESENT_EXTRAM  
%01000000
DEVPRESENT_NOEXTRAM
%10111111
DEVPRESENT_BANKRAM 
100000
DEVPRESENT_NOBRAM  
%11011111
DEVPRESENT_UART    
010000
DEVPRESENT_NOUART  
%11101111
DEVNOEXTRAM        
DEVPRESENT_NOEXTRAM & DEVPRESENT_NOBRAM

          Customizable jump vectors
          Program loaded and run in RAM can modify these vectors
          to drive custom I/O console hardware and attach/change
          handler to IRQ procedure. Interrupt flag should be
          set before changes are applied and cleared when ready.
          Custom IRQ handler routine should make a jump to standard
          handler at the end. Custom I/O function routine should
          end with RTS.

               StoreAcc  =     $11           ; Temporary Accumulator store.
               IrqVect   =        $0012      ; Customizable IRQ vector
               GetChVect =        $0014      ; Custom GetCh function jump vector
               PutChVect =        $0016      ; Custom PutCh function jump vector
               GetsVect  =        $0018      ; Custom Gets function jump vector
               PutsVect  =        $001a      ; Custom Puts function jump vector

I/O space / address range:

$C000 .. $C7FF, 8 pages (8 x 256 bytes):

Internal (non-buffered) I/O bus:

$C000 .. $C0FF - slot 0 (RAM bank switching register)
$C100 .. $C1FF - slot 1 (RTC registers)
$C200 .. $C2FF - slot 2 (Reserved for Prioritized Interrupt Controller)
$C300 .. $C3FF - slot 3 (Reserved for built in I/O parallel interface PIA or VIA)

External (buffered/expansion) I/O bus:

$C400 .. $C4FF - slot 4 (UART)
$C500 .. $C5FF - slot 5
$C600 .. $C6FF - slot 6
$C700 .. $C7FF - slot 7

RAM bank switching.

NOTE: Because RAM bank switching hardware register is write only, we cannot read from it to determine which bank is selected. The purpose of bank# RAM register at $E6 is just that - remembering last selected bank#.

 Address:
$C000
 Value:
$00 .. $07
 Banked memory:
$8000 .. $BFFF
 Bank number RAM register:
$E6

Memory map:

$0000 - $7FFF: Base RAM, 32 kB. $0000 - $03FF space is used by the system.
$6000 - $7FFF: Optional Video RAM, 8 kB. (takes away from Base RAM, leaving 24 kB for general purpose)
$8000 - $BFFF: Banked RAM, 16 kB space x 8 banks = 128 kB.
$C000 - $C7FF: I/O space, 8 slots x 256 Bytes = 2 kB.
$C800 - $FFFF: EPROM, 14 kB.


System programs:

System programs currently consist of:

enhshell.c - combines rudimentary command line interface with additional functions for RTC, Banked RAM and more.
setdt.c - program that allows to set and show date / time.
date.c  - programs that shows date / time.
d2hexbin.c - conversion tool from decimal to hexadecimal / binary code.

Programs written in C (CC65) or CA65 assembly for MKHBC-8-Rx computer / MKHBC OS use library written in C and assembly languages which implements standard C library (CC65), I/O console and RTC functions and are compiled into library archive mkhbcos.lib.
Corresponding C header files are:


  • mkhbcos_ansi.h - ANSI terminal API
  • mkhbcos_ds1685.h - DS1685 RTC API
  • mkhbcos_lcd.h - standard LCD 16x2 API
  • mkhbcos_ml.h - C header with definitions of MKHBCOS API and internals
  • mkhbcos_ml.inc - assembly header with definitions for MKHBCOS API and internals
  • mkhbcos_serialio.h - serial I/O API

Thank you for visiting my blog.

---
MK
Feb 13 2018

Saturday, February 3, 2018

RTC library moved to EPROM, kernel jump table extended.

Hello 8-bit computing enthusiasts.

A little update on the MKHBC-8-Rx project.
Recently having (mostly) completed my home improvement projects, I have some more time to play with my pet project.
I have been mainly working on code, cleaning it up and moving some essential parts to EPROM. It is still a mess, but I think it is good enough to be published, so I put it on github.

I moved assembly code of RTC DS-1685 driver to EPROM, enabled periodic interrupts from RTC and added interrupt routine for RTC. I also expanded kernel jump table and added some new functions to my so called 'enhanced shell', although calling this program a shell is a bit of an overkill. :-)

Below I present the current documentation of the OS / firmware fir this system:


System programming / Operating System of MKHBC-8-Rx is divided into 2 parts:

  • Firmware, which resides in EPROM.
  • System programs, which are loaded to RAM via serial port.

Firmware consists of hexadecimal machine code monitor, hardware drivers code, MOS-6502 mandatory vectors: Reset, NMI, IRQ and Kernel Jump Table.
Firmware is implemented in MOS 6502 assembly language.
Kernel Jump Table is a series of jump commands that redirect to code performing certain system functions.
These jump commands are guaranteed to be always at the same memory locations, so the system programs using them don't have to be rebuilt each time the implementation of firmware is changed and some internal addresses has moved. The jump table is always in the same place jumping from the same locations to the same functions that they should perform, even if the functions themselves change the location in the EPROM due to code refactoring or relocation of binary code.
The new entries can be added to the Kernel Jump Table though, so it is guaranteed to be backwards compatible.

Theory of operation:

When computer is started, the reset circuit holds the reset line low long enough for CPU to initialize, then the CPU performs startup routine which consists of reading the Reset vector from the EPROM memory (which must be at fixed address) and executes the routine pointed by that vector.
The routine contains initialization code for OS and then goes into the eternal loop which sends the output via serial port and expects input on the serial port. By connecting text serial terminal device configured to be the same speed as computer's UART speed, it is possible to interface with the computer to send commands and receive output.

The command UI is very simple and consists of very rudimentary hexadecimal machine code monitor which allows to read/dump values in computer's memory, write/modify values in Random Access Memory and execute code at provided address.
This UI is rudimentary but sufficient for entering code into computer's RAM and executing it.

Programming API / Kernal Jump Table:

Function
Address (hex)
Parameters
Return
Description
CallDS1685Init
FFD2
RegB, RegA, RegXB, RegXA
RegC in Acc
Initialize RTC chip.
CallDS1685ReadClock
FFD5
n/a
Data is returned via hardware stack. Calling subroutine is responsible for allocating 8 bytes on stack before calling this function. Clock data are stored in following order below the subroutine return address: seconds, minutes, hours, dayofweek, date, month, year, century. Valid return data on stack only if Acc > 0 (Acc = # of bytes on stack). Calling subroutine still should de-allocate stack space by calling PLA x 8 after reading or discarding returned data.
Read RTC clock data.
CallDS1685SetClock
FFD8
Parameters are passed via hardware stack: seconds, minutes, hours, day of week, day of month, month, year, century. Calling subroutine is responsible for allocating 8 bytes on stack and filling the space with valid input data before calling this function. Calling subroutine is also responsible for freeing stack space (PLA x 8).
n/a
Set date/time of RTC chip.
CallDS1685SetTime
FFDB
Parameters are passed via hardware stack: seconds, minutes, hour. Calling subroutine is responsible for allocating space on stack and filling it with valid input data before calling this function.  Calling subroutine is also responsible for freeing stack space (PLA x 3).
n/a
Set time of RTC chip.
CallDS1685StoreRam
FFDE
BankNum, RamAddr, RamVal
n/a
Store a value in non-volatile RTC memory bank.
CallDS1685ReadRam
FFE1
BankNum, RamAddr
value in Acc
Read value from non-volatile RTC memory bank.
CallReadMem
FFE4
PromptLine (contains hexadecimal address range)
n/a (output)
Machine code monitor function - read memory.
CallWriteMem
FFE7
PromptLine (contains hexadecimal address and values)
n/a (memory is modified)
Machine code monitor function - write memory.
CallExecute
FFEA
PromptLine (contains hexadecimal address)
n/a (code is executed)
Machine code monitor function - execute code in memory.
CallGetCh
FFED
n/a
Character code in Acc
Standard I/O function - get character.
CallPutCh
FFF0
Character code in Acc.
n/a (output)
Standard I/O function - put/print character.
CallGets
FFF3
n/a (input)
PromptLine, PromptLen
Standard I/O function - get string.
CallPuts
FFF6
StrPtr
n/a (output)
Standard I/O function - put/print string.
CallBankRamSel
FFCF
Banked RAM bank # in Acc. (0..7)
n/a (selects RAM bank, updates shadow register in RAM)
Banked RAM bank selection.

WARNING:
Disable interrupts before calling any RTC function:
SEI
<call to RTC API>
CLI

Registers, buffers, memory:

RTC RAM shadow:
               RegB        = $F6
               RegA        = $F7
               RegXB       = $F8
               RegXA       = $F9
               RegC        = $FA
               Temp        = $FB
               BankNum     = $FC
               RamAddr     = $FD
               RamVal      = $FE

Uart Queues (after stack)
               UartTxQue   = $200          ; 256 byte output queue
               UartRxQue   = $300          ; 256 byte input queue

          MOS Prompt variables
               PromptLine  = $80           ; Prompt line (entered by user)
               PromptMax   = $50           ; An 80-character line is permitted ($80 to $CF)
               PromptLen   = $D0           ; Location of length variable

          MOS I/O Function variables
               StrPtr      = $E0           ; String pointer for I/O functions

 Other variables:
 Timer64Hz   = $E2            ; 4-byte (32-bit) counter incremented 64 times / sec
   ; $E2,$E3,$E4,$E5 (unsigned long, little endian)

          Customizable jump vectors
          Program loaded and run in RAM can modify these vectors
          to drive custom I/O console hardware and attach/change
          handler to IRQ procedure. Interrupt flag should be
          set before changes are applied and cleared when ready.
          Custom IRQ handler routine should make a jump to standard
          handler at the end. Custom I/O function routine should

          end with RTS.

               StoreAcc  =  $11          ; Temporary Accumulator store.
               IrqVect   =  $0012        ; Customizable IRQ vector
               GetChVect =  $0014        ; Custom GetCh function jump vector
               PutChVect =  $0016        ; Custom PutCh function jump vector
               GetsVect  =  $0018        ; Custom Gets function jump vector
               PutsVect  =  $001a        ; Custom Puts function jump vector

I/O space / address range:

$C000 .. $C7FF, 8 pages (8 x 256 bytes):

Internal (non-buffered) I/O bus:

$C000 .. $C0FF - slot 0 (RAM bank switching register)
$C100 .. $C1FF - slot 1 (RTC registers)
$C200 .. $C2FF - slot 2 (Reserved for Prioritized Interrupt Controller)
$C300 .. $C3FF - slot 3 (Reserved for built in I/O parallel interface PIA or VIA)

External (buffered/expansion) I/O bus:

$C400 .. $C4FF - slot 4 (UART)
$C500 .. $C5FF - slot 5
$C600 .. $C6FF - slot 6
$C700 .. $C7FF - slot 7

RAM bank switching.

 Address:
$C000
 Value:
$00 .. $07
 Banked memory:
$8000 .. $BFFF
 Bank number RAM register:
$E6

Memory map:

$0000 - $7FFF: Base RAM, 32 kB.
$6000 - $7FFF: Optional Video RAM, 8 kB.
$8000 - $BFFF: Banked RAM, 16 kB space x 8 banks = 128 kB.
$C000 - $C7FF: I/O space, 8 slots x 256 Bytes = 2 kB.
$C800 - $FFFF: EPROM, 14 kB.


System programs:

System programs currently consist only one - enhanced shell.
It is written in C and compiled with CC65.
Programs written in C (CC65) or CA65 assembly for MKHBC-8-Rx computer / MKHBC OS use library written in C and assembly languages which implements standard C library (CC65), I/O console and RTC functions and are compiled into library archive mkhbcos.lib.
Corresponding C header files are:


  • mkhbcos_ansi.h - ANSI terminal API
  • mkhbcos_ds1685.h - DS1685 RTC API
  • mkhbcos_lcd.h - standard LCD 16x2 API
  • mkhbcos_ml.h - machine code monitor API
  • mkhbcos_serialio.h - serial I/O API

That's all for today.
Thank you for visiting my blog.

Marek K. 2/4/2018.

Reset And Panic Buttons, Buttons Debouncing

Reset And Panic Buttons, Buttons Debouncing The monitor program in my home brew computer has a nice debug feature. The NMI (Non-Maskab...