Generate precise square signals on several pins (PWM screws up)

Hi,

I use a Lilypad board to control 4 vibrators that work basically like speakers. I control them through the serial port to activate them and control the duration, frequency, amplitude and which vibrator to activate (possibly several at the same time).

So I plugged each of the vibrators to a PWM pin (3, 5, 9 and 11), added an amplifier transistor to provide a sufficient current etc.

I control the amplitude using analogOutput, and I generate the frequency I want using a for loop, with something similar to this http://web.media.mit.edu/~leah/LilyPad/07_sound.html. I just replaced digitalWrite by AnalogWrite, and added support for several pins at the same time. I also use delay instead of delayMicroseconds when the frequency is low since the delay is too high for delayMicroseconds (http://www.arduino.cc/en/Reference/DelayMicroseconds). The frequency range I use is around 10Hz to 400Hz.

void beep(byte pattern, long duration, unsigned int frequency, byte amplitude) const
{
    int i;
    long del = (long)(1000000 / ((long)frequency));
    long looptime = (long)((duration * 1000) / (del * 2));
    long udel = del % 1000;
    long mdel = del / 1000;
    for (i = 0 ; i < looptime ; i++)
    {
		for (int j = 0 ; j < 4 ; j++)
			if (j < _nbtactors && (pattern & (0x01 << j)))
				analogWrite(_pins[j], amplitude);
		//delayMicroseconds is not accurate over 16383µs
		if (del > 10000)
		{
			//delayMicroseconds(udel);
			delay(mdel);
		}
		else
			delayMicroseconds(del);
		for (int j = 0 ; j < 4 ; j++)
			if (j < _nbtactors && (pattern & (0x01 << j)))
				analogWrite(_pins[j], 0);
		if (del > 10000)
		{
			//delayMicroseconds(udel);
			delay(mdel);
		}
		else
	

		delayMicroseconds(del);
    }
}

So If I understand correctly the analogOutput function generates a high frequency (compared to the ones I use) signal. So this function generates a low frequency square signal whose up states are full of "holes" of the high frequency signal.

Everything looks to work fine, however I have some problems when looking closely.

Sometimes (I am unsure about the circumstances) low frequencies vibrations are too long, and therefore the frequency is wrong. It is due to a too loo long delay. There are several factors that I suspect to influence on that:

  • low accuracy of delay. However I am unsure that this could lead to a 16Hz vibration of 1s (delay of 31.25ms) lasting more than 2s.
  • different settings of PWM pins. pin 5 uses timer0, pin 9 uses timer1 and pins 3 and 11 use timer2. Knowing that timer0 is used for millis and micros, is it possible that doing frequently analogOutput on pin 5 screws up the value of millis and micros? timer1 is a 16bit timer, does it change anything compared to others? timer0 uses fast PWM whereas others use Phase correct PWM. Can this have an influence on the accuracy of one or more pins?

I tried to disable interrupts around my function. However it looks like my program stops to handle incoming serial packets after a short duration. What happens if I receive packets on the serial port while the interrupts are disabled? If the packets are lost my data is randomly shifted and this explains why my program does not handle them anymore.

Is it possible to generate directly a low frequency signal on a pin with a given amplitude (voltage) for a certain duration? This could replace this ugly for loop. The tone function looks interesting, however it only uses one pin at a time, and it is not possible to change the amplitude. I looked at the documentation of timers and interrupts, but it looks complicated. I would appreciate tips from someone experienced with this.

Thank you

Is it possible to generate directly a low frequency signal on a pin with a given amplitude (voltage) for a certain duration?

The analogWrite() function does not modify the amplitude. It turns the pin on and off pretty rapidly, modifying the on time ratio to match the input value (from 0 (always off) to 255 (on 100% of the time).

There is no way, without external hardware, to generate a voltage output of less than 5V (or 3.3V on some Arduinos).

What happens if I receive packets on the serial port while the interrupts are disabled? If the packets are lost...

They are. If you are not allowed to process an interrupt when it occurs, only the first one will be processed when interrupts are again allowed. The data that was present to be captured when the interrupt was not processed will not still be hanging around somewhere when the interrupt is finally processed.

PaulS:
The analogWrite() function does not modify the amplitude. It turns the pin on and off pretty rapidly, modifying the on time ratio to match the input value (from 0 (always off) to 255 (on 100% of the time).

There is no way, without external hardware, to generate a voltage output of less than 5V (or 3.3V on some Arduinos).

OK. However the RMS voltage changes, no? I am sure that "something" happens since I have the desired effect from the device.

Besides what you tell means that I cannot change the amplitude using registers and hacking with the timers.

PaulS:
What happens if I receive packets on the serial port while the interrupts are disabled? If the packets are lost...

They are. If you are not allowed to process an interrupt when it occurs, only the first one will be processed when interrupts are again allowed. The data that was present to be captured when the interrupt was not processed will not still be hanging around somewhere when the interrupt is finally processed.
[/quote]
OK this is what I thought. So I cannot disable interrupts.

However the RMS voltage changes, no? I am sure that "something" happens since I have the desired effect from the device.

Some devices are happy with PWM (changing duty cycle), such as LEDs. Others are not. A simple low-pass filter made from some resistors and capacitors can smooth the output so those devices are happy, to.

However, the answer to your question is no. RMS refers to AC voltage, not DC. (I think). So, it's irrelevant.

PaulS:
Some devices are happy with PWM (changing duty cycle), such as LEDs. Others are not. A simple low-pass filter made from some resistors and capacitors can smooth the output so those devices are happy, to.

However, the answer to your question is no. RMS refers to AC voltage, not DC. (I think). So, it's irrelevant.

My devices are like speakers (voice coils). They can work with AC or DC, and they seem happy with changing duty cycle. The perceived "amplitude" changes. Maybe it is not related to voltage. Maybe it is a mechanical effect. Anyway.

Is it possible to

  1. generate a PWM signal by combining two timers? a slow one for the vibration, a fast one for the "amplitude"
  2. send this "combined" signal to several (PWM) pins (3, 5, 9 and 11)?

The idea of the combination is to generate a high frequency (don't care about the value) PWM signal (duty cycle => amplitude) and a slow one corresponding to the precise frequency I need (10Hz to 400Hz). The signal of the fast one would be sent to the pins when and only when the slow one is in the HIGH state.

I am currently looking at the code of the tone library. I think this is doable by writing the appropriate code in the ISR functions of timer 1 and 2 for instance. But I am totally new in this. I read that pins are mapped to specific timers. Is this a hardware link or software link? (it is my 2nd question above actually)

I read that pins are mapped to specific timers. Is this a hardware link or software link?

Hardware.

Is it possible to

  1. generate a PWM signal by combining two timers? a slow one for the vibration, a fast one for the "amplitude"

There are ways to change the PWM frequency (how fast it toggles between on and off). The "amplitude" as you refer to it is the %age of on time. The %age of on time is not a factor of the frequency that the duty cycle completes.

I confess that I really don't understand the question, or what you are doing.

OK Just imagine I want to generate basic sounds on several speakers at various volumes. At any time I can send a command on the serial port that says "play this tone (frequency) during X milliseconds on these pins at this volume".
So I have 4 variables:

  • location, that I control by choosing the pin(s)
  • frequency, that I currently control with my for loop alterning a PWM signal and LOW states
  • duration, that I control with the number of state change knowing the frequency
  • amplitude, that I control with the duty cycle % of the PWM signal mentionned above

I remarked that the result is not always precise, probably because I use delay and microdelay. So I would like to know if I can use several timers and do something like this:

bool active = false;

void loop()
{
  read on serial port command
  setup timer 1 for a fast and imprecise loop <= analogOutput(amplitude) may be sufficient
  setup timer 2 for a slow and precise loop <= have to change TCCR2X OCR2X...
}

ISR(TIMER1_COMPA_vect)
{
  if (active)
    change state of selected pins
}

ISR(TIMER2_COMPA_vect)
{
 active != active;
}
  • amplitude, that I control with the duty cycle % of the PWM signal mentionned above

You can not control the amplitude of an output signal with out using external hardware.
You seem to not understand what a PWM signal is, please read this link:-

http://www.thebox.myzen.co.uk/Tutorial/PWM.html

I don't mean the amplitude of the signal sent, but the amplitude of the perceived stimulation from the actuator.

I am close to do what I want, however I still have problems. I am unable to use whatever timer and set the output on whatever pin. I ust tried the example given with the Tone library (source of the library: Google Code Archive - Long-term storage for Google Code Project Hosting.)
I managed to play different frequencies at the same time on pins supposed to be on the same timer. This means that as far as I understood the Tone library's code, they actually use any timer to output on any pin. I notice they didn't set the COMx bits, which explains why the pins don't echo the signal directly. They manually send the signal to the pins by the ISRs. However I do the same but I don't get any output. I don't know if my ISR are called.

In fact what I want to do is just being able to change the duty cycle % on the tone library, and control one or several pins with one signal (one play command). It is like being able to change the volume and the number of speakers.

From the tone write up in the Arduino playground concern use of the timers:

Ugly Details¶

The library uses the hardware timers on the microcontroller to generate square-wave tones in the audible range.
You can output the tones on any pin (arbitrary). The number of tones that can be played simultaneously depends on the number of hardware timers (with CTC capability) available on the microcontroller.
•ATmega8: 2 (timers 2, and 1)
•ATmega168/328: 3 (timers 2, 1, and 0)
•ATmega1280: 6 (timers 2, 3, 4, 5, 1, 0)
The timer order given above is the order in which the timers are allocated. Timer 0 is a sensitive timer on the Arduino since it provides millis() and PWM functionality.
The range of frequencies that can be produced depends on the microcontroller clock frequency and the timer which is being used:

MCU clock 8 bit timer Flow 16 bit timer Flow Fhigh
8 MHz 16 Hz 1 Hz (1/16 Hz) 4 MHz
16 MHz 31 Hz 1 Hz (1/8 Hz) 8 MHz

Although Fhigh can go as high as 8 MHz, the human hearing range is typically as high as 20 kHz.
Tone accuracy is dependent on the timer prescalar. Frequency quantization occurs as the frequencies increase per prescalar.
If you used a 16 bit timer (e.g. timer 1, or timers 3,4,5 on '1280), you could generate "tones" down to 1/8 Hz (one cycle every 8 seconds), although the library only accepts integers for frequency.
After all is said and done, because play() only accepts unsigned integers for frequency, the maximum frequency that can be produced is 65535 Hz - which, after rounding, results in a 65573.77 Hz "tone" on a 16 MHz part. Even if play accepted larger values for frequency, you couldn't achieve better than around 80KHz with the Tone library because the pin toggling is done in software. Each toggle, in software, requires AT LEAST 50+ cycles.


I don't have the 3 vibrators limitation since I activate one or more vibrators with the same signal, thus with the same timer.

I use timer1 in CTC mode to generate the good frequency (as in Tone), and timer2 in Fast PWM mode to generate the "amplitude" signal. I switch the state (activated/desactivated) in ISR(TIMER1_COMPA_vect), and swith my pins in ISR(TIMER2_OVF_vect) and ISR(TIMER2_COMPA_vect) when the state is activated.

It seems to work so far. I only have one bug left: my vibrations are not as long as they should. There must be a problem with the frequency since I checked the toogle count.

I have a problem with the CTC timer, and I cannot see the problem.

Here is my initialization:

	for (int i = 0 ; i < nbtactors ; i++)
	{
		pinMode(_pins[i], OUTPUT);     
		_bits[i] = digitalPinToBitMask(_pins[i]);
		_ports[i] = portOutputRegister(digitalPinToPort(_pins[i]));
	}	
	TCCR1A = 0; // CTC mode
	TCCR1B = (1 << WGM12) | (1 << CS10);
	TIMSK1 = 0; //not sure it is necessary
	TCNT1 = 0; //not sure it is necessary

Here is how I set the frequency:

	unsigned long ocr = F_CPU / _frequency / 2 - 1;
	byte prescalarbits = (1 << CS10);
	
	if (ocr > 0xffff)
	{
		ocr = F_CPU / _frequency / 2 / 8 - 1;
		prescalarbits = (1 << CS11);
	}

	TCCR1B = (TCCR1B & 0b11111000) | prescalarbits;
	OCR1A = ocr;
}

According to my calculations it should cover easily frequencies between 10 and 400Hz without overflow.

Here is how I compute the duration:

_toggle_count = 2 * _frequency * _duration / 1000;

My ISR is this:

ISR(TIMER1_COMPA_vect)
{
	if (_toggle_count > 0)
	{
		_toggle_count--;
		TactonPlayer::tooglePins();
	}
	else
	{
		TIMSK1 &= ~(1 << OCIE1A);
		TactonPlayer::turnOffPins();
	}
}

I toggle my pins like this:

	for (byte i = 0 ; i < _nbtactors ; i++)
	{
		if ((_pattern & (1 << i))) 
			*(_ports[i]) ^= _bits[i];
	}

I turn them off like this:

	for (byte i = 0 ; i < _nbtactors ; i++)
	{
		if ((_pattern & (1 << i)))
			*(_ports[i]) &= ~(_bits[i]);
	}
}

Finally I play the vibration with this code:

	setFrequency(frequency);
	setAmplitude(amplitude);
	setDuration(duration);
	setPattern(pattern);
	TIMSK1 |= 1 << OCIE1A;
	TIFR1 |= 1 << OCF1A; //not sure it is necessary

It is closely inspired by the Tone library. I just impose timer1, and manage several pins. However the result is not good. The ocr and _toggle_count values looks good. However when I use a fixed duration (1000ms), different frequencies have different durations and they are shorter than 1000ms. The ICR is called the right number of times, so I guess the timer is counting too fast...

Anyone has an hint?

Thanks

Theoretically it should be right. For instance with f=256Hz, d=1000ms :

ocr = F_CPU/frequency/prescale/2 - 1 = 8*10^6/256/1/2 - 1 = 15624 (the program gives me this value)
_toggle_count = 2 * frequency * duration / 1000 = 2 * 256 * 1000 / 1000 = 512 (the program gives me this value)

So the program is supposed to count 512 times until 15624, and the duration is supposed to be ocr * _toggle_count / (F_CPU / prescale). In the example: (512 * 15 624) / 8 000 000 = 0.999936s the little shift (1ms) is due to the -1 in the ocr formula, corresponding to half a cycle of the vibration. However the duration I observe is around 100ms. The difference is huge.

I have no idea where is the problem. :~