; KCPSM3 Program - Control and calculation for Frequency Generator design using the ; Spartan-3E Starter Kit. ; ; Interfaces with the rotary encoder and LCD display to enable a frequency to be set. ; Converts the BCD frequency value into a binary integer and then performs the high ; precision calculation necessary to derive the control numbers required by the high ; performance Direct Digital Synthesis (DDS) circuit implemented in hardware. ; ; LEDs are connected and used as edit mode indicators. ; ; Substantial comments are included in line with the code below and should be used ; in conjunction with the documentation provided with the complete reference design. ; ; ; ; Ken Chapman - Xilinx Ltd ; ; Version v1.00 - 13th July 2006 ; ;************************************************************************************** ;Port definitions ;************************************************************************************** ; ; ; CONSTANT LED_port, 80 ;8 simple LEDs CONSTANT LED0, 01 ; LED 0 - bit0 CONSTANT LED1, 02 ; 1 - bit1 CONSTANT LED2, 04 ; 2 - bit2 CONSTANT LED3, 08 ; 3 - bit3 CONSTANT LED4, 10 ; 4 - bit4 CONSTANT LED5, 20 ; 5 - bit5 CONSTANT LED6, 40 ; 6 - bit6 CONSTANT LED7, 80 ; 7 - bit7 ; ; CONSTANT rotary_port, 00 ;Read status of rotary encoder CONSTANT rotary_left, 01 ; Direction of last move Left=1 Right=0 - bit0 CONSTANT rotary_press, 02 ; Centre press contact (active High) - bit1 ; ; ;LCD interface ports ; ;The master enable signal is not used by the LCD display itself ;but may be required to confirm that LCD communication is active. ;This is required on the Spartan-3E Starter Kit if the StrataFLASH ;is used because it shares the same data pins and conflicts must be avoided. ; CONSTANT LCD_output_port, 40 ;LCD character module output data and control CONSTANT LCD_E, 01 ; active High Enable E - bit0 CONSTANT LCD_RW, 02 ; Read=1 Write=0 RW - bit1 CONSTANT LCD_RS, 04 ; Instruction=0 Data=1 RS - bit2 CONSTANT LCD_drive, 08 ; Master enable (active High) - bit3 CONSTANT LCD_DB4, 10 ; 4-bit Data DB4 - bit4 CONSTANT LCD_DB5, 20 ; interface Data DB5 - bit5 CONSTANT LCD_DB6, 40 ; Data DB6 - bit6 CONSTANT LCD_DB7, 80 ; Data DB7 - bit7 ; ; CONSTANT LCD_input_port, 01 ;LCD character module input data CONSTANT LCD_read_DB4, 10 ; 4-bit Data DB4 - bit4 CONSTANT LCD_read_DB5, 20 ; interface Data DB5 - bit5 CONSTANT LCD_read_DB6, 40 ; Data DB6 - bit6 CONSTANT LCD_read_DB7, 80 ; Data DB7 - bit7 ; ; ; ;DDS control ports ; ;DDS control word is 32-bits ; CONSTANT DDS_control0_port, 02 ; dds_control_word(7:0) CONSTANT DDS_control1_port, 04 ; dds_control_word(15:8) CONSTANT DDS_control2_port, 08 ; dds_control_word(23:16) CONSTANT DDS_control3_port, 10 ; dds_control_word(31:24) ; ;Frequency scaling control word is 5-bits ; CONSTANT DDS_scaling_port, 20 ; dds_scaling_word(4:0) ; ; ;************************************************************************************** ;Special Register usage ;************************************************************************************** ; ;************************************************************************************** ;Scratch Pad Memory Locations ;************************************************************************************** ; CONSTANT rotary_status, 00 ;Status of rotary encoder CONSTANT rotary_event, 80 ; flag set by interrupt in 'rotary_status' - bit7 ; CONSTANT ISR_preserve_s0, 01 ;Preserve s0 contents during ISR ; CONSTANT LED_pattern, 02 ;LED pattern used in rotation mode ; ; ;BCD digits representing selected and displayed frequency ; CONSTANT BCD_digit0, 03 ; value 1 CONSTANT BCD_digit1, 04 ; 10 CONSTANT BCD_digit2, 05 ; 100 CONSTANT BCD_digit3, 06 ; 1,000 CONSTANT BCD_digit4, 07 ; 10,000 CONSTANT BCD_digit5, 08 ; 100,000 CONSTANT BCD_digit6, 09 ; 1,000,000 CONSTANT BCD_digit7, 0A ; 10,000,000 CONSTANT BCD_digit8, 0B ; 100,000,000 ; ; ;Binary integer representation of BCD value ; CONSTANT frequency0, 0C ;LS byte CONSTANT frequency1, 0D CONSTANT frequency2, 0E CONSTANT frequency3, 0F ;MS byte ; ; ;Control of frequency selection values ; CONSTANT cursor_position, 10 ; Pointer to edit position on LCD CONSTANT edit_digit_pointer, 11 ; BCD digit to be changed ; ; ; ;80-bit product resulting from 32-bit frequency x 48-bit scaling constant ; CONSTANT product0, 12 ;LS byte CONSTANT product1, 13 CONSTANT product2, 14 CONSTANT product3, 15 CONSTANT product4, 16 CONSTANT product5, 17 CONSTANT product6, 18 CONSTANT product7, 19 CONSTANT product8, 1A CONSTANT product9, 1B ;MS byte ; ;Local copies of the DDS control word and DDS scaling word ; CONSTANT DDS_control0, 1C ; dds_control_word(7:0) CONSTANT DDS_control1, 1D ; dds_control_word(15:8) CONSTANT DDS_control2, 1E ; dds_control_word(23:16) CONSTANT DDS_control3, 1F ; dds_control_word(31:24) CONSTANT DDS_scaling, 20 ; dds_scaling_word(4:0) ; ;************************************************************************************** ; Useful data constants ;************************************************************************************** ; ; To convert the frequency into a DDS control value a high precision scaling ; factor is used. This is a 48-bit number which converts the frequency presented ; as an 32-bit integer into the 32-bit value required by the phase accumulator ; to synthesize the desired frequency. The scaling factor is derived using the ; following method. First I will consider the scaling factor which results in the ; desired frequency being generated directly at the output of the phase accumulator ; which is suitable for low frequencies in which a few ns of jitter is acceptable. ; ; 'Fpa' is frequency generated by the MSB of the phase accumulator. ; 'p' is number of phase accumulator which in this case is 32 bits. ; 'clk' is the input clock frequency to the phase accumulator which is 200MHz. ; 'N' is the DDS control word value which is also 'p' bits (32 in this case). ; ; Frequency at MSB of phase accumulator is then ; ; Fpa = clk x N / (2^p) ; ; Note that the maximum value allowed for 'N' is (2^p)/2 which results in Fpa=clk/2. ; for 'N' greater than that value 'Fpa' would decrease in frequency (aliasing). ; ; ; By simple reorganisation of the equation we can compute 'N' ; ; N = Fpa x (2^p) / clk ; ; ; Now it is easier to approach the next step using specific example. ; ; So for a frequency of Fpa = 1MHz then ; N = 1MHz x (2^32)/200MHz = 21474836.48 ; ; We must use the nearest 32-bit integer value 21474836 and this in turn ; is best viewed as the 32-bit hexadecimal value 0147AE14. ; ; In this case the value we have to work with is a 32-bit integer frequency ; value of 1 million which is 000F4240. ; ; So now we need to translate the value 000F4240 into 0147AE14. This is ; where a 48-bit scaling value is used together with a full precision multiplier ; as this ensures adequate accuracy of the final frequency. ; ; 32-bit frequency value ffffffff ; 48-bit scaling value x ssssssssssss ; -------------------- ; 80-bit product nnnnnnnnnnnnnnnnnnnn ; ; The art is to organise the scaling factor into the range where the most is made of ; the 48-bit resolution available but which will result in the correct 32-bit output. ; The way this is achieved is the select an appropriate 32-bits from the available 80-bit ; product for use as 'N' and truncate 'y' least significant bits. ; ; From this we can deduce that for a target frequency 'Ft' at the input then the ; scaling value 'S' is given by ; ; S = N x (2^y) / Ft with the condition that S < 2^48 but as large as possible ; ; For best accuracy we calculate 'S' using the full precision value of 'N' divided ; by Ft and then multiply continuously by 2 until we reach the biggest value less ; that 2^48. The number of multiplications by 2 indicating the value of 'y'. ; ; In this case we find that 'y' is 43..... ; S = 21474836.48 x (2^43) / 1000000 = 21.47483648 x (2^43) ; = 188894659314785.80854784 ; ; ...round to nearest integer and convert to hexadecimal S = ABCC77118462 ; ; N will be taken from the 80 bit product by removing the 43 LSBs and the 5 MSBs ; to leave the 32 active bits required. This is best achieved by shifting left ; by 5 places (multiply by 2^5=32) and keeping the upper 32-bits. ; ; ; Sanity check.... ; Note that most calculators do not support >64 bit values to you will either ; need to decompose your calculation and perform some of it manually or trust ; the PicoBlaze implementation :-) ; ; Ft = 1MHz = 000F4240 ; S = x ABCC77118462 ; -------------------- ; 000A3D70A3D70A405C80 ; ; shift left 5 places x 20 ; -------------------- ; 0147AE147AE1480B9000 ; ; As expected, the most significant 32-bit (4 bytes) are 0147AE14 hex which is ; the DDS control word for 1MHz calculated previously. ; ; *** ; ; Now I will consider how this needs to be modified for the circuit presented ; which has a second DCM connected to the output of the phase accumulator to ; multiply the synthesized frequency and reduce cycle to cycle jitter at ; the same time. There is then a clock divider circuit connected to the output ; of the DCM which allows lower frequencies to be formed a different way (more of ; that later). As a minimum that divider circuit will divide by 2 which ensures that ; a square wave is presented to the clocked put pin. So in this circuit the fundamental ; multiplication factor is 8 formed by a 16 times multiplication by the DCM (256/16) and ; then a divide by 2. ; ; The overall multiplication factor of this sebsequent circuit means that for final ; output from the DCM to be the desired frequency, the output from the phase accumulator ; needs to be the same number of times smaller. This is not a bad thing because the ; percentage jitter of waveforms produced by the phase accumulator is better for lower ; frequencies made from more clock cycles. ; ; So we modify the basic equation to ; ; Fout = Frequency at output of DCM ; M = Multiplying factor of DCM ; ; Fout = M x Fpa = M x clk x N / (2^p) ; ; ; By simple reorganisation of the equation we can compute 'N' ; ; N = Fout x (2^p) / (clk x M) ; ; ; In this design M=8, p=32, clk=200MHz ; ; So now consider generating a nominal maximum frequency of 100MHz which will require ; the frequency synthesized by the phase accumulator to be 12.5MHz. ; ; N = 100MHz x (2^32) / (200MHz x 8) = 268435456 = 10000000 Hex ; ; This all seems like a very convenient number but it simply reflects that 12.5MHz ; is a perfect division of the 200MHz clock and that that output from the phase ; accumulator will be formed perfectly of 16 of the 200MHz clock periods every time ; (8 Low and 8 High) with no additional jitter. ; ; So now we work out the scaling factor with the same rules as used previously that ; the scaling factor should be as large as possible within the 48-bits allocated. ; ; S = N x (2^y) / Ft with the condition that S < 2^48 but as large as possible ; ; In this case Ft = 100MHz = 055FE100 and the biggest value for S is found when using ; y=46 ; ; S = 268435456 x (2^46) / 100000000 = 2.68435456 x (2^46) ; = 188894659314785.80854784 ; ; round to 188894659314786 = ABCC77118462 ; ; Actually this is the exact same scaling constant as previously because the ; frequency to be synthesized by the phase accumulator is 8 times smaller but the ; value of 'S' is deliberate scaled to be as large as possible. In fact, 'S' in this ; case has been scaled up by a factor of 8 to arrive at the same value. So after ; using the scaling constant to form the 80 bit product, this time we will remove ; the 46 LSBs and the 2 MSBs to leave the 32 active bits required. This is best ; achieved by shifting left by 2 places (multiply by 2^2=4) and keeping the upper ; 32-bits (last time we multiplied by 32 which was 8 times more). ; ; ; Sanity check.... ; ; Ft = 100MHz = 055FE100 ; S = x ABCC77118462 ; -------------------- ; 04000000000001242200 ; ; shift left 5 places x 20 ; -------------------- ; 1000000000001C908800 ; ; As expected, the most significant 32-bit (4 bytes) are 10000000 hex which is ; the DDS control word for 12.5MHz at the phase accumulator output calculated ; previously. ; ; ; ******** ; ; ; 48-bit Scaling factor constant to generate the phase accumulator control word ; from the integer frequency value. ; ; S = AB CC 77 11 84 62 ; ; Notes ; ; The 80-bit product must be shifted left 5 times and then most significant 32-bits ; used to provide DDS control word if the frequency required is to be synthesized ; directly at the output of the phase accumulator. ; ; The 80-bit product must be shifted left 2 times and then most significant 32-bits ; used to provide DDS control word if the frequency required is to be synthesized ; by the phase accumulator followed by a multiplying DCM and divider with overall ; frequency gain of 8 times. ; CONSTANT scale_constant0, 62 ;LS byte CONSTANT scale_constant1, 84 CONSTANT scale_constant2, 11 CONSTANT scale_constant3, 77 CONSTANT scale_constant4, CC CONSTANT scale_constant5, AB ;MS byte ; ; ; ; ************************ ; ;Constant to define a software delay of 1us. This must be adjusted to reflect the ;clock applied to KCPSM3. Every instruction executes in 2 clock cycles making the ;calculation highly predictable. The '6' in the following equation even allows for ;'CALL delay_1us' instruction in the initiating code. ; ; delay_1us_constant = (clock_rate - 6)/4 Where 'clock_rate' is in MHz ; ;Example: For a 50MHz clock the constant value is (10-6)/4 = 11 (0B Hex). ;For clock rates below 10MHz the value of 1 must be used and the operation will ;become lower than intended. ; CONSTANT delay_1us_constant, 0B ; ; ; ;ASCII table ; CONSTANT character_a, 61 CONSTANT character_b, 62 CONSTANT character_c, 63 CONSTANT character_d, 64 CONSTANT character_e, 65 CONSTANT character_f, 66 CONSTANT character_g, 67 CONSTANT character_h, 68 CONSTANT character_i, 69 CONSTANT character_j, 6A CONSTANT character_k, 6B CONSTANT character_l, 6C CONSTANT character_m, 6D CONSTANT character_n, 6E CONSTANT character_o, 6F CONSTANT character_p, 70 CONSTANT character_q, 71 CONSTANT character_r, 72 CONSTANT character_s, 73 CONSTANT character_t, 74 CONSTANT character_u, 75 CONSTANT character_v, 76 CONSTANT character_w, 77 CONSTANT character_x, 78 CONSTANT character_y, 79 CONSTANT character_z, 7A CONSTANT character_A, 41 CONSTANT character_B, 42 CONSTANT character_C, 43 CONSTANT character_D, 44 CONSTANT character_E, 45 CONSTANT character_F, 46 CONSTANT character_G, 47 CONSTANT character_H, 48 CONSTANT character_I, 49 CONSTANT character_J, 4A CONSTANT character_K, 4B CONSTANT character_L, 4C CONSTANT character_M, 4D CONSTANT character_N, 4E CONSTANT character_O, 4F CONSTANT character_P, 50 CONSTANT character_Q, 51 CONSTANT character_R, 52 CONSTANT character_S, 53 CONSTANT character_T, 54 CONSTANT character_U, 55 CONSTANT character_V, 56 CONSTANT character_W, 57 CONSTANT character_X, 58 CONSTANT character_Y, 59 CONSTANT character_Z, 5A CONSTANT character_0, 30 CONSTANT character_1, 31 CONSTANT character_2, 32 CONSTANT character_3, 33 CONSTANT character_4, 34 CONSTANT character_5, 35 CONSTANT character_6, 36 CONSTANT character_7, 37 CONSTANT character_8, 38 CONSTANT character_9, 39 CONSTANT character_colon, 3A CONSTANT character_stop, 2E CONSTANT character_semi_colon, 3B CONSTANT character_minus, 2D CONSTANT character_divide, 2F ;'/' CONSTANT character_plus, 2B CONSTANT character_comma, 2C CONSTANT character_less_than, 3C CONSTANT character_greater_than, 3E CONSTANT character_equals, 3D CONSTANT character_space, 20 CONSTANT character_CR, 0D ;carriage return CONSTANT character_question, 3F ;'?' CONSTANT character_dollar, 24 CONSTANT character_exclaim, 21 ;'!' CONSTANT character_BS, 08 ;Back Space command character ; ; ; ; ; ;************************************************************************************** ;Initialise the system ;************************************************************************************** ; cold_start: CALL LCD_reset ;initialise LCD display ; ;Write 'Frequency Generator' to LCD display and display for 4 seconds ; LOAD s5, 10 ;Line 1 position 0 CALL LCD_cursor CALL disp_Frequency LOAD s5, 22 ;Line 2 position 2 CALL LCD_cursor CALL disp_Generator CALL delay_1s ;wait 4 seconds CALL delay_1s CALL delay_1s CALL delay_1s CALL LCD_clear ;clear screen ; ; ;Initial frequency of 100MHz ; LOAD s0, 00 LOAD s1, 01 STORE s0, BCD_digit0 STORE s0, BCD_digit1 STORE s0, BCD_digit2 STORE s0, BCD_digit3 STORE s0, BCD_digit4 STORE s0, BCD_digit5 STORE s0, BCD_digit6 STORE s0, BCD_digit7 STORE s1, BCD_digit8 ; LOAD s0, 04 ;Start position for editing frequency is 1MHz digit STORE s0, cursor_position LOAD s0, BCD_digit6 STORE s0, edit_digit_pointer ; ; ENABLE INTERRUPT ;interrupts are used to detect rotary controller CALL delay_1ms LOAD s0, 00 ;clear the status of any spurious rotary events STORE s0, rotary_status ; as a result of system turning on. ; ;************************************************************************************** ; Main program ;************************************************************************************** ; ; The main program is centred on the task of editing the frequency. It waits until the ; rotary control is used and then makes the appropriate changes. If the actual digit ; digit value is changed then the calculation to drive the DDS is performed each time. ; ; The start state is that of allowing the edit cursor position to be moved. Rotary ; inputs are detected by the interrupt service routine and set a flag bit which the ; main program then uses to adjust the cursor position and pointer to the corresponding ; BCD digit in memory. ; ; A press of the rotary control is detected by polling and used to change to the digit ; editing mode. ; ; move_mode: CALL compute_DDS_words ;compute DDS control values CALL display_freq ;refresh display with cursor position shown LOAD s0, LED0 ;indicate move mode on LEDs OUTPUT s0, LED_port move_wait: INPUT s0, rotary_port ;read rotary encoder TEST s0, rotary_press ;test for press of button which changes mode JUMP NZ, edit_mode FETCH s0, rotary_status ;check for any rotation of rotary control TEST s0, rotary_event JUMP Z, move_wait ; AND s0, 7F ;clear flag now that action it is being processed STORE s0, rotary_status FETCH sA, cursor_position ;read current position FETCH sB, edit_digit_pointer TEST s0, rotary_left ;determine direction to move cursor JUMP Z, move_right ; move_left: COMPARE sB, BCD_digit8 ;can not move left of 100MHz digit JUMP Z, move_mode ADD sB, 01 ;move to next higher BCD digit SUB sA, 01 ;move cursor to match digit to be edited COMPARE sA, 09 ;must skip over space separator JUMP Z, skip_left COMPARE sA, 05 ;must skip over decimal point JUMP NZ, edit_point_update skip_left: SUB sA, 01 ;move cursor further left JUMP edit_point_update ; move_right: COMPARE sB, BCD_digit0 ;can not move right of 1Hz digit JUMP Z, move_mode SUB sB, 01 ;move to next lower BCD digit ADD sA, 01 ;move cursor to match digit to be edited COMPARE sA, 09 ;must skip over space separator JUMP Z, skip_right COMPARE sA, 05 ;must skip over decimal point JUMP NZ, edit_point_update skip_right: ADD sA, 01 ;move cursor further right ; edit_point_update: STORE sA, cursor_position ;update edit value in memory STORE sB, edit_digit_pointer JUMP move_mode ; ; ; The edit mode is reached by pressing the rotary control. Since this is a simple switch ; a software de-bounce delay is used to wait for the knob to be released fully before ; entering the digit editing mode fully. ; ; In this mode rotations of the detected by the interrupt service routine are used to ; increment or decrement the digit value at the cursor position with carry/borrow to ; the left. ; ; A new press of the rotary control is detected by polling and used to change back to the ; cursor moving mode. ; ; edit_mode: CALL wait_switch_release ;wait for switch press to end edit_display: CALL compute_DDS_words ;compute DDS control values CALL display_freq ;refresh display with new values LOAD s0, LED1 ;indicate edit mode on LEDs OUTPUT s0, LED_port edit_wait: INPUT s0, rotary_port ;read rotary encoder TEST s0, rotary_press ;test for press of button which changes mode JUMP NZ, end_edit_mode FETCH s0, rotary_status ;check for any rotation of rotary control TEST s0, rotary_event JUMP Z, edit_wait ; AND s0, 7F ;clear flag now that action it is being processed STORE s0, rotary_status FETCH sB, edit_digit_pointer ;read pointer to BCD digit for initial change TEST s0, rotary_left ;determine direction to increment or decrement JUMP Z, inc_digit ; ; Decrement the value starting at the current position and borrowing from the left. ; However the value needs to bottom out at all 0's from the editing position. ; ; dec_digit: FETCH sA, (sB) ;read digit value at pointer position SUB sA, 01 ;decrement digit COMPARE sA, FF ;test for borrow from next digit JUMP Z, dec_borrow STORE sA, (sB) ;store decremented digit value JUMP edit_display ;decrement task complete dec_borrow: LOAD sA, 09 ;current digit rolls over to nine STORE sA, (sB) ;store '9' digit value COMPARE sB, BCD_digit8 ;check if working on 100MHz digit JUMP Z, set_min_value ADD sB, 01 ;increment pointer to next most significant digit JUMP dec_digit ;decrement next digit up. ; set_min_value: FETCH sB, edit_digit_pointer ;Must fill digits from insert to MS-Digit with 000... LOAD sA, 00 fill_min: STORE sA, (sB) COMPARE sB, BCD_digit8 ;check if filled to 100MHz digit JUMP Z, edit_display ADD sB, 01 ;fill next higher digit JUMP fill_min ; ; Increment the value starting at the current position and carrying to the left. ; However the value needs to saturate to all 9's from the editing position. ; inc_digit: FETCH sA, (sB) ;read digit value at pointer position ADD sA, 01 ;increment digit COMPARE sA, 0A ;test for carry to next digit JUMP Z, inc_carry STORE sA, (sB) ;store incremented digit value JUMP edit_display ;increment task complete inc_carry: LOAD sA, 00 ;current digit rolls over to zero STORE sA, (sB) ;store zero digit value COMPARE sB, BCD_digit8 ;check if working on 100MHz digit JUMP Z, set_max_value ADD sB, 01 ;increment pointer to next most significant digit JUMP inc_digit ;increment next digit up. ; set_max_value: FETCH sB, edit_digit_pointer ;Must fill digits from insert to MS-Digit with 999... LOAD sA, 09 fill_max: STORE sA, (sB) COMPARE sB, BCD_digit8 ;check if filled to 100MHz digit JUMP Z, edit_display ADD sB, 01 ;fill next higher digit JUMP fill_max ; end_edit_mode: CALL wait_switch_release ;wait for end of switch press JUMP move_mode ;then go to move cursor mode ; ; ; Routine to poll the press switch with de-bounce delay and wait for it to be ; released. Any rotation inputs detected by the interrupt ; service routine are cleared before returning. ; wait_switch_release: CALL delay_20ms ;delay to aid switch de-bounce INPUT s0, rotary_port ;read rotary encoder TEST s0, rotary_press ;test if button is still being pressed JUMP NZ, wait_switch_release LOAD s0, 00 ;clear flag indicating any rotary events STORE s0, rotary_status RETURN ; ;************************************************************************************** ; Compute DDS control words from currently display frequency value ;************************************************************************************** ; ; This routine reads the current BCD value and converts it into a 32-bit binary ; integer. It then multiplies it by a 48-bit scaling factor (see notes in the ; constants section above) to form a full precision 80-bit product. ; ; From this product the 32-bit DDS control word must be extracted. For frequencies of ; 50MHz or above the DDS control word is formed by shifting the product left by 2 places ; (multiply by 4) and then keeping only the most significant 32-bits (4 bytes). ; ; Also for frequencies of 50MHz and above, there is no additional division performed ; after the DCM which multiplies frequency and reduces the jitter. Therefore the DDS_scaling ; word will be set to zero and the output of the DCM will divide by 2. ; ; Freq DDS control word DDS Scaling Synthesized Frequency ; of Phase Accumulator ; ; 50MHz 08000000 00 6.25MHz ; 100MHz 10000000 00 12.50MHz ; ; You will notice that for frequencies of 50MHz and above, the upper byte of the ; DDS control word is 08 hex or greater. In other words, bit3 and/or bit4 of that byte ; are High (bits 27 and/or 28 of the full 32-bit word). This is the indication that ; the control words are complete. ; ; For frequencies below 50MHz an additional process is required. The reason for this ; becomes clear if we think about the lowest frequency of 1Hz. In that case the 80-bit ; product is the same as the 48-bit scaling constant 00000000ABCC77118462. Once this ; has been multiplied by 4 (shifted left 2 places) it becomes 00000002AF31DC461188 and the ; most significant 32-bits are only 00000002 hex. If we put this back into the basic ; equations for the phase accumulator we find that the output frequency of the phase ; accumulator would be ; ; Fout = M x clk x N / (2^p) ; ; = 8 x 200MHz x 2 / (2^32) = 0.745 Hz ; ; There are two important observations we can make. Firstly we have lost accuracy because ; the resolution of the DDS control word has become too granular at low amplitudes. ; Secondly this would never even work because the frequency synthesized by the phase ; accumulator would be 0.745/8 = 0.0931 Hz which is seriously slow and a way below the ; frequency at which the DCM can even work. ; ; The solution to both of these issues is to ensure that the DDS control word is always ; formed to be in the range that would result in an output of 50MHz or above. In other ; words to keep the phase accumulator output in the range 6.25MHz to 12.5MHz such that ; the DCM is able to work and only has to deal with one octave of input variation. This ; can be achieved by shifting the 80-bit product left more times until bits 27 and 28 ; of the most significant 32-bits are not zero. ; ; For each shift left the synthesized frequency is being doubled and therefore the final ; output from the DCM must be divided by a further factor of 2. This is achieved using ; a multiplexer which is guided to select the appropriate output from a simple binary ; counter. ; ; Returning to the example of 1Hz, the 80-bit product will be shifted left by the default ; 2 places (multiplied by 4), but will then need to be shifted left by a further 26 places ; which is like multiplying by 67108864 (04000000 hex). ; ; 00000000ABCC77118462 ; x 4 ; -------------------- ; 00000002AF31DC461188 ; ; ; x 04000000 ; -------------------- ; 0ABCC771184620000000 ; ; So now the DDS control word is 0ABCC771 (180143985 decimal)and the frequency synthesized ; by the phase accumulator will be.... ; ; Fpa = clk x N / (2^p) = 200MHz x 180143985 / (2^32) = 8388608Hz ; ; The DCM will multiply this by a factor of 16 to give 134217728Hz and this will then ; be divided by the counter of which the 26th bit selected (26 decimal = 1A hex). ; ; Fout = Fpa x 16 / (2^(D+1)) = 8388608Hz x 16 / (2^(26+1)) = 0.99999999947 Hz ; ; 'D' is the DDS Scaling factor ; Note that bit0 of a counter is a clock division by 2 and hence the 'D+1' ; ; Clearly this implementation style has provided much greater accuracy and enables ; the DCM to work for all desired frequencies. ; ; ; Freq DDS control word DDS Scaling Synthesized Frequency ; of Phase Accumulator ; ; 100 MHz 10000000 00 12.50MHz ; 50 MHz 08000000 00 6.25MHz ; 25 MHz 08000000 01 6.25MHz ; 12.5 MHz 08000000 02 6.25MHz ; ; 1Hz 0ABCC771 1A 8.388608 MHz ; ; ; ; In order to ensure the DCM is always provided with a frequency in an acceptable ; range, the value of absolute zero is never implemented and instead just a very low ; frequency is produced. ; 6.25MHz x 16 / (2^31+1) = 0.0233 Hz ; which is 1 cycle every 43 seconds and that is pretty slow :-) ; ; ; ; compute_DDS_words: CALL BCD_to_integer ;convert BCD display value to 32-bit value CALL scale_frequency ;80-bit product of 32-bit frequency x 48-bit scaling value FETCH sA, product9 ;read the upper part of the 80-bit product into [sA,s9,s8,s7,s6,s5,s4] FETCH s9, product8 ; The least significant 24-bits of the 80-bit product will never FETCH s8, product7 ; be used for frequencies above 1Hz. FETCH s7, product6 ;The final 32-bit DDS control word will be formed in FETCH s6, product5 ; [sA,s9,s8,s7] FETCH s5, product4 FETCH s4, product3 CALL shift80_left ;multiply DDS control word by 4 to achieve default value CALL shift80_left LOAD sB, 00 ;default scaling factor is 2 (select counter bit0) normalise_loop: TEST sA, 18 ;Test bits 27 and 28 of 32-bit DDS control word JUMP NZ, store_DDS_words ;DDS control word is normalised to above 50MHz output CALL shift80_left ;multiply DDS control word by 2 ADD sB, 01 ;Divide final value by 2 to compensate COMPARE sB, 1F ;Test for maximum division factor JUMP NZ, normalise_loop LOAD sA, 08 ;Set for minimum frequency LOAD s9, 00 ; with phase accumulator set to generate 6.25MHz LOAD s8, 00 LOAD s7, 00 store_DDS_words: STORE s7, DDS_control0 ;store local copy of control word STORE s8, DDS_control1 ;store local copy of control word STORE s9, DDS_control2 ;store local copy of control word STORE sA, DDS_control3 ;store local copy of control word STORE sB, DDS_scaling CALL drive_DDS_words ;output control words to DDS circuit RETURN ; shift80_left: SL0 s4 ;shift (most of the) 80-bit value in SLA s5 ; [sA,s9,s8,s7,s6,s5,s4] left 1 place SLA s6 SLA s7 SLA s8 SLA s9 SLA sA RETURN ; ;************************************************************************************** ; Set DDS control words ;************************************************************************************** ; ; Because multiple ports are used, the idea is to update all of them in ; rapid succession to avoid too much disturbance in the frequency synthesis. ; ; dds_control_word should be supplied in register set [sA,s9,s8,s7] ; dds_scaling_word should be supplied in register s6. ; drive_DDS_words: FETCH s7, DDS_control0 FETCH s8, DDS_control1 FETCH s9, DDS_control2 FETCH sA, DDS_control3 FETCH s6, DDS_scaling OUTPUT s7, DDS_control0_port OUTPUT s8, DDS_control1_port OUTPUT s9, DDS_control2_port OUTPUT sA, DDS_control3_port OUTPUT s6, DDS_scaling_port RETURN ; ; ;************************************************************************************** ; Display frequency on top line of the LCD and DDS data on the lower line ;************************************************************************************** ; ; The BCD value should be stored in scratch pad memory in 9 ascending locations ; called BCD_digit0 to BCD_digit8. ; ; The value is displayed in the format xxx.xxx xxxMHz ; ; However, the most significant 2 digits will be blanked if zero. ; ; registers used s0,s1,s2,s3,s4,s5,s6,s7 ; ; display_freq: CALL display_DDS_data ;display DDS information on lower line LOAD s5, 12 ;Line 1 position 2 CALL LCD_cursor FETCH s5, BCD_digit8 ;read 100MHz digit COMPARE s5, 00 ;test for blanking JUMP Z, blank_100M_digit CALL display_digit ;display non zero digit FETCH s5, BCD_digit7 ;read 10MHz digit and display CALL display_digit JUMP disp_1M_digit ; blank_100M_digit: CALL display_space ;blank 100MHz digit FETCH s5, BCD_digit7 ;read 10MHz digit COMPARE s5, 00 ;test for blanking JUMP Z, blank_10M_digit CALL display_digit ;display non zero digit JUMP disp_1M_digit ; blank_10M_digit: CALL display_space ;blank 10MHz digit ; disp_1M_digit: FETCH s5, BCD_digit6 ;read 1MHz digit and display CALL display_digit LOAD s5, character_stop ;display decimal point CALL LCD_write_data ; LOAD s2, BCD_digit5 ;set pointer to 100KHz digit CALL display_3_digits CALL display_space LOAD s2, BCD_digit2 ;set pointer to 100Hz digit CALL display_3_digits LOAD s5, character_M ;display 'MHz' CALL LCD_write_data LOAD s5, character_H CALL LCD_write_data LOAD s5, character_z CALL LCD_write_data ; FETCH s5, cursor_position ;reposition edit cursor on display ADD s5, 10 ;on line 1 CALL LCD_cursor RETURN ; display_3_digits: LOAD s3, 03 ;3 digits to display threedigit_loop: FETCH s5, (s2) CALL display_digit SUB s2, 01 ;decrement digit pointer SUB s3, 01 ;count digits displayed JUMP NZ, threedigit_loop RETURN ; display_digit: ADD s5, 30 ;convert BCD to ASCII character CALL LCD_write_data RETURN ; display_space: LOAD s5, character_space CALL LCD_write_data RETURN ; ; ;************************************************************************************** ; Convert 9 digit BCD frequency into 32-bit binary integer ;************************************************************************************** ; ;Both values are stored in scratch pad memory ; BCD values in ascending locations BCD_digit0 to BCD_digit8 ; Binary frequency in ascending locations frequency0 to frequency3 ; ;Each digit is read in turn and its value is determined by repeated ;decrement until reaching zero. Each decrement causes a value to be added ;to the memory locations forming the frequency value as binary integer. ;The process requires approximately 1600 instructions to convert the highest ;value 999,999,999 which is approximately 64us at 50MHz clock rate. ; ;Registers used s0,s1,s2,s3,s4,s5,s6,s7,s8,s9,sA,sB ; BCD_to_integer: LOAD s2, 09 ;9 digits to convert LOAD s0, 00 ;clear frequency value ready to accumulate result STORE s0, frequency0 STORE s0, frequency1 STORE s0, frequency2 STORE s0, frequency3 LOAD sB, 00 ;initialise BCD digit weighting [sB,sA,s9,s8] to 1 LOAD sA, 00 LOAD s9, 00 LOAD s8, 01 LOAD s3, BCD_digit0 ;locate LS-digit next_BCD_to_int_digit: FETCH s1, (s3) BCD_digit_convert: COMPARE s1, 00 ;test for zero JUMP Z, next_digit_value FETCH s0, frequency0 ;add 32-bit digit weighting to memory value ADD s0, s8 STORE s0, frequency0 FETCH s0, frequency1 ADDCY s0, s9 STORE s0, frequency1 FETCH s0, frequency2 ADDCY s0, sA STORE s0, frequency2 FETCH s0, frequency3 ADDCY s0, sB STORE s0, frequency3 SUB s1, 01 ;decrement digit value JUMP BCD_digit_convert ;Increase weighting by 10x next_digit_value: LOAD s7, sB ;copy existing weighting LOAD s6, sA LOAD s5, s9 LOAD s4, s8 SL0 s8 ;multiply weight by 4x (shift left 2 places) SLA s9 SLA sA SLA sB SL0 s8 SLA s9 SLA sA SLA sB ADD s8, s4 ;add previous weight to form 5x multiplication ADDCY s9, s5 ADDCY sA, s6 ADDCY sB, s7 SL0 s8 ;multiply weight by 2x (shift left 1 places) SLA s9 SLA sA SLA sB ;weight value is now 10x previous value ADD s3, 01 ;move to next digit for conversion SUB s2, 01 JUMP NZ, next_BCD_to_int_digit RETURN ; ; ;************************************************************************************** ; 32-bit x 48-bit multiply to scale the integer frequency ;************************************************************************************** ; ;Multiply the 32-bit frequency binary integer by the 48-bit scaling factor ;to form a full precision 80-bit product. ; ;The frequency binary integer is stored in scratch pad memory using ascending ;locations frequency0 to frequency3 ; ;The product will be stored in scratch pad memory using ascending ;locations product0 to product9 ; ;The scaling factor is provided directly as constants ; scale_constant0 to scale_constant5 ; ;The multiplication is performed as a 32-bit 'shift and add' process in which the ;integer frequency is examined LSB first using a register set [sB,sA,s9,s8] and ;a scaling accumulator is formed directly in the 'product' memory locations. ; ;The process requires up to 1772 instructions which is 3544 clock cycle or ;approximately 71us at 50MHz clock rate. ; ;Registers used s0,s1,s8,s9,sA,sB (s1,s8,s9,sA,sB clear on return) ; scale_frequency: LOAD s0, 00 ;clear accumulator section of 'product' STORE s0, product9 STORE s0, product8 STORE s0, product7 STORE s0, product6 STORE s0, product5 STORE s0, product4 FETCH sB, frequency3 ;read frequency integer value FETCH sA, frequency2 FETCH s9, frequency1 FETCH s8, frequency0 LOAD s1, 20 ;32-bit multiply scale_mult_bit: SR0 sB ;shift right frequency integer SRA sA SRA s9 SRA s8 JUMP NC, product_shift ;no add if bit is zero (note carry is zero) FETCH s0, product4 ;addition of scaling factor to most significant bits of product ADD s0, scale_constant0 STORE s0, product4 FETCH s0, product5 ADDCY s0, scale_constant1 STORE s0, product5 FETCH s0, product6 ADDCY s0, scale_constant2 STORE s0, product6 FETCH s0, product7 ADDCY s0, scale_constant3 STORE s0, product7 FETCH s0, product8 ADDCY s0, scale_constant4 STORE s0, product8 FETCH s0, product9 ADDCY s0, scale_constant5 STORE s0, product9 ;carry holds any overflow of addition product_shift: FETCH s0, product9 ;Divide product by 2 (shift right by 1) SRA s0 ;overflow of addition included in shift STORE s0, product9 FETCH s0, product8 SRA s0 STORE s0, product8 FETCH s0, product7 SRA s0 STORE s0, product7 FETCH s0, product6 SRA s0 STORE s0, product6 FETCH s0, product5 SRA s0 STORE s0, product5 FETCH s0, product4 SRA s0 STORE s0, product4 FETCH s0, product3 SRA s0 STORE s0, product3 FETCH s0, product2 SRA s0 STORE s0, product2 FETCH s0, product1 SRA s0 STORE s0, product1 FETCH s0, product0 SRA s0 STORE s0, product0 SUB s1, 01 ;move to next bit JUMP NZ, scale_mult_bit RETURN ; ;************************************************************************************** ; Display DDS control information on the lower line of the LCD display. ;************************************************************************************** ; ;Display the 32-bit DDS control word and 8-bit DDS scaling word. ; display_DDS_data: LOAD s5, 20 ;Line 2 position 0 CALL LCD_cursor LOAD s5, character_N CALL LCD_write_data LOAD s5, character_equals CALL LCD_write_data LOAD s7, DDS_control3 ;pointer to most significant byte in memory CALL display_hex_32_bit CALL display_space LOAD s5, character_D CALL LCD_write_data LOAD s5, character_equals CALL LCD_write_data FETCH s0, DDS_scaling CALL display_hex_byte RETURN ; ;************************************************************************************** ; Routines to display hexadecimal values on LCD display ;************************************************************************************** ; ; ; Convert hexadecimal value provided in register s0 into ASCII characters ; ; The value provided must can be any value in the range 00 to FF and will be converted into ; two ASCII characters. ; The upper nibble will be represented by an ASCII character returned in register s3. ; The lower nibble will be represented by an ASCII character returned in register s2. ; ; The ASCII representations of '0' to '9' are 30 to 39 hexadecimal which is simply 30 hex ; added to the actual decimal value. The ASCII representations of 'A' to 'F' are 41 to 46 ; hexadecimal requiring a further addition of 07 to the 30 already added. ; ; Registers used s0, s2 and s3. ; hex_byte_to_ASCII: LOAD s2, s0 ;remember value supplied SR0 s0 ;isolate upper nibble SR0 s0 SR0 s0 SR0 s0 CALL hex_to_ASCII ;convert LOAD s3, s0 ;upper nibble value in s3 LOAD s0, s2 ;restore complete value AND s0, 0F ;isolate lower nibble CALL hex_to_ASCII ;convert LOAD s2, s0 ;lower nibble value in s2 RETURN ; ; Convert hexadecimal value provided in register s0 into ASCII character ; ;Register used s0 ; hex_to_ASCII: SUB s0, 0A ;test if value is in range 0 to 9 JUMP C, number_char ADD s0, 07 ;ASCII char A to F in range 41 to 46 number_char: ADD s0, 3A ;ASCII char 0 to 9 in range 30 to 40 RETURN ; ; ; Display the two character HEX value of the register contents 's0' on the LCD ; at the current cursor position. ; ; Registers used s0, s1, s2, s3, s4, s5 ; display_hex_byte: CALL hex_byte_to_ASCII LOAD s5, s3 CALL LCD_write_data LOAD s5, s2 CALL LCD_write_data RETURN ; ; ; ; Display the 32-bit value stored in 4 ascending memory locations as an 8 character ; HEX value at the current cursor position. Register s7 must contain the memory ; location of the most significant byte (which is also the highest address). ; ; Registers used s0, s1, s2, s3, s4, s5, s6, s7 ; display_hex_32_bit: LOAD s6, 04 ;4 bytes to display disp32_loop: FETCH s0, (s7) ;read byte CALL display_hex_byte ;display byte SUB s7, 01 ;decrement pointer SUB s6, 01 ;count bytes displayed RETURN Z JUMP disp32_loop ; ; ;************************************************************************************** ;LCD text messages ;************************************************************************************** ; ; ;Display 'Frequency' on LCD at current cursor position ; disp_Frequency: LOAD s5, character_F CALL LCD_write_data LOAD s5, character_r CALL LCD_write_data LOAD s5, character_e CALL LCD_write_data LOAD s5, character_q CALL LCD_write_data LOAD s5, character_u CALL LCD_write_data LOAD s5, character_e CALL LCD_write_data LOAD s5, character_n CALL LCD_write_data LOAD s5, character_c CALL LCD_write_data LOAD s5, character_y CALL LCD_write_data RETURN ; ;Display 'Generator' on LCD at current cursor position ; disp_Generator: LOAD s5, character_G CALL LCD_write_data LOAD s5, character_e CALL LCD_write_data LOAD s5, character_n CALL LCD_write_data LOAD s5, character_e CALL LCD_write_data LOAD s5, character_r CALL LCD_write_data LOAD s5, character_a CALL LCD_write_data LOAD s5, character_t CALL LCD_write_data LOAD s5, character_o CALL LCD_write_data LOAD s5, character_r CALL LCD_write_data CALL display_space LOAD s5, character_v CALL LCD_write_data LOAD s5, character_1 CALL LCD_write_data LOAD s5, character_stop CALL LCD_write_data LOAD s5, character_2 CALL LCD_write_data RETURN ; ; ; ; ;************************************************************************************** ;Software delay routines ;************************************************************************************** ; ; ; ;Delay of 1us. ; ;Constant value defines reflects the clock applied to KCPSM3. Every instruction ;executes in 2 clock cycles making the calculation highly predictable. The '6' in ;the following equation even allows for 'CALL delay_1us' instruction in the initiating code. ; ; delay_1us_constant = (clock_rate - 6)/4 Where 'clock_rate' is in MHz ; ;Registers used s0 ; delay_1us: LOAD s0, delay_1us_constant wait_1us: SUB s0, 01 JUMP NZ, wait_1us RETURN ; ;Delay of 40us. ; ;Registers used s0, s1 ; delay_40us: LOAD s1, 28 ;40 x 1us = 40us wait_40us: CALL delay_1us SUB s1, 01 JUMP NZ, wait_40us RETURN ; ; ;Delay of 1ms. ; ;Registers used s0, s1, s2 ; delay_1ms: LOAD s2, 19 ;25 x 40us = 1ms wait_1ms: CALL delay_40us SUB s2, 01 JUMP NZ, wait_1ms RETURN ; ;Delay of 20ms. ; ;Delay of 20ms used during initialisation. ; ;Registers used s0, s1, s2, s3 ; delay_20ms: LOAD s3, 14 ;20 x 1ms = 20ms wait_20ms: CALL delay_1ms SUB s3, 01 JUMP NZ, wait_20ms RETURN ; ;Delay of approximately 1 second. ; ;Registers used s0, s1, s2, s3, s4 ; delay_1s: LOAD s4, 32 ;50 x 20ms = 1000ms wait_1s: CALL delay_20ms SUB s4, 01 JUMP NZ, wait_1s RETURN ; ; ; ;************************************************************************************** ;LCD Character Module Routines ;************************************************************************************** ; ;LCD module is a 16 character by 2 line display but all displays are very similar ;The 4-wire data interface will be used (DB4 to DB7). ; ;The LCD modules are relatively slow and software delay loops are used to slow down ;KCPSM3 adequately for the LCD to communicate. The delay routines are provided in ;a different section (see above in this case). ; ; ;Pulse LCD enable signal 'E' high for greater than 230ns (1us is used). ; ;Register s4 should define the current state of the LCD output port. ; ;Registers used s0, s4 ; LCD_pulse_E: XOR s4, LCD_E ;E=1 OUTPUT s4, LCD_output_port CALL delay_1us XOR s4, LCD_E ;E=0 OUTPUT s4, LCD_output_port RETURN ; ;Write 4-bit instruction to LCD display. ; ;The 4-bit instruction should be provided in the upper 4-bits of register s4. ;Note that this routine does not release the master enable but as it is only ;used during initialisation and as part of the 8-bit instruction write it ;should be acceptable. ; ;Registers used s4 ; LCD_write_inst4: AND s4, F8 ;Enable=1 RS=0 Instruction, RW=0 Write, E=0 OUTPUT s4, LCD_output_port ;set up RS and RW >40ns before enable pulse CALL LCD_pulse_E RETURN ; ; ;Write 8-bit instruction to LCD display. ; ;The 8-bit instruction should be provided in register s5. ;Instructions are written using the following sequence ; Upper nibble ; wait >1us ; Lower nibble ; wait >40us ; ;Registers used s0, s1, s4, s5 ; LCD_write_inst8: LOAD s4, s5 AND s4, F0 ;Enable=0 RS=0 Instruction, RW=0 Write, E=0 OR s4, LCD_drive ;Enable=1 CALL LCD_write_inst4 ;write upper nibble CALL delay_1us ;wait >1us LOAD s4, s5 ;select lower nibble with SL1 s4 ;Enable=1 SL0 s4 ;RS=0 Instruction SL0 s4 ;RW=0 Write SL0 s4 ;E=0 CALL LCD_write_inst4 ;write lower nibble CALL delay_40us ;wait >40us LOAD s4, F0 ;Enable=0 RS=0 Instruction, RW=0 Write, E=0 OUTPUT s4, LCD_output_port ;Release master enable RETURN ; ; ; ;Write 8-bit data to LCD display. ; ;The 8-bit data should be provided in register s5. ;Data bytes are written using the following sequence ; Upper nibble ; wait >1us ; Lower nibble ; wait >40us ; ;Registers used s0, s1, s4, s5 ; LCD_write_data: LOAD s4, s5 AND s4, F0 ;Enable=0 RS=0 Instruction, RW=0 Write, E=0 OR s4, 0C ;Enable=1 RS=1 Data, RW=0 Write, E=0 OUTPUT s4, LCD_output_port ;set up RS and RW >40ns before enable pulse CALL LCD_pulse_E ;write upper nibble CALL delay_1us ;wait >1us LOAD s4, s5 ;select lower nibble with SL1 s4 ;Enable=1 SL1 s4 ;RS=1 Data SL0 s4 ;RW=0 Write SL0 s4 ;E=0 OUTPUT s4, LCD_output_port ;set up RS and RW >40ns before enable pulse CALL LCD_pulse_E ;write lower nibble CALL delay_40us ;wait >40us LOAD s4, F0 ;Enable=0 RS=0 Instruction, RW=0 Write, E=0 OUTPUT s4, LCD_output_port ;Release master enable RETURN ; ; ; ; ;Read 8-bit data from LCD display. ; ;The 8-bit data will be read from the current LCD memory address ;and will be returned in register s5. ;It is advisable to set the LCD address (cursor position) before ;using the data read for the first time otherwise the display may ;generate invalid data on the first read. ; ;Data bytes are read using the following sequence ; Upper nibble ; wait >1us ; Lower nibble ; wait >40us ; ;Registers used s0, s1, s4, s5 ; LCD_read_data8: LOAD s4, 0E ;Enable=1 RS=1 Data, RW=1 Read, E=0 OUTPUT s4, LCD_output_port ;set up RS and RW >40ns before enable pulse XOR s4, LCD_E ;E=1 OUTPUT s4, LCD_output_port CALL delay_1us ;wait >260ns to access data INPUT s5, LCD_input_port ;read upper nibble XOR s4, LCD_E ;E=0 OUTPUT s4, LCD_output_port CALL delay_1us ;wait >1us XOR s4, LCD_E ;E=1 OUTPUT s4, LCD_output_port CALL delay_1us ;wait >260ns to access data INPUT s0, LCD_input_port ;read lower nibble XOR s4, LCD_E ;E=0 OUTPUT s4, LCD_output_port AND s5, F0 ;merge upper and lower nibbles SR0 s0 SR0 s0 SR0 s0 SR0 s0 OR s5, s0 LOAD s4, 04 ;Enable=0 RS=1 Data, RW=0 Write, E=0 OUTPUT s4, LCD_output_port ;Stop reading 5V device and release master enable CALL delay_40us ;wait >40us RETURN ; ; ;Reset and initialise display to communicate using 4-bit data mode ;Includes routine to clear the display. ; ;Requires the 4-bit instructions 3,3,3,2 to be sent with suitable delays ;following by the 8-bit instructions to set up the display. ; ; 28 = '001' Function set, '0' 4-bit mode, '1' 2-line, '0' 5x7 dot matrix, 'xx' ; 06 = '000001' Entry mode, '1' increment, '0' no display shift ; 0E = '00001' Display control, '1' display on, '1' cursor off, '0' cursor blink off ; 01 = '00000001' Display clear ; ;Registers used s0, s1, s2, s3, s4 ; LCD_reset: CALL delay_20ms ;wait more that 15ms for display to be ready LOAD s4, 30 CALL LCD_write_inst4 ;send '3' CALL delay_20ms ;wait >4.1ms CALL LCD_write_inst4 ;send '3' CALL delay_1ms ;wait >100us CALL LCD_write_inst4 ;send '3' CALL delay_40us ;wait >40us LOAD s4, 20 CALL LCD_write_inst4 ;send '2' CALL delay_40us ;wait >40us LOAD s5, 28 ;Function set CALL LCD_write_inst8 LOAD s5, 06 ;Entry mode CALL LCD_write_inst8 LOAD s5, 0E ;Display control CALL LCD_write_inst8 LCD_clear: LOAD s5, 01 ;Display clear CALL LCD_write_inst8 CALL delay_1ms ;wait >1.64ms for display to clear CALL delay_1ms RETURN ; ;Position the cursor ready for characters to be written. ;The display is formed of 2 lines of 16 characters and each ;position has a corresponding address as indicated below. ; ; Character position ; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ; ; Line 1 - 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F ; Line 2 - C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ; ;This routine will set the cursor position using the value provided ;in register s5. The upper nibble will define the line and the lower ;nibble the character position on the line. ; Example s5 = 2B will position the cursor on line 2 position 11 ; ;Registers used s0, s1, s2, s3, s4 ; LCD_cursor: TEST s5, 10 ;test for line 1 JUMP Z, set_line2 AND s5, 0F ;make address in range 80 to 8F for line 1 OR s5, 80 CALL LCD_write_inst8 ;instruction write to set cursor RETURN set_line2: AND s5, 0F ;make address in range C0 to CF for line 2 OR s5, C0 CALL LCD_write_inst8 ;instruction write to set cursor RETURN ; ;This routine will shift the complete display one position to the left. ;The cursor position and LCD memory contents will not change. ; ; ;Registers used s0, s1, s2, s3, s4, s5 ; LCD_shift_left: LOAD s5, 18 ;shift display left CALL LCD_write_inst8 RETURN ; ;************************************************************************************** ;Interrupt Service Routine (ISR) ;************************************************************************************** ; ;Interrupts occur when the rotary control has been moved. ; ;The ISR captures the state of the direction which it writes to scratch pad memory (SPM). ;The most significant bit is also set at this location to provide a 'flag' to the ;main body of the program. ; ; ISR: STORE s0, ISR_preserve_s0 ;preserve s0 INPUT s0, rotary_port ;read rotary encoder OR s0, rotary_event ;set flag STORE s0, rotary_status ;put result in SCM FETCH s0, ISR_preserve_s0 ;restore s0 RETURNI ENABLE ; ; ;************************************************************************************** ;Interrupt Vector ;************************************************************************************** ; ADDRESS 3FF JUMP ISR ; ;