BBC MICROBASE SERIES BBC 6502 Machine Code by Geoff Cox ---------------------------------- This series was first published on Micronet between April and October 1991 The BBC Micro Operating System Part One: The moving electron writes ------------------------------------ In the last series we reviewed the basics of machine code programming using the 6502. You will have noticed that not too many examples of programming were given. This is because there are two levels of programming on any machine, machine level and operating system level. Operating Systems ----------------- An operating system is basically a group of routines that sit between the user and the electronics of the computer. To illustrate what an operating system does we have to turn briefly from the path of the series. We'll imagine that you want to write the letter A on the screen of your monitor. First we have to work out the shape of the character and slice it horizontally into eight sections, one for each of eight screen scanning lines on the monitor. Now we need to detect when the frame synchronising pulse for the monitor is sent by the computer. Next we need to count the line synchronising pulses to find the one corresponding to the start of the first line of the character. Then we must time from this pulse to the start of the first line of the character, turn on the appropriate electron guns in the monitor and turn them off at the correct time for the end of the of the character. Finally we have a few microseconds to do it all again for the next line. That's more or less what happens fifty times a second on your monitor screen. Mapped Screens -------------- This makes even the easiest task very complex. We can make life easier by storing a picture or map of the screen in memory and writing the character shape to the appropriate map locations. The map can then be scanned in synchronisation with the electron beam on the monitor. This can either be via a clever piece of electronics or a software routine. To illustrate the BBC map, type the following program on any 6502- based BBC without screen RAM shadowing. 10 MODE 0 20 ?&7D00=65 You should see two little white dots towards the bottom right of the screen. Now two dots are not the letter A so if we want to write A we have to designthe character and write it to the map. This program does just that. 10 MODE 0 20 FOR A=&7D00 TO &7D07 30 READ B 40 ?A=B 50 NEXT 60 DATA &3C, &66, &66, &7E 70 DATA &66, &66, &66, &00 The sharp-eyed among you will have spotted something a little odd here. The screen seems to be arranged in blocks of eight bytes in this mode. Don't worry about this - it simply makes character table design simpler. Character Tables ---------------- We can make life even easier by designing a set of characters and putting them in an area of memory where they can be looked up. In the BBC B this area is in ROM starting at &C000. A quick diversion here. If we have to have a series of character designs in memory it helps to have a standard method of accessing them. The standard character ordering is called ASCII. A space has ASCII number 32 and this is the first "printable" character in the set. The last is 127 (Delete). There are eight bytes in each character matrix so the design for any character is at &C000+(8*(ASCII - 32)). This program examines the ROM character table and shows how characters are arranged. 10 MODE 0 20 PRINT "CHARACTER ?" 30 A$=GET$ 40 PRINT A$ 50 START=(8(ASC(A$)-32))+&C000 60 FIN=START+7 70 FOR BYTE=START TO FIN 80 PEEK=?BYTE 90 PROC_BIN 100 PRINT~BYTE;TAB(15);~?BYTE;TAB(20); 110 NEXT 120 END 130 DEFPROC_BIN 140 A$="" 150 FOR BIT=7 TO 0 STEP-1 160 X=(PEEK AND 2^BIT) 170 IF X>1 THEN A$=A$+"" ELSE A$=A$+"." 180 NEXT 190 ENDPROC As you will often want to write characters on the screen it makes a lot of sense to have a little routine to take a character from a register and print it on the screen. The routine will look up a character in the table and print it to the appropriate position on the screen. There will also need to be a record of the cursor position on the screen. Routines for printing and moving a cursor on the screen will also be useful. You could, of course, write all of these routines yourself as part of every program but as the computer manufacturer has to provide them in order to tell you the machine is working he usually leaves an access point for you to use the routines yourself. Incidentally if you rewrite the first program but change line 10 to read MODE 7 you will see a letter A on the screen. This is an example of clever electronics. A chip on the BBC board contains a character generator. Instead of the character design being stored in the screen map the character's ASCII value is stored. This is read by the electronics and used to generate characters directly on the screen. This allows a 40 x 80 text and block graphics screen to be generated in just 1K of RAM. Using the Operating System -------------------------- The screen handling routines and several others make up an operating system. In the BBC micro the series of routines that write a character on the screen can be accessed though a point called OSWRCH at &FFFE. So instead of having to design characters, time lines and do all of the other things, you can output a letter A to the screen simply using: LDA #65 JSR &FFEE ;OSWRCH and leave the operating system to do the rest. BBC 6502 Machine Code Part Two: In the beginning -------------------------- Last week we looked at the reasons for an operating system and how it can simplify the programmer's task. Unfortunately you will not always be able to use the operating system. There may not be a suitable routine or it may be too slow. In these cases you have to write to the device itself. So to continue our look at machine code we need to examine programming with device handling and the operating system. This series will attempt to kill two birds with one stone by taking avery close look at Acorn's OS 1.20 used on Model Bs. This changed little for the B+ and Master so while the routines may be in a slightly different place the basic system will be the same. The Master uses 65C02 code which has a few extra instructions so there may be some differences in the length of the code. What you will need ------------------ A disassembler or machine code monitor that can handle 6502 or 65C02 codes. If If you don't have either you can use: PRINT ~?address to get the hexadecimal codes which you can then look up in the back of the BBC User Guide to get the relevant command. Suitable Monitors are EXMON, BBC Monitor or the SYSTEM monitor. (I use "Maxim" - Ed.) If you have a single- stepping monitor like one of these, you will be able to trace some of the routines for yourself. For this series we will use standard 6502 mnemonics except that DB and DW will be used to show byte assignments rather than EQUS, EQUW and EQUB. This is because the disassembler I am using produces these codes and it's a lot easier to follow than convoluted BBC-type statements. First things first ------------------ Remember that an operating system is not a program in the usual sense. Normal programs have a defined entry and exit routines. An operating system can have a large number of entry and exit points as well as interlocking routines. So to examine the operating system we need a starting point. The 6502 regards memory as a series of 256-byte pages 0 to &FF (255). Any address can be considered to be a page number plus an offset within the page. Both figures can be represented by a single byte. So address &FF01 is on Page &FF offset 01. The concept of offsets is very useful if you ever get involved in 80n86 programming. The BBC Manual gives a series of system entry points on page FF. Most of these are indirected through Page 2 and as we cannot guarantee what the contents of Page 2 should be (the vectors can be and are changed) these are useless as starting points. This leaves three sensible entry points. 6502 Vectors FFFA DW &0D00 ;NMI address FFFC DW &D9CD ;RESET address FFFE DW &DC1C ;IRQ address The NMI address is in RAM so no joy there, but the other two look fine. The best is RESET as this is where the machine starts when it is turned on or BREAK is pressed. In the case of Model B and OS 1.20 that address is &D9CD, so what happens? In the beginning ---------------- Reset can be effected by turning on the computer or pressing BREAK. If it is a power-up then the system VIA and processor are reset electronically. If this is a power on situation then nothing has been set up. The first thing that happens when power is turned on is that the 6522 VIAs, the processor and the floppy disc controller are reset. This is done by means of one of three printed circuit tracks. The tracks are RSTA, RST and NOTRESET. RSTA is only connected to the system 6522 Versatile Interface adaptor (VIA). This operates through a little resistor/capacitor circuit that only works when the power is turned on. The effect of this is that the 6522 System VIA Interrupt Enable Register (IER) bits 0 to 6 will be clear (0) only if the reset is caused by a power on condition. If the Reset is caused by BREAK being pressed then the machine must have been on and therefore one or more of the System VIA IER bits will be set (to 1). If one or more bits are set then bit 7 of the VIA will also be set. This is used to determine the type of Reset. So let's look at the operating system more closely. D9CD LDA #&40 ;set NMI first instruction to RTI D9CF STA &0D00 ;NMI RAM start RESET is the ultimate Act of God as far as the machine is concerned. Anything could be happening so the operating system has to clean up the system as its first act. These first instructions just make sure that if a disc is running no more information will be read or written from or to the disc. This illustrates why you shouldn't press BREAK when a disc is being accessed! The next section sets up the stack: + D9D2 SEI ;disable interrupts just in case D9D3 CLD ;clear decimal flag D9D4 LDX #&FF ;reset stack to where it should be D9D6 TXS ;(&1FF) Next find out if a power-up reset or a BREAK press by examining the System VIA IER register. D9D7 LDA &FE4E ;read interrupt enable register of the system VIA D9DA ASL ;shift bit 7 into carry D9DB PHA ;save what's left D9DC BEQ &D9E7 ;if Power up A=0 so go to D9E7 to clear memory That's probably enough for this time. Don't worry! I don't intend to do a complete disassembly of the operating system in this series but we will follow through the power-on sequence to the end because a lot of interesting things happen at this time. We'll take a look at D9E7 and the next routine in this sequence (D9DE) in the next part. BBC 6502 Machine Code Part Three: Cleaning up the mess -------------------------------- In the last part we looked at what happens when you press BREAK or switch on the machine. We'll now continue with a look at an undocumented (at least officially) routine. The byte at &258 can be used to contain information about what the machine should do if BREAK is pressed. FX200,n is used to set this byte. If n=2 or n=3 then the memory must be cleared. This is often used in program protection. D9DE LDA &0258 ;else if BREAK pressed read BREAK Action flags (set by FX200,n) D9E1 LSR ;divide by 2 D9E2 CMP #&01 ;if &0258 <> 2 or 3 D9E4 BNE &DA03 ;then Goto &DA03 D9E6 LSR ;divide A by 2 again (A=0 if FX200,2/3 else A=n/4 Pages 4-&7F are cleared by a simple loop if &258=2 or 3 or it is a power on reset. Look out for the clever way of avoiding problems on 16K machines. D9E7 LDX #&04 ;get page to start clearance from (4) D9E9 STX &01 ;store it in ZP 01 D9EB STA &00 ;store A at 00 D9ED TAY ;and in Y to set loop counter ;LOOP STARTS D9EE STA (&00),Y ;clear RAM D9F0 CMP &01 ;until page address (in &01) =0 D9F2 BEQ &D9FD ; D9F4 INY ;increment pointer D9F5 BNE &D9EE ;if not zero loop round again D9F7 INY ;else increment again (Y=1) this avoids overwriting the RTI ;instruction at &D00 D9F8 INX ;increment X D9F9 INC &01 ;increment &01 D9FB BPL &D9EE ;loop until Page (in 01)=&80 then exit Note that RAM addressing for 16K loops around to &4000=&00 hence the checking of &01 for 00. This avoids overwriting zero page on BREAK which would cause the machine to crash! D9FD STX &028E ;writes marker for available RAM 40 =16K,80=32 DA00 STX &0284 ;write soft key consistency flag This routine shows the basic structure of a loop. Those of you who program in BASIC will recognise it as a very simple structure: 10 A=A+1 20 IF A<20 GOTO 10 The loop uses zero page addressing with the target address in 00 and 01 (Page) and the index in Y. The loop is exited when the value in 01 becomes negative. Remember that all values between 0 and &7F are considered to be positive, so the BPL instruction can be used to exit the loop at page &80, the first negative number. This is the first of the useful loop techniques we'll see in this series. Notice that the first byte of each page is left unchanged. This is useful if you want information to survive a BREAK of this type. This clearing of memory is not normally carried out. Next week we'll have a look at the normal RESET path. BBC 6502 Machine Code Part Four: Cleaning up even more mess ------------------------------------- As we saw last week, a normal warm reset avoids the memory clearance and proceeds to set up the System VIA. DA03 LDX #&0F ;set PORT B data direction register to output on bits ;0-3 and input bits 4-7 DA05 STX &FE42 ; The next bit is a little more complicated and is intimately bound up with hardware. The function is to set up the addressable latch IC 32 for peripherals via PORT B. The latch value is written by writing the value to &FE40 bits 0 to 2 and either a 1 or 0 to bit 3. Writing the value + 8 therefore writes a 1 to the latched address, otherwise a 0 is written. Value Peripheral Effect + 0 8 0 Sound chip Enabled Disabled Speech Chip 1 (RS) Low High 2 (WS) Low High 2 (WS) Low High 3 Keyboard Write Disabled Enabled 4 C0 address modifier Low High 5 C1 address modifier Low High 6 Caps LED On Off 7 Shift LED On Off C0 and C1 are involved with hardware scroll screen address. ;X=&F on entry DA08 DEX ;loop start DA09 STX &FE40 ;Write latch IC32 DA0C CPX #&09 ;Is it 9? DA0E BCS &DA08 ;If not go back and do it again ;X=8 at this point ;Caps Lock On, SHIFT Lock undetermined ;Keyboard Autoscan on ;Sound disabled (may still sound) Next the keyboard is scanned to determine the values of the keyboard links and whether a Ctrl-Break has been performed. Remember that although we have spent a lot of time reading this, we are probably less than 200 microseconds after BREAK was pressed. The check for Ctrl-Break is effectively looking for simultaneous keypresses. DA10 INX ;X=9 DA11 TXA ;A=X DA12 JSR &F02A ;Interrogate keyboard DA15 CPX #&80 ;for keyboard links 9-2 and CTRL key (1) DA17 ROR &FC ;rotate MSB into bit 7 of &FC DA19 TAX ;Get back value of X for loop DA1A DEX ;Decrement it DA1B BNE &DA11 ;and if >0 do loop again ;On exit if Carry set link 3 is made ;link 2 = bit 0 of &FC and so on ;If CTRL pressed bit 7 of &FC=1 X=0 DA1D STX &028D ;Clear last BREAK flag DA20 ROL &FC ;CTRL is now in carry &FC is keyboard links DA22 JSR &EEEB ;Set LEDs ;Carry set on entry is in bit 7 of A on exit DA25 ROR ;Get carry back into carry flag To review what the operating system has done so far, about 400 microseconds after a BREAK press or about 2 milliseconds from a power on. Memory may have been cleared, NMIs have been short circuited, IRQs disabled. The keyboard has been scanned for made links and for Ctrl being pressed. We have also located two important and undocumented subroutines: &F02A to scan the keyboard and &EEEB to set the keyboard LEDs. The F02A routine scans for the key whose code is in X being pressed: F02A LDY #&03 ;Stop Auto scan F02C STY &FE40 ;by writing to system VIA F02F LDY #&7F ;Set bits 0 to 6 of port A to input on bit 7. ;Output on bits 0 to 6 F031 STY &FE43 ; F034 STX &FE4F ;Write X to Port A system VIA (key to check) F037 LDX &FE4F ;Read back &80 if key pressed (M set) F03A RTS ;And return The routine at &EEEB switches on the selected keyboard lights. EEEB PHP ;Save flags EEEC LDA &025A ;Read keyboard status ;Bit 7=1 shift enabled ;Bit 6=1 control pressed ;Bit 5 =0 shift lock ;Bit 4 =0 Caps lock ;Bit 3 =1 shift pressed EEEF LSR ;Shift Caps bit into bit 3 EEF0 AND #&18 ;Mask out all but 4 and 3 EEF2 ORA #&06 ;Returns 6 if caps lock OFF &E if on. ;Remember add 8 to the value for the addressable ;latch to send a 1. EEF4 STA &FE40 ;Turn on or off caps light if required EEF7 LSR ;Bring shift bit into bit 3 EEF8 ORA #&07 ; EEFA STA &FE40 ;Turn on or off shift lock light EEFD JSR &F12E ;Set keyboard counter EF00 PLA ;Get back flags into A EF01 RTS ;Return In this part we've had a look at subroutines using JSR and RTS, the machine code equivalent of GOSUB, PROC or FN. Subroutines are often used in machine code to perform such frequently needed functions as scanning a keyboard or turning on and off lights. We've also discovered that the byte at &25A contains the keyboard status. Try changing it for yourself. You can therefore use OR and AND to set the shift and Caps lock status of the machine for a particular program. Next week we'll examine setting up the default vector table in memory. BBC 6502 Machine Code Part Five: Vectors Victor ------------------------- The next stage is to set up the vectors on page 2. DA26 LDX #&9C ; DA28 LDY #&8D ; DA2A PLA ;Get back A from &D9DB DA2B BEQ &DA36 ;If A=0 power up reset so go to DA36 with X=&9C ;Y=&8D DA2D LDY #&7E ;else let Y=&7E DA2F BCC &DA42 ;and if not CTRL- BREAK go to DA42 for a WARM RESET DA31 LDY #&87 ;else Y=&87 COLD RESET DA33 INC &028D ;&28D=1 DA36 INC &028D ;&28D=&28D+1 DA39 LDA &FC ;Get keyboard links set DA3B EOR #&FF ;Invert DA3D STA &028F ;and store at &28F DA40 LDX #&90 ;X=&90 What we have done is to set up the high water marks for the reset of vectors. &28D=0 Warm reset, X=&9C, Y=&7E &28D=1 Power up , X=&90, Y=&8D &28D=2 Cold reset, X=&9C, Y=&87 DA42 LDA #&00 ;A=0 DA44 CPX #&CE ;zero &200+X to &2CD DA46 BCC &DA4A ; DA48 LDA #&FF ;then set &2CE to &2FF to &FF DA4A STA &0200,X ; DA4D INX ; DA4E BNE &DA44 ; ;A=&FF X=0 This is another IF-GOTO loop, but in this case it is a double function loop. The test at DA44 to DA46 means that A is 0 only for values of X between the high water mark and &CD. Above this value A is set to &FF by the instruction at &DA48. This saves a few bytes of space, essential when writing a tightly-filled ROM. The next instructions set up the printer port. The only reason for doing this now is to save two bytes. A must be &FF at this point so it is used to set up the User VIA for outputs as the printer port. DA50 STA &FE63 ;Set port A of user VIA to all outputs (printer out) DA53 TXA ;A=0 DA54 LDX #&E2 ;X=&E2 START OF LOOP DA56 STA &00,X ;set zero page addresses &E2 to &FF to zero DA58 INX ; DA59 BNE &DA56 ;X=0 Now set up the vectors in page 2 from the table at &D940: DA5B LDA &D93F,Y ;copy data from &D93F+Y DA5E STA &01FF,Y ;to &1FF+Y DA61 DEY ;until DA62 BNE &DA5B ;1FF+Y=&200 Note that this is a decrementing loop which, for loops ending when an index register reaches zero, is faster and shorter because no compare is needed. More space saved! Now the RS423 port is set up via a subroutine affecting the ACIA. (Asynchronous Communications Interface Adaptor) DA64 LDA #&62 ;A=&62 DA66 STA &ED ;store in &ED DA68 JSR &FB0A ;set up ACIA ;X=0 Now Acorn clears the interrupt and enable registers of both VIAs. DA6B LDA #&7F ;bit 7 is 0! DA6D INX ; DA6E STA &FE4D,X ; DA71 STA &FE6D,X ; DA74 DEX ; DA75 BPL &DA6E ; ;This loop only has two passes as X=0 on entry. DA77 CLI ;Briefly allow interrupts to clear anything ;pending DA78 SEI ;Disallow again NB: all VIA IRQs are disabled DA79 BIT &FC ;If bit 6=1 then JSR &F055 as there must be a ;hardware interrupt! DA7B BVC &DA80 ;else DA80 DA7D JSR &F055 ; What have we here? Another undocumented routine. If bit 6 of &FC is set there must have been a hardware interrupt when the SEI occurred. From the circuit diagram the only place that this IRQ could have come from is the 1MHz bus - let's have a look at the routine at &F055. F055 JMP (&FDFE) ;Jim paged entry vector So we jump to some piece of hardware on the 1MHz bus. This would probably be a ROM which would take over the system at power on and Break. This has some very interesting applications. It was designed by Acorn to provide a crude Econet facility to allow a batch of machines to be functionally tested without the need to install a full Econet kit. Next week we shall examine the VIA bus. BBC 6502 Machine Code Part Six: The VIA bus --------------------- The next interesting routine we find in the BBC operating system is the one that sets up the system VIA interrupts. It is located at &DA80. Refer to the manual for the meanings of Sheila addresses. DA80 LDX #&F2 ;Enable interrupts 1,4,5,6 of system VIA DA82 STX &FE4E ; ;0 Keyboard enabled as needed ;1 Frame sync pulse ;4 End of A/D conversion ;5 T2 counter (for speech) ;6 T1 counter (10 mSec intervals) DA85 LDX #&04 ;set system VIA PCR DA87 STX &FE4C ; ;CA1 Interrupt on negative edge (Frame sync) ;CA2 Handshake output for keyboard ;CB1 Interrupt on negative edge (end of conversion) ;CB2 Negative edge (Light pen strobe) DA8A LDA #&60 ;Set system VIA ACR DA8C STA &FE4B ; ;Disable latching ;Disable shift register ;T1 counter continuous interrupts ;T2 counter timed interrupt DA8F LDA #&0E ;Set system VIA T1 counter (low) DA91 STA &FE46 ; ;This becomes effective when T1 hi set DA94 STA &FE6C ;Set user VIA PCR ;CA1 interrupt on -ve edge (Printer Acknowledge) DA80 LDX #&F2 ;enable interrupts ;CA2 High output (printer strobe) ;CB1 Interrupt on -ve edge (user port) ;CB2 Negative edge (user port) DA97 STA &FEC0 ;Set up A/D converter Bits 0 and 1 determine ;channel selected ;If Bit 3=0 it is set for an 8-bit conversion. ;If bit 3=1 12-bit conversion. Now although the machine now knows how much RAM it has it still doesn't know if it's a Model A or Model B, so it does not know if a user VIA is present at &FE60-FE6F. The next routine tests for the presence of a user VIA. The system timers are then set up to interrupt every 10mSec. Sound channels are cleared and the serial ULA is set up. Then the function keys are reset. Now we need a catalogue of sideways ROMS. This is not a catalogue in the conventional sense as the ROM title is always at the same place in the ROM itself and can be read from there. It is a catalogue of the ROM types and positions. There is a ROM latch at &FE30. Writing a number between 0 and 15 to this switches the corresponding ROM into the area between &8000 and &BFFF. A short subroutine does this and maintains a copy of the current ROM in zero page at location &F4. ;on entry X=required ROM number DC16 STX &F4 ;RAM copy of ROM latch DC18 STX &FE30 ;Write to ROM latch DC1B RTS ;and return You should use this subroutine if you want to switch ROMs. Now we can look at the ROM cataloguing routines; A ROM is considered to be valid if it contains a string identical to astring at location &DF0C in the Operating System ROM. DF0C DB ')C(' ; DF0F DB 0 ; The location of this string is pointed to by an offset byte located at &8007. ;X=0 on entry DABD JSR &DC16 ;Set up ROM latch and RAM copy to X DAC0 LDX #&03 ;Set X to point to offset in table DA80 LDX #&F2 ;Enable interrupts DAC2 LDY &8007 ;Get copyright offset from ROM DAC5 LDA &8000,Y ;Get first byte DAC8 CMP &DF0C,X ;Compare it with table byte DACB BNE &DAFB ;If not the same then goto DAFB DACD INY ;Point to next byte DACE DEX ;(s) DACF BPL &DAC5 ;and if still +ve go back to check next byte. ;This point is reached if 4 bytes indicate ;valid ROM Next the first 1K of each ROM is checked against higher priority ROMs to ensure that there are no matches. If a match is found, the lower priority ROM is ignored. A ROM type byte is located at &8006. A catalogue of these bytes is held at &2A1-&2B0. If bit 7 of this byte is 0 then the ROM is BASIC. The position of this ROM is stored at &24B. Now the ROMs are catalogued it is time to set up the speech system and screen. More about that next week. BBC 6502 Machine Code Part Seven: Talk to me ---------------------- The operating system start-up routines next checks the SPEECH system. At this point the X register is set to 16 (&10) by previous routines. This is one of the reasons why this routine is inserted here. Setting X to the required value would use two more bytes. This is not much space but it can make the difference between all of the OS fitting into a single ROM and a complete hardware or software redesign. DB11 BIT &FE40 ;If bit 7 low then we have speech system fitted DB14 BMI &DB27 ;else goto DB27 for screen set up routine. DB16 DEC &027B ;(027B)=&FF a RAM flag that indicates that a speech ;chip is present. DB19 LDY #&FF ;Y=&FF DB1B JSR &EE7F ;Initialise speech generator DB1E DEX ;via this DB1F BNE &DB19 ;loop Now X = 0 so: DB21 STX &FE48 ;Set T2 timer for speech DB24 STX &FE49 ; Screen set-up ------------- X=0 on entry to this routine which gets the default screen mode and then goes off to the screen setup routine. DB27 LDA &028F ;Get back start up options (mode) DB2A JSR &C300 ;then jump to initialise screen. One of the things that I wondered when I got a BBC was how the RESET key could possibly act as a soft key. As we all know BREAK acts as soft key 10. But the keyboard buffer is cleared by the Reset. Tucked away is the five-byte routine that makes the BREAK key act as soft key 10. Soft keys work by inserting a byte greater than 127 into the keyboard buffer. &CA is the code for key 10. DB2D LDY #&CA ;Y=&CA DB2F JSR &E4F1 ;to enter this value in the keyboard buffer Simple isn't it? You can use the routine yourself although further investigation will show that E4F1 is part of an OSbyte call. Remember that the keyboard buffer is buffer 0. E4F1 LDX #&00 ;X=0 keyboard buffer ************************************** * * * OSBYTE 153 Put byte in input * * Buffer checking for ESCAPE * * * ************************************** On entry X = buffer number which is either 0 or 1. If it's 0 then the keyboard buffer is selected. If it's 1 then it is the RS423 buffer. Notice that the JSR to EF41 ensures that ONLY the keyboard buffer can be selected. Once again we are looking at coding economy, in this case with a specific keyboard buffer entry routine. Y contains the character to be written. E4F3 TXA ;A=buffer number E4F4 AND &0245 ;and with RS423 mode (0 treat as keyboard 1 ignore ;Escapes no events no soft keys) E4F7 BNE &E4AF ;so if RS423 buffer AND RS423 in normal mode (1) E4AF ; E4F9 TYA ;else Y=A character to write E4FA EOR &026C ;compare with current escape ASCII code (0=match) E4FD ORA &0275 ;or with current ESCAPE status (0=ESC, 1=ASCII) E500 BNE &E4A8 ;if ASCII or no match E4A8 to enter byte in buffer E502 LDA &0258 ;else get ESCAPE / BREAK action byte E505 ROR ;Rotate to get ESCAPE bit into carry E506 TYA ;get character back in A E507 BCS &E513 ;and if escape disabled exit with carry clear E509 LDY #&06 ;else signal EVENT 6 Escape pressed E50B JSR &E494 ; E50E BCC &E513 ;if event handles ESCAPE then exit with carry clear E510 JSR &E674 ;else set ESCAPE flag E513 CLC ;clear carry E514 RTS ;and exit This routine will normally be accessed by assembly language programmers by OSbyte 138 which calls EF43. BBC 6502 Machine Code Part Eight: Breaker Break ------------------------- One of the 'secret' features of the BBC Micro OS 1.20 when it was arrived was the BREAK intercept. This is a useful method of taking over the machine and is sometimes used by ROM software. There are two entry points, entered with the carry flag reset to 0 and set to 1 respectively. The first call comes before sideways ROM calls. Enter BREAK intercept with Carry Clear DB32 JSR &EAD9 ;check to see if BOOT address is set up if so ;JMP to it The address &287 is written by OSbyte 247 and the jump addresses in &288 and &289 by OSbytes 248 and 249. The machine code for JMP is &4C. EAD9 LDA &0287 ;get BREAK vector code EADC EOR #&4C ;produces 0 if JP (4C) not in &287 EADE BNE &EAF3 ;if not goto EAF3 EAE0 JMP &0287 ;else jump to use BREAK code EAF3 RTS ;Return The RTS at the end of another routine is used because it saves code. Frequently you will find machine code routines where a lot of branches go to a single RTS for just this reason. If you are writing your own code remember that the RTS must be within range of the branch. One of the most common assembler errors is a branch out of range that in turn causes more errors when you add an extra RTS. Obviously at this point the machine could be totally in your control. You can return control to the OS with an RTS or just continue on your merry way. Remember that the sideways ROMs don't have any workspace yet and you can't really run BASIC or any other language as the workspace will not exist. But, assuming that you don't want to do any of this, let's go back to the OS routines after testing for BREAK intercept. DB35 JSR &F140 ;set up cassette options DB38 LDA #&81 ;test for tube to FIFO buffer 1 DB3A STA &FEE0 ; DB3D LDA &FEE0 ; DB40 ROR ;put bit 0 into carry DB41 BCC &DB4D ;if no tube then DB4D DB43 LDX #&FF ;else DB45 JSR &F168 ;issue ROM service call &FF to initialise TUBE system DB48 BNE &DB4D ;if not 0 on exit (tube not initialised) DB4D DB4A DEC &027A ;else set tube flag to show its active Now the Tube is flagged as active, or not as the case may be. We continue next week, with the setup routines for the sideways ROMs. BBC 6502 Machine Code Part Nine: A ROM with a view ----------------------------- Now we nearly have a working system, we are, perhaps, 400 milliseconds into the Power up routine. Now is the time to set up all of those nice sideways ROMs we catalogued earlier. First we set up workspace and hence the value of BASIC's PAGE variable. The call to ROMs is made via F168. This is available to the programmer as OSBYTE 143. A ROM can have a number between 0 and 15 and will have two entry points - a Service entry at &8003 and a Language entry at &8000. If the ROM does not contain language code it will not have a language entry. ROMs are paged into main memory by writing the ROM number to a latch at &FE30. Hardware could be arranged to allow 256 ROMs although the operating system does not support this. The Break Intercept code could be used to make drastic hardware modifications like this. ************************************** * * * OSBYTE 143 * * Pass service commands * * to sideways ROMs * * * ************************************** ;on entry X=command number F168 LDA &F4 ;get current ROM number F16A PHA ;store it F16B TXA ;command in A F16C LDX #&0F ;set X=15 The next bit of code is a countdown loop to send the command code to each enabled ROM in turn. The Map at &2A1 is used to decide which ROMs are active. Note the use of a countdown loop. This gives code economy and explains why the highest ROM number has priority. F16E INC &02A1,X ;read bit 7 on ROM map (no ROM has type 254 &FE) F171 DEC &02A1,X ; F174 BPL &F183 ;if not set (+ve result) F176 STX &F4 ;else store ROM number in &F4 F178 STX &FE30 ;switch in paged ROM F17B JSR &8003 ;and jump to service entry F17E TAX ;on exit put A in X F17F BEQ &F186 ;if 0 (command recognised by ROM) reset ROMs & exit F181 LDX &F4 ;else point to next lower ROM F183 DEX ; F184 BPL &F16E ;and go round loop again F186 PLA ;get back original ROM number F187 STA &F4 ;store it in RAM copy F189 STA &FE30 ;select original page F18C TXA ;put X back in A F18D RTS ;and return Couldn't be easier! So we can now return to the main body of the routine. DB4D LDY #&0E ;set current value of PAGE DB4F LDX #&01 ;issue call to claim absolute workspace DB51 JSR &F168 ;via F168 DB54 LDX #&02 ;send private workspace claim call DB56 JSR &F168 ;via F168 OSHWM is OS High Water Mark. The highest address used by the operating system. DB59 STY &0243 ;set primary OSHWM DB5C STY &0244 ;set current OSHWM DB5F LDX #&FE ;issue call for Tube to explode character set etc. DB61 LDY &027A ;Y=FF if tube present else Y=0 DB64 JSR &F168 ;and make call via F168 We now have the machine set up to enter a language, all the filing systems have been set up and the sideways ROMs activated. Next week we finally start the screen messages. BBC 6502 Machine Code Part Ten: Stringing it along ----------------------------- The next routine shows why the Machine start up message is not always seen on third-party kit. DB67 AND &0267 ;if A=&FE and bit 7 of 0267 is set then continue DB6A BPL &DB87 ;else ignore start up message DB6C LDY #&02 ;output to screen DB6E JSR &DEA9 ;'BBC Computer ' message Looking at the routine in DEA9 we find a very useful string printing routine. Remember that Y = 2 on entry. DEA9 LDA #&C3 ;point to start &C300 DEAB STA &FE ;store it DEAD LDA #&00 ;point to lo byte DEAF STA &FD ;store it and start loop with Y=2 DEB1 INY ;print character in string DEB2 LDA (&FD),Y ;pointed to by &FD/E +Y DEB4 JSR OSASCI ;print it expanding Carriage returns DEB7 TAX ;store A in X DEB8 BNE &DEB1 ;and loop again if not =0 DEBA RTS ;else exit Here is the string delimited by BRK. The code for BRK is 00. Y is 3 when the first character is read so its address is &C303. C303 DB 13 ;Carriage Return C304 DB 'BBC Computer ' C311 BRK Notice that the routine uses TAX to set the zero flag which marks the end of the string. This is a useful tip. The next part of the Operating system deals with printing correct messages on the screen. DB71 LDA &028D ;0=warm reset, If a cold reset continue DB74 BEQ &DB82 ; DB76 LDY #&16 ;by checking length of RAM DB78 BIT &028E ; DB7B BMI &DB7F ;and either DB7D LDY #&11 ; DB7F JSR &DEA9 ;finishing message with '16K' or '32K' DB82 LDY #&1B ;and two new lines DB84 JSR &DEA9 ; Notice that Y is used to pick the appropriate message. C312 DB '16K' C315 DB 7 ;Bell C316 BRK C317 DB '32K' C31A DB 7 ;Bell C31B BRK C31C DB 08,0D,0D Notice the BBC Beep at this point indicates that nearly all set up procedures have been finished. The hum is generated by the Sound channel which is reset as part of the start routine. Hence the HUM-BEEP start up. If the machine does not start properly the sound signals give a strong clue to the nature of the problem. Having got this far the OS gives us another chance to take control. Enter BREAK INTERCEPT ROUTINE WITH CARRY SET (call 1) DB87 SEC ; DB88 JSR &EAD9 ;look for break intercept jump ;SEE EARLIER PART Next we set up the keyboard lights DB8B JSR &E9D9 ;set up LEDs in accordance with keyboard status This is another 'undocumented' OSBYTE call. ************************************** * * * OSBYTE &76 (118) * * SET LEDs to Keyboard Status * * * ************************************** ;osbyte entry with carry set E9D9 PHP ;PUSH P E9DA SEI ;DISABLE INTERRUPTS E9DB LDA #&40 ;switch on CAPS and SHIFT lock lights E9DD JSR &E9EA ;via subroutine E9E0 BMI &E9E7 ;if ESCAPE exists (M set) E9E7 E9E2 CLC ;else clear V and C E9E3 CLV ;before calling main keyboard routine to E9E4 JSR &F068 ;switch on lights as required E9E7 PLP ;get back flags E9E8 ROL ;and rotate carry into bit 0 E9E9 RTS ;Return to calling routine ; * Turn on keyboard lights and * Test Escape flag ; E9EA BCC &E9F5 ;if carry clear E9EC LDY #&07 ;switch on shift lock light E9EE STY &FE40 ; E9F1 DEY ;Y=6 E9F2 STY &FE40 ;switch on Caps lock light E9F5 BIT &FF ;set minus flag if bit 7 of &00FF is set indicating E9F7 RTS ;that ESCAPE condition exists, then return The Keyboard routine continues via the KEYV. This is a little long to include here so we'll leave it until a later part. So back to the Start up routine next week with the cassette system. BBC 6502 Machine Code Part Eleven: Language! ---------------------- Having got the keyboard nicely set up the machine proceeds to initialise a filing system and run a !BOOT file if one exists. The start up options are already read from the keyboard links. DB8E PHP ;save flags DB8F PLA ;and get back in A DB90 LSR ;zero bits 4-7 and bits 0-2 bit 4 which was bit 7 DB91 LSR ;may be set DB92 LSR ; DB93 LSR ; DB94 EOR &028F ;EOR with start up options which may or may not DB97 AND #&08 ;invert bit 4 DB99 TAY ;Y=A DB9A LDX #&03 ;make initialisation call, if Y=0 on entry DB9C JSR &F168 ;RUN, EXEC or LOAD !BOOT file from a filing system. DB9F BEQ &DBBE ;if a ROM accepts this call then DBBE DBA1 TYA ;else put Y in A DBA2 BNE &DBB8 ;if Y<>0 DBB8 DBA4 LDA #&8D ;else set up standard cassette baud rates DBA6 JSR &F135 ;via &F135 which is OSBYTE 140. DBA9 LDX #&D2 ; DBAB LDY #&EA ; DBAD DEC &0267 ;decrement ignore start up message flag DBB0 JSR OSCLI ;and execute /!BOOT DBB3 INC &0267 ;restore start up message flag DBB6 BNE &DBBE ;if not zero then DBBE DBB8 LDA #&00 ;else A=0 DBBA TAX ;X=0 DBBB JSR &F137 ;set tape speed via OSBYTE 140. We now have an active filing system. The next job is to preserve the current language on soft RESET. DBBE LDA &028D ;get last RESET Type DBC1 BNE &DBC8 ;if not soft reset DBC8 DBC3 LDX &028C ;else get current language ROM address DBC6 BPL &DBE6 ;if +ve (language available) then skip search ;routine For a cold break we search for the language with the highest priority. DBC8 LDX #&0F ;set pointer to highest available ROM DBCA LDA &02A1,X ;get ROM type from map DBCD ROL ;put hi-bit into carry, bit 6 into bit 7 DBCE BMI &DBE6 ;if bit 7 set then ROM has a language entry so DBE6 DBD0 DEX ;else search for language until X=&ff Check for Tube if no language found. DBD1 BPL &DBCA ;check if tube present DBD3 LDA #&00 ;if bit 7 of tube flag is set BMI succeeds DBD5 BIT &027A ;and TUBE is connected else DBD8 BMI &DC08 ;make error No language error DBDA BRK ; DBDB DB &F9 ;error number DBDC DB 'Language?' ;message DBE5 BRK ; This might seem odd as BRK is handled by the current language BRK handler, but we don't have a language! We need to investigate further in another part. DBE6 CLC ; OSBYTE 142 enter Language ROM at &8000 X=ROM number. Carry is set if this is an OSBYTE call and clear if this is an initialisation routine. DBE7 PHP ;save flags DBE8 STX &028C ;put X in current ROM page DBEB JSR &DC16 ;select that ROM DBEE LDA #&80 ;A=128 DBF0 LDY #&08 ;Y=8 DBF2 JSR &DEAB ;display text string held in ROM at &8008,Y DBF5 STY &FD ;save Y on exit (end of language string) DBF7 JSR OSNEWL ;two line feeds DBFA JSR OSNEWL ;are output DBFD PLP ;then get back flags DBFE LDA #&01 ;A=1 required for language entry DC00 BIT &027A ;check if tube exists DC03 BMI &DC08 ;and goto DC08 if it does DC05 JMP &8000 ;else enter language at &8000 TUBE FOUND enter tube software DC08 JMP &0400 ;enter tube environment The Tube initialisation would have read the language across to the TUBE usually but it could be loaded by a !BOOT file from the filing system initialisation. The operating system now stops general control of the system and hands this to the language which looks after command lines etc. The OS however still handles the screen, keyboard and much else. Notice how every possible eventuality was taken into account during the initialisation routine. This is one of the things that made the Beeb a very powerful machine. Next week we'll have a look at the Interrupt code. BBC 6502 Machine Code Part Twelve: Pardon me! ----------------------- We finished the last part at the point where the operating systems power up routine handed over control to the language. We'll write our own language later in the series but for now let's dive into another entry point. When the processor's IRQ pin (4) goes low (0V) the processor finishes off the current instruction and then goes off to run some microcode of its own. This checks that the RDY (2) pin is high and that the interrupt flag in the status register is 0 (not set). If it is set the interrupt is ignored and the processor goes to the next instruction. This continues when the IRQ pin is low. If the flag is clear then the processor stores the program counter and status register on the stack and sets the interrupt flag. The 6502 then gets the address stored in &FFFE and &FFFF and executes this instruction next. If a BRK instruction is found in executing code then the processor performs exactly the same actions except that it does not check the status register for the interrupt flag, it does set a flag in the status register, the BRK flag. The main entry point for IRQ (and BRK) for OS 1.20 is &DC51. MAIN IRQ Entry point ;ON ENTRY STACK contains STATUS REGISTER,PCH,PCL DC1C STA &FC ;save A DC1E PLA ;get back status (flags) DC1F PHA ;and save again DC20 AND #&10 ;check if BRK flag set DC22 BNE &DC27 ;if so goto DC27 DC24 JMP (&0204) ;else JUMP through IRQ1V That's pretty straightforward so far. As you can see IRQ1V allows you to put your own hardware at a higher priority than anything else in the machine. You can also write your own hardware interrupt handler if you wish. This is the flexibility that made the BBC machine so remarkably successful among knowledgeable users. Let's look at the BRK handler now. * BRK handling routine * DC27 TXA ;save X on stack DC28 PHA ; DC29 TSX ;get status pointer DC2A LDA &0103,X ;get Program Counter low byte DC2D CLD ; DC2E SEC ;set carry DC2F SBC #&01 ;subtract 2 (1+carry) DC31 STA &FD ;and store it in &FD DC33 LDA &0104,X ;get hi byte DC36 SBC #&00 ;subtract 1 if necessary DC38 STA &FE ;and store in &FE DC3A LDA &F4 ;get currently active ROM DC3C STA &024A ;and store it in &24A DC3F STX &F0 ;store stack pointer in &F0 DC41 LDX #&06 ;and issue ROM service call 6 DC43 JSR &F168 ;(User BRK) to ROMs ;now &FD/E points to byte after BRK ;ROMS may use BRK for their own purposes ;and many do! It's interesting to see what happens with the ROM handler. This is also an entry point for OSBYTE 143 so you can use this in your own code. * OSBYTE 143 * *Pass service commands to sideways ROMs * ;on entry X=command number F168 LDA &F4 ;get current ROM number F16A PHA ;store it F16B TXA ;command in A F16C LDX #&0F ;set X=15 ;send commands loop F16E INC &02A1,X ;read bit 7 on ROM map (no ROM has ;type 254 &FE) F171 DEC &02A1,X ; F174 BPL &F183 ;if not set (+ve result) F176 STX &F4 ;else store ROM number in &F4 F178 STX &FE30 ;switch in paged ROM F17B JSR &8003 ;and jump to service entry F17E TAX ;on exit put A in X F17F BEQ &F186 ;if 0 (command recognised by ROM) ;reset ROMs & exit F181 LDX &F4 ;else point to next lower ROM F183 DEX ; F184 BPL &F16E ;and go round loop again F186 PLA ;get back original ROM number F187 STA &F4 ;store it in RAM copy F189 STA &FE30 ;select original page F18C TXA ;put X back in A F18D RTS ;and return Useful little routine that. So back to the BRK handler. DC46 LDX &028C ;get current language DC49 JSR &DC16 ;and activate it DC4C PLA ;get back original value of X DC4D TAX ; DC4E LDA &FC ;get back original value of A DC50 CLI ;allow interrupts DC51 JMP (&0202) ;and JUMP via BRKV (normally into current language) Next week we'll carry on by taking a look at the BRK handler. BBC 6502 Machine Code Part Thirteen: Give us a BRK ---------------------------- BRK is usually handled by the default language (or by a Sideways ROM). However, it may be that you are running a machine code program before a current language is set up or perhaps your language doesn't handle BRK (it should but you never know). That's when a default BRK handler takes over. * DEFAULT BRK HANDLER * DC54 LDY #&00 ;Y=0 to point to byte after BRK DC56 JSR &DEB1 ;print message Let's have a look at the print routine. Remember that the error- handling layout is: BRK Error Number (1 byte) Message BRK Y plus the address in &FD &FE points to the error message on entry. DEB1 INY ;point to first ;character in string DEB2 LDA (&FD),Y DEB4 JSR OSASCI ;print it ;expanding ;Carriage ;returns DEB7 TAX ;store A in X to change flags DEB8 BNE &DEB1 ;and loop again if not =0 DEBA RTS ;else exit A standard print routine, nothing out of the ordinary but nice and compact. You can use this in your own print routines by changing the zero page values. Back to the default BRK handler and an interesting bit of code. DC59 LDA &0267 ;if BIT 0 set and DISK EXEC error DC5C ROR ;occurs DC5D BCS &DC5D ;hang up machine! Nasty! But the machine has to be in a pretty unusual configuration for this to happen. Mind you, setting 0267 then doing a JSR to DC59 would confuse the average user. DC5F JSR OSNEWL ;else print two newlines DC62 JSR OSNEWL ; DC65 JMP &DBB8 ;and set tape speed before entering the current ;language DBB8 LDA #&00 ;else A=0 DBBA TAX ;X=0 DBBB JSR &F137 ;set tape speed via OSBYTE 141. There's the end of the BRK handling code. As I said before this is generally handled by the default language but you can arrange for your own code or a Sideways ROM to handle it. Next week we'll return to the interrupt system with a look at the default entry point for IRQ1. BBC 6502 Machine Code Part Fourteen: The story so far... ---------------------------------- We left the interrupt-handling routine just after it had gone off to the IRQ1V vector. If you don't change the vector the code continues from DC93. One very important thing to remember about an interrupt-driven machine like the BBC is that the interrupt flag is not set for too long. If it is the machine could crash. This means that interrupt routines are short and snappy. * Main IRQ Handling routines, default IRQIV destination * DC93 CLD ;clear decimal flag DC94 LDA &FC ;get original value of A DC96 PHA ;save it DC97 TXA ;save X DC98 PHA ; DC99 TYA ;and Y DC9A PHA ;on the stack ;note the pre-CMOS code! DC9B LDA #&DE ;A=&DE DC9D PHA ;store it DC9E LDA #&81 ;save &81 DCA0 PHA ;store it (a RTS will now jump to DE82) This is quite a useful technique as we will see later. If we now use JMP to go to an OS routine we can ensure that the routine, which ends with an RTS, causes execution to go to a specified point. This saves a lot of code as it can be arranged that the first device found that called the interrupt will be the only one handled. This, in turn, saves time! We now poll the hardware looking for who caused it. The first routine deals with the serial/tape system. DCA1 CLV ;clear V flag DCA2 LDA &FE08 ;get value of status register of ACIA DCA5 BVS &DCA9 ;if this was source then DCA9 to process DCA7 BPL &DD06 ;else if no interrupt requested DD06 DCA9 LDX &EA ;read RS423 timeout counter DCAB DEX ;decrement it DCAC BMI &DCDE ;and if <0 DCDE DCAE BVS &DCDD ;else if >&40 DCDD (RTS to DE82) DCB0 JMP &F588 ;else read ACIA via F588 ;RTS ends routine!! DCB3 LDY &FE09 ;read ACIA data DCB6 ROL ; DCB7 ASL ; DCB8 TAX ;X=A DCB9 TYA ;A=Y DCBA LDY #&07 ;Y=07 DCBC JMP &E494 ;check and service EVENT 7 RS423 error DCBF LDX #&02 ;read RS423 output buffer DCC1 JSR &E460 ; DCC4 BCC &DCD6 ;if C=0 buffer is not empty goto DCD6 DCC6 LDA &0285 ;else read printer destination DCC9 CMP #&02 ;is it serial printer?? DCCB BNE &DC68 ;if not DC68 DCCD INX ;else X=3 DCCE JSR &E460 ;read printer buffer DCD1 ROR &02D2 ;rotate to pass carry into bit 7 DCD4 BMI &DC68 ;if set then DC68 DCD6 STA &FE09 ;pass either printer or RS423 data to ACIA DCD9 LDA #&E7 ;set timeout counter to stored value DCDB STA &EA ; DCDD RTS ;and exit (to DE82) ;A contains ACIA status DCDE AND &0278 ;AND with ACIA bit mask (normally FF) DCE1 LSR ;rotate right to put bit 0 in carry DCE2 BCC &DCEB ;if carry clear receive register not full so DCEB DCE4 BVS &DCEB ;if V is set then DCEB DCE6 LDY &0250 ;else Y=ACIA control setting DCE9 BMI &DC7D ;if bit 7 set receive interrupt is enabled so DC7D DCEB LSR ;put BIT 2 of ACIA status into DCEC ROR ;carry if set then Data Carrier Detected applies DCED BCS &DCB3 ;jump to DCB3 DCEF BMI &DCBF ;if original bit 1 is set TDR is empty so DCBF DCF1 BVS &DCDD ;if V is set then exit to DE82 DCF3 LDX #&05 ;X=5 DCF5 JSR &F168 ;issue ROM call 5 'unrecognised ;interrupt' We've seen this ROM service routine call before. DCF8 BEQ &DCDD ;if a ROM recognises it then exit to DE82 DCFA PLA ;otherwise get back DE82 address from stack DCFB PLA ; DCFC PLA ;and get back X, Y and A DCFD TAY ; DCFE PLA ; DCFF TAX ; DD00 PLA ; DD01 STA &FC ;&FC=A DD03 JMP (&0206) ;and offer to the user via IRQ2V That was a little convoluted, to say the least. Next week we look at how the VIAs are dealt with. BBC 6502 Machine Code Part Fifteen: Hardware VIA interrupts ------------------------------------- After deciding that it wasn't the ACIA that caused the interrupt, the VIAs are the next port of inquisition. * VIA INTERRUPTS ROUTINES * DD06 LDA &FE4D ;read system VIA interrupt flag register DD09 BPL &DD47 ;if bit 7=0 the VIA has not caused interrupt goto DD47 DD0B AND &0279 ;mask with VIA bit mask DD0E AND &FE4E ;and interrupt enable register DD11 ROR ;rotate right twice to ;check for IRQ 1 (frame sync) DD12 ROR ; DD13 BCC &DD69 ;if carry clear then no IRQ 1, else IRQ 1 means ;interrupt request 1. This is different from the ;vector IRQ1. DD15 DEC &0240 ;decrement vertical sync counter DD18 LDA &EA ;A=RS423 Timeout counter DD1A BPL &DD1E ;if +ve then DD1E DD1C INC &EA ;else increment it DD1E LDA &0251 ;load flash character counter DD21 BEQ &DD3D ;if 0 then flash system is not in use, ignore it DD23 DEC &0251 ;else decrement counter DD26 BNE &DD3D ;and if not 0 go on past reset routine This routine resets the flashing character system. DD28 LDX &0252 ;get mark period count in X DD2B LDA &0248 ;current VIDEO ULA control setting in A DD2E LSR ;shift bit 0 into C to ;check if first colour DD2F BCC &DD34 ;is effective if so C=0. Jump to DD34 DD31 LDX &0253 ;else get space period count in X DD34 ROL ;restore bit DD35 EOR #&01 ;and invert it DD37 JSR &EA00 ;then change colour DD3A STX &0251 ;&0251=X resetting the counter DD3D LDY #&04 ;Y=4 and call E494 to check and implement vertical DD3F JSR &E494 ;sync event (4) if necessary DD42 LDA #&02 ;A=2 DD44 JMP &DE6E ;clear interrupt 1 and exit Remember the RTS routine last time? * PRINTER INTERRUPT USER VIA 1 * DD47 LDA &FE6D ;Check USER VIA interrupt flags register DD4A BPL &DCF3 ;if +ve USER VIA did not call interrupt DD4C AND &0277 ;else check for USER IRQ 1 printer interrupt. DD4F AND &FE6E ; DD52 ROR ; DD53 ROR ; DD54 BCC &DCF3 ;if bit 1=0 then no ;interrupt 1 so DCF3 DD56 LDY &0285 ;else get printer type DD59 DEY ;decrement DD5A BNE &DCF3 ;if not parallel then :CF3 DD5C LDA #&02 ;reset interrupt 1 flag DD5E STA &FE6D ; DD61 STA &FE6E ;disable interrupt 1 DD64 LDX #&03 ;and output data to parallel printer DD66 JMP &E13A ;and exit via RTS * SYSTEM INTERRUPT 5 Speech * DD69 ROL ;get bit 5 into bit 7 DD6A ROL ; DD6B ROL ; DD6C ROL ; DD6D BPL &DDCA ;if not set this is not ;a speech interrupt so DDCA DD6F LDA #&20 ; DD71 LDX #&00 ; DD73 STA &FE4D ; DD76 STX &FE49 ;and zero high byte of Timer t2 DD79 LDX #&08 ;&FB=8 DD7B STX &FB ; DD7D JSR &E45B ;and examine buffer 8 DD80 ROR &02D7 ;shift carry into bit 7 DD83 BMI &DDC9 ;and if set buffer is empty so exit DD85 TAY ;else Y=A DD86 BEQ &DD8D ; DD88 JSR &EE6D ;control speech chip DD8B BMI &DDC9 ;if negative exit DD8D JSR &E460 ;else get a byte from buffer DD90 STA &F5 ;store it to indicate speech or file ROM DD92 JSR &E460 ;get another byte DD95 STA &F7 ;store it DD97 JSR &E460 ;and another DD9A STA &F6 ;giving address to be accessed in paged ROM DD9C LDY &F5 ;Y=&F5 DD9E BEQ &DDBB ;and if =0 then DDBB DDA0 BPL &DDB8 ;else if +ve DDB8 DDA2 BIT &F5 ;if bit 6 of F5 =1 (&F5)>&40 DDA4 BVS &DDAB ;then DDAB DDA6 JSR &EEBB ;else continue for more speech processing DDA9 BVC &DDB2 ;if bit 6 clear then DDB2 DDAB ASL &F6 ;else double address in &F6/7 DDAD ROL &F7 ; DDAF JSR &EE3B ;and call EE3B DDB2 LDY &0261 ;get speech enable/disable flag into Y DDB5 JMP &EE7F ;and JMP to EE7F DDB8 JSR &EE7F ;Call EE7F DDBB LDY &F6 ;get address pointer in Y DDBD JSR &EE7F ; DDC0 LDY &F7 ;get address pointer high in Y DDC2 JSR &EE7F ; DDC5 LSR &FB ; DDC7 BNE &DD7D ; DDC9 RTS ;and exit Next week we continue with a look at the remaining System Interrupts. BBC 6502 Machine Code Part Sixteen: Timers and Keyboard Interrupts -------------------------------------------- The last part showed how the BBC Micro handles some of the system interrupt calls. Most of these are pretty routine so we won't continue with an interminable list. The next interesting routines concern how the timers and keyboard interrupts are handled. * SYSTEM INTERRUPT 6 10mS Clock * DDCA BCC &DE47 ;bit 6 is in carry so if clear there is no 6 so go ;on to DE47 DDCC LDA #&40 ;Clear interrupt 6 DDCE STA &FE4D ; This is the start of the update timers routine, This is interesting because of the way that the timer information is stored. It's very clever. There are two timer stores, &292-6 and &297-B. These are updated by adding 1 to the current timer and storing the result in the other, the direction of transfer being changed each time of update. This ensures that at least one timer is valid at any call as the current timer only is read. Other methods would cause inaccuracies if a timer was read while being updated. DDD1 LDA &0283 ;get current system clock store pointer (5,or 10) DDD4 TAX ;put A in X DDD5 EOR #&0F ;and invert lo nybble (5becomes 10 and vv) DDD7 PHA ;store A DDD8 TAY ;put A in Y. Carry is always set at this point DDD9 LDA &0291,X ;get timer value DDDC ADC #&00 ;update it DDDE STA &0291,Y ;store result in alternate DDE1 DEX ;decrement X DDE2 BEQ &DDE7 ;if 0 exit DDE4 DEY ;else decrement Y DDE5 BNE &DDD9 ;and go back and do next byte DDE7 PLA ;get back A DDE8 STA &0283 ;and store back in clock pointer (ie. inverse ;previous contents) DDEB LDX #&05 ;set loop pointer for countdown timer DDED INC &029B,X ;increment byte and DDF0 BNE &DDFA ;if not 0 then DDFA DDF2 DEX ;else decrement pointer DDF3 BNE &DDED ;and if not 0 do it again DDF5 LDY #&05 ;process EVENT 5 interrupt timer DDF7 JSR &E494 ; DDFA LDA &02B1 ;get byte of inkey countdown timer DDFD BNE &DE07 ;if not 0 then DE07 DDFF LDA &02B2 ;else get next byte DE02 BEQ &DE0A ;if 0 DE0A DE04 DEC &02B2 ;decrement 2B2 DE07 DEC &02B1 ;and 2B1 DE0A BIT &02CE ;read bit 7 of envelope processing byte DE0D BPL &DE1A ;if 0 then DE1A DE0F INC &02CE ;else increment to 0 DE12 CLI ;allow interrupts DE13 JSR &EB47 ;and do routine sound processes DE16 SEI ;bar interrupts DE17 DEC &02CE ;DEC envelope processing byte back to 0 DE1A BIT &02D7 ;read speech buffer busy flag DE1D BMI &DE2B ;if set speech buffer is empty, skip routine DE1F JSR &EE6D ;update speech system variables DE22 EOR #&A0 ; DE24 CMP #&60 ; DE26 BCC &DE2B ;if result >=&60 DE2B DE28 JSR &DD79 ;else more speech work DE2B BIT &D9B7 ;set V and C DE2E JSR &DCA2 ;check if ACIA needs attention DE31 LDA &EC ;check if key has been pressed DE33 ORA &ED ; DE35 AND &0242 ;(this is 0 if keyboard is to be ignored, else ;&FF) DE38 BEQ &DE3E ;if 0 ignore keyboard DE3A SEC ;else set carry DE3B JSR &F065 ;and call keyboard DE3E JSR &E19B ;check for data in use defined printer channel DE41 BIT &FEC0 ;if ADC bit 6 is set ADC is not busy DE44 BVS &DE4A ;so DE4A DE46 RTS ;else return * SYSTEM INTERRUPT 4 ADC end of conversion * DE47 ROL ;put original bit 4 from FE4D into bit 7 of A DE48 BPL &DE72 ;if not set DE72 DE4A LDX &024C ;else get current ADC channel DE4D BEQ &DE6C ;if 0 DE6C DE4F LDA &FEC2 ;read low data byte DE52 STA &02B5,X ;store it in &2B6,7,8 or 9 DE55 LDA &FEC1 ;get high data byte DE58 STA &02B9,X ;and store it in hi byte DE5B STX &02BE ;store in Analogue system flag marking last channel DE5E LDY #&03 ;handle event 3 conversion complete DE60 JSR &E494 ; DE63 DEX ;decrement X DE64 BNE &DE69 ;if X=0 DE66 LDX &024D ;get highest ADC channel present DE69 JSR &DE8F ;and start new conversion DE6C LDA #&10 ;reset interrupt 4 DE6E STA &FE4D ; DE71 RTS ;and return * SYSTEM INTERRUPT 0 Keyboard * DE72 ROL ;get original bit 0 in bit 7 position DE73 ROL ; DE74 ROL ; DE75 ROL ; DE76 BPL &DE7F ;if bit 7 clear not a keyboard interrupt DE78 JSR &F065 ;else scan keyboard DE7B LDA #&01 ;A=1 DE7D BNE &DE6E ;and off to reset interrupt and exit DE7F JMP &DCF3 ;and again a subroutine to exit. Now we come to the point you've all been waiting for. This mystery RTSreturns all subroutines to &DE82. ************** exit routine DE82 PLA ;restore registers DE83 TAY ; DE84 PLA ; DE85 TAX ; DE86 PLA ; DE87 STA &FC ;store A * IRQ2V default entry * DE89 LDA &FC ;get back original value of A DE8B RTI ;and return to calling routine. NEXT WEEK: OSBYTE entry. BBC 6502 Machine Code Part Seventeen: The BBC Operating System ---------------------------------------- We've been examining the BBC operating system in some detail over the last few weeks. Unfortunately the demise of Micronet means that we cannot finish completely, as we hoped. So we've put together the next twenty weeks' articles in the form of a completely commented disassembly of OS 1.20. This is an excellent example of BBC programming and is full of tips. Just to remind you of the main points of the software. Entry points are pointed to by a jump table in the last six bytes of the ROM. The font characters are located from &C000 to &C2FF. OK, so here it is all commented and ready for you to peruse. Ed says: I have uploaded the series of disassembly articles as ten short TSW files. Look on Micronet on 700100239 (before it's too late!) *********** THE END **********