I need to control a 24 kHz ultrasonic transducer and decided to use an arduino uno I have lying around.
Using tone()
or the atmega's hardware pwm is not very suitable because I need two 50/50 square waves with a phase difference of pi (which is equal to two XOR'ed pins) to switch the output polarity of the h-bridge synchronously. As the uC dosen't have anything else to do I wrote the following sketch where I just wait for an interrupt and toggle pins 2&3:
bool do_task=false;
void timer_init () {
TCCR0A |= (1 << WGM01);
OCR0A = 83;
TIMSK0 |= (1 << OCIE0A);
TCCR0B |= (1 << CS01);
}
ISR (TIMER0_COMPA_vect) {
do_task=true;
}
bool state=false;
void toggle() {
state=!state;
digitalWrite(2,state);
digitalWrite(3,!state);
}
void setup() {
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
timer_init();
}
void loop() {
if(do_task) {
do_task=false;
toggle();
}
}
I expected the output to have a frequency of ~24 kHz, but it is 500 Hz instead.
From my understanding:
- 16 MHz CPU frequency
- timer0 prescaler is 8 (
TCCR0B |= (1 << CS01)
) - output compare value (
OCR0A
) is 83
would result in a frequency of 16 MHz / 8 / 83 = 24096,4 kHz
-but as I mentioned- it's 500 Hz and doesn't seem to depend on the prescaler.
2 Answers 2
what exactly is it that makes a simple function like digitalWrite() so damn slow??! Why isn't it just a wrapper function for direct port manipulation?
Well, it can take a variable as an argument. And the contents of the variable have to be looked up in a table to see which port and which bit.
If you use the DigitalWriteFast library then that turns your writes into direct port manipulation providing you use constants.
So you can use:
digitalWriteFast(2,HIGH); // or LOW
But not:
digitalWriteFast(myPort,someState); // assuming they are not constants
In fact you can send variables, but it degrades into the same slow calls that digitalWrite uses.
I now decided not to use the arduino core
The core is absolutely fine. However you need to understand what it is doing. To be nice to newbies things like digitalWrite can take variables as arguments, so this sort of thing is possible:
for (int i = 0; i < 13; i++)
digitalWrite (i, HIGH);
As I hinted in a comment, you could do it all in the ISR:
ISR (TIMER0_COMPA_vect) {
static bool state;
if (state)
{
digitalWriteFast(2,HIGH);
digitalWriteFast(3,LOW);
state = false;
}
else
{
digitalWriteFast(2,LOW);
digitalWriteFast(3,HIGH);
state = true;
}
} // end of TIMER0_COMPA_vect
Initially, I planned to read the frequency from a potentiometer after each interrupt, wouldn't this be easily possible in ×ばつ83=664 cycles?
Analog reads (reads from the ADC) take around 104μs using the default ADC prescaler. That is, 1664 clock cycles. So, no, you can't read in that time. That is a hardware limitation. You can reduce the prescaler without losing much accuracy.
See my page about the ADC. Making the prescaler 16 rather than 128 will significantly increase speed with minimal accuracy loss.
To do that on the Uno:
ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // clear prescaler bits
ADCSRA |= bit (ADPS2); // 16
-
I'm not that new to uC programming, I just tend to use programing registers directly rather than using the arduino environment. I thought this little program might be faster to implement using the arduino core without having to look up things in the datasheet, but now I did so anyway. Concerning the adc: if it is in free-running mode, the hardware is doing the conversion in parallel. So if I only check the status bit for a completed conversion, this shouldn't be a problem and the actual time needed to handle the analog value would decrease a lot. Isn't that true?Sim Son– Sim Son2019年07月23日 16:13:55 +00:00Commented Jul 23, 2019 at 16:13
-
I only need the analog value every 100ms and I'm not doing very much processing with it...Sim Son– Sim Son2019年07月23日 16:15:18 +00:00Commented Jul 23, 2019 at 16:15
You wrote:
Using [...] the atmega's hardware pwm is not very suitable because I need two 50/50 square waves with a phase difference of pi
I believe hardware PWM is the best solution to your problem. The PWM outputs can be set to either "inverting" or "non-inverting" mode. Set the two channels of a single timer to the same duty cycle, with one in non-inverting mode and the other one in inverting mode, and you have your complementary outputs.
Example using Timer 1:
const float SIGNAL_FREQUENCY = 24e3;
const uint16_t HALF_PERIOD = round(F_CPU / SIGNAL_FREQUENCY / 2);
const uint16_t PERIOD = 2 * HALF_PERIOD;
int main()
{
/* Set the PWM pins as outputs. */
DDRB |= _BV(PB1); // digital 9 = PB1 = OC1A
DDRB |= _BV(PB2); // digital 10 = PB2 = OC1B
/* Configure Timer 1. */
ICR1 = PERIOD - 1;
OCR1A = PERIOD/2 - 1;
OCR1B = PERIOD/2 - 1;
TCCR1A = _BV(COM1A1) // non-inverting PWM on OC1A
| _BV(COM1B0) // inverting PWM on OC1B
| _BV(COM1B1) // ditto
| _BV(WGM11); // mode 14: fast PWM, TOP = ICR1
TCCR1B = _BV(WGM12) // ditto
| _BV(WGM13) // ditto
| _BV(CS10); // clock at F_CPU, start timer
}
-
Thanks for your answer! Yes, I know the inverting mode, but I will soon have to be able to also change the frequency in a little range. I'm now doing direct port manipulation and it works fine so far. But a question: what is this
_BV()
you are using?Sim Son– Sim Son2019年07月23日 16:18:50 +00:00Commented Jul 23, 2019 at 16:18 -
@SimSon:
_BV(n)
is a macro for(1<<(n))
, from avr-libc. I guess it stands for "byte value". I like to use it when writing AVR-idiomatic code.Edgar Bonet– Edgar Bonet2019年07月23日 17:06:52 +00:00Commented Jul 23, 2019 at 17:06
Explore related questions
See similar questions with these tags.
digitalWrite()
are taking to long. It`s a rather big function. Use direct portmanipulation|=
digitalWrite()
so damn slow??! Why isn't it just a wrapper function for direct port manipulation?