I'm having issues using a shift register for serial comm over a 100khz stream. I'm using a sn74hc165n shift register.
There can be no breaks in between each byte sent so i setup a few timers to keep everything flowing nicely.
The issue i have is that there is a very quick data pulse when the parallel load pin is pulsed.
I have attached a few images to show the issue. The first two images are clock and data signals. The third is data and parallel load signals.
Timer 0 - 100khz clock
Timer 1 - Parallel load shift register
Timer 2 - Prep data for parallel out
I'm using the code below for debugging purposes. The main loop simply stuffs a two byte pattern into a byte array.
TIMER2_COMPB_vect pulls data from byte array and outputs on pins a0-a5 and 8-9.
TIMER1_COMPB_vect enables clock inhibit, pulses parallel load and then disables clock inhibit. The clock inhibit section was just added as part of my troubleshooting but the output didn't change with the addition of the code.
#include <avr/interrupt.h>
#define HALFPERIODTICKS 10
byte* buffer_data;
byte buffer_capacity;
volatile byte buffer_position;
volatile byte buffer_length;
ISR(TIMER2_COMPB_vect) {
byte data = 0;
if(buffer_length){
data = buffer_data[buffer_position];
buffer_position = (buffer_position+1) & 63;
buffer_length--;
}
PORTB = (PORTB & B11111100) | (data >> 6);
PORTC = (PORTC & B11000000) | (data & B00111111);
}
volatile byte sreg;
ISR(TIMER1_COMPB_vect, ISR_NAKED){
//sreg = SREG;
//PORTD = (PORTD & B11110111);
//PORTD = PORTD | B00001000;
//try hardware xor- faster??
//Clock Inhibit
PIND = B10000000;
//Parallel load
PIND = B00001000;
PIND = B00001000;
//Clock Inhibit
PIND = B10000000;
//SREG = sreg;
reti();
}
void clearDiskTimers()
{
TCCR0B &= ~((1<<CS02) | (1<<CS01) | (1<<CS00));
TCCR1B &= ~((1<<CS12) | (1<<CS11) | (1<<CS10));
TCCR2B &= ~((1<<CS22) | (1<<CS21) | (1<<CS20));
//Ensure parallel load high
PORTD = PORTD | B00001000;
//Ensure clock High
PORTD = PORTD | B01000000;
}
void setupDiskTimers()
{
//no interrupts
cli();
//********* Load Shift Register Timer **********************
// reset settings of Timer/Counter register 1
TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0) | (1<<WGM11) | (1<<WGM10));
TCCR1B &= ~((1<<WGM13) | (1<<WGM12) | (1<<CS12) | (1<<CS11) | (1<<CS10));
// set compare match output A to normal
TCCR1A |= (0<<COM1A1) | (0<<COM1A0);
//// set compare match output B to normal
TCCR1A |= (0<<COM1B1) | (0<<COM1B0);
// set waveform generation mode to CTC (Clear Counter on Match)
TCCR1A |= (0<<WGM11) | (0<<WGM10);
TCCR1B |= (0<<WGM13) | (1<<WGM12);
// set clock prescaler to clock/8
TCCR1B |= (0<<CS12) | (1<<CS11) | (0<<CS10);
// set output compare register B
// we want to parallel load shift register
OCR1B = (HALFPERIODTICKS * 15);
// set output compare register A
// reset timer after all 8 bits have been sent
OCR1A = (HALFPERIODTICKS * 16)- 1;
//enable interrupt on match for output B
TIMSK1 = (1<<OCIE1B);
//set initial count to 0
TCNT1 = 0;
//***************************************************
//********* 100 KHZ clock**********************
// reset settings of Timer/Counter register 0
TCCR0A &= ~((1<<COM0A1) | (1<<COM0A0) | (1<<WGM01) | (1<<WGM00));
TCCR0B &= ~((1<<WGM02) | (1<<CS02) | (1<<CS01) | (1<<CS00));
// set compare match output A to toggle
TCCR0A |= (0<<COM0A1) | (1<<COM0A0);
// set waveform generation mode to CTC (Clear Counter on Match)
TCCR0A |= (1<<WGM01) | (0<<WGM00);
TCCR0B |= (0<<WGM02);
// set clock prescaler to clock/8
TCCR0B |= (0<<CS02) | (1<<CS01) | (0<<CS00);
// set output compare register A
OCR0A = HALFPERIODTICKS -1;
//set initial count to 0
TCNT0 = 0;
//*****************************************************
//********* Prepare Data For Shit Register**********************
// reset settings of Timer/Counter register 2
TCCR2A &= ~((1<<COM2A1) | (1<<COM2A0) | (1<<WGM21) | (1<<WGM20));
TCCR2B &= ~((1<<WGM22) | (1<<CS22) | (1<<CS21) | (1<<CS20));
// set compare match output A to normal
TCCR2A |= (0<<COM2B1) | (0<<COM2B0);
// set waveform generation mode to CTC (Clear Counter on Match)
TCCR2A |= (1<<WGM21) | (0<<WGM20);
TCCR2B |= (0<<WGM22);
// set clock prescaler to clock/8
TCCR2B |= (0<<CS22) | (1<<CS21) | (0<<CS20);
//set initial count to 0
TCNT2 = 0;
// set output compare register B - we're going to fire an interrupt
// mid way through the byte to prepare for the next byte
OCR2B = (HALFPERIODTICKS * 2);
// set output compare register A
OCR2A = (HALFPERIODTICKS * 16) - 1;
// enable interrupt on match for output B
TIMSK2 = (1<<OCIE2B);
//*****************************************************
//Ensure parallel load high
PORTD = PORTD | B00001000;
//Ensure clock High
PORTD = PORTD | B01000000;
//interrupts
sei();
}
void setup() {
//Setup Parallel load pin
pinMode(3, OUTPUT);
digitalWrite(3, HIGH);
//Setup SR Clock pin
pinMode(6, OUTPUT);
digitalWrite(6, HIGH);
//Setup SR Clock Inhibit pin
pinMode(7, OUTPUT);
digitalWrite(7, LOW);
/****Data*******/
pinMode(A0, OUTPUT);
pinMode(A1, OUTPUT);
pinMode(A2, OUTPUT);
pinMode(A3, OUTPUT);
pinMode(A4, OUTPUT);
pinMode(A5, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
buffer_init();
setupDiskTimers();
}
void loop() {
byte result;
do{
result = buffer_put(B11101010);
}while (result == false);
do{
result = buffer_put(B00000000);
}while (result == false);
}
void buffer_init(){
buffer_data = (byte*)malloc(64);
buffer_capacity = 64;
buffer_position = 0;
buffer_length = 0;
}
void buffer_clear(){
buffer_position = 0;
buffer_length = 0;
}
byte buffer_getSize(){
return buffer_length;
}
byte buffer_getCapacity(){
return buffer_capacity;
}
byte buffer_put(byte in){
if(buffer_length < buffer_capacity){
cli();
buffer_data[(buffer_position+buffer_length) & 63] = in;
buffer_length++;
sei();
return 1;
}
return 0;
}
byte buffer_get(){
byte b = 0;
if(buffer_length){
b = buffer_data[buffer_position];
buffer_position = (buffer_position+1) & 63;
buffer_length--;
}
return b;
}
Graynomad:
Ooo that code looks like a maintenance nightmare watign to happen.
serial comm over a 100khz stream
What type of "serial comm" it looks much like a standard SPI-style clocked data stream. Is that the case? And if so why all the heroic low-level code?
outputs on pins a0-a5 and 8-9.
Or is this a byte stream?
Rob
Yes, Serial Comm aka byte stream.
"In telecommunication and computer science, serial communication is the process of sending data one bit at a time, sequentially, over a communication channel or computer bus."
Apparently, this was caused by a floating Ser input pin on the shift register being affected by the PL input getting pulsed. I tied this pin to ground and the issue appears to have been resolved.
Thanks
Unless you know exactly what you are doing, I wouldn't suggest that your use naked isrs.
I was trying to eliminate as much overhead from the ISR as possible. I was trying to eliminate this being a side effect of the parralel load occuring at the wrong time in the clock cycle.
That's 8 pins and implies to me that you are transmitting a series of bytes in parallel. If not why so many pins?
Can SPI be configured to run at 100khz?
SPI in general can run from DC speeds to 30MHz or more, I'm not sure exactly how slow the Arduino/AVR implementation can be set too though.
Can the Serial or I2C be configured to send bytes without start and stop bits?
Serial no. I2C has a very specific protocol that doesn't have a start bit as such but you have no control over the way it works.
100kHz is really slow, there really shouldn't be a need for direct port manipulation and dozens of direct register accesses, and certainly not a naked ISR, that's a recipe for disaster unless you really know what you are doing.
I think we're going around in circles here, can you tell us exactly what you are talking to and what format it needs? Some data sheets would be nice as well if available.
The project i'm working on is SD card driven replacement for Famicom Disk System. I have the arduino interfacing directly with the famicom ram cart. It's already interfacing with the ram cart using the required control signals: ready, media set, stop motor etc.
If your interested in the specs you can read up on it here -> http://nesdev.com/fdstech.txt it's pretty interesting.
100Khz is slow if you have hardware assistance if youre bit banging serial data the cycles are used up very quickly and you have little time to do anything other then toggle pins and delay. A loop with two digital writes only gets me ~150khz in my testing, add in a few function calls and some logic and the frequency drops dramatically.
Originally, I was loading the rom in realtime from the SD card and calculating/injecting crc's and gap data. Reading from the SD Card, parsing headers and calculating CRC's all the while trying to stream at a constant 96.4khz rate proved difficult. I ended up with segments of code running in the 5us gaps in between half bit periods(I need to manually toggle the clock pin) . While this technique basically works cycle counting compiler generated code and stuffing code in the half bit periods is a bit tedious.
Anyway, I moved away from the realtime processing of CRC and the data not required by nes emulators to pre-processing the rom and storing in a format which is just about ready to stream direct to the ram cart.
Typical of some old floppies though clock and data are encoded into a single serial stream similar to FM1 encoding. We just XOR the clock and data signals and the resultant signal is fed into the ram cart where it detects rising edges in the signal and combined with the known clock rate it is able to decode the data. This is where the 5us half bit periods i was stuffing code into came from... the serial data would be XOR'd in software against both the High and Low states of the clock.
The ram cart expects the serial data in a format as would be read directly from the read/write head in the drive. So, I feed the 100khz clock and the output of the shift register into an XOR IC where it is combined and then can be fed directly into the ram cart. This is why i discounted serial, spi and i2c. SPI didnt provide a clock division compatible, Serial uses stop/start bits andI2C seems to use a basic messaging protocol which would "corrupt" the stream.
As far as ISR_NAKED goes simply toggling pins shouldn't modify anything important. In any case, the overhead of an ISR using arduino libraries seems to be upwards of 50 cycles. When your talking a 5us half bit period 1-3us just to enter an isr can become an issue.
I don't think the design is overly complicated.. I'm sure if you shaved the top off the usart the arduino uses for spi you'd find something like a buffer, a piso shift register and a clock to spit out bits serially. That being said, I'm open to suggestions on a design which allows for streaming directly from SD Card(using SPI) to a 100khz bit stream with no gaps in transmission and keeping in mind the maximum time between any two bits is ~11.5 us.
Just for giggles i located the atmega datasheet and found the spi block diagram. As I suspected, the design very closely relates to how I implemented the serial output. Of course, I don't have status/control registers. Ignoring that your left with clock logic, 8 bit data bus, a piso shift register, logic for pin control and an IRQ line presumably triggered at "end of byte".
If you really care about speed, use a hardware spi/uart to send the data;
If you for some reason have to use software spi, it may make sense to do so without interrupt. It takes about 100 instructions to send a byte via software spi. It takes at least 15 - 20 instructions (interrupt latency) to send a bit using interrupt. So you are actually better off to simply unroll the loop and go without the interrupt.
If you put the SPI in slave mode the clock pin becomes an input. You can connect your 100kHz signal to the CLK and the SPI data will be clocked out using it.