******************************************************************************************************************* * DTMF.ASM written by Josh Lindsay 021403 * * v3.6 * * Still some bugs to work out... but altogether a success. This code is very scattered and, in some places, * * written in unorthodox ways. I deemed this an acceptable concession due to the fact that this whole program must * * fit in a space of 1984 bytes, UI and all. So when you see me making weird subroutine calls and striving like * * mad to save half a dozen bytes, just remember: that space savings means a better UI, or a cool feature. A * * worthwhile sacrafice, even at the expense of clearly readable code. * * * * Holy crap! I found a HUGE bug while I was commenting this! I had the upper boundry of the 24C256 set as 0xFFFF! * * 0xFFFF is 16 bits of address space. The 24C256 only has 15 bits! Doh! The HC11 was talking to a 64KB EEPROM * * when it should have been talking to a 32KB EEPROM! Thats why my format routine always overwrote the first byte. * * The address wrapped, and the EEPROM overwrote the first byte. * ******************************************************************************************************************* NUM_DIALED EQU 128 ; Set if a number was dialed while the OFF_HOOK bit is set. I2C_ACKD EQU 064 ; Set if the I2C bus saw a valid ACK bit. OFF_HOOK EQU 032 ; Set if the sense circuit detects an off-hook condition. FOUND_ZERO EQU 016 ; Set when the seeker routine finds 0xFF. SDA_HI EQU 008 ; Bit reflects SDA level at last execution of View_SDA routine MESS_B EQU 004 ; Set when Disp_Mess should output to the lower half of LCD. LINE_2_A EQU 002 ; These two bits are used to keep track of which line the LCD LINE_2_B EQU 001 ; functions should print on. SHOW_NUMS EQU 128 ; If set, RTI interrupt displays logged numbers when On-hook. INHIB_AUTO_HEX EQU 064 ; If clear, a call to HEX_2_DEC will automatically display. SDA EQU $10 ; PortD Pin Defs SCL EQU $08 BUTTON_0 EQU $80 ; PortC Pin Defs BUTTON_1 EQU $40 BUTTON_2 EQU $20 SENSE_PIN EQU $10 LCD_E1 EQU $40 ; PortA Pin Defs LCD_E2 EQU $20 LCD_RS EQU $10 RTI_SCALER EQU 31 ; This number is how many RTI interrupts occur per second. ROM_READ EQU $A1 ; I2C Device Read Address for 24C256. ROM_WRITE EQU $A0 ; I2C Device Write Address for 24C256. ORG $0041 ; Beginning of free RAM. SYS_FLAGS RMB 1 ; System control flags UI_FLAGS RMB 1 ; User Input Flags RTI_COUNT RMB 1 ; Number of RTI interrupts DURATION_H RMB 1 ; High byte of call duration variable DURATION_L RMB 1 ; Low byte of call duration variable ROM_INDEXH RMB 1 ; An address pointer within the EEPROM ROM_INDEXL RMB 1 TEMP_VAL RMB 1 ; A temporary variable. I2C_DATA RMB 1 ; A place for I2C data in transit. FIRST_BYTE RMB 2 ; The first byte of availible ROM. TMP_READER RMB 2 ; An echo of ROM_INDEX used for reading. ASCII_BUF RMB 6 ; 6 character ASCII Buffer. 5 can be used. ORG $FFF2 ; IRQ Pin Vector FDB IRQ_PIN ; IRQ_PIN is the ISR referanced by vector 0xFFF2 ORG $FFF0 ; RTI Vector FDB RTI_IRQ ; RTI_IRQ is the ISR referanced by vector 0xFFF0 ORG $FFFE ; Reset Vector FDB $F800 ; At reset, JMP to the beginning of EEPROM. ORG $F800 ; Start writting instructions at beginning of EEPROM. LDS #$40 ; LoaD Stack pointer with 0x40 (64 bytes of stack). LDX #$1000 ; LoaD X with 0x1000 to facillitate bit-banged I/O. LDAA #$30 ; LoaD Accumulator A with 0x30. STAA $1039 ; Make IRQ pin edge-sensitive. STAA $102B ; Setup SCI baud rate. CLRA ; Zero Accumulator A. STAA $1007 ; All 8-bits of PortC are inputs. STAA $102C ; More SCI setup. (8,1,p) LDAA #$20 ; LoaD Accumulator A with 0x20. STAA $1028 ; PortD is now in wired OR mode. LDAA #$18 ; LoaD Accumulator A with 0x18. STAA $1008 ; PortD Data Register. LDAA #$38 STAA $1009 ; PortD Data Direction Register. BCLR $08,X #$20 ; Turn off the Status LED on PortD. CLRA ; Zero Accumulator A. CLRB ; Zero Accumulator B. STAA RTI_COUNT ; Variable initilization: STD DURATION_H ; Set all variables to zero. This insures STAA SYS_FLAGS ; that things will work as planned. All STAA UI_FLAGS ; Important variables are now known values. STD ROM_INDEXH ; STore accumulator D (A concatenated with B) STD TMP_READER ; is a shortcut. Instead of storing 0x00 at two * ; distinct places, Store 0x0000 at the lowest address. * ; Since the two variables are adjacent, both will be * ; written with zeros. And we save 1 byte of EEPROM! SEI ; SEt I bit. This inhibits maskable interrupts. RTI_SETUP: LDAA #$03 ; LoaD Accumulator A with 0x03. STAA $1026 ; Set RTI to interrupt every 32.768ms. LDAA #$40 ; Set the behavior of the timer system to allow STAA $1024 ; periodic Real-Time Interrupts. This lets the STAA $1025 ; HC11 keep track of the time. SCI_SETUP: LDAA #$0C ; Finish setting up the SCI. Sorry this code is STAA $102D ; so scattered. Please referance my earlier disclaimer. LCD_Init: JSR DELAY ; Jump to SubRoutine DELAY. JSR is the coolest op-code LDAA #$38 ; ever! Directly analagous to BASIC's GOSUB. JSR Disp_CMD ; Send the LCD the command 0x38 over and over again. JSR Disp_CMD ; This is the initiallization sequence needed to setup JSR Disp_CMD ; the LCD: No cursor, left-to-right text, display on. JSR Disp_CMD ; Disp_CMD sends the command to both halves of the LCD. LDAA #$0C ; Continuing the LCD init... The LCD uses the popular JSR Disp_CMD ; Hitachi HD controller chip, and therefore can have a LDAA #$01 ; max of 80 spaces, so the 4x40 LCD is actually two JSR Disp_CMD ; seperate LCD modules as far as software is concerned. JSR I2C_Start ; Send some random address down the I2C pipe to reset JSR I2C_Write ; the 24C256. If this isn't done, the 24C256 might JSR I2C_Stop ; return bad data on the first read. Bad. Very bad. JSR Get_Byte ; Get a byte from 24C256 address 0x0000. CMPA #$CC ; If the byte is 0xCC, we assume this 24C256 to be BEQ Formatted ; already formatted. If its anything else, we erase JSR Format ; the 24C256. Formatted: LDY #Find_Zero ; LoaD index register Y with the address of Find_Zero. JSR Disp_Mess ; JSR to Disp_Mess to show this message to the user. LDAA #$A8 ; 0xA8 is the command to tell the LCD to seek to the start JSR Disp_C_A ; of the second line. Disp_C_A only commands the top half. Seeker: BCLR SYS_FLAGS #FOUND_ZERO ; Bit CLeaR the FOUND_ZERO bit of SYS_FLAGS. Seek_Zero: LDD ROM_INDEXH ; LoaD accumulator D with the contents of ROM_INDEX. CPD #$8000 ; ComPare D to the number 0x8000. BNE ROM_SPACE ; Branch if Not Equal to ROM_SPACE. JMP ROM_FULL ; JuMP to ROM_FULL. ROM_SPACE: ADDD #1 ; ADD to accumualtor D the number 0x01. STD ROM_INDEXH ; STore accumulator D at ROM_INDEX. JSR Get_Byte ; This routine is checking every byte of the 24C256 to CMPA #$FF ; find the first occurance of two consecutive 0xFF's. BNE Seeker ; The address of the first 0xFF is the first byte free. BRSET SYS_FLAGS #FOUND_ZERO I_Am_Zero ; BRanch if SET SYS_FLAGS, bit FOUND_ZERO to I_Am_Zero. BSET SYS_FLAGS #FOUND_ZERO ; Set the SYS_FLAGS bit to indicate occurance of 0xFF. BRA Seek_Zero ; BRAnch to Seek_Zero. I_Am_Zero: LDD ROM_INDEXH ; Loading D with an any address n is just like saying: SUBD #1 ; LDAA n <-- 2 bytes of program space STD FIRST_BYTE ; + LDAB n + 1 <-- 2 bytes of program space STD ROM_INDEXH ; -------------------------------------------- JSR HEX_2_DEC ; = 4 bytes of program space (and uglier code) LDY #Bytes ; Therefore, LDD not only saves 1 byte, but also makes JSR Disp_Mess ; our code prettier. This assumes n and n + 1 are both LDY #$FFFF ; within the first 256 bytes of address space. JSR DELAY1 ; Sleep for several milliseconds. MAIN_0: CLI ; CLear I bit. Enable maskable interrupts. MAIN_1: JSR DELAY ; Just hold on a microsecond! LDAA $1003 ; LDAA with data at PortC. BITA #BUTTON_0 ; Check if BUTTON_0 is pressed. BEQ MAIN_2 ; No? BRA to MAIN_2. JSR Export ; Yes? JSR to Export. BRA MAIN_1 ; Restart loop at MAIN_1; MAIN_2: BITA #BUTTON_1 ; Check if BUTTON_1 is pressed. BEQ MAIN_3 ; No? BRA to MAIN_3. JSR Format ; Yes? JSR to Format. BRA Seeker ; Then go find FIRST_BYTE again. BRA MAIN_1 ; Restart loop at MAIN_1. Never executed. Here for clarity. MAIN_3: BITA #BUTTON_2 ; Check if BUTTON_2 is pressed. BEQ MAIN_5 ; No? BRA to MAIN_5. LDAB UI_FLAGS ; LoaD Accumulator B with the data at UI_FLAGS. EORB #SHOW_NUMS ; Exclusive-OR accumulator B with the SHOW_NUMS value. STAB UI_FLAGS ; STore Accumulator B back at UI_FLAGS. BRA MAIN_1 ; Restart loop at MAIN_1. MAIN_5: BRSET $03,X #SENSE_PIN MAIN_1 ; Is the phone off the hook? BSET SYS_FLAGS #36 ; Set the OFF_HOOK and MESS_B flags. BRSET SYS_FLAGS #LINE_2_B NoCLR_Yet ; These lines keep track of whats been displayed so BSET SYS_FLAGS #LINE_2_B ; far. It allows us to display two numbers per frame. LDAA #$01 ; 0x01 is the LCD command to clear the display. JSR Disp_C_B ; Send the command to only one half of the LCD. JSR DELAY ; Wait... The LCD is much slower than the HC11. BRA Skip_LOC ; Skip the alternate condition code. NoCLR_Yet: LDAA #$A8 ; The alternate condition code doesn't clear the screen, JSR Disp_C_B ; but sets the cursor to the start of the second line, and BCLR SYS_FLAGS #LINE_2_B ; clears the SYS_FLAGS bit to clear the screen next time. Skip_LOC: LDY #Off_Hook ; LDY with the address of the message Off_Hook. JSR Disp_Mess ; Display the message. BRCLR $03,X #SENSE_PIN * ; Wait here for the phone to be hung up. No UI availible LDY #On_Hook ; while the phone is off-hook. This is intentional. JSR Disp_Mess ; Display the On_Hook message. BCLR SYS_FLAGS #36 ; Clear the OFF_HOOK and MESS_B flags. BRCLR SYS_FLAGS #NUM_DIALED MAIN_6 ; Was there really a number dialed, Or was it just a ring? JSR HUNG_UP ; Yes, there was a number dialed. JSR HUNG_UP. MAIN_6: CLRA ; Start the whole process over again. STAA DURATION_H ; If there were no numbers dialed, we don't want to log STAA DURATION_L ; the time as being spent talking. Reset DURATION. BRA MAIN_1 ; Return to the beginning of the MAIN loop. ROM_FULL: SEI ; Inhibit interrupts. BCLR UI_FLAGS #SHOW_NUMS ; Turn off the number display. LDY #Full ; Kindly inform the user that the 24C256 is completly full. JSR Disp_Mess ; This will give them the choice to either dump the 24C256 Wait_Init: BRSET $03,X #$40 Init_For ; to the serial port, or simply format the ROM. A provision BRSET $03,X #$80 Init_Dump ; to view the contents of the ROM wasn't provided, because BRA Wait_Init ; nobody is going to want to sift thru 32000+ digits. Ick. Init_For: JSR Format ; The Wait_Init loop simply waits for the users choice. JMP #$F800 ; JMP $F800 is the equivilent to a reset. Init_Dump: LDAA #$01 ; Clear the display and export the contents of the 24C256. JSR Disp_C_A ; Once the export is done, tell the user and return to JSR Export ; ROM_FULL, since the problem remains that the 24C256 is LDY #Exported ; still full. The only way out of this situation is a full JSR Disp_Mess ; format. BRA ROM_FULL One_Num_Out: BRSET SYS_FLAGS #LINE_2_A NoCLRYetA ; One_Num_Out reads one phone number entry from the 24C256 BSET SYS_FLAGS #LINE_2_A ; and displays it. This routine keeps track of where it LDAA #$01 ; wrote the last line and places the next one accordingly. JSR Disp_C_A ; This code is very similar to the code at the end of the JSR DELAY ; MAIN_1 loop, so I will go into some other tiny details: BRA SKIP_LOCATION ; Here I shall speak of The Stack! Stacks are an important NoCLRYetA: LDAA #$A8 ; concept in computer science. They allow us to pass values JSR Disp_C_A ; between subroutines, provide temporary holding space for BCLR SYS_FLAGS #LINE_2_A ; data, or simply hold a CPU state so an interrupt can be SKIP_LOCATION: LDY #Off_Hook ; handled. With such a small processor (HC11), when one says LDD ROM_INDEXH ; stack, they mean the memory referanced by the SP register PSHA ; rather than some user created stack. Please look at the PSHB ; first couple lines of executed code. You should see the LDS Next_Num: BSR DONT_CALL ; opcode. LoaD Stack pointer. Notice I loaded it with 0x40. CMPA #$0D ; This program doesn't make much use of the 512 bytes of RAM BEQ Next_Num ; that the HC11 has, so I was very liberal with the stack. CMPA #$FF ; This attitude MUST BE USED WITH CAUTION! The HC11 can only BEQ END_OF_STRING ; Use the direct addressing mode within the first 256 bytes JSR Disp_D_A ; of its address space. Consider this bug: CMPA #$20 ; 10 ORG $0100 BNE Next_Num ; 20 SOME_VAR RMB 1 BSR DONT_CALL ; 30 PSHA ; 40 ORG $F800 BSR DONT_CALL ; 50 LDS #$FF TAB ; 60 BCLR SOME_VAR #$10 PULA ; 70 BRCLR SOME_VAR #$10 FLASH_LED BSR HEX_2_DEC ; 80 EOS_RETURN: PULB ; 90 NULL_LOOP: BRA NULL_LOOP PULA ; STD ROM_INDEXH ; Syntactically and mechanically, this code is completely BRSET $03,X #$20 * ; valid. It will assemble and execute. Why then does the HC11 RTS ; seem to hang? A variable has been declared outside the range END_OF_STRING: CLRA ; of direct addressing. The actual behavior will vary from CLRB ; assembler to assembler, but none will make the appropriate STD TMP_READER ; corrections. The only way for this to work would be to alter BRA EOS_RETURN ; lines 60 and 70 to use another addressing mode. More later. DONT_CALL: LDD TMP_READER ; Ah yes... the DONT_CALL routine... DONT CALL IT! ADDD #1 ; This is an example of me going nuts to save a couple STD ROM_INDEXH ; bytes. This code is only called at three points in STD TMP_READER ; the program, but the space saved by moving this code JSR Get_Byte ; into its own subroutine was large enough to justify it. RTS ; I think the net savings here was 35 bytes... ASCII_0: ADDA #$30 ; This converts the raw data from the MT8870 into ASCII. CMPA #$3A ; Since the MT8870 is nice and outputs numbers in a logical BNE ASCII_1 ; manner, all we need to do to convert 1-9 is add 0x30 to LDAA #$30 ; bring the real number to an ASCII representation. *, # BRA RET_ASCII ; and 0 need to specifically checked for however. ASCII_1: CMPA #$3B ; This is not at all difficult. BNE ASCII_2 LDAA #$2A BRA RET_ASCII ASCII_2: CMPA #$3C BNE RET_ASCII LDAA #$23 RET_ASCII: RTS HEX_2_DEC: PSHX ; This routine takes a 16-bit number in D and produces six PSHA ; ASCII characters representing the number's decimal value. PSHB ; Oooo! Look at this! PSH and PUL! Very useful... Use these LDX #10000 ; to save the contents of a register by PuSHing them onto BSR COM_HEX ; the stack. You can then use the register for whatever you STAB ASCII_BUF ; need to do, and then when you need the original value XGDX ; back, you can PUlL it back off the stack. A few important LDX #1000 ; things to remember tho: The stack is LIFO, so whatever BSR COM_HEX ; got PuSHed on last will be PUlLed off first. Be careful. STAB ASCII_BUF+1 ; Also, the stack is not infinite. Be sure to analyze the XGDX ; worst case scenario for stack use, and set your stack LDX #100 ; pointer accordingly. BTW, stack starts high and grows down. BSR COM_HEX ; One more thing: If you manipulate the stack pointer AT ALL STAB ASCII_BUF+2 ; within a subroutine or interrupt service routine, you must XGDX ; always be sure that that code either RTS's or RTI's with LDX #10 ; the same stack pointer value that it had upon entering the IDIV ; routine. If the SP is not set correctly, the RTS or RTI ADDB #$30 ; instruction will pull the wrong PC value off of the stack STAB ASCII_BUF+4 ; and cause your program to resume execution at a random XGDX ; address. In other words, nasty bug that will cause much ADDB #$30 ; hair-pulling when you try to track it down. Even harder STAB ASCII_BUF+3 ; is the bug where somewhere in your main loop, the SP is LDAB #$40 ; not kept track of, and your stack grows (or shrinks) into STAB ASCII_BUF+5 ; the memory that you reserved for your variables. The stack PULB ; is an incredibly powerful asset in a microprocessor, but PULA ; it is very easy to slip up and find yourself chasing bugs. PULX ; There is no instruction to PuSH or PUlL D, so we must PuSH BRSET UI_FLAGS #INHIB_AUTO_HEX NO_HEX ; and PUlL A and B seperatly. Just remember to PUlL them in LDY #ASCII_BUF ; the reverse order that they were PuSHed. Otherwise, D will BSR Disp_Mess ; get swapped, and you will be confused. NO_HEX: RTS ; COM_HEX is another space saver, but not as much as COM_HEX: IDIV ; DONT_CALL. In fact, I think this only saved 6 bytes or so. XGDX ; Still... thats six extra bytes! Sweet! <-- (6 bytes) ADDB #$30 RTS Disp_D_A: BSET $00,X #LCD_E1 ; These are the compressed LCD display routines. BRA Disp_Data ; Disp_D_A = Display_Data_OnLCDA. Disp_D_B: BSET $00,X #LCD_E2 ; Disp_C_A = Display_Command_OnLCDA. Disp_Data: BSET $00,X #LCD_RS ; This might appear a little weird to someone who has never CMD_Link: STAA $1004 ; used an LCD with the HD44780 controller. The HD44780 is BCLR $00,X #96 ; beautifully simple to use, but it only allows for 80 RTS ; characters. So if we want to have a 4x40 LCD, we need 2 Disp_C_A: BSET $00,X #LCD_E1 ; controller chips. Both chips share all of the bus interface BRA CMD_Comm ; connections with the exception of their ENABLE lines. The Disp_C_B: BSET $00,X #LCD_E2 ; top half of the LCD is handled by one HD44780, and the lower BRA CMD_Comm ; half by another. As far as software is concerned, there are Disp_CMD: BSET $00,X #96 ; two seperate LCD displays. This can get tricky when you want CMD_Comm: BCLR $00,X #LCD_RS ; large messages to have continuity over the whole display PSHY ; area. I tried to avoid these problems as much as possible, BSR DELAY ; but the fact remains that a provision must be made to PULY ; address each HD44780 seperatly. There are 5 routines here BRA CMD_Link ; that are made to be called: Disp_C's, Disp_D's, Disp_CMD. HUNG_UP: LDAA #$20 ; HUNG_UP is what is called when the SENSE_PIN goes high STAA I2C_DATA ; after a number has been dialed. This appends a space to JSR Give_Byte ; the phone number, appends a 16-bit field after that, and LDAA DURATION_H ; finally appends a ASCII CR character. All this formatting STAA I2C_DATA ; is so we have less work to do in the Export sub. The idea JSR Give_Byte ; is to get as close to storing the raw exportable ASCII as LDAA DURATION_L ; possible. The only reason that we didn't use HEX_2_DEC STAA I2C_DATA ; here is because doing so would have blown 16-bits into JSR Give_Byte ; 40 for no real gain. LDD DURATION_H BSET SYS_FLAGS #MESS_B JSR HEX_2_DEC LDAA #$0D STAA I2C_DATA JSR Give_Byte BCLR SYS_FLAGS #164 ; Clears the OFF_HOOK, NUM_DIALED and MESS_B flags. RTS Disp_Mess: LDAA $00,Y ; Will display the character string that starts at the INY ; address contained in index register Y. The character CMPA #$40 ; string is straight-up ASCII with a '@' as the terminating BEQ END_MESS ; character. Yeah, I hear you already... "Why not make it BRSET SYS_FLAGS #MESS_B Disp_On_B ; null terminated like every other character string?" JSR Disp_D_A ; The truth is, there was no paticular reason. I just felt BRA DiMe_Comm ; like using '@'. Ok? Disp_On_B: JSR Disp_D_B ; If MESS_B flag is set, the message will display on the DiMe_Comm: PSHY ; lower half of the LCD. LDY #50 ; The DELAY1 call is to account for the fact that the LCD is BSR DELAY1 ; agonizingly slow compared to how fast we can write to it. PULY ; So we must wait 40µS before giving it another byte. BRA Disp_Mess ; As you have probably surmised by now, Y is used as our END_MESS: RTS ; general pupose register for delay loops and indexing. DELAY: LDY #5000 ; DEY = 4 cycles BNE = 3 cycles RTS = 5 cycles BRA DELAY1 ; E-CLOCK assumed for this program is 2MHz. Note: DELAY_I2C: LDY #$0005 ; DEY preceeds BNE so if Y = 0x0000, this corosponds DELAY1: DEY ; to 65535. Delay (in seconds) can then be calculated by: BNE DELAY1 ; (1/E-CLOCK) * ((4 + 3) * (VALUE OF Y) + 5) RTS ; MAX DELAY is therefore 0.2293785 seconds. Format: SEI ; Inhibit interrupts so that a wild IRQ doesn't hijack the LDY #New_PROM ; software-driven I²C bus. JSR Disp_Mess ; Inform the user that the 24C256 is being formatted. CLRA ; Reset the address pointer to zero. We DO want to start the STAA ROM_INDEXH ; format at address 0x0000, right? STAA ROM_INDEXL LDAA #$CC ; Store 0xCC at the first byte of the 24C256 so that if the NextAdrs: STAA I2C_DATA ; program is reset, it won't format again for no reason. In JSR Give_Byte ; other words, the program is saying, "Hey, I've seen this LDD ROM_INDEXH ; 24C256 before." CPD #$7FFF ; 0x7FFF is the end of a 15-bit address range. This range BEQ EndFormat ; corrosponds to 32768 bytes. LDAA #$FF ; Write all the remaining bytes-1 with 0xFF (blank state). BRA NextAdrs EndFormat: LDY #Done JSR Disp_Mess ; Tell the user that formatting is complete. CLRA ; Reset all the address indicies. STAA ROM_INDEXH INCA STAA ROM_INDEXL CLI RTS Export: SEI ; This is where the device really shows its utility. This is BSET UI_FLAGS #INHIB_AUTO_HEX ; the part of the program that reads the whole (used portion) LDY #Device_Data ; of the 24C256 and spits it out the HC11's serial port. I BSR Message_2_SCI ; can promise you that noboby wants to read thru a 32KB long LDD ROM_INDEXH ; report one entry at a time. This allows them to download PSHA ; the logs so that they may be searched, archived, etc... PSHB ; Notice the first thing we do is inhibit maskable interrupts LDD #0001 ; so that no other I²C operations screw up what we are doing. NEXT_G: STD ROM_INDEXH ; Next thing that happens is that we save the contents of the JSR Get_Byte ; ROM_INDEX variable on the stack. We are going to be using LDAA I2C_DATA ; ROM_INDEX extensively here, and we want to be able to return CMPA #$FF ; the program to the state that it was in before we started BEQ EndExport ; dumping the logs. Also note the SCI polling loop to check SCI_BUSY: LDAB $102E ; and see if the SCI is ready for another byte. Once again, ANDB #$80 ; peripherals are almost always slower than the CPU. If SCI BEQ SCI_BUSY ; is free, the store the byte we got at the SCI data register STAA $102F ; (0x102F). Next, compare what we just sent to the ASCII code CMPA #$20 ; for space. If it was a space, we can infer that the next two BNE Not_Space ; bytes will be the DURATION field. Go snag those and convert BSR Convert_Time ; them to ASCII. If the character wasn't a space, then check Not_Space: LDD ROM_INDEXH ; ROM_INDEX and make sure we aren't at the end of the 24C256. CPD #$7FFF ; Oh, by the way... BSET UI_FLAGS #INHIB_AUTO_HEX stops BEQ EndExport ; HEX_2_DEC from sending the results of its work to the LCD. ADDD #1 ; Instead, we want to direct the contents of ASCII_BUF to the BRA NEXT_G ; SCI. I wrote Message_2_SCI to make pretty formatting a EndExport: LDY #Dashed_Line ; possibility. The same call syntax as Disp_Mess. BSR Message_2_SCI PULB ; Restore accumulator D. Notice that I'm PUlLing the values PULA ; in the reverse order that I PuSHed them. STD ROM_INDEXH ; Restore ROM_INDEX. BCLR UI_FLAGS #INHIB_AUTO_HEX ; Allow HEX_2_DEC to resume displaying results. CLI ; Re-enable maskable interrupts. RTS Convert_Time: LDY #Duration ; This sub is called on the assumption that the next two bytes BSR Message_2_SCI ; will be the DURATION values. Unless you want to write your BSR Tmp_Routine ; own PC-side software to interpret the raw integers, you want PSHB ; this sub. I tried to make it reasonably clean, but the BSR Tmp_Routine ; output still suffers slightly. If anyone has been giving my PULA ; commentary their full attention, they will see what's going JSR HEX_2_DEC ; on here easily. HEX_2_DEC needs a 16-bit integer in AccD. LDY #ASCII_BUF ; AccA is the most significant byte. Hint: Tmp_Routine and BSR Message_2_SCI ; Convert_Time are VERY closely intertwined. Another hint? LDY #Seconds ; Pay close attention to how I used the accumulators. The BSR Message_2_SCI ; contents of ASCII_BUF have been directed at the SCI rather RTS ; then the LCD. Tmp_Routine: LDD ROM_INDEXH ADDD #1 STD ROM_INDEXH BSR Get_Byte LDAB I2C_DATA RTS Message_2_SCI: LDAA $00,Y ; This routine is the SCI version of Disp_Mess. As high-level INY ; as this program gets. The string format is the same as CMPA #$40 ; Disp_Mess. This is intentional. It allows the strings to be BEQ END_MESS1 ; easily reused. Once again, SCI is used in polling mode. SCI_BUSY1: LDAB $102E ANDB #$80 BEQ SCI_BUSY1 STAA $102F BRA Message_2_SCI END_MESS1: RTS ******************************************************************************************************************* * I2C Communication Routines: * * This group of subroutines is what provides the means to talk to an * * I2C peripheral. In this case, a serial EEPROM. * ******************************************************************************************************************* TRYAGAIN1: JSR I2C_Stop ; Give_Byte and Get_Byte are nothing more than a collection of Give_Byte: BSET $08,X #$20 ; calls to other subroutines that do the real work of talking JSR I2C_Start ; to the bus. This layer of abstraction was necessary to deal LDAA #ROM_WRITE ; with the dynamic ways that the I²C bus can be used. BSR I2C_Write ; Basically it sends the appropriate device address (Read or BRCLR SYS_FLAGS #I2C_ACKD TRYAGAIN1 ; Write), the sub-address of the byte we want to read/write, LDAA ROM_INDEXH ; then the byte itself is transfered. Also in the mix are the BSR I2C_Write ; I²C protocol initiators: Start and Stop. This code would be LDAA ROM_INDEXL ; very bloated if this layer were not used. Unfortunatly, BSR I2C_Write ; there is some level of flexibility lost. Paged I/O was not LDAA I2C_DATA ; used in this program as it would have increased code size BSR I2C_Write ; dramatically. But this is ok since we are dealing touch JSR I2C_Stop ; tones and not real-time multimedia or some other bandwidth LDD ROM_INDEXH ; intensive application. Also, this extra layer causes us to ADDD #1 ; incur some stack related penalties. For every layer we CPD #$8000 ; descend into, the stack goes at least 2 bytes deeper. For BNE CONTIN ; an I²C I/O call from the main loop, there are at least four JMP ROM_FULL ; layers of abstraction to go thru: CONTIN: STD ROM_INDEXH ; Give_Byte => I2C_Write => View_SDA => SDA_Hi STD FIRST_BYTE ; Add this on top of a few others: BCLR $08,X #$20 ; HUNG_UP => Disp_Mess => DELAY for instance... RTS ; And all that within an intrrupt service routine (which is * ; 12 bytes of stack, I think...) each subroutine having its TRYAGAIN5: BSR I2C_Stop ; own stack overhead from PuSH's, and there could easilly be Get_Byte: BSET $08,X #$20 ; a stack overflow if we aren't careful. The second penalty BSR I2C_Start ; doesn't really affect us as profoundly: The time it takes to LDAA #ROM_WRITE ; push the Program Counter (PC) register onto the stack and BSR I2C_Write ; pull it back off can amount to as many as 12 cycles! With a BRCLR SYS_FLAGS #I2C_ACKD TRYAGAIN5 ; 2MHz E-CLOCK, that means 6µS! Granted, for our purposes here LDAA ROM_INDEXH ; fretting over a 6µS penalty is silly, but if the HC11 were BSR I2C_Write ; counting clock pulses from a high-frequency source for LDAA ROM_INDEXL ; instance, it might be a wholly different matter. The talk of BSR I2C_Write ; stack usage brings us back to the matter I brought up BSR I2C_Stop ; earlier during the One_Num_Out routine. Stack size must be BSR I2C_Start ; carefully decided. This program does alot of jumping around LDAA #ROM_READ ; and makes extensive use of the stack, so I gave 64 bytes of BSR I2C_Write ; the most precious memory (the first 256 bytes) in the HC11 BSR I2C_Read ; to the stack. Bear in mind!!! Any variables beyond the first STAA I2C_DATA ; 256 will have to be referanced in a method other than direct BSR I2C_Stop ; such as indexed. It is not only faster to access memory via BCLR $08,X #$20 ; direct addressing, but the code to do so is smaller. Using RTS ; indexed addressing to manipulate variables is inconveinent. I2C_Read: LDAB #$80 ; Reads a byte from the I²C bus. I like the way this was CLRA ; written. It shifts a referance byte right and adds its Next_RBit: BSR Scl_Hi ; value to a temporary variable if the SDA pin is set. BSR View_Sda ; This causes the data to be shifted in most-significant-bit BRCLR SYS_FLAGS #SDA_HI Skip_R ; first. ABA is Add accumulator B to accumulator A. For the ABA ; purposes of this program, it is important that when we shift Skip_R: BSR Scl_Low ; the bits of AccB, the incoming bits are all zero. Otherwise, LSRB ; we would be adding a value that didn't corrospond to a BNE Next_RBit ; single bit value. Bad Data! BSR Sda_Hi ; I was quite happy with how this turned out... The BSR Scl_Hi ; I2C_Write function works in a very similar fashion. The only BSR Scl_Low ; structural difference is that the I2C_Write function has to RTS ; Record what the 24C256 sent as its ACK bit. I2C_Write: LDAB #$80 ; Just one more thing about the stack... View it like this: STAA TEMP_VAL ; 0x00 D C B A ISP 0xFF Next_WBit: TBA ; /--------|---|---|---|---|--------------------------------\ ANDA TEMP_VAL ; | | BEQ Skip_W ; \---------------------------------------------------------/ BSR Sda_Hi ; ISP = Initial Stack Pointer (In this program, its 0x40). BRA Meet ; A, B, C, and D are all different memory locations. Let's Skip_W: BSR Sda_Low ; say that they're 0x3E, 0x3C, 0x3A, and 0x38 respectively. Meet: BSR Scl_Hi ; Let us then assume that the programmer has stored a variable BSR Scl_Low ; at D. When we JSR or BSR, the contents of the PC register LSRB ; (which is 2 bytes) are pushed onto the stack. So after a JSR BNE Next_WBit ; the SP will be equal to A. Another JSR will cause the SP to BSR Scl_Hi ; be B, a PSHA is only 1 byte, so after a PSHA, the SP will be BSR View_Sda ; B-1. Now lets say that we use the stack to the point where BRCLR SYS_FLAGS #SDA_HI ACK ; it overwrites VariableD with the contents of the PC. Ooops. BSR Scl_Low ; You will have the wrong value in VariableD, and until you BCLR SYS_FLAGS #I2C_ACKD ; run a step by step of your code, you will have idea why. RTS ; Worse, say this happens and you write to VariableD within ACK: BSR Scl_Low ; the sub whose calling overwrote VariableD in the first BSET SYS_FLAGS #I2C_ACKD ; place. When you try to RTS from the sub, the HC11 will put RTS ; bad data back into the PC and resume execution at that * ; address. CRASH! Why?!?! It's time to step-thru your program! * ; The best physical metaphor for the stack would be this: * ; Take a piece of 8.5 x 11 paper. Write in bold: "Initial SP". * ; Place this paper on a flat surface. Next get some Jewel * ; cases. Both single and double-wide. For each byte placed on * ; the stack, place a Jewel case on the paper. PSHA, DES, PSHB * ; would all be one Jewel case. For each 16-bit value, place a * ; double-Jewel on the stack. JSR, BSR, PSHX, PSHY would all be * ; double-Jewels. The stack grows toward 0x00. PULA, PULB, INS * ; would all cause the stack to shrink by one Jewel case. It's * ; quite literally a STACK of data! But the bottom line is * ; KEEP TRACK OF YOUR STACK! OTHERWISE YOU WILL PULL HAIR! I2C_Start: BSR Sda_Low ; This gives the software I²C bus its Start condition... BSR Scl_Low RTS I2C_Stop: BSR Sda_Low ; ...And its Stop condition. BSR Scl_Hi BSR Sda_Hi RTS Sda_Low: BCLR $08,X #SDA ; I must explain myself here... BRN *?? What is that?? BSET $09,X #SDA ; BRA * means to BRAnch always to this line. The program BRN * ; continuously branches back to the branch it just executed. RTS ; But this is a BRN. BRanch Never. It never branches. Weird. Sda_Hi: BCLR $09,X #SDA ; The only reason I used this is because I needed to give the BRN * ; I²C bus 3 extra cycles to breathe. Together with the RTS and RTS ; JSR opcodes there is just enough delay to fit into I²C spec. Scl_Low: BCLR $08,X #SCL ; This should be noted and taken into account if anyone ever BRN * ; decides to use a faster E-Clock than 2MHz. :) RTS Scl_Hi: BSET $08,X #SCL BRN * RTS View_Sda: JSR Sda_Hi BRCLR $08,X #SDA EView_Sda BSET SYS_FLAGS #SDA_HI RTS EView_Sda: BCLR SYS_FLAGS #SDA_HI RTS ******************************************************************************************************************* * Interrupt Service Routines: * * These are all of the various ISR's that this program uses. * * RTI and IRQ * ******************************************************************************************************************* RTI_IRQ: LDAA #$40 ; This is the RTI ISR. STAA $1025 ; If you've been following the code so far, this should seem INC RTI_COUNT ; very straight forward. Store 0x40 at 0x1025 to clear the LDAA RTI_COUNT ; interrupt. Check the scaler to see if one second has passed. CMPA #RTI_SCALER ; If yes and the phone is off the hook, INC DURATION and CLR BNE NO_TIME ; the scaler. If yes and the phone is on the hook and the CLR RTI_COUNT ; SHOW_NUMS bit is set, then display a previously stored BRSET $03,X #SENSE_PIN Disp_Nums ; record on the LCD. * COM $08,X #$20 ; Un-comment this to blink the AUX LED when Off-hook. LDD DURATION_H ; If the scaler value was still too small, INC it and RTI. ADDD #1 ; COM is COMplement. This just inverts the bit in question. STD DURATION_H ; Again, when INCing 16-bit values, we have to load them into RTI ; AccD. Otherwise, the high order byte won't INC when the low Disp_Nums: BRCLR UI_FLAGS #SHOW_NUMS NO_TIME ; order byte rolls over. ADD to D #1 is the fastest way to do JSR One_Num_Out ; this and still ad to 16-bits. NO_TIME: RTI ; Branch here when we're done with the handler. IRQ_PIN: BRSET $03,X #SENSE_PIN NON_POINT ; The IRQ pin is wired to the STD pin of the MT8870, so when LDAA $1003 ; an IRQ from this pin is received, we know that a new digit ANDA #$0F ; was dialed. Read PortC. Since there is other I/O on PortC, JSR ASCII_0 ; the value we read must be ANDed with 0x0F to eliminate the JSR Disp_D_B ; non-relevant bits. 0x0F because the MT8870's data pins are STAA I2C_DATA ; on PortC's least significant nibble. Then, convert the byte JSR Give_Byte ; into its appropriate ASCII representation and send it to the BSET SYS_FLAGS #NUM_DIALED ; 24C256. Also, set the NUM_DIALED bit to signify that a digit NON_POINT: RTI ; was dialed. ******************************************************************************************************************* * Human Interface Messages: * * These are the messages to be displayed on the LCD. 0x40 terminates * * the mesage. LDY with the tag of the message and JSR Disp_Mess. * ******************************************************************************************************************* Exported: FCC 'Records export to serial port finished! ' Full: FCC 'ROM full. You must 1)Export or 2)Format.@' New_PROM: FCC 'Formatting...@' Bytes: FCC '/32767 bytes used.@' Done: FCC 'Done@' On_Hook: FCC ' On Hook @' Off_Hook: FCC 'Off Hook @' Duration: FCB $09 FCB $09 FCC 'Duration: @' Seconds: FCC ' Seconds@' Device_Data FCB $0D FCC '-==-' FCB $0D FCC 'Version: 3.6' FCB $0D FCC 'Built and programmed by Josh Lindsay 030303' FCB $0D Dashed_Line: FCC '-----------------------------------------------' FCB $0D FCC '@' Find_Zero: FCC 'Seeking...This may take several seconds.@'