- English
- Deutsch
Im folgeden ist der gesamte Quellcode des DDS-Experimentes dargestellt. Um für eine Softwarelösung doch zu einer brauchbaren Performance zu gelangen, wurde der DDS-Algorithmus incl. der Steuerung (Drehgeber, Display) in Assembler realisiert.
Folgende Komponenten wurden zusätzlich noch verwendet:
.nolist .include "tn2313def.inc" .list .def da_out = r1 ; buffer for the byte to be written to DAC .def X0 = R2 ; X-register (5 byte, 40 bit) .def X1 = R3 .def X2 = R4 .def X3 = R5 .def X4 = R6 .def Y0 = R7 ; Y-register (4 byte, 32 bit) .def Y1 = R8 .def Y2 = R9 .def Y3 = R10 .def Z0 = R11 ; Z-register (5 byte; 32 bit) .def Z1 = R12 .def Z2 = R13 .def Z3 = R14 .def Z4 = R15 .def A = r16 ; something like THE accu - not the phase accumulator! .def zero = r17 ; a register being filled with 0 .def kbd_stat = r18 ; the status of the rotary encoder .def digit_adm = r19 ; the next digit getting data .def tw_0 = r20 ; the tuning word .def tw_1 = r21 .def pa_0 = r22 ; the pase accumulator .def pa_1 = r23 .def wz_a1 = r24 .equ CNT = 100 .equ F_STEP = 20 .cseg ; the jump table for all the interrupts rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp main rjmp irq_service rjmp main rjmp main rjmp main rjmp main rjmp main irq_service: ; the irq service routine ; save some registers on the stack push A in A,SREG push A push ZL push ZH ; write the data byte to the bus and hence to the DAC out PORTB,da_out ; sync with timer - an algorithm to avoid phase jitter coming fron ; interrupts on one, two or three cycle operations in A,TCNT0 subi A,0x0f ldi ZH,high(delay) ldi ZL,low(delay) add ZL,A adc ZH,zero ijmp delay: nop nop nop nop nop nop ; now trigger the DAC - the value on the bus is valid ; /ENABLE of ZN478 goes low (active) cbi PORTD,3 ; determine next value: add tuning word to phase accumulator ; ignore carry add pa_0,tw_0 adc pa_1,tw_1 ; the phase accumulator is like a saw tooth signal. ; convert is to a sine by using a sin-table ldi ZH,high(SIN_TABLE<<1) ldi ZL,low(SIN_TABLE<<1) add ZL,pa_1 adc ZH,zero lpm A,Z ; store the value in da_out - we use it in the next interrupt mov da_out,A ; now the /ENABLE pulse for the DAC was long enough ; /ENABLE of ZN478 goes high (inactive) sbi PORTD,3 ; now the display - 4 digits will be managed. In each IRQ ; another one will be processed ; something to do? sbrc digit_adm,7 ; if the MSB is set, the data for display are not yet ready - try ; in next interrupt ... rjmp irq_exit ; this ends up in a computed goto - depending on the digit ; to be processed mov A,digit_adm lsl A lsl A add A,digit_adm ldi ZH,high(computed_goto) ldi ZL,low(computed_goto) add ZL,A adc ZH,zero ; this is the computed goto based on ZH/ZL content ijmp computed_goto: do_nothing: ; we are done - nothing to do rjmp irq_exit nop nop nop nop do_digit_0: ; set the address for digit0 cbi PORTD,0 cbi PORTD,1 ; load the value of the digit from SRAM lds A,digit0 rjmp digit_exit do_digit_1: sbi PORTD,0 cbi PORTD,1 lds A,digit1 rjmp digit_exit do_digit_2: cbi PORTD,0 sbi PORTD,1 lds A,digit2 rjmp digit_exit do_digit_3: sbi PORTD,0 sbi PORTD,1 lds A,digit3 digit_exit: ; send the byte out to the data bus PORTB out PORTB,A ; set /WR of the display to low (active) cbi PORTD,2 ; advance to the 'next' digit which is the previous one ; the cycle over all digits is over if 0xff. Then no further ; display update will happen until tuning word has changed dec digit_adm irq_exit: ; restore some register pop ZH pop ZL pop A out SREG,A pop A ; the write-pulse was long enough ; set /WR of the display to high (inactive) sbi PORTD,2 ; end of interrupt reti main: ; initialization ; fill the zero-register with zero ldi zero,0 ; set stack pointer to top of memory ldi A,0xdf out SPL,A ; initialize port B ; PB0-PB7: data bus, no pullup ; this is the data bus for the DAC and for the display ; we always send data out - we never read from that bus ldi A,0b11111111 out DDRB,A ; initialize port D ; out PD0-PD1: A0-A1, digit select A0 and A1 for display ; out PD2: WR\ for display ; out PD3: ENABLE\ for DAC ; in PD4: Phase1 with pull-up - one pin of the rotary encoder ; in PD5: Phase2 with pull-up - the other pin of the rotary encoder ; in PD6: Pushbutton with pull-ap - the switch function (press, push) ; of the rotary encoder ; in PD7: not available - not used ldi A,0b01001111 out DDRD,A ; set /WR and /ENABLE to inactive (high) and ; set the pull-up resisors for the inputs of the rotary encoder to high ldi A,0b00111100 out PORTD,A ; set tuning word to 1 kHz (328) ldi A,0x48 mov tw_0,A ldi A,0x01 mov tw_1,A ; clear the pase accumulator - not really necessary mov pa_0,zero mov pa_1,zero ; display to status 0x80 ldi A,0x80 mov digit_adm,A main_50: rcall update_display sbrc digit_adm,7 rjmp main_50 ; set an initial value for the DAC ldi A,0x80 mov da_out,A ; initialize timer 0 ; no usage of any port ; CTC-mode ; no prescaler ldi A,0b00000010 out TCCR0A,A ldi A,0b00000001 out TCCR0B,A ; Counter register (100-1) ldi A,CNT-1 out OCR0A,A ; interrupt on compare match ldi A,0b0000001 out TIMSK,A ; poll keys for the first time in kbd_stat,PIND lsr kbd_stat lsr kbd_stat andi kbd_stat,0b00001100 ; interrupt enable sei loop: ; poll rotary encoder ; determine direction (left, right) by use of a ; state engine ldi ZH,high(kbd_state_engine<<1) ldi ZL,low(kbd_state_engine<<1) mov A,kbd_stat andi A,0x3c add ZL,A adc ZH,zero in A,PIND andi A,0x30 swap A add ZL,A adc ZH,zero lpm kbd_stat,Z sbrc kbd_stat,0 ; a step in CCW direction was detected. Decrement ; the tuning word rjmp cmd_dn sbrc kbd_stat,1 ; a step in CW direction was detected. Increment ; the tuning word rjmp cmd_up cpi kbd_stat,0x3c breq kbd_stat_error ; nothing interesting happened - so we try to update the ; display but maybe even there is nothing to do ... rcall update_display rjmp loop cmd_up: ; incrementing tuning word ldi A,F_STEP add tw_0,A adc tw_1,zero andi kbd_stat,0xfc ; this causes a recalculation of the frequency and thus updating ; the display ldi A,0x80 mov digit_adm,A rjmp loop cmd_dn: ; decrementing tuning word ldi A,F_STEP sub tw_0,A sbc tw_1,zero andi kbd_stat,0xfc ; this causes a recalculation of the frequency and thus updating ; the display ldi A,0x80 mov digit_adm,A rjmp loop kbd_stat_error: ; some unexpected state in the state machine of the rotary encoder ; entered. Initialize the whole stuff ... sbi PORTD,6 in kbd_stat,PIND lsr kbd_stat lsr kbd_stat andi kbd_stat,0b00001100 cbi PORTD,6 rjmp loop update_display: ; here, the digits of the display will be calculated ; this step consists of multiple phases which are ; distributed over multiple cycles of the main loop ; if bit 7 of digit_adm is set, we do not do anything sbrs digit_adm,7 ret ; each phase corresponds to one computed goto ldi ZH,high(jump_table) ldi ZL,low(jump_table) mov A,digit_adm andi A,0x07 clc adc ZL,A adc ZH,zero ; the computed goto ijmp jump_table: rjmp update_display_prepare rjmp update_display_d0 rjmp update_display_d1 rjmp update_display_d2 rjmp update_display_d3 rjmp update_display_d4 rjmp update_display_final ret update_display_prepare: ; do some calculations ; f_out = ((F_CPU div 256) * tw) div (CNT * 256) ; 1. F_CPU (20 MHz) div 256 -> X (=0x01312c) clr X4 clr X3 ldi A,0x01 mov X2,A ldi A,0x31 mov X1,A ldi A,0x2d mov X0,A ; 2. tw -> Y mov Y0,tw_0 mov Y1,tw_1 clr Y2 clr Y3 rcall multiply_24_by_16 ; 3. Z->X mov X0,Z0 mov X1,Z1 mov X2,Z2 mov X3,Z3 mov X4,Z4 ; 4. CNT -> Y ldi A,CNT mov Y0,A clr Y1 rcall divide_40_by_16 ; rounding sbrs Z0,7 rjmp udp_90 ldi A,1 adc Z1,A adc Z2,zero adc Z3,zero adc Z4,zero udp_90: mov X1,Z2 mov X0,Z1 inc digit_adm ; now we have calculated the output frequency in Hz based on ; CPU-clock frequency, tuning word and divider factor of ; the counter (CNT) ; note: the frequency must be somewhere between 0 and 99999 Hz. ; of course, already above a few kHz, the output signal is far away ; from a sine; ret update_display_d0: ; determine the 1 Hz digit rcall divide_by_ten mov A,X0 ori A,0x30 sts digit0,A inc digit_adm ret update_display_d1: ; determine the 10 Hz digit mov X0,Z0 mov X1,Z1 rcall divide_by_ten mov A,X0 ori A,0x30 sts digit1,A inc digit_adm ret update_display_d2: ; determine the 100 Hz digit mov X0,Z0 mov X1,Z1 rcall divide_by_ten mov A,X0 ori A,0x30 sts digit2,A inc digit_adm ret update_display_d3: ; determine the 1 kHz digit mov X0,Z0 mov X1,Z1 rcall divide_by_ten mov A,X0 ori A,0x30 sts digit3,A inc digit_adm ret update_display_d4: ; determine the 10 kHz digit mov X0,Z0 mov X1,Z1 rcall divide_by_ten mov A,X0 ori A,0x30 sts digit4,A inc digit_adm ret update_display_final: ; we can display only 4 digits. Above 10 kHz, we simply skip the 1 Hz ; digit. digit 1 to 4 will be moved to digit 0 to 3. lds A,digit4 cpi A,0x30 breq udf_90 lds A,digit1 sts digit0,A lds A,digit2 sts digit1,A lds A,digit3 sts digit2,A lds A,digit4 sts digit3,A udf_90: ; all digits are calculted; they are ready for being displayed ldi digit_adm,0x04 ret ; now some arithmetic routines divide_by_ten: clr Y1 ldi A,0x0a mov Y0,A clr Z0 clr Z1 rcall normalize dbt_10: rcall test_subtract rol Z0 rol Z1 sbrc Z0,0 rcall subtract dec wz_a1 brne dbt_20 ret dbt_20: lsr Y1 ror Y0 rjmp dbt_10 divide_40_by_16: ; clear Z-register clr Z0 clr Z1 clr Z2 clr Z3 clr Z4 ; shift X, Y commonly to left div_01: sbrc X4,7 rjmp div_02 sbrc X4,6 rjmp div_02 sbrc Y1,7 rjmp div_02 sbrc Y1,6 rjmp div_02 lsl X0 rol X1 rol X2 rol X3 rol X4 lsl Y0 rol Y1 rjmp div_01 div_02: ldi wz_a1,25 div_03: sbrc Y1,7 rjmp div_10 sbrc Y1,6 rjmp div_10 lsl Y0 rol Y1 inc wz_a1 rjmp div_03 div_10: clc cpc X3,Y0 cpc X4,Y1 brcc div_12 clc rjmp div_13 div_12: sbc X3,Y0 sbc X4,Y1 sec div_13: rol Z0 rol Z1 rol Z2 rol Z3 rol Z4 dec wz_a1 brne div_20 ret div_20: lsl X0 rol X1 rol X2 rol X3 rol X4 rjmp div_10 normalize: ; shifts wy left as long as there is no leading '1' in wy ;number of needed shift operations +1 in wz_a1 ldi wz_a1,1 n_10: sbrc Y1,7 ret rcall test_subtract brcs n_20 ret n_20: lsl Y0 rol Y1 inc wz_a1 rjmp n_10 test_subtract: ; wx-wy - no data change ; C=0: wx<wy ; C=1: wx>=wy clc cpc X0,Y0 cpc X1,Y1 brcc ts_10 clc ret ts_10: sec ret subtract: ; wx-wy -> wx ; C=0: wx<wy ; C=1: wx>=wy clc sbc X0,Y0 sbc X1,Y1 ret multiply_24_by_16: ; 24 bit rx x 16 bit ry => 40 bit rz clr Z0 clr Z1 clr Z2 clr Z3 clr Z4 ldi A,0x10 m_10: sbrs Y0,0 rjmp m_20 add Z0,X0 adc Z1,X1 adc Z2,X2 adc Z3,X3 adc Z4,X4 m_20: lsr Y1 ror Y0 lsl X0 rol X1 rol X2 rol X3 rol X4 dec A brne m_10 ret SIN_TABLE: .DB 0x80,0x83 .DB 0x86,0x89 .DB 0x8C,0x90 .DB 0x93,0x96 .DB 0x99,0x9C .DB 0x9F,0xA2 .DB 0xA5,0xA8 .DB 0xAB,0xAE .DB 0xB1,0xB3 .DB 0xB6,0xB9 .DB 0xBC,0xBF .DB 0xC1,0xC4 .DB 0xC7,0xC9 .DB 0xCC,0xCE .DB 0xD1,0xD3 .DB 0xD5,0xD8 .DB 0xDA,0xDC .DB 0xDE,0xE0 .DB 0xE2,0xE4 .DB 0xE6,0xE8 .DB 0xEA,0xEB .DB 0xED,0xEF .DB 0xF0,0xF1 .DB 0xF3,0xF4 .DB 0xF5,0xF6 .DB 0xF8,0xF9 .DB 0xFA,0xFA .DB 0xFB,0xFC .DB 0xFD,0xFD .DB 0xFE,0xFE .DB 0xFE,0xFF .DB 0xFF,0xFF .DB 0xFF,0xFF .DB 0xFF,0xFF .DB 0xFE,0xFE .DB 0xFE,0xFD .DB 0xFD,0xFC .DB 0xFB,0xFA .DB 0xFA,0xF9 .DB 0xF8,0xF6 .DB 0xF5,0xF4 .DB 0xF3,0xF1 .DB 0xF0,0xEF .DB 0xED,0xEB .DB 0xEA,0xE8 .DB 0xE6,0xE4 .DB 0xE2,0xE0 .DB 0xDE,0xDC .DB 0xDA,0xD8 .DB 0xD5,0xD3 .DB 0xD1,0xCE .DB 0xCC,0xC9 .DB 0xC7,0xC4 .DB 0xC1,0xBF .DB 0xBC,0xB9 .DB 0xB6,0xB3 .DB 0xB1,0xAE .DB 0xAB,0xA8 .DB 0xA5,0xA2 .DB 0x9F,0x9C .DB 0x99,0x96 .DB 0x93,0x90 .DB 0x8C,0x89 .DB 0x86,0x83 .DB 0x80,0x7D .DB 0x7A,0x77 .DB 0x74,0x70 .DB 0x6D,0x6A .DB 0x67,0x64 .DB 0x61,0x5E .DB 0x5B,0x58 .DB 0x55,0x52 .DB 0x4F,0x4D .DB 0x4A,0x47 .DB 0x44,0x41 .DB 0x3F,0x3C .DB 0x39,0x37 .DB 0x34,0x32 .DB 0x2F,0x2D .DB 0x2B,0x28 .DB 0x26,0x24 .DB 0x22,0x20 .DB 0x1E,0x1C .DB 0x1A,0x18 .DB 0x16,0x15 .DB 0x13,0x11 .DB 0x10,0x0F .DB 0x0D,0x0C .DB 0x0B,0x0A .DB 0x08,0x07 .DB 0x06,0x06 .DB 0x05,0x04 .DB 0x03,0x03 .DB 0x02,0x02 .DB 0x02,0x01 .DB 0x01,0x01 .DB 0x01,0x01 .DB 0x01,0x01 .DB 0x02,0x02 .DB 0x02,0x03 .DB 0x03,0x04 .DB 0x05,0x06 .DB 0x06,0x07 .DB 0x08,0x0A .DB 0x0B,0x0C .DB 0x0D,0x0F .DB 0x10,0x11 .DB 0x13,0x15 .DB 0x16,0x18 .DB 0x1A,0x1C .DB 0x1E,0x20 .DB 0x22,0x24 .DB 0x26,0x28 .DB 0x2B,0x2D .DB 0x2F,0x32 .DB 0x34,0x37 .DB 0x39,0x3C .DB 0x3F,0x41 .DB 0x44,0x47 .DB 0x4A,0x4D .DB 0x4F,0x52 .DB 0x55,0x58 .DB 0x5B,0x5E .DB 0x61,0x64 .DB 0x67,0x6A .DB 0x6D,0x70 .DB 0x74,0x77 .DB 0x7A,0x7D kbd_state_engine: ;A .DB 0x00, 0x04, 0x08, 0x3c ;B .DB 0x00, 0x04, 0x3c, 0x12 ;C .DB 0x00, 0x3c, 0x08, 0x15 ;D .DB 0x3c, 0x18, 0x1c, 0x0c ;E .DB 0x3c, 0x18, 0x1c, 0x0c ;F .DB 0x3c, 0x18, 0x1c, 0x0c ;G .DB 0x21, 0x18, 0x3c, 0x0c ;H .DB 0x26, 0x3c, 0x1c, 0x0c ;K .DB 0x00, 0x04, 0x08, 0x3c ;L .DB 0x00, 0x04, 0x08, 0x3c .DSEG digit0: .BYTE 1 digit1: .BYTE 1 digit2: .BYTE 1 digit3: .BYTE 1 digit4: .BYTE 1 ;table: .BYTE tab_size ; reserve tab_size bytes.