AVR128DB28: Potentiometer, analogRead fail

Hello,

I am trying to control the frequency of a wave output through the DAC using a potentiometer, but am getting no changes from the potentiometer.

Complete program code is below, followed by further details as to what I've tried.

//pottest01.txt
//Testing pot to control frequency of a wavetable wave

#include <avr/io.h>
#define F_CPU               4000000UL
#include <util/delay.h>
#include <avr/interrupt.h>

#define TIMER_PERIOD        0x2000
/* VREF start-up time */
#define VREF_STARTUP_TIME       (50)
/* Mask needed to get the 2 LSb for DAC Data Register */
#define LSB_MASK                (0x03)

static void VREF_init(void);
static void DAC0_init(void);
static void DAC0_setVal(uint16_t value);

const byte saw[256] PROGMEM = {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
};

const byte triangle[256] PROGMEM = {
  0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 255, 253, 251, 249, 247, 245, 243, 241, 239, 237, 235, 233, 231, 229, 227, 225, 223, 221, 219, 217, 215, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 191, 189, 187, 185, 183, 181, 179, 177, 175, 173, 171, 169, 167, 165, 163, 161, 159, 157, 155, 153, 151, 149, 147, 145, 143, 141, 139, 137, 135, 133, 131, 129, 127, 125, 123, 121, 119, 117, 115, 113, 111, 109, 107, 105, 103, 101, 99, 97, 95, 93, 91, 89, 87, 85, 83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1,
};


int scale[15] = {65, 73, 82, 87, 98, 110, 123, 131, 147, 165, 175, 196, 220, 247, 262};

int potValue = 0;
int mappedPotValue = 0;

unsigned int accumulator;
volatile int note = 65; // Middle C

static void VREF_init(void)
{
  VREF.DAC0REF = VREF_REFSEL_2V048_gc /* Select the 2.048V Internal Voltage Reference for DAC */
                 | VREF_ALWAYSON_bm;    /* Set the Voltage Reference in Always On mode */
  /* Wait VREF start-up time */
  _delay_us(VREF_STARTUP_TIME);
}

static void DAC0_init(void)
{
  /* Disable digital input buffer */
  PORTD.PIN6CTRL &= ~PORT_ISC_gm;
  PORTD.PIN6CTRL |= PORT_ISC_INPUT_DISABLE_gc;
  /* Disable pull-up resistor */
  PORTD.PIN6CTRL &= ~PORT_PULLUPEN_bm;
  DAC0.CTRLA = DAC_ENABLE_bm          /* Enable DAC */
               | DAC_OUTEN_bm           /* Enable output buffer */
               | DAC_RUNSTDBY_bm;       /* Enable Run in Standby mode */
}

static void DAC0_setVal(uint16_t value)
{
  /* Store the two LSbs in DAC0.DATAL */
  DAC0.DATAL = (value & LSB_MASK) << 6;
  /* Store the eight MSbs in DAC0.DATAH */
  DAC0.DATAH = value >> 2;
}

ISR(TCA0_OVF_vect) {

  int16_t total = 0;

  accumulator = accumulator + note;
  total += pgm_read_byte(&saw[accumulator >> 8]);

  DAC0_setVal(total);

}

void setup()
{

  ADC0.CTRLA &= ~(ADC_ENABLE_bm);

  TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm;
  TCA0.SINGLE.PER = TIMER_PERIOD;
  TCA0.SINGLE.CTRLA = (TCA_SINGLE_CLKSEL1_bm | TCA_SINGLE_CLKSEL2_bm);
  TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;

  VREF_init();
  DAC0_init();

  sei();
}


void loop() {

  potValue = analogRead(PIN_PD1); //Read the potentiometer
  mappedPotValue = map(potValue, 0, 1047, 0, 15);
  note = scale[mappedPotValue];

}

The chip is being programmed via UDPI via jtag2UPDI.

I am using DxCore by SpenceKonde. At the Github page for the project,

I found this description as to how to reference and analog pins:
This is the recommended way to refer to pins. Defines are provided of form PIN_Pxn, where x is the letter of the port (A through G), and n is a number 0 ~ 7

Thusly, I am referencing the potentiometer pin as such:
potValue = analogRead(PIN_PD1); //Read the potentiometer
mappedPotValue = map(potValue, 0, 1047, 0, 15);
note = scale[mappedPotValue];

PIN_PD1, According to the data sheet is a pin with analog function. I did also try the PIN_PA(0-6) pins, just in case there was something I didn't understand.

I can get sound out of the DAC, and change the note value in the variable declaration to get a different pitch. I can also hardcode the mappedPotValue
the get a different frequency.

The part that isn't working seems to be the potentiometer/analog input itself.

I have 4 pots on the bread board and tried connecting to each, with the same failure to get a change in values.

In case it helps, I've also attached a breadboard pic.

Would greatly appreciate any help with this issue.

Tom

It would help if you included a schematic, not a frizzy thing. Be sure all the grounds are connected.

Here is a hand drawn rendering of the circuit. It's pretty basic. PD6 goes through a 470 Ohm resistor to a headphone jack. Potentiometer is attached to PD1.

Tom

I don't suppose you've tried just like... printing the analog values?

I'm pretty sure analogRead does work, at least since 1.3.1 was released a few days ago (in 1.3.0, it just hung the chip because while reorganizing the initialization routines, I'd forgotten to call init_ADC0(), so it was just sitting there in a busy-wait loop waiting for a disabled ADC to report results. Short of a cosmic ray flipping just the right bit, you'd be waiting forever.

If you're currently using 1.3.0, though, that's the problem.

I notice that you have not even the slightest sanity check in that code (for example: Printing something to serial - I usually print something every pass through loop, and on startup, I print a newline and some information - even if I can't abide by the slowness of serial (in which case it would probably be my first debugging step). - a blink-without-millis type heartbeat is also a useful sanity check if you need it to run too fast for it to be logging something to serial every pass through loop. Even in cases where the chip isn't on a new and immature core like DxCore, sanity checks like that constantly save me from assorted fumbles.

Edit: read the code again.

  1. Use 1.3.1, and don't write enable to ADC0.CTRLA, that's done for you (the function I forgot to call is also when the prescaler gets set up. Without setting the prescaler correctly, ADC will not work right.

  2. You are not configuring TCA0 the way you think you are. The core configures both TCAs in split mode to give people tons of PWM channels. 1.3.1 adds takeOverTCA0() which tells the core that you wish to take full control of that timer - it will no longer be reconfigured when you analogWrite or digitalWrite to pins that nominally have PWM. It also should get the "reset" command applied to it, and be back in it's power-on state after doing takeOverTCA0, rather than left in split mode. If you don't use that, you at least need to turn off split mode. In it's current configuration, that interrupt is firing WAY faster than you think! It's acting as 2 single byte downcounters, one's gonna be set to 0x20, the other to 0x00.

  3. Why are you using TCA0 to generate periodic interrupts? Wouldn't a TCB in periodic interrupt mode, with TCB_CLKSRC_DIV2_gc for it's clock and TIMER_PERIOD of 0x8000 be at least as good, while taking a utility timer that's made for generating periodic interrupts like that, instead of the PWM powerhouse TCA0? There are 3 on the 28-pin parts, TCB2 is used for millis timekeeping, but TCB0, TCB1 are free.

  4. You are doing total+=something(); but you declared total as a local variable a couple of lines earlier, so in practice you might as well be using = instead of += - either the use of += was unintended, or you meant for total to be something other than a normal local variable (maybe a static one?)

  5. Oh - one more thing - note is being accessed in the interrupt but gets set outside the interrupt. Note is a datatype larger than 1 byte. You need to disable interrupts when writing it, otherwise the interrupt could fire while it's in the middle of updating note, and the low byte will have a new value while the high byte still has the old value. Since it's not inside a function that could be called with interrupts off, you can feel safe doing:

cli();
variableReadFromISR=something;
sei();

instead of

uint8_t oldSREG=SREG
cli();
variableReadFromISR=something;
SREG=oldSREG;

Thanks for the detailed response, it was very helpful.

I was running 1.3.0, updated to 1.3.1 and the issue is now resolved. I can now change frequencies via potentiometer.

My use of timers was the result of a quick mash up of code from microchips example code:

Making a sine wave:

Use of a timer to blink a light via interrupt:

I was a bit of a rush just to see how the audio from the 10 bit DAC would sound compared to the same type of code run on an Atmega328. Thus I didn't pay too much attention of optimizing at the time.

I certainly have to understand the timers better so thanks for your pointers on that, More study of this subject on my part is required. Hopefully a clearer picture will lead to audio output that is even more satisfying than what I've currently been able to manage.

the total+= point you made: I am actually planning to add accumulators together in order to get more than one note or wavetable out at a time. I neglected to change that when boiling the code down for a forum post.

I see your point about setting the note value, but perhaps doing cli(), sei() gets awkward when setting notes for two independent melodies? Besides note values, there could be other parameters, like for effects, that get used in the ISR. With, for example, 8 pots changing variables, and sometimes 2 pots getting turned at the same time. Would disable/enable interrupts in such quantity & frequency create choppy behavior?

Regarding sanity checks, I was running the chip from a breadboard and still haven't figured out how to get Serial.print messages from the breadboard to my Arduino IDE serial monitor. If you have any leads as to how I might do so, that would be greatly appreciated.

serial? Same as any other chip - wire to a USB serial adapter, TX of adapter to RX of chip RX of adapter to TX of chip, and connect the ground of adapter tp ground of breadboard (I usually also power things off the serial adapter, but it sounds like you have power sorted out.

After that, should "just work" - it did for me when I wired up one of those on prototyping board to test (i[ve since hacked some of my DA breakout boards to work with a DB - cut the power trace to split off VDDIO2, ran another wire to it (well, I did on one of them, screwed that bit up on the other) to let me put a voltage on VDDIO2,, and soldered some higher value caps on top of the 0.1uF ones I'd installed originally when I was expecting to use them with a DA. ) TX, RX for USART0 (Serial) with defaault pin mapping are PA0, PA1 See the pinout chart DxCore/DB28.md at master · SpenceKonde/DxCore · GitHub.

I usually run 1 adapter connected to Serial (115200 baud is my default, though you can go faster no problem), 115200 is a good basic speed, because even at 1 MHz, you're fine, and it's a standard speed that's on all the menus. Keep a serial terminal open full time in one window (on my breakout boards I have a DTR autoreset circuit, and I use hTerm which gives a convenient button to click toggle the modem control lines - so I can reset it without reaching over)... And then I use a separate serial adapter (or, very often nano doing jtag2updi) for programming it,

As long as you don't leave interrupts disabled for long you're fine, it's like, It's WAY less disruptive that it the note occasionally changing to a drastically different one because the value of the note got changed half-way through your writing it. assigning 2 bytes to a global variable in RAM the compiler is almost certain to give you a pair of srs instructions at 2 clocks each, so they're only disabled for 5 clocks. between 1/4th (at 20 MHz and 1/6th (at 32 MHz - they work fine at 32 :stuck_out_tongue: ) of a microsecond. They claim 140k sps on DAC sample rate, and human hearing doesn't start picking up on sound until what 20-some kHz? Not that you shouldn't have hesitation, but you're not in dangerous territory just assigning to variables, and not turning off interrupts while assigning multibyte variables that the interrupt needs is the definition of dangerous territory!

But note that you don't need to disable interrupts for 1-byte values - only multi-byte ones (basically the rule is that anything with more than 1 byte, if the consequences would be undesirable if it got half of a new value and half of an old one you need to make the write atomic with cli/sei - meaning that in some plausible normal use cases, one might need to keep them disabled long enough to write several variables, if they don't make sense if they're not all from the same set.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.