问题
I'm using an ATmega328. The thing is that I want to generate a square wave of a given frequency and of a given amplitude. PWM can't be used because I was given a board that has already been soldered, so the wave has to be put at the output of an R2R resistor ladder that is connected to the B port of the processor. So, basically, the idea is that I have to put the pins of port B in 0 and VOLUME (VOLUME is a number that goes from 1 to 255) periodically, with a given frequency and a duty cycle of 50%. And remember: NO PWM. The frequency should be able to change every 100 ms, but I'm not being able to make this work so I'm just trying to generate a constant frequency and see what happens at first.
I'm running the clock at 1MHz. I wrote the following code:
.DSEG
.ORG 0x100
.CSEG
.ORG 0x100
;Initializing stack pointer
LDI R16,HIGH(RAMEND)
OUT SPH,R16
LDI R16,LOW(RAMEND)
OUT SPL,R16
MAIN:
CALL GENERATE ;Calling the generating routine
RJMP MAIN ;Repeat this forever
;I will generate a 440Hz frequency. It has an approximate period of 2273 microseconds
;This means that half a period stands for approximately 1136 clocks
GENERATE:
LDI R17, 0x70
LDI R18, 0x04 ;Half the period in hexadecimal is 0x0470
LDI R19, 243 ;Volume = 243 (arbitrary, it could be any number)
LDI R21, 88 ;The amount of half-periods in 100 ms (arbitrary election, too)
LDI R25, 0xFF
OUT DDRB, R25 ;Port B is an output port
LDI R24, 0xFF ;R25R24 = 0xFFFF
CLC ;Clean the carry
SBC R24, R17
SBC R25, R18 ;R25R24 = 0xFFFF - Halfperiod
ADIW R25:R24, 1 ;R25R24 = 0xFFFF - Halfperiod +1
OUT PORTB, R18 ;The wave starts at 0
BEGIN:
CALL LOOP_1
EOR R19, R19 ;It varies between 0 and volume
OUT PORTB, R19 ;It puts the output to the actual value of R19 (0 or volume)
CLZ ;Clean Z flag
DEC R21
BREQ END ;When 100ms have passed, generation is over
JMP BEGIN ;If not, generation continues
LOOP_1: STS TCNT1H, R25
STS TCNT1L, R24 ;Loading the amount of clocks the timer has to count
LDI R16, 0x00
STS TCCR1A, R16
LDI R16, 0x01
STS TCCR1B, R16 ;Timer operating in normal mode, no prescaler
LOOP_2: IN R16, TIFR1
SBRS R16, TOV1 ;If timer's over, skip the next jump
JMP LOOP_2
LDI R16, 0x00
STS TCCR1B, R16 ;Stopping the timer
LDI R16, 0x04
OUT TIFR1, R16 ;Clean TOV1
RET ;Back to BEGIN
END:
RET ;Back to MAIN
It's one of my first approaches to Assembly, so this may be pretty ugly to read. The code is currently not working. Any ideas?
EDIT:
Thanks to Spektre who pointed this out to me, I corrected a piece of the code above. The code is the same except that
GENERATE:
.
.
.
LDI R21, 44 ;The amount of PERIODS (not half-periods as before) in 100 ms
.
.
.
BEGIN:
OUT PORTB, R18 ;This was before the BEGIN tag, now it is after it
CALL LOOP_1 ;It counts a halfperiod with output=0
OUT PORTB, R19 ;Now output=volume
CALL LOOP_1 ;It counts a halfperiod with output=volume
CLZ ;Clean Z Flag
DEC R21
BREQ END ;When 100ms have passed, generation is over
JMP BEGIN ;If not, generation continues
回答1:
It is been years I did something for such architecture ... I am using UC3 (successor) and there on MCU 66 MHz
clock I can achieve 2-5 MHz
polling frequency. But I am afraid on your Target platform this would be a lot lower. So first measure polling frequency to see the limits of your HW.
1 MHz
is the output clock or the MCU clock? What max frequency you need to have at output?
There are more ways to achieve this the obvious are:
Timer/Counter
You can set up Timer for example 1 us and do the timing and switching there. Or you can use this just to set the new frequency. But beware any interrupt can cause timing problems in the output signal like jitter ... These can be avoided if there is enough time but it usually limits max output frequency a lot.
Main thread polling
It looks like this is what you are doing. But you are forgetting all the instructions has their own timing. And conditionals can have different timing so you should compensate.
exploiting interface (if any present)
Look at the datasheet and see if any HW is connected to that port. MCUs has usually multiplexed more features on the pins sometimes allowing to exploit them. For example I once made an VGA image generator by exploiting serial interface, DMA + ABI/SD card interface and. Each method produced 16 colored VGA signal correctly but with completely different code and HW architecture on the same chip and pins... To compare with your platform on ATMega168 with
20 MHz
clock (it is the same core as ATMega328) I was able to produce 1bit LCD640x480
signal for LCD (many times lower then what is needed for colored VGA signal). Sometimes you can even change the meaning of pins during runtime... For example to achieve100 000 rpm
BLDC controller I change the internal circuitry few times and electric period of the motor just to be able to produce such high speeds (otherwise non-achievable by direct means.
You need to experiment which approach is fast/accurate/comfortable enough for you. From the first look at you code I stumble on this:
EOR R19, R19 ;It varies between 0 and volume
OUT PORTB, R19 ;It puts the output to the actual value of R19 (0 or volume)
If I see it right you are XORing R19
and R19
... destroing the content of R19
after first pass. So you are outputting 0
to the B
port all the time:
XOR Volume,Volume -> Volume = 0
XOR 0,0 -> 0
Instead I would:
- out port volume
- wait half period
- out port zero
- wait half period
- if 100ms reached change frequency
- goto #1
You should use the Port access as least often as you can. Not sure if it is also the case for ATMega but on newer architectures the Port is accessed by HW interface/API and is really slow for high speed switching if not used wisely.
PS.
Hope your R2R ladder has diodes before the input resistors or at least PORTB has open collector output otherwise it will not work because of the currents between neighbor pins if both set to different value.
[Edit1] Some clarification
To be more clear about the R2R current problem
The diodes should have the same PN barrier voltage !!!
The 1MHz clock
You wrote
I'm running the clock at 1MHz
but did not clarify if it is MCU/CPU clock, Timer Clock or output square wave signal frequency. Now it is clear your output signal is<130,523> Hz
which should be easily achievable on your platform. I was not sure before ...I have generated signals even around 30 MHz on MCU's so 1 MHz was a possibility.Timing
Conditional instructions like
BREQ END
on most platforms have different timing when the condition is true and when false so to maintain the synchronism you should account for that ... sometimes well placednop
can handle the problem ... Yu shoudl check the instruction timing in the documentation if it is the case or not.You stated that you are using counter/timer I do not see it in your code. For proper use you should
- configure the HW for mode and frequency you want to
- register the interrupt subroutine
- enable the interrupt and or trigger timer counter event...
inside interrupt event handler
reset the counter value if needed and clear the interrupt flag so another event can occur. Sometimes even correction of the counter value is needed for proper timings if you are near edge of chip capabilities.
What I see is some configuration of timer/counter not sure if rightly done as I do not use that platform a long time. I see no interrupt handler subroutine anywhere... possibly you are polling the counter register but again not sure if it is the right way (not all HW registers are read able) you should confront the documentation. But anyway it is polling so timing of instruction matters ...
If it helps I dig one of my ancient generators (generate square wave on PortC) asm codes for ATMega168:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; generator ver: 0.00 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
/*
CPU: ATMega168 8MHz internal RC
OUT: PC
*/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; ATMega168 startup: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; fuses:BOOTRST=1 disabled
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.equ stack =0x04FF
.equ cpuclk =8000000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.device ATmega168 ; device selection
.equ PINB =0x03 ; IO reg adresses definition in/out
.equ DDRB =0x04
.equ PORTB =0x05
.equ PINC =0x06
.equ DDRC =0x07
.equ PORTC =0x08
.equ PIND =0x09
.equ DDRD =0x0A
.equ PORTD =0x0B
.equ TIFR0 =0x15
.equ TIFR1 =0x16
.equ TIFR2 =0x17
.equ PCIFR =0x1B
.equ EIFR =0x1C
.equ EIMSK =0x1D
.equ GPIOR0=0x1E
.equ EECR =0x1F
.equ EEDR =0x20
.equ EEARL =0x21
.equ EEARH =0x22
.equ GTCCR =0x23
.equ TCCROA=0x24
.equ TCCROB=0x25
.equ TCNT0 =0x26
.equ OCROA =0x27
.equ OCROB =0x28
.equ GPIOR1=0x2A
.equ GPIOR2=0x2B
.equ SPCR =0x2C
.equ SPSR =0x2D
.equ SPDR =0x2E
.equ ACSR =0x30
.equ SMCR =0x33
.equ MCUSR =0x34
.equ MCUCR =0x35
.equ SPMCSR=0x37
.equ SPL =0x3D
.equ SPH =0x3E
.equ SREG =0x3F
.equ WDTCSR=0x60 ; sts,lds
.equ CLKPR =0x61
.equ PRR =0x64
.equ OSCCAL=0x66
.equ PCICR =0x68
.equ EICRA =0x69
.equ PCMSK0=0x6B
.equ PCMSK1=0x6C
.equ PCMSK2=0x6D
.equ TIMSK0=0x6E
.equ TIMSK1=0x6F
.equ TIMSK2=0x70
.equ ADCL =0x78
.equ ADCH =0x79
.equ ADCSRA=0x7A
.equ ADCSRB=0x7B
.equ ADMUX =0x7C
.equ DIDR0 =0x7E
.equ DIDR1 =0x7F
.equ TCCR1A=0x80
.equ TCCR1B=0x81
.equ TCCR1C=0x82
.equ TCNT1L=0x84
.equ TCNT1H=0x85
.equ ICR1L =0x86
.equ ICR1H =0x87
.equ OCR1AL=0x88
.equ OCR1AH=0x89
.equ OCR1BL=0x8A
.equ OCR1BH=0x8B
.equ TCCR2A=0xB0
.equ TCCR2B=0xB1
.equ TCNT2 =0xB2
.equ OCR2A =0xB3
.equ ASSR =0xB6
.equ TWBR =0xB8
.equ TWSR =0xB9
.equ TWAR =0xBA
.equ TWDR =0xBB
.equ TWCR =0xBC
.equ TWAMR =0xBD
.equ UCSR0A=0xC0
.equ UCSR0B=0xC1
.equ UCSR0C=0xC2
.equ UBRR0L=0xC4
.equ UBRR0H=0xC5
.equ UDR0 =0xC6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.cseg ; Start of code segment
.org 0x0000 ; interrupts, highest priority first
jmp RESET ; 3 Reset Handler
jmp EXT_INT0 ; 3 IRQ0 Handler
jmp EXT_INT1 ; 3 IRQ1 Handler
jmp PCINT0 ; 3 PCINT0 Handler
jmp PCINT1 ; 3 PCINT1 Handler
jmp PCINT2 ; 3 PCINT2 Handler
jmp WDT ; 3 Watchdog Timer Handler
jmp TIM2_COMPA ; 3 Timer2 Compare A Handler
jmp TIM2_COMPB ; 3 Timer2 Compare B Handler
jmp TIM2_OVF ; 3 Timer2 Overflow Handler
jmp TIM1_CAPT ; 3 Timer1 Capture Handler
jmp TIM1_COMPA ; 3 Timer1 Compare A Handler
jmp TIM1_COMPB ; 3 Timer1 Compare B Handler
jmp TIM1_OVF ; 3 Timer1 Overflow Handler
jmp TIM0_COMPA ; 3 Timer0 Compare A Handler
jmp TIM0_COMPB ; 3 Timer0 Compare B Handler
jmp TIM0_OVF ; 3 Timer0 Overflow Handler
jmp SPI_STC ; 3 SPI Transfer Complete Handler
jmp USART_RXC ; 3 USART, RX Complete Handler
jmp USART_UDRE ; 3 USART, UDR Empty Handler
jmp USART_TXC ; 3 USART, TX Complete Handler
jmp ADC_DONE ; 3 ADC Conversion Complete Handler
jmp EE_RDY ; 3 EEPROM Ready Handler
jmp ANA_COMP ; 3 Analog Comparator Handler
jmp TWI ; 3 2-wire Serial Interface Handler
jmp SPM_RDY ; 3 Store Program Memory Ready Handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RESET: cli ; Reset Handler
ldi r16,high(stack)
out SPH,r16
ldi r16,low(stack)
out SPL,r16
ldi r16, 0xFF ; DDRB 1 - output, 0 - input direction
out DDRB, r16
ldi r16, 0xFF ; DDRC 1 - output, 0 - input direction
out DDRC, r16
ldi r16, 0xFF ; DDRD 1 - output, 0 - input direction
out DDRD, r16
ldi r16, 0xFF ; all outputs high,and all inputs Pull Up to Ucc
out PORTB,r16
out PORTC,r16
out PORTD,r16
jmp main
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EXT_INT0: ; IRQ0 Handler
reti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EXT_INT1: ; IRQ1 Handler
reti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PCINT0: ; PCINT0 Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PCINT1: ; PCINT1 Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PCINT2: ; PCINT2 Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WDT: ; Watchdog Timer Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM2_COMPA: ; Timer2 Compare A Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM2_COMPB: ; Timer2 Compare B Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM2_OVF: ; Timer2 Overflow Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM1_CAPT: ; Timer1 Capture Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM1_COMPA: ; Timer1 Compare A Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM1_COMPB: ; Timer1 Compare B Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM1_OVF: ; Timer1 Overflow Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM0_COMPA: ; Timer0 Compare A Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM0_COMPB: ; Timer0 Compare B Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TIM0_OVF: ; Timer0 Overflow Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SPI_STC: ; SPI Transfer Complete Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
USART_RXC: ; USART, RX Complete Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
USART_UDRE: ; USART, UDR Empty Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
USART_TXC: ; USART, TX Complete Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ADC_DONE: ; ADC Conversion Complete Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EE_RDY: ; EEPROM Ready Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ANA_COMP: ; Analog Comparator Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TWI: ; 2-wire Serial Interface Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SPM_RDY: ; Store Program Memory Ready Handler
reti ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
main: ldi r16,0
main0: out PORTC,r16 ; 1 8T / 8MHz = 1MHz PC0, 0.5MHz PC1, ...
inc r16 ; 1
nop ; 1
nop ; 1
nop ; 1
nop ; 1
rjmp main0 ; 2
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
waitms: push r31 ; 2 wait cca r31 [ms] +(15+r31*4)T
push r30 ; 2
push r29 ; 2
waitms2:ldi r30,cpuclk/200000;1 1ms
waitms1:ldi r29,49 ; 1 200T
waitms0:nop ; 1
dec r29 ; 1
brne waitms0 ; 2/1
dec r30 ; 1
brne waitms1 ; 2/1
dec r31 ; 1
brne waitms2 ; 2/1
pop r29 ; 2
pop r30 ; 2
pop r31 ; 2
ret ; 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; ID: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; end. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
It is my standard template I used at that time for these devices ... Take a look at the waitms
subroutine it waits as close to r31 [ms]
as it can. The timings of instructions are present and yes the conditional instructions have different timing on that chip. As ATMega328 has the same core then it is the same for your chip as well.
Heh my ATMega emulator with Oscilloscope in C++ still works on Win7 ... (Written on w9x or w2k not sure now) (used with hex compiled from the posted source of mine).
来源:https://stackoverflow.com/questions/38005093/generating-square-wave-in-avr-assembly-without-pwm