So I have been trying to do an ADC on my Analog Voltage signal (0.5-2.5 V) to a PWM digital signal to control a dc motor drive at around a frequency of high frequency of 6-7 KHz for my Ball on Beam project. For this it is quite clear that I need enter the register, port, timers etc. and ofcourse the absence of AnalogRead(). I have successfully programmed both the parts, ADC & PWM individually, however have been wrecking weeks behind their synchronization at that frequency, in phase frequency correct mode.
My goal is to achieve 2 PWM at pins 9-10 respectively; also they are inverted to each other. On their timing triangle, I'd like to have two interrupts; one at compare match each & at the overflow point. In these interrupts, I want the last adc value, print it on my serial monitor and start the next one exactly at that instant. I expect some delays, ADC values from ~100-512 digital value.
Also I would have used Serial.println() in my interrupt, but internet shined some wisdom on me, was stated that the function itself has interrupts but can be done in the main loop by checking the interrupt flag status(TIFR1) but maybe Im wrong.
I have my code below, Kindly have a look.
I am dying for the past weeks, some hints and tips would be really appreciated.
Cheers! - Dhruv
My code is as follows:
volatile uint16_t AnalogReadings; // Wanted 10 bit value as a variable
const byte InputPin = A0; // Input 8-bit Voltage signal at A0
ISR(ADC_vect) // ADC_vect which store the 10 bit value
{
while(ADCSRA & (1<<ADSC); // wait for conversion
ADC = (ADCL | (ADCH << 8)); // Bit shifting for 10-bit resolution from 8-bit
return(ADC);
}
ISR(TIMER1_COMPA_vect) // COMPA_interrupt_vect at OCR1A compare match event
{
AnalogReadings = ADC; // OR itoa(ADC, AnalogReadings, 1) idk if we can use this
Serial.println(AnalogReadings); // print these readings on serial monitor
ADCSRA |= (1<<ADSC); // Start the next conversion
}
ISR(Timer1_COMPB_vect) // COMPB_interrupt_vect at OCR1B compare match event
{
AnalogReadings = ADC; // updates the AnalogReadings
Serial.println(AnalogReadings); // print these readings on Serial monitor
ADCSRA |= (1<<ADSC); // Start the next conversion
}
ISR(TIMER1_OVF_vect) //Overflow vector at BOTTOM (0)
{
AnalogReadings = ADC;
Serial.println(AnalogReadings); // print twice at compare match each and Overflow per period for both signals;
TCNT1 = 0; // TCNT1 set to initialise again; maybe not needed as TCNT starts by itself
ADCSRA |= (1<<ADSC); // Event the conversion start
}
void setup()
{
Serial.begin(115200); // set baudrate
//CONFIGURE ADC
cli(); //disable all interrupts
ADCSRA = 0;
ADCSRB= 0; // Initialize ADC
ADMUX = 0;
// Enable Input channel A0;
ADMUX |= (1<<ADLAR) // (1<<ADLAR) for bit shifting for 8-10 bit conversion
InputPin &= 0b00000111; // Input at A0; Sine wave (0.5-2.5V)
ADMUX &= (ADMUX & 0xF8) | (InputPin); // AREF; so not bit to set
ADCSRA |= (1<<ADEN);
ADCSRA |= (1<<ADIF); // enable interrupt flag
ADCSRA |= (1<<ADIE); // enable interrupts
sei(); // enable global interrupts
ADCSRA |= (1<<ADPS2); // Prescalar of 16; 16MHz/ 16 = 1MHz/13 clock cycles= ~76Khz (ADC Sampling rate) = ~13 uS (one conversion period)
ADCSRB |= (1<<ADTS2) | (1<<ADTS0); //
ADCSRA |= (1<<ADSC); // Start the first conversion
while (ADCSRA & (1<<ADSC)); //wait for the completion of ADC conversion
cli(); // disable interrupts
// Timer Settings
TCCR1A = 0;
TCCR1B = 0; // Initialise timer1
TCNT1 = 0;
// choose phase and frequency correct PWM, TOP defined by ICR1
TCCR1A |= (1<<COM1A0) | (1<<COM1A1); // toggle outputs OC1A and OC1B
TCCR1A |= (1<<COM1B1); // OC1A should be logical negated to OC1B
TCCR1B |= (1<<CS10) | (1<<WGM13); // choose PWM mode (see above) and set prescaler to 1
ICR1 = 255; // set TOP value to 255 --> resulting PWM-frequency = 16MHz / (2 * 1 * 255) = 31.372kHz = 32uS ;one pwm period
OCR1A = 128; //Duty cyycle 50%
OCR1B = 128; // for both signals
TIMSK1 |= (1<<OCIE1A)| (1<<OCIE1B) |(1<<TOIE1); // set 2 compare match interrupt & 1 overflow event masks
TIFR1 |= (1<<OCF1A)| (1<<OCF1B) |(1<<TOV1); // may not be needed as the flag is reset when the interrupt vector is initiated
sei(); // enable interrupts again
DDRB |= (1<<PB1) | (1<<PB2); // Enable output ports 9&10 digital PWM pin
ADCSRA |= (1<<ADATE); // enable auto triggering mode
}
void loop()
{
while(1) {} // Above program continues
while(ADCSRA & (1<<ADSC)); // wait for the ADC conversion to complete
if(TIFR1 & ~(1<<OCF1A) && ~(1<<OCF1B)) // check for compare match event flags
{
Serial.print("Compare match point");
Serial.println(AnalogReadings); // print the adc values which would be the same for 2 signals
}
if(TIFR1 & ~(1<<TOV1)) // Check if overflow flag is closed
{
Serial.print("Overflow point");
Serial.println(AnalogReading):
}
Serial.flush(); // emptys the AnalogReading variable, may not be needed be
}​
Got it in code tags.
So you mean include the whole loop function in that certain while loop?
How else can I print the digital points without the interrupts?
Gesus, embedded programming kinda hard.
Not sure. Looks like there's typos and compilation issues in your code. Does it compile for you?
What else doesn’t work when you stop printing in the interrupts?
You can't return a value from an ISR-- there is nothing that calls it. What is this “return ADC” intended for?
And If you don’t print in the interrupts, how are you going to print? Instead, I’d save a value in the interrupt and then in loop I’d periodically report.
Print statements go outside of the interrupts, in the main program. The interrupt should set a global flag (declared volatile) saying that the new measurement is ready for the main loop to print.
Greetings Lads!
Sorry I had my university exams going on, couldnt reply back
Altho I was still trying to figure this thing out, I tried updating my code with all of yer guys tips.
So here's the thing, my code compiles and shows me my pwm output with ~31.25KHz, but the ADC is still down and doesnt show a single digit (empty serial monitor and plot).
I tried using the variable "AnalogReadings" to store the value of the ADC after each ADC conversion, and then I use this variable to print out the value in the main loop by checking the TIMSK1 flags (which might not be needed as that bit is clear as soon as the interrupt ends.).
Also, I want to start the next ADC conversion in my interrupt, which keeps updating the ADC(AnalogReadings) values which in order senses my motor driving current and lets it oscillate between a certain range to drive the motor.
All I want do with the ADC is to read the values and print it at my desired interrupts (Compare Matches A&B as well as Overflow) and start the nexct conversion within those interrupts as I want to sync the ADC with my PWM.
Ay Dave, I dont understand a single thing with the macros part lmao, Would I really need to use such complex things?
The code is adjoined below, lets get it done with hehe.
Thanks
volatile uint16_t AnalogReadings; // Wanted 10 bit value as a variable
const byte InputPin = A0; // Input 8-bit Voltage signal at A0
ISR(ADC_vect)
{
while(ADCSRA & (1<<ADSC)); // wait for conversion
ADC = (ADCL | (ADCH << 8)); // Bit shifting for 10-bit resolution from 8-bit
AnalogReadings = ADC;
}
ISR(TIMER1_COMPA_vect)
{
AnalogReadings = ADC;
ADCSRA |= (1<<ADSC); // Start the next conversion
}
ISR(Timer1_COMPB_vect)
{
AnalogReadings = ADC;
ADCSRA |= (1<<ADSC); // Start the next conversion
}
ISR(TIMER1_OVF_vect)
{
AnalogReadings = ADC;
TCNT1 = 0; // TCNT1 set to initialise again; maybe not needed as TCNT starts by itself
ADCSRA |= (1<<ADSC); // Event the conversion start
}
void setup()
{
Serial.begin(115200); // set baudrate
//CONFIGURE ADC
cli(); //disable all interrupts
ADCSRA = 0;
ADCSRB= 0; // Initialize ADC
ADMUX = 0;
// Enable Input channel A0;
ADMUX |= (1<<ADLAR); // (1<<ADLAR) for bit shifting for 8-10 bit conversion
ADMUX &= (ADMUX & 0xF8) | (InputPin); // AREF; so not bit to set
ADCSRA |= (1<<ADEN);
ADCSRA |= (1<<ADIF); // enable interrupt flag
ADCSRA |= (1<<ADIE); // enable interrupts
sei(); // enable global interrupts
ADCSRA |= (1<<ADPS2); // Prescalar of 16; 16MHz/ 16 = 1MHz/13 clock cycles= ~76Khz (ADC Sampling rate) = ~13 uS (one conversion period)
ADCSRB |= (1<<ADTS2) | (1<<ADTS0); //
ADCSRA |= (1<<ADSC); // Start the first conversion
while (ADCSRA & (1<<ADSC)); //wait for the completion of ADC conversion
cli(); // disable interrupts
// Timer Settings
TCCR1A = 0;
TCCR1B = 0; // Initialise timer1
TCNT1 = 0;
// choose phase and frequency correct PWM, TOP defined by ICR1
TCCR1A |= (1<<COM1A0) | (1<<COM1A1); // toggle outputs OC1A and OC1B
TCCR1A |= (1<<COM1B1); // OC1A should be logical negated to OC1B
TCCR1B |= (1<<CS10) | (1<<WGM13); // choose PWM mode (see above) and set prescaler to 1
ICR1 = 255; // set TOP value to 255 --> resulting PWM-frequency = 16MHz / (2 * 1 * 255) = 31.372kHz = 32uS ;one pwm period
OCR1A = 128; //Duty cyycle 50%
OCR1B = 128; // for both signals
TIMSK1 |= (1<<OCIE1A)| (1<<OCIE1B) |(1<<TOIE1); // set 2 compare match interrupt & 1 overflow event masks
TIFR1 |= (1<<OCF1A)| (1<<OCF1B) |(1<<TOV1); // may not be needed as the flag is reset when the interrupt vector is initiated
sei(); // enable interrupts again
DDRB |= (1<<PB1) | (1<<PB2); // Enable output ports 9&10 digital PWM pin
ADCSRA |= (1<<ADATE); // enable auto triggering mode
}
void loop()
{
while(1)
{
while(ADCSRA & ~(ADSC));
if(TIMSK1 & ~(1<<OCF1A))
{
Serial.println(AnalogReadings);
Serial.flush();
}
else if(TIMSK1 & ~(1<<OCF1B))
{
Serial.println(AnalogReadings);
Serial.flush();
}
else if(TIMSK1 & ~(1<<TOV1))
{
Serial.println(AnalogReadings);
Serial.flush();
}
}
}
I'd try adding more Serial.print(), replacing blocking loops with ifs and commenting things out until you get at least some program-flow expected output.
Yes, I did that
Im checking my interrupt flags in the main loop, where I make sure that the interrupt flags in the mask register. So when the interrupt ends and the value of the ADC is ready, I check it in my if loops and then print it accordingly.
Any more tips?
cheers!
But I have two outputs, one in pin9 and the other one which is the inverted of it, on pin 10. Also I need a overflow interrupt.
So basically I am using 2 interrupts per signal period. I am not sure if I can do it, but this is my goal.
Cheers
How else can i get the ADC bit value with 10 bit resolution?
I am shifting my ADCH by 8 on the right hand side.
Maybe I will not need the while loop (which lets me know when the conversion is done) in the ADC vector as I set the mode to free running.
You think it is possible to have interrupt at the TOP value and at the BOTTOM (Overflow) too in one signal period?
You just read the "ADC" macro and it does it for you.
Here's how analogRead() gets its 10 bits:
It doesn't grab the two bytes ADCL and ADCH, assemble it into a 16 bit value, and then write it back into ADC to be read later, it lets the compiler handle that as needed.
Another way to think of this is "Where do you think you are writing the result of (ADCL | (ADCH << 8))?" Your code doesn't declare ADC as a variable.
I slowed down your prescaler with this code:
volatile uint16_t AnalogReadings; // Wanted 10 bit value as a variable
const byte InputPin = A0; // Input 8-bit Voltage signal at A0
ISR(ADC_vect)
{
while(ADCSRA & (1<<ADSC)); // wait for conversion
ADC = (ADCL | (ADCH << 8)); // Bit shifting for 10-bit resolution from 8-bit
AnalogReadings = ADC;
}
ISR(TIMER1_COMPA_vect)
{
AnalogReadings = ADC;
ADCSRA |= (1<<ADSC); // Start the next conversion
}
ISR(Timer1_COMPB_vect)
{
AnalogReadings = ADC;
ADCSRA |= (1<<ADSC); // Start the next conversion
}
ISR(TIMER1_OVF_vect)
{
AnalogReadings = ADC;
TCNT1 = 0; // TCNT1 set to initialise again; maybe not needed as TCNT starts by itself
ADCSRA |= (1<<ADSC); // Event the conversion start
}
void setup()
{
Serial.begin(115200); // set baudrate
//CONFIGURE ADC
cli(); //disable all interrupts
ADCSRA = 0;
ADCSRB= 0; // Initialize ADC
ADMUX = 0;
// Enable Input channel A0;
ADMUX |= (1<<ADLAR); // (1<<ADLAR) for bit shifting for 8-10 bit conversion
ADMUX &= (ADMUX & 0xF8) | (InputPin); // AREF; so not bit to set
ADCSRA |= (1<<ADEN);
ADCSRA |= (1<<ADIF); // enable interrupt flag
ADCSRA |= (1<<ADIE); // enable interrupts
sei(); // enable global interrupts
ADCSRA |= (1<<ADPS2); // Prescalar of 16; 16MHz/ 16 = 1MHz/13 clock cycles= ~76Khz (ADC Sampling rate) = ~13 uS (one conversion period)
ADCSRB |= (1<<ADTS2) | (1<<ADTS0); //
ADCSRA |= (1<<ADSC); // Start the first conversion
while (ADCSRA & (1<<ADSC)); //wait for the completion of ADC conversion
cli(); // disable interrupts
// Timer Settings
TCCR1A = 0;
TCCR1B = 0; // Initialise timer1
TCNT1 = 0;
// choose phase and frequency correct PWM, TOP defined by ICR1
TCCR1A |= (1<<COM1A0) | (1<<COM1A1); // toggle outputs OC1A and OC1B
TCCR1A |= (1<<COM1B1); // OC1A should be logical negated to OC1B
TCCR1B |= (0b101<<CS10) | (1<<WGM13); // choose PWM mode (see above) and set prescaler to 1
// TCCR1B https://onlinedocs.microchip.com/pr/GUID-80B1922D-872B-40C8-A8A5-0CBE009FD908-en-US-3/index.html?GUID-07C6751D-0319-41A4-AC9F-9B0AFDA21A07
// prescaler /1024 for slower output
ICR1 = 255; // set TOP value to 255 --> resulting PWM-frequency = 16MHz / (2 * 1 * 255) = 31.372kHz = 32uS ;one pwm period
OCR1A = 128; //Duty cyycle 50%
OCR1B = 128; // for both signals
TIMSK1 |= (1<<OCIE1A)| (1<<OCIE1B) |(1<<TOIE1); // set 2 compare match interrupt & 1 overflow event masks
TIFR1 |= (1<<OCF1A)| (1<<OCF1B) |(1<<TOV1); // may not be needed as the flag is reset when the interrupt vector is initiated
sei(); // enable interrupts again
DDRB |= (1<<PB1) | (1<<PB2); // Enable output ports 9&10 digital PWM pin
ADCSRA |= (1<<ADATE);
Serial.println("Setup Complete");
// enable auto triggering mode
}
void loop()
{
while(1)
{
while(ADCSRA & ~(ADSC));
if(TIMSK1 & ~(1<<OCF1A))
{
Serial.println(AnalogReadings);
Serial.flush();
}
else if(TIMSK1 & ~(1<<OCF1B))
{
Serial.println(AnalogReadings);
Serial.flush();
}
else if(TIMSK1 & ~(1<<TOV1))
{
Serial.println(AnalogReadings);
Serial.flush();
}
}
}
If you are going to bitbang the low-level registers, make sure you are doing it carefully. It would be hard work to decode your bit settings. Are they all set as you want? To check, start slowing things down and simplifying them.
oi man! you've been a god himself.
This simulator is quite useful when it comes to simulating, although only got some settings and tools. One question tho, Why did you connect A0 and A2?
Nonetheless, trying it out with one interrupt sounds great to me, I tried it on the simulator and atleast my code compiled and showed "Setup complete".
All these settings sound fine to me atleast, all the modes have been setup correctly and my timer clock and PWM settings seem fine as well. I have a PWM signal, so I reckon the problem only exists with the ADC configuration.
Clearly the code doesnt have a variable called ADC, but doesnt the compiler understand that I returning my ADC values and equaling these values to my variable "AnalogReadings" in the interrupt? So i should expect Digital points on the monitor whenever the interrupt closes? As in the main loop, I am looking for the instant when the "ADIF" flag clears?
Also,I was wondering if the values are getting stored in the USART buffer, so I'd need to introduce the USART register, buffer up my values, have a bit of delay and then showcase them on my monitor. Whatchu think?
Cheers!
Note that Wokwi's simulator doesn't do free-running or other ADCSRA |= (1<<ADATE); modes because it would essentially require another interrupt triggering system:
My issue was with the your ADC = ADCL <<8 | ADCH; line.
No. Even if the interrupts capture the reading, the main loop is polling and needs to somehow detect when the value updated. Some folks set a one-shot flag in their interrupt routines, and then act on it at their leisure in loop().
Doesn't this line make all those ifs false?
Where did you get this code?
I'd do it all at slow speed with polling in loop() to make sure my logic worked before I started working on interrupts.
No, these masks just enable the interrupts which can be triggered on compare match when the TCNT1 equals the OCRnX values. The interrupt does the bit-banging as the value received is 8-bit, and hence by bit-banging I am just converting that value to a 10 bit value.
Now, I guess my problem is with my volatile variable 10-bit "AnalogReadings". From our previous knowledge, we know that,
These flags when set to 1, enable our interrupt flags (do things in the interrupt) right?, and then gets cleared (goes to 0) whenever the "things" in the interrupt are finished. In our case,
When we equal the ADC value to our dynamic variable "AnalogReadings", and start the next conversion.
Now hopefully (pun intended), we have the ADC value stored as our "AnalogReadings".
in my main loop,
Firstly, keep my loop iterating. Then wait for the ADC conversion to finish, and then look for those interrupt flag to get cleared (so when the interrupt finishes and we have the value in our "AnalogReadings" variable). Print this value on our monitor and then get the variable cleared by Serial.flush() function.
This is my logic. Darning stuck on it for weeks now.
All I want are the ADC values whenever there is a compare match event. (TCNT1=OCRnX).
Hope you understand the topic clearly.
Btw, you mean bit-shifting by polling right?
Cheers!