Note C5 on 3216 #787
Replies: 3 comments 4 replies
-
|
Beta Was this translation helpful? Give feedback.
-
Cool. BTW - you may be disappointed with the frequencies achievable. You
definitely will be with analogWrite(). Especially if you need 1024 samples
per cycle. Directly setting DAC0.DATA will blow the doors off analogWrite
for performance, to do it without analogWrite you just set
DAC0.CTRLA yourself (0x41 to turn on, 0x01 to turn off (leaves DAC enabled
but output buffer off)). Like, try it and compare the frequencies you get
;-) I think you'll be shocked by how... bad... things like analogWrite()
are (digitalWrite is bad for the same reason, and IIRC it's more than a
little worse on Dx-series.
You will likely be using cyclecounting delays or delayMicrosecods(). And
you'll need to turn off millis to avoid transient garbage every time the
millis timer fires (though note that delay does still give you some ability
to do timekeeping, and if 1 NOP was enough to make a difference (and from
theoretical considerations) you would need times between 1 and 2 us, and
such. It also begs the question of how you'd tell it to stop.
Trying to do that while getting good performance will be tricky, as you
really, really care about the details here if you want a clean sine wave
instead of a noisy garbage one. You want it to never depend on anything
other than how long of a delay you asked for, and certainly not on where in
the wave it is. Below is untested code, s it'll probably have some kind of
bug, but I think it will be pretty close to working.
This needs two tables, const byte SineTable[x], and const byte
LoopIterTable[y] which contains an entry for every supported note
(represented by a number between 0 and y-1. The number of entries in the
sine table, x, must be a multiple of 256 for the trick I use to "reset" it
to the start in a non-phase-dependent number of clocks (it takes just as
long to run when it doesn't have to reset NextStep as when it does. once it
reaches the end of the table, I rely on the fact that I can compare it to
the maximum value - but shit, how do I go back to the start? It'd be a
shame to have to waste 2 registers and use another operand just to store
the start address as a read-only input operand so we can copy it to
NextStep with movw (though that's what could be done to adapt it to cases
where application constraints prevent you from using a multiple of 256
entries in the SineTable) - but since we're subtracting a value that is a
multiple of 256, we know that the low byte won't change - only the high
byte will, so we can use a single subi to do the math, and since branch
takes 2 clocks if branch taken 1 clock if not taken, a branch with .+2 as
the jump destination can jump over a single clock instruction in the same
amount of time as it takes to execute a single clock instruction (like
subi), but jumping to .+4 to skip a subi, sbci sequence will have a
different runtime than skipping it. aybe not a deal breaker for an
implementation, but if we're using a multiple of 256 values on our sine
table, why not take advantage?
```
void playNote(uint8_t note) {
static uint16_t NextStep = (uint16_t) &SineTable; // global or static
scoped local, either way initally set to the start of SineTable, and we
probably want to keep track of what this is.
uint8_t loopiters = LoopIterTable[note]; //Yes, we do need to put this
into a local variable
// value from a lookup table for the note - minimum 1. Difference between
two consecutive integers is 3 clocks. (`loopiters = 2` is 3 clocks longer
per cycle than `loopiters` = 1 and 3 clocks shorter than `loopiters = 3`),
but when loopiters = 1, that cycle
// I think more like 14 clocks. A value of 0 here would act like 256,
since we decrement it once immediately (the alternatives significantly
increase overall overhead) so if loopiters = n, at each of the 1024 values
it spends (11+3n)/F_CPU seconds, so period is 1024*(11+3n)/F_CPU, rearrange
bit of algebra to solve for n in terms of f - I would do this up in excel
for the frequencies of target notes, and copy the values over. With a good
find/replace you can copy/paste in a column of numbers, then find and
replace linebreaks with `, ` (comma space) add braces to start and end, and
there you have your
__asm__ __volatile__(
"ldi r18, 0x41" "\n\t"
"st Z, r18" "\n\t"
"startcycle:" "\n\t"
"mov r0, %1" "\n\t" // copy loopiters to temp_reg
"dec r0" "\n\t" // decrement it -
"brneq .-4" "\n\t" // jump back to the dec isn if not at 0.
"ld r0, X+" "\n\t" // load next value to r0, with postincrement. If my
understanding of the instruction set manual is correct - it's not exactly a
beacon of clarity here, since the device datasheet to which they refer us
doesn't even mention that there's an extra clock cycle delay, but the
instruction set manual
"std Z+1, r0" "\n\t" // store that value to dac data register - Z+2 on
Dx if using 8 bit values (probably what you want to do) write takes effect
when you write to high byte, so that's why they put the 8 MSBs in the high
byte
"cp r26, %A3" "\n\t" // compare NextStep with &SineTable + 1024
"cpc r27 %B3" "\n\t" // as above, high byte
"brneq .+2" "\n\t" // skip next isn unless X is pointing to
&SineTable + 1024, in which case next value we should write has wrapped
around.
"subi, r27, 4" "\n\t" // if we just did 1023rd value, this is now
pointing to &sinetable+1024 (aka sinetable[1024]) Don't need to touch low
byte to subtract a multiple of 256, though!
"sbis 29, 0" "\n\t" // skip next instruction if low bit of GPIOR1 is
set, indicating we want to stop the note.
"rjmp startcycle" "\n\t"//jump back to the start
"cbi 29,0" "\n\t" // clear the low bit of GPIOR1
"ldi r18, 1 "\n\t" // disable the output buffer - makes sure we;re not
applying a constant voltage to the output.
"st Z, r18" "\n\t"
:"+x" ((uint16_t) NextStep) // Output Operands - use the X pointer to
hold the next position in the sine table to use
:"=r" ((uint8_tloopiters), // Input Operands - 2 of them. 1) number of
times to pass through timing loop per cycle per sine table entry, exit
loop with an interrupt that sets the lsb of GPIOR1
"=z" (&DAC0.CTRLA), // we turn the output buffer on and off, so we
have to have CTRLA register, and since it's no added pain to write to DATA
given only
"=r" (((uint16_t) &SineTable) + 1024) // address of byte immediately
after the sine value table
:"r18" // Clobbers: we scribble over r18 because we have nothing to use
as a scratch register that is less undesirable in principle, and there are
4 registers that we know would impose hidden extra costs (and obviously,
the 6 registers we're already using are not an option).
);
}
```
Okay, so stopping it - you break out of that by firing an interrupt. I'm
thinking you want to have it play each note for a given period of time, Set
up a TCB in single shot mode, and either clock it from the TCA and set it's
prescale to 1024,(so you get unacceptable flicker from the pwm pins now,
(note - PC0, PC1 pwm is uneffected) and enable the interrupt, and the
interrupt need only set low bit of GPIOR1 and clear it's own intflags.
instead of using TCA0 directly, on the DX-series, I think you can also
clock on event and use the TCA0 overflow as event generator, which
effectively gives you a 16320 prescaler (255*64 - megaTinyCore sets up the
timer correctly, ie, counting 255 ticks. I think that's what I'd do. then
you'd be able to play quite long notes.
```
ISR(TCB0_INT_vect) {
GPIOR1 |= 1; //set the stop this stop this note flag in GPIOR1
TCB0.INTFLAGS=TCB0.INTFLAGS; //clear intflags.
}
```
At least, that's the approach I'd take to try to get more frequencies that
we can hear (because by the numbers you won't be happy with what you'd get
using delayMicroseconds through aggressive performance optimization.
The event stuff, and conversion of human units into timer overflows is left
as an exercise for the reader.
…On Wed, Aug 31, 2022 at 5:33 PM mosqu-ito ***@***.***> wrote:
Still having fun. Got some 3216s just for the DAC. And today's mail
brought me some assorted DDs and DBs, so I'll soon be moving up to 10 bit
DACs. :)
But for today's show and tell, I just brought a pretty little sine wave,
just 0.2% flat of C5. Voila, I'm a musician! (not so much) ;)
At first I just got some random frequency (having somewhat arbitrarily
written 1024 samples per wavelength from my PC). I was going to fine tune
the data but on a whim I tried adding a NOP and landed smack dab on a C.
(blind luck)
Thanks again, oh Great One et al.
#include "SineData.h" // const byte Sine[1024] = { 128, 129, 130, 130...
#define DAC PIN_PA6
word Index = 0;
void setup() { DACReference(INTERNAL4V34); }
void loop() {
analogWrite(DAC,Sine[Index++ & 0x03FF]);
asm("NOP");
}
[image: 3216]
<https://user-images.githubusercontent.com/106849308/187787781-4a35f5e1-5a36-4a83-9942-7d1f9db6a0ea.jpg>
[image: C5]
<https://user-images.githubusercontent.com/106849308/187787819-1d586148-f7f2-4954-be67-71f6a48ab686.jpg>
—
Reply to this email directly, view it on GitHub
<#787>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABTXEW4I5NJLNNTEEW4VIKTV37FT7ANCNFSM6AAAAAAQBYNL5U>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
--
____________
Spence Konde
Azzy’S Electronics
New products! Check them out at tindie.com/stores/DrAzzy
GitHub: github.com/SpenceKonde
ATTinyCore <https://github.com/SpenceKonde/ATTinyCore>: Arduino support for
all pre-2016 tinyAVR with >2k flash!
megaTinyCore <https://github.com/SpenceKonde/megaTinyCore>: Arduino support
for all post-2016 tinyAVR parts!
DxCore <https://github.com/SpenceKonde/DxCore>: Arduino support for the AVR
Dx-series parts, the latest and greatest from Microchip!
Contact: ***@***.***
|
Beta Was this translation helpful? Give feedback.
-
Okay, home again. Your assembler is still flying well above my head but I did try getting rid of AnalogWrite and tightening things up like so: void setup() { VREF_CTRLA = VREF_DAC0REFSEL_4V34_gc; DAC0_CTRLA = 0x41; while (true) { DAC0_DATA = Sine[Index++ & 0x03FF]; } } I can't say that made much difference. It barely came up to a G in the same octave, not so far from where I started before I added the NOP. I hadn't really thought ahead to how I might modulate the frequency or stop it for that matter. I was just so pleased to see something other than square edges coming out of a microcontroller. :D |
Beta Was this translation helpful? Give feedback.
-
Still having fun. Got some 3216s just for the DAC. And today's mail brought me some assorted DDs and DBs, so I'll soon be moving up to 10 bit DACs. :)
But for today's show and tell, I just brought a pretty little sine wave, just 0.2% flat of C5. Voila, I'm a musician! (not so much) ;)
At first I just got some random frequency (having somewhat arbitrarily written 1024 samples per wavelength from my PC). I was going to fine tune the data but on a whim I tried adding a NOP and landed smack dab on a C. (blind luck)
Thanks again, oh Great One et al.
Beta Was this translation helpful? Give feedback.
All reactions