I want to make sure my code looks like working, since I don't have a lot of time with a signal generator tomorrow and I want to know how to set the sample rate.
I want to sample a 2kHz signal with a samplerate of 6kHz with a Arduino MEGA 2560.
It, doesn't have to be in real time, so i'm thinking of filling a buffer and then sending those over the serial connection.
Can anyone say if this code defenitly wouldn't work for this?
And how could i set the samplerate to 6kHz?
void setup() {
Serial.begin(9600);
}
void loop() {
for(int x = 0; x < 1000; x++){
// read the input on analog pin 0:
int sensorValue[x] = analogRead(A0);
}
for( x = 0; x < 1000; x++){
// Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
float voltage[x] = sensorValue[x] * (5.0 / 1023.0);
// print out the value you read:
Serial.println(voltage[x]);
}
}
Thank you.
Well, as I've mentioned in another thread, you can use auto triggering mode of ADC (for UNO and ATMega328p based Arduinos):
void setup() {
Serial.begin(256000);
// ADC setup is done by arduino framework, but it's possible to change it slightly (for ATMega328) :
ADCSRB = _BV(ADTS2) | _BV(ADTS1) | _BV(ADTS0); // ADTS2..0 = 111, Timer 1 input capture event trigger source
ADCSRA |= _BV(ADATE); // enable auto trigger mode
ADCSRA |= _BV(ADIF); // reset conversion end flag (= interrupt flag)
// timer 1 setting:
TCCR1A = 0; // clear all
ICR1 = F_CPU/6000U; // 1 should be substracted here but result is about 2665.7 and it will be truncated to 2665
TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10); // CTC mode with ICR1 as TOP value, enabled with no prescaling
TIMSK1 = _BV(ICF1); // not working without this... Flag must be cleaned up after the trigger ADC, otherwise it's stucked
analogRead(A0); // dummy read to set correct channel and to start auto trigger mode
pinMode(13, OUTPUT);
}
void loop() {
if (ADCSRA & _BV(ADIF)) {
ADCSRA |= _BV(ADIF); // reset flag by writing logic 1
Serial.println(ADC);
}
}
ISR(TIMER1_CAPT_vect) { // to clear flag
PINB = _BV(PB5); // and toggle d13 so frequency can be measured (it'd be half of real rate)
// it might be enabled on PWM pin too by setting force output compare and some compare register to half of value ICR1
}
This sketch uses baud rate 250000 but it's still too slow. The space character can be used as an separator, this'll save one character (as new line are usually two characters: \r\n). One value can be 1 to 4 characters long so for values:
0-9 - 3B you need baud rate 3*10*6000 = 180000
10-99 - 4B and you need baud rate 240000
and for the rest of cases you're too slow.
So the only way is sending those integers binary and without separator it'd be even better. The 2B per value results into minimal baud rate around 120000 baud/s.
Related
I am using a Remote Control from FlySky. For my robotics project, I want to read PWM from the receiver on an Arduino. I came across 2 options:
pulseIn() arduino function
ISR(PCINTx_vect) (interrupt)
I cant use the first option of pulseIn() because I want my robot to continue with the operation if receiver signal are not coming (Tx not available etc.) So I used ISR.
Most reliable source : Mr. Brookings channel on YouTube.
Here is what I did (Only the required part for 1 axis):
// [R] where R is defined as 0 => [R] == [0]
volatile long CH[4]; //4 pwms to read so array of 4
float IN[3]={0,0,0}; // throttle is directly written
unsigned long timer[4],curr_time;
byte last[4];
void setup(){
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << PCINT0);
PCMSK0 |= (1 << PCINT1);
PCMSK0 |= (1 << PCINT2);
PCMSK0 |= (1 << PCINT3);
/* There is some more code here */
Serial.begin(115200);
}
void loop(){
/* There is some more code here */
IN[R] = ((CH[ROLL] - (1500 + R_TRIM))/11.0); // eg.: (1200 - (1500 + 8))/11.0 = -28 (interpreted as setpoint of -28° by the robot)
Serial.println(IN[R]);
}
ISR(PCINT0_vect){
curr_time = micros();
//channel 1 roll
if(PINB & B00000001){
if(last[ROLL] == 0){
last[ROLL] = 1;
timer[ROLL] = curr_time;
}
}
else if(last[ROLL] == 1){
last[ROLL] = 0;
CH[ROLL] = ((curr_time - timer[ROLL]));
}
}
I can read the PWM actually, but the robot keeps showing random twitches in its control at a given set point. I managed to trace the reason and found out that the PWM is insanely ridden by noise. Its not stable like it should be - steady. I have a MATLAB plot I used for analysis:
Signal (IN[R]):
Close up (when Tx stick was in the middle w/o movement) :
There are such spikes coming which is adding up to the control signal eventually making my robot to twitch. I tried some filtering techniques like 'moving average' and '1st and 2nd order exponential filters'. Also checked if it was due to power supplied to it - tried putting a capacitor or an iron core to the power lines but in vain. I can figure out how to remove them as their some constrains :
platform is Arduino Uno (slower in heavy computation)
Control loop shall not go below 100Hz (Currently its at 108Hz exponential filters on 4 axes took it to
~85Hz)
I would appreciate some guidance!
There's no way of telling from this if the input is noisy, or if your code is reading the PWM wrong, of if something else is going on, like external noise on the line, the Arduino's clock jitter, or other interrupts taking time. Also note that micros() on an Arduino Uno only has a resolution of 4µs, not 1µs.
You should check the input for jitter and noise, and try fast code that isn't influenced by other interrupts.
A fairly simple and fast way of getting the PWM pulse width is something like this, preferably without using anything else that uses interrupts:
volatile int pwmPulseWidth = 0;
volatile unsigned long int previousTime = 0;
void setup() {
attachInterrupt(0, rising, RISING);
}
void loop() {
// pwmPulseWidth is available here.
}
void rising() {
attachInterrupt(0, falling, FALLING);
previousTime = micros();
}
void falling() {
attachInterrupt(0, rising, RISING);
pwmPulseWidth = micros() - previousTime;
}
Untested, but it should give you an idea. This will return the width of the PWM pulse.
There are other ways of doing this, of course, like using a timer in capture mode.
Knowing the PWM frequency and the width of the PWM pulse is enough to reconstruct the PWM signal, should you want to.
I am trying to follow, adapt, understand (and clean up a bit) a variation around the code available there, for the Arduino Due: https://forum.arduino.cc/index.php?topic=589213.0 . I do not like the forum format, as things end up buried deep, so asking here instead. Unfortunately this means that there is quite a lot of explanations before the question. If you think this is wrong to post it here, let me know, and I can move.
Basically, the idea is to log several ADC channels in a buffer, using timer-based triggering. There is a bit of setup:
// sample rate in Hz
constexpr int sample_rate = 1000;
constexpr uint8_t channels[] = {7, 6, 5, 4, 3};
constexpr int nbr_channels = sizeof(channels);
Then time counter 0 channel 2 is set at the right frequency for triggering the ADC conversion:
// use time counter 0 channel 2 to generate the ADC start of conversion signal
// i.e. this sets a rising edge with the right frequency for triggering ADC conversions corresponding to sample_rate
// for more information about the timers: https://github.com/ivanseidel/DueTimer/blob/master/TimerCounter.md
// NOTE: TIOA2 should not be available on any due pin https://github.com/ivanseidel/DueTimer/issues/11
void tc_setup() {
PMC->PMC_PCER0 |= PMC_PCER0_PID29; // TC2 power ON : Timer Counter 0 channel 2 IS TC2
TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2 // clock 2 has frequency MCK/8, clk on rising edge
| TC_CMR_WAVE // Waveform mode
| TC_CMR_WAVSEL_UP_RC // UP mode with automatic trigger on RC Compare
| TC_CMR_ACPA_CLEAR // Clear TIOA2 on RA compare match
| TC_CMR_ACPC_SET; // Set TIOA2 on RC compare match
constexpr int ticks_per_sample = F_CPU / 8 / sample_rate; // F_CPU / 8 is the timer clock frequency, see MCK/8 setup
constexpr int ticks_duty_cycle = ticks_per_sample / 2; // duty rate up vs down ticks over timer cycle; use 50%
TC0->TC_CHANNEL[2].TC_RC = ticks_per_sample;
TC0->TC_CHANNEL[2].TC_RA = ticks_duty_cycle;
TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable
}
Finally this can be used to trigger the ADC:
// start ADC conversion on rising edge on time counter 0 channel 2
// perform ADC conversion on several channels in a row one after the other
// report finished conversion using ADC interrupt
void adc_setup() {
PMC->PMC_PCER1 |= PMC_PCER1_PID37; // ADC power on
ADC->ADC_CR = ADC_CR_SWRST; // Reset ADC
ADC->ADC_MR |= ADC_MR_TRGEN_EN | // Hardware trigger select
ADC_MR_PRESCAL(1) | // the pre-scaler: as high as possible for better accuracy, while still fast enough to measure everything
// see: https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due
ADC_MR_TRGSEL_ADC_TRIG3; // Trigger by TIOA2 Rising edge
ADC->ADC_IDR = ~(0ul);
ADC->ADC_CHDR = ~(0ul);
for (int i = 0; i < nbr_channels; i++)
{
ADC->ADC_CHER |= ADC_CHER_CH0 << channels[i];
}
ADC->ADC_IER |= ADC_IER_EOC0 << channels[nbr_channels - 1];
ADC->ADC_PTCR |= ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS; // Disable PDC DMA
NVIC_EnableIRQ(ADC_IRQn); // Enable ADC interrupt
}
and the ADC output can be captured in the corresponding ISR:
void ADC_Handler() {
for (size_t i = 0; i < nbr_channels; i++)
{
SOME_BUFFER[i] = static_cast<volatile uint16_t>( * (ADC->ADC_CDR + channels[i]) & 0x0FFFF ); // get the output
}
}
I think this is quite understandable, but I have one question: the setting of the pre-scaler.
if I understand well discussions online, the pre-scaler should be set so that frq_ADC >= sample_rate * nbr_channels, basically because the chip is just multiplexing the ADC through several channels
if I understand well, we want to set such pre-scaler value as high as possible given the previous constraint, so that the ADC frequency is as low as possible, because this improves ADC conversion quality
Is that right?
The problem is that I am confused about how to set the pre-scaler, and what value corresponds to what, because what I find in the datasheet disagree with some other online responses I read.
From the datasheet https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf : "The ADC clock range is between MCK/2, if PRESCAL is 0, and MCK/512, if PRESCAL is set to 255 (0xFF).". This is consistent with what I find on page 1334: "ADCClock = MCK / ( (PRESCAL+1) * 2 )". But page 1318, it is written that the conversion rate is 1MHz. Then how is that compatible with having a MCK frequency of 84MHz on the Due? 84/2 = 48MHz, 84/512 = 0.164MHz, the high freq value is too high.
Then to add to the confusion I have found this issue: https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due/21054#21054 that also seem to conflict with the 1MHz upper bound.
Any idea where I misunderstand something? (and any more comments around the general working of the program?).
Ok, so I did some tests with the code, checking when I was missing some conversions depending on the timer frequency and the prescaler value. The code is a bit long, so I post it at the end of the answer. Basically:
// pre-scalor analysis using 5 channels;
// quantities indicated are sampling frequency of the 5 channels
// i.e. necessary ADC sampling frequency is 5 x higher, and value
// of the prescaler ps
// --------------------
// 100kHz ps 1 ok
// 100kHz ps 2 ok
// 100kHz ps 3 fail
// 100kHz ps 255 fail
// 100kHz ps 256 ok
// this indicates: prescaler is 8 bits from 0 to 255, after this wraps up
// ADC frequency is max something like 1MHz in practice: 5 * 100 * 2 (may loose a bit
// due to other interrupts hitting ours?)
// --------------------
// 10kHz ps 38 ok
// 10kHz ps 39 fail
// 10 * 5 * 40 = 2000kHz: ADC is lower than 2MHz
// --------------------
// 1kHz ps 255 ok
// --------------------
I think this indicates that:
the pre-scaler value is well an 8 bits int, between 0 and 255, as it wraps up at 256
I have difficultie matching the results to the formula in the datasheet. I guess this is because there is some overhead switching channels etc (?). For example:
the results are consistent with ```ADC_freq = 1MHz / ( ps ) at the highest frequencies, but I suppose this is because there is a bit of overhead switching channels
the results are consistent with ```ADC_freq = 2MHz / ( ps ) at 10 kHz, and at 1kHz, even using the highest prescaler is fine.
The code I was using is the following, and the criterion for deciding that things fail is that the code reports a drop in the effective sample frequency over the 5 channels:
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// timer driven ADC convertion captured by interrupt on n adc_channels for Arduino Due
//
// this is for Arduino Due only!
//
// the interrupt based ADC measurement is adapted from:
// https://forum.arduino.cc/index.php?topic=589213.0
// i.e. adc_setup(), tc_setup(), ADC_handler() are inspired from the discussion there.
//
// written with VSCode + Platformio and Due board setup
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// make my linter happy
#include "Arduino.h"
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
// some vital ADC grabbing setup
// sample rate in Hz, should be able to go up to several 10s ok kHz at least
constexpr int adc_sample_rate = 1000;
// size of the data buffers "in time"
// i.e. how many consecutive measurements we buffer for each channel
constexpr size_t adc_buffer_nbr_consec_meas = 5;
// the adc_channels to read, in uC reference, NOT in Arduino Due pinout reference
// for a mapping, see: https://components101.com/microcontrollers/arduino-due
// i.e. A0 is AD7
// A1 AD6
// A2 AD5
// A3 AD4
// A4 AD3
// A5 AD2
// A6 AD1
// A7 AD0
constexpr uint8_t adc_channels[] = {7, 6, 5, 4, 3};
constexpr int nbr_adc_channels = sizeof(adc_channels);
// the buffer containing the measurements for all adc_channels over several measurements in time
volatile uint16_t adc_meas_buffer[adc_buffer_nbr_consec_meas][nbr_adc_channels];
// flag when a full vector of conversions is available
volatile bool adc_flag_conversion = false;
// time index of the current measurement in the adc reads buffer
volatile size_t crrt_adc_meas_buffer_idx = 0;
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
// some non-vital printing config
// a bit of time tracking, just to analyze how good performance
unsigned long current_us = 0;
unsigned long previous_us = 0;
unsigned long delta_us = 0;
float delta_us_as_s = 0;
float delta_us_as_ms = 0;
int nbr_readings_since_reduced_time_stats = 0;
unsigned long current_reduced_time_stats_us = 0;
unsigned long previous_reduced_time_stats_us = 0;
float delta_reduced_time_stats_us_as_s = 0;
float effective_logging_frequency = 0;
// decide what to print on serial
constexpr bool print_reduced_time_stats = true;
constexpr bool print_time_stats = false;
constexpr bool print_full_buffer = false;
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
// low level functions for setting clock and ADC
// start ADC conversion on rising edge on time counter 0 channel 2
// perform ADC conversion on several adc_channels in a row one after the other
// report finished conversion using ADC interrupt
// tests about pre-scaler: formula should be:
// pre-scalor analysis using 5 channels;
// quantities indicated are sampling frequency of the 5 channels
// i.e. necessary ADC sampling frequency is 5 x higher, and value
// of the prescaler ps
// --------------------
// 100kHz ps 1 ok
// 100kHz ps 2 ok
// 100kHz ps 3 fail
// 100kHz ps 255 fail
// 100kHz ps 256 ok
// this indicates: prescaler is 8 bits from 0 to 255, after this wraps up
// ADC frequency is max something like 1MHz in practice: 5 * 100 * 2 (may loose a bit
// due to other interrupts hitting ours?)
// --------------------
// 10kHz ps 38 ok
// 10kHz ps 39 fail
// 10 * 5 * 40 = 2000kHz: ADC is lower than 2MHz
// --------------------
// 1kHz ps 255 ok
// --------------------
// CCL: use ps 2 at 100kHz with 5 channels, 20 at 10kHz, 200 at 1kHz
void adc_setup()
{
PMC->PMC_PCER1 |= PMC_PCER1_PID37; // ADC power on
ADC->ADC_CR = ADC_CR_SWRST; // Reset ADC
ADC->ADC_MR |= ADC_MR_TRGEN_EN | // Hardware trigger select
ADC_MR_PRESCAL(200) | // the pre-scaler: as high as possible for better accuracy, while still fast enough to measure everything
// see: https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due
// unclear, asked: https://stackoverflow.com/questions/64243073/setting-right-adc-prescaler-on-the-arduino-due-in-timer-and-interrupt-driven-mul
ADC_MR_TRGSEL_ADC_TRIG3; // Trigger by TIOA2 Rising edge
ADC->ADC_IDR = ~(0ul);
ADC->ADC_CHDR = ~(0ul);
for (int i = 0; i < nbr_adc_channels; i++)
{
ADC->ADC_CHER |= ADC_CHER_CH0 << adc_channels[i];
}
ADC->ADC_IER |= ADC_IER_EOC0 << adc_channels[nbr_adc_channels - 1];
ADC->ADC_PTCR |= ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS; // Disable PDC DMA
NVIC_EnableIRQ(ADC_IRQn); // Enable ADC interrupt
}
// use time counter 0 channel 2 to generate the ADC start of conversion signal
// i.e. this sets a rising edge with the right frequency for triggering ADC conversions corresponding to adc_sample_rate
// for more information about the timers: https://github.com/ivanseidel/DueTimer/blob/master/TimerCounter.md
// NOTE: TIOA2 should not be available on any due pin https://github.com/ivanseidel/DueTimer/issues/11
void tc_setup()
{
PMC->PMC_PCER0 |= PMC_PCER0_PID29; // TC2 power ON : Timer Counter 0 channel 2 IS TC2
TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2 // clock 2 has frequency MCK/8, clk on rising edge
| TC_CMR_WAVE // Waveform mode
| TC_CMR_WAVSEL_UP_RC // UP mode with automatic trigger on RC Compare
| TC_CMR_ACPA_CLEAR // Clear TIOA2 on RA compare match
| TC_CMR_ACPC_SET; // Set TIOA2 on RC compare match
constexpr int ticks_per_sample = F_CPU / 8 / adc_sample_rate; // F_CPU / 8 is the timer clock frequency, see MCK/8 setup
constexpr int ticks_duty_cycle = ticks_per_sample / 2; // duty rate up vs down ticks over timer cycle; use 50%
TC0->TC_CHANNEL[2].TC_RC = ticks_per_sample;
TC0->TC_CHANNEL[2].TC_RA = ticks_duty_cycle;
TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable
}
// ISR for the ADC ready readout interrupt
// push the current ADC data on all adc_channels to the buffer
// update the time index
// set flag conversion ready
void ADC_Handler()
{
for (size_t i = 0; i < nbr_adc_channels; i++)
{
adc_meas_buffer[crrt_adc_meas_buffer_idx][i] = static_cast<volatile uint16_t>(*(ADC->ADC_CDR + adc_channels[i]) & 0x0FFFF);
}
crrt_adc_meas_buffer_idx = (crrt_adc_meas_buffer_idx + 1) % adc_buffer_nbr_consec_meas;
adc_flag_conversion = true;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
// a simple script: setup and print information
void setup()
{
Serial.begin(115200);
delay(100);
adc_setup();
tc_setup();
}
void loop()
{
if (adc_flag_conversion == true)
{
adc_flag_conversion = false;
if (print_reduced_time_stats)
{
nbr_readings_since_reduced_time_stats += 1;
if (nbr_readings_since_reduced_time_stats == adc_sample_rate)
{
current_reduced_time_stats_us = micros();
delta_reduced_time_stats_us_as_s = static_cast<float>(current_reduced_time_stats_us - previous_reduced_time_stats_us) / 1000000.0;
effective_logging_frequency = static_cast<float>(adc_sample_rate) / delta_reduced_time_stats_us_as_s;
previous_reduced_time_stats_us = current_reduced_time_stats_us;
Serial.print(F("Effective logging freq over nbr spls that should correspond to 1 second: "));
Serial.println(effective_logging_frequency);
nbr_readings_since_reduced_time_stats = 0;
}
}
if (print_time_stats)
{
current_us = micros();
delta_us = current_us - previous_us;
delta_us_as_s = static_cast<float>(delta_us) / 1000000.0;
delta_us_as_ms = static_cast<float>(delta_us) / 1000.0;
Serial.println(F("ADC avail at uS"));
Serial.println(micros());
Serial.println(F("elapsed us"));
Serial.println(delta_us);
Serial.println(F("elapsed ms"));
Serial.println(delta_us_as_ms);
Serial.println(F("elapsed s"));
Serial.println(delta_us_as_s);
Serial.println(F("updated idx:"));
size_t last_modified_buffer_idx;
if (crrt_adc_meas_buffer_idx > 0){
last_modified_buffer_idx = crrt_adc_meas_buffer_idx - 1;
}
else{
last_modified_buffer_idx = nbr_adc_channels - 1;
}
Serial.println(last_modified_buffer_idx);
previous_us = current_us;
}
if (print_full_buffer)
{
for (size_t i = 0; i < nbr_adc_channels; i++)
{
Serial.print(F(" ADC "));
Serial.print(adc_channels[i]);
Serial.println(F(" meas in time:"));
for (size_t j = 0; j < adc_buffer_nbr_consec_meas; j++)
{
Serial.print(adc_meas_buffer[j][i]);
Serial.print(F(" "));
}
Serial.println();
}
}
}
}
Trying to understand timing / dimming and interrupts using an Arduino Uno (or any other AVR) is being made very difficult by a serious lack of example code. Having found a sketch that starts from zero and ramps up the brightness, I have tried to adapt the code to prevent the continuous loop which occurs when the 16-bit register overflows.
The attached sketch starts up from zero light output and increases over a period of time - currently using the delay() function.
Attempting to adapt the code to prevent the loop from starting the entire process again and to allow the led to remain at the "top" brightness output for x (variable) number of hours has proved to be most elusive. As one of the contributors have noted this area of coding is one of the most difficult to master.
Any advice or guidance which will put me in the right direction will be most appreciated.
...
//fade over 65535 steps
// 16 bit PWM on any pin
// Example uses built in LED on pin 13 (PORTB bit 5)
// https://forum.arduino.cc/index.php?topic=348170.0
void setup() {
pinMode(13, OUTPUT);
cli(); // Disable all interrupts
TCCR1A = 0; // Clear all flags in control register A
TCCR1B = 0; // Clear all flags in control register B
TCNT1 = 0; // Zero timer 1 count
OCR1A = 32768; // Preload compare match register (50% duty cycle)
// No prescaler
//TCCR1B |= _BV(CS12);
//TCCR1B |= _BV(CS11);
TCCR1B |= _BV(CS10);
TIMSK1 |= _BV(OCIE1A); // Enable timer compare interrupt
TIMSK1 |= _BV(TOIE1); // Enable timer overflow interrupt
sei(); // enable all interrupts
}
void loop() {
for (unsigned int x = 1; x < 65535; x++) {
//cli();
OCR1A = x;
//sei();
delay(20);
}
}
ISR(TIMER1_OVF_vect) { // Timer1 overflow interrupt service routine
PORTB |= _BV(PORTB5); // Turn LED (pin 13) on
}
ISR(TIMER1_COMPA_vect) { // Timer1 compare interrupt service routine
PORTB &= ~_BV(PORTB5); // Turn LED off
}
...
I made a nice code which generates fast PWM with 50% duty cycle and I can change the frequency with a potentiometer. It outputs straight and inverted channels with some dead time. I am using Arduino Micro aka ATmega32U4. The code is actually "Atmel" code. Code is working fine until I power Arduino Micro off and then on again.
I have programmed the code and registers so that the frequency is changeable from 10kHz to 100kHz. But after power on/off the frequency changes from 5kHz to 50kHz. After this has happened I have to program the board again using Arduino IDE, to make it work correctly. Again after power on/off it has changed. I am quite sure that one of the registers is overwritten by the "Arduino hardware abstraction layer" or however we should name it. I have not yet read out all the registers so I do not know which one is overwritten. I guess it's the prescaler.
How do I prevent this from happening? Should I write the register contents somewhere else? Or should I write it few times to be sure?
Why or how this is happening anyway?
Here's the code:
#define OSC1 5
#define OSC2 13
uint8_t read_reg1;
uint8_t read_reg2;
int pot, freq;
void setup() {
pinMode(OSC1, OUTPUT);
pinMode(OSC2, OUTPUT);
Serial.begin(9600);
cli(); // disable global interrupts
TCCR4A=0; // clear register
TCCR4B=0x06; // configure prescaler to 64 (CK = CLK / 64 = 1.5 MHz)
TCCR4C=0;
TCCR4D=0; // select Fast PWM operation (0 << WGM41)|(0 << WGM40)
PLLFRQ=(PLLFRQ&0xCF)|0x30; // select clock source and frequency
OCR4C=150; // select PWM frequency
OCR4A=150/2; // set duty cycle
DT4 = 0x55; // set dead times. DT = (1 / 48Mhz) * 0...15
// enable interrupt on timer4 overflow
TIMSK4|=(1 << TOIE4);
// This register write has to be after others. Otherwise the PWM generation will not work. I do not know why.
TCCR4A=0x42; // COM4A1..0 = 01, OC4A and !OC4A connected. PWM4A = 1 (activate channel A PWM output)
sei(); // enable global interrupts
}
void loop() {
//cli();
pot = analogRead(A0);
freq = map(pot, 0, 1023, 14, 166);
//sei();
/*
Serial.print("Pot value: ");
Serial.print(pot);
Serial.print("\tFreq value: ");
Serial.println(1500000/freq);
*/
}
ISR(TIMER4_OVF_vect){
OCR4C = freq;
OCR4A = freq / 2;
}
I am not sure exactly why you got different behavior right after programming, but the bootloader that the Arduino Micro uses (Caterina) does not perform a full reset after it runs, so changes that the bootloader made to the AVR's registers are often visible to the user's sketch.
I was able to fix the problem by removing the line that modifies PLLFRQ. Here is a simplified version of your code that always produces 3.31 kHz PWM:
void setup()
{
pinMode(5, OUTPUT);
pinMode(13, OUTPUT);
TCCR4A = 0;
TCCR4B = 0x06; // configure prescaler to 64 (CK = CLK / 64 = 1.5 MHz)
TCCR4C = 0;
TCCR4D = 0; // select Fast PWM operation (0 << WGM41)|(0 << WGM40)
OCR4C = 150; // select PWM frequency
OCR4A = 150 / 2; // set duty cycle
DT4 = 0x55; // set dead times. DT = (1 / 48Mhz) * 0...15
// This register write has to be after others.
// Otherwise the PWM generation will not work. I do not know why.
// COM4A1..0 = 01, OC4A and !OC4A connected.
// PWM4A = 1 (activate channel A PWM output)
TCCR4A = 0x42;
}
void loop()
{
}
It's not a great idea to mess with the PLL postscaler since it will probably affect every other Arduino library that uses timers, including the USB stack.
I'm trying to use an arduino uno to show some students how to make their own 'auto tune' however the code that I wrote is not outputting any signal. The goal is to sample values into an array at one rate and output the data from the array(FIFO)at a slower rate. My understanding is that TCNT1 increments each clock tick, I'm using 16 MHz in my case, and that I can base if logic on the value of TCNT1, I use a mod function here to take and store a single adc value and then play that value to the dac at a later time. acdT dacT represent my timing logic. I've built an external DAC to read only 8 (of 10) bit values from d0-d7 (PORTD). Why am I not seeing a signal?
int i = 0;
int j = 0;
int adcT = 328; // 329 clock tics
int dacT = 349; // 350 clock tics
int buff[15]; // 16 length buffer to store adc values
void setup ()
{
PRR &= ~(1<<PRADC); //ADC turned on
ADMUX = 0x60; //AVcc, left adjusted, ADC0 pin
ADCSRA = 0xC0;//ADC Enabled, no auto trigger
DDRD=0xFF; // set portd to d0 thru d7 digital pins
DDRC=0x00; // accept input from any analog input
TCCR1B |= 1<<CS10; // sets the clock to the system clock ie no pre scaler
}
void loop ()
{
if((TCNT1%acdT == 0) || TCNT1 == 0) // execute at 0 and mod329 clock tics
{
ADCSRA|=(1<<ADSC); // take one adc reading
while(!(ADCSRA & (1<<ADIF))); // wait until the reading is complete
ADCSRA|=(1<<ADIF); //reset adc for next command
buff[i] = ADCH; // take the adc value into the array
i++ // increment
}
if((TCNT1%dacT == 0)) %% TCNT1 ~= 0// execute at mod350 clock tics
{
PORTD = buff[j]; // send the adc reading to digital output
j++;
}
if(TCNT1 == 5262 ) // LCM/3 of 329(16samples) and 350(15samples)
{
TCNT1 = 0;// reset ticker
i = 0;
j = 0;
}
if(TCNT1 == 336)
{
PORTD = buff[15]; // play 16th adc sample to clear array
}
}
TCCR1B |= 1<<CS10; // sets the clock to the system clock ie no pre scaler
And there's your problem. You're attempting to find the modulus of a counter that runs faster than your code. Use the output capture and other features of the timer to trigger interrupts and reset the timer at the appropriate times instead of trying to catch a passing bullet with your bare hands.