C2
1
I was trying to write a pulse burst generator code, but ran into some issues with timing. For example:
if (micros() - time >= 20){
time = micros();
PORTD = B10000000;
PORTD = B00000000;
The code executes, but there are regular miss-timings that make subsequent pulses either early or late by 2 or 4 µs (and I think I even catch some pulses that are offset by one core clock cycle, but not sure). I also tried incrementing the "time" variable by 20 and comparing micros() >= time directly, and same basic result.
In the end, the code should be able to take inputs to several variables that define:
Burst repetition rate (with 1 pulse per burst, 125 ns)
Number of pulses per burst (something reasonable 65k?)
pulse width (minimum possible, 62.5 ns)
pulse repetition rate (down to 125 ns would be ideal)
And maybe later even arbitrary pulse intervals...
I've read that the reason for the miss-timing is due to interrupts with updating the timers and setting overflow counters, etc. I can convince myself that this makes sense, but not really since I'd still expect that stuff to happen very regularly so I could offset my variables to compensate...
So, I then thought I'd have a go at setting Timer2 to count every core clock cycle and use my same approach as above to poll the timer count, and then do something. I could not figure out how to set up the timer, much less use it once configured. I think I came across bitwise coding and got confused (I read so many sites about setting up the timers, but just cant get around it). Alas, I suspect that even if I could set up the timer the way I want, I'd still have the same issue I'm having with polling micros().
So this is where I'm stuck.
The time jitter comes from the way you capture the time for the next event
if (micros() - time >= 20){
time = micros();
PORTD = B10000000;
PORTD = B00000000;
Try
if (micros() - time >= 20){
time = time + 20;
PORTD = B10000000;
PORTD = B00000000;
}
as micros() in the if statement will be different than micros() in the time = statement
If the time check is within loop(), there will also be some jitter just from the code that runs in the background as part of loop().
Try this for a 1.333MHz burst of 50 cycles (50% duty) with 10µs delay between bursts. Turning off timer0 will prevent it's interrupt interference. Modify to suit:
byte val;
void setup() {
DDRD |= _BV (7); // pinMode (7, OUTPUT);
val = TIMSK0;
}
void loop() {
TIMSK0 = 0; // disable timer0
for (int i = 0; i < 100; i++) {
// PORTD &= ~_BV (7); // digitalWrite (7, LOW);
// __builtin_avr_delay_cycles (5);
// PORTD |= _BV (7); // digitalWrite (7, HIGH);
PIND |= _BV (7); // digitalWrite (7, TOGGLE);
}
__builtin_avr_delay_cycles (160); // for 16MHz CPU, 160 cycles will be 10µs
// TIMSK0 = val; //enable timer0
}
6v6gt
4
A controlled repeated 125ns pulse (8MHz) is not feasible in software on a 16Mhz controller. I'd use some faster board for such a project.
DrDiettrich:
A controlled repeated 125ns pulse (8MHz) is not feasible in software on a 16Mhz controller.
I played with my new Salea clone and writing to PINx, so I have to disagree.
Hardcoded bursts or single pulses are possible with 8MHz.
const byte pinCha0 = 10; // PB2
const byte pinCha1 = 9; // PB1
const byte pinCha2 = 8; // PB0
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(pinCha0, OUTPUT);
pinMode(pinCha1, OUTPUT);
pinMode(pinCha2, OUTPUT);
Serial.begin(115200);
Serial.println(F("Salea DigitalWrite 1.00.000"));
}
void loop() {
PINB = 1;
PINB = 1;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
PINB = 4;
digitalWrite(pinCha0, HIGH);
digitalWrite(pinCha0, LOW);
digitalWrite(pinCha0, HIGH);
digitalWrite(pinCha0, LOW);
}
Such code works only with sequences of a hard coded length. But the wanted flexibility, or 64k pulses/train, are very hard to implement without loops.
I only disagree with the 'not feasible'. It is.
dlloyd
9
The Arduino Due might be better suited ... only there's an overwhelming amount of registers to work with. There's an interesting register for the SPI ... "SPI Chip Select Register" which has a "Delay Between Consecutive Transfers" field. The description states "When DLYBCT equals zero, no delay between consecutive transfers is inserted and the clock keeps its duty cycle over the character transfers."
In theory (I haven't tried it), could set the SPI clock to 8MHz (or higher) then just transfer data that creates the required pulse train. The data itself controls the pulse width and number of pulses ... all software programmable.
C2
10
This is awesome! Thanks all. I think it is the timer0 loop activity that was causing some of my issue. With the code and tips provided, I'm close enough to getting what I wanted. dlloyd, I was also thinking of the same type of thing as the analogy I was going to make was to akin to high speed morse code. As that thought set in, I started noodling on how that could be done as a data transmission.
dlloyd
11
I just tested a repeating pulse burst using SPI on the DUE with no delay between transfers ... it works!
The test pattern produces a growing pulse width ending with 10101010 and repeats without any delay. Still need to disable the millis() timer during transfer (not done).
MOSI output rate: 59.524ns/bit (16.8MHz SPI Clock):
#include <SPI.h>
byte pattern[] = {0x5B, 0xBB, 0xDF, 0xAA};
byte count = 0;
void setup() {
Serial.begin(115200);
SPI.begin(10);
SPI.setClockDivider(10, 5); // 16.8MHz Clock, 59.524ns/bit
REG_SPI0_CSR &= 0x00FFFFFF; // DLYBCT = 0
}
void loop() {
while (1) {
if ((REG_SPI0_SR & 2) != 0) { // transmit when data register empty
REG_SPI0_TDR = pattern[count & 3];
count++;
}
}
}
C2
13
I managed to get the SPI bus working, but not able to remove the delay between transfers (DLYBCT=0?), nor get the IF condition to work. I'm using mostly a pro mini or Uno. Is this not available? I ended up sending an 8 bit word, then an 8 bit length of delay, etc... in the while loop via SPI.transfer(pattern[count & 3]) or just SPI.transfer(some value).
Also, it seems that I will be able to get 250 ns minimum pulse width resolution since I have to alternate each SPI clock to realize an actual pulse (with DIV2 being 8 MHz clock, the fastest rate).
My programming is weak, what is "& 3"?
I can see using the loop as a function with an IRQ triggered through serial to change a flag that would break out of the loop. I could configure the pulse by sending the array value via serial, then call the function again.
Thanks for all the tips.
You should reduce your expectations if you want to use an 8 bit Arduino.
dlloyd
15
DLYBCT is only available on the DUE. I don't think its possible on an AVR to stream the SPI without delay between transfers. The &3 was only a bit mask to look only at the 2 least significant bits, hence the counter becomes a 2-bit counter.
With 16.8 MHz clock on the DUE, there isn't many cycles left to do much between transfers. Maybe 2-4 cycles available per 8-bit transfer. However, on the DUE, the transfer size could be increased up to 16-bit. Also there's DMA available for SPI which I haven't investigated.
On the AVR, you might want to try using the USART in SPI mode. I see it doesn't use a SS pin, so maybe there isn't any delay between transfers. I haven't tested this.
The USART can be used in SPI mode which does have a buffer to prevent the delay between transfers.
dlloyd
17
Tested MSPIM on UNO ... no delay between transfers works up to 4MHz SPI clock.
pulse repetition interval = 250 ns
byte pattern[] = {0x5B, 0xBB, 0xDF, 0xAA};
byte count = 0;
void setup() {
USART_MSPIM_Init();
}
void loop() {
while (1) {
if ((UCSR0A & 32) != 0) { // transmit when data register empty
UDR0 = pattern[count & 3];
count++;
}
}
}
void USART_MSPIM_Init()
{
UBRR0H = 0;
UBRR0L = 0;
DDRD |= _BV (4); // XCK as output enables master mode
UCSR0C = (1<<UMSEL01)|(1<<UMSEL00)|(0<<UCPHA0)|(0<<UCPOL0); // Master SPI, mode 0
UCSR0B = (1<<RXEN0)|(1<<TXEN0); // Enable receiver and transmitter
UBRR0L = 1; // 4MHz XCK on pin 4
}
C2
18
You've done it! A 4 MHz arbitrary pulse pattern generator. WooHoo!!!
I think this will disable the hardware serial of the chip, so I'm wondering how to feed the byte pattern.
Maybe an ISR triggered by a button to set a flag, breaking out of the loop (which I'd move to a function), and then use software serial on another pin, or even manual input via a keypad and an LCD to see what's going on.
Thanks!
dlloyd
19
Maybe setup the main SPI as a slave, then monitor its shift register receive buffer. The device sending the data would be the master and could use any clock rate needed up to 8MHz.
The receivers are slower, need less than 4MHz clock rate.