Created Mon, 06 Jan 2014 04:16:21 +0000 by bhclowers
Mon, 06 Jan 2014 04:16:21 +0000
I am interested in using the faster clock of the Fubarino to accurately produce digital pulse delays on the microsecond time scale. I'd like to have a series of pules (ideally 4) each at 10 us a piece with the first recurring every 100 us. This would mean pin A starts at t = 0 is high for 10 us and turns to the "LOW" state at the same time pin B goes to the "HIGH" state for another 10 us--rinse and repeat.
My first attempts at using "delayMicroseconds" have produced less than ideal results with a fair amount of jitter and execution delay when operating at the 10 us scale. Through some experimentation I found that using a series of digitalWrite calls for a number of cycles is the most accurate way of producing pulses. Unfortunately, however, in order to place the pin into the "LOW" state a series of clock cycles are required which appears to prevent multiple pulses across different pins from being synced.
For an example the problem please see the code below. Is there a way to produce an accurate interrupt that will produce pulses on different output pins?
Additionally, which pins are for the 16-bit timers as the documentation does not explicitly state this fact?
Any help would be appreciate.
#define P1 8
#define P2 7
void setup()
{
pinMode(P1, OUTPUT);
pinMode(P2, OUTPUT);
}
void loop()
{
delayMicroseconds(20);
digitalWrite(P1,HIGH);
digitalWrite(P2,HIGH);
int pulseMultiple = 6;
for (int pCount = 0; pCount < pulseMultiple; pCount++){
if (pCount < 3){
digitalWrite(P1, HIGH);
}
else {
digitalWrite(P2, HIGH);
}
}
digitalWrite(P1, LOW);
digitalWrite(P2, LOW);
}
Mon, 06 Jan 2014 10:55:15 +0000
It sounds to me like you want to use both a timer and do direct port manipulation (TRISx / LATx).
I would:
Then in the timer ISR:
1a. If phase is 0 then set pin A high. Set timer for 10µs. 1b. If phase is 1 then set pin A low and pin B high. Set timer for 10µs. 1c. If phase is 2 then set pin B low. Set timer for 80µs. 2. Increment phase 3. If phase is 3 then set phase to 0.
That's pretty much what SoftPWMServo does, but without the two differing phases.
Mon, 06 Jan 2014 17:00:03 +0000
I have to admit that explanation is at a level that I have not reached quite yet. Would you be kind enough to point me in the direction of an example? I tried exploring the use of the SoftPMWServo library but was unable to find a way to insert a delay between the pulses. Granted, that may have not been the intent of the original library. I'll keep digging but any help would be appreciated.
Regards,
Brian
ps--here is the code I tried with the PMW library and as the comments illustrate the delayMicroseconds call is ignored when it comes to output of the pulses. There is, in fact, no delay between pulses with this code but the widths are correct.
#include <SoftPWMServo.h>
int pos1 = 0; // variable to store the servo position, in microseconds
int pos2 = 0;
const int pin1 = 8; // Choose _any_ pin number on your board
const int pin2 = 7;
void setup()
{
// The Write() calls normally call Init(), but since we're doing some setup
// before we do a Write(), we need to do this Init() call ourselves here.
SoftPWMServoInit();
// Set the FrameTime (in 40MHz ticks) to 50 us
SoftPWMServoSetFrameTime(usToTicks(50));
// And set the frames between rising edges to 2 (~100us)
SoftPWMServoSetServoFrames(2);
}
void loop()
{
pos1 = 10;
SoftPWMServoServoWrite(pin1, pos1);
delayMicroseconds(20);//This appears to be ignored? How to set a delay in SoftPMWServo?
pos2 = 10;
SoftPWMServoServoWrite(pin2, pos2); // tell servo to go to position in variable 'pos'
}
Mon, 06 Jan 2014 18:32:26 +0000
You want to look at what SoftPWMServo does and replicate it in a slightly different way. You're going to need to manipulate the TxCON register to set up the timer, configure the interrupts with the IECx/IPCx registers, set up the period with the PRx register, and that latter is the register you will tweak to change the interrupt period from 10uS to 80uS and back again.
Actually, looking at the SoftPWMServo it uses the core timer service - that's not something I am familiar with, so I don't know how granular or accurate it is.
Mon, 06 Jan 2014 21:49:27 +0000
The core timer itself is really cool. Because it's really simple. It's a 32 bit counter that counts up at 40MHz. There is a 'compare' register, and when the two match, you can get an interrupt.
That's about it.
Oh, and you can read and write both registers.
So, as long as only one 'thing' is using the compare register, you can get interrupts that fire quite accurately. That's what the SoftPWMServo library does. There is some jitter introduced because the library is written to handle a number of use cases that I thought important. If you narrowed your requirements down (like in this case) you can probably use the same technique with reduced jitter.
Of course, doing anything in software is going to have jitter, so make sure you have a good way of constantly monitoring the jitter of your system to understand if any changes you made push you over your limit or not.
*Brian
Mon, 06 Jan 2014 21:53:00 +0000
Isn't the millis() counter using the core timer? Won't that add a little jitter at high frequencies?
Mon, 06 Jan 2014 22:17:48 +0000
So I've been trying to follow the SoftPMWServo library and understand its mode of operation and have a few questions that hopefully someone can answer. Specifically, is the variable "NextTime" something that I should try to expose (see below) and adjust to establish a delay or am I going about this in the wrong way?
I'm struggling with where exactly I might modify the library to introduce the delay I'm after. As for the jitter that is observed with the SoftPMWLibrary, at present it seems to be well with the tolerance levels that I'm after so I'm hopeful that a small modification can help me arrive at a solution.
Cheers,
Brian
########
uint32_t HandlePWMServo(uint32_t CurrentCount)
{
uint32_t NextTime = 0; // The number of CoreTimer counts into the future when our next edge should occur
uint32_t OldPeriod; // The CoreTimer value that caused the ISR to fire
static ChanType * CurChanP = NULL; // Pointer to the current channel we're operating on
bool DoItAgain = false; // True if we don't have time to leave the ISR and come back in
uint32_t NextTimeAcc = CurrentCount; // Records the sum of NextTime values while we stay in the do-while loop
Mon, 06 Jan 2014 22:40:51 +0000
Isn't the millis() counter using the core timer? Won't that add a little jitter at high frequencies?
I'm pretty sure that the millis counter simply reads the current core timer value. It never writes it, and it never uses the core timer compare register.
As many different software entities can read the core timer registers as you want. As long as nobody writes it, all of them will be able to keep track of elapsed time.
I could be wrong on this (I can't check the code at the moment), but I think we're OK.
*Brian
Mon, 06 Jan 2014 22:56:56 +0000
The millis() returns the contents of a variable. That variable is incremented once a millisecond by the millisecondCoreTimerService. So I would expect that once a millisecond the CT would jitter slightly as that extra code executes - that is if the millisecondCoreTimerService coincides with your service of course - which as yours is a higher frequency and an integer multiple of the millisecondCoreTimerService it would either never do it or do it every single millisecond, depending on when the service is registered.
Or that's my understanding of it anyway...
Tue, 07 Jan 2014 04:25:27 +0000
Yes, I see what you're saying now. Yes, there will be a small amount of jitter ever 1ms because of the 1ms counter. That code is pretty fast thought, so it doesn't impact the servo pulses in an observable way. (at least with the servos I'm running)
*Brian
Tue, 07 Jan 2014 04:31:31 +0000
Brian,
Yes, NextTime is what you want to modify to schedule the core timer interrupt to happen at a certain time in the future. When the CoreTimer handler is called, it is passed the current value of the core timer (CurrentCount in my code). So that is the 'zero' point in time - when we were called. We then return a new value from the handler (NextTimeAcc in my case) which is the value of the CoreTimer when we want to be called next, which will be the time that we were called (CurrentCount) plus whatever delay we want before our next invocation.
*Brian