Saturday, February 22, 2014

Logic Analyzer - part2


Here is a new logic capture of exactly the same circuit using my new code that I just wrote. It's interrupt timer based and uses the usb_serial library to stream the data back to the computer at 62.5 kHz. That figure comes from the 16 MHz clock triggering a timer overflow every 256 clocks.

This capture came out absolutely perfect, compare it to the previous one - it's beautiful! show's how misleading a capture can be without enough samples. I also put an error detector into the code which should light up the LED on the board if a sample is missed from the usb_serial communication taking too much time - this should tell me if try running the capture with too many samples per second although I may need more error detection.

The first thing I did was run the usb_serial tx benchmark and then wrote my own code to send data - at the start it was getting 1/3'd the speed but that's because I was sending one character at a time rather than sending a reasonable sized buffers.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stdint.h>

#include "usb_serial.h"

#define LED_CONFIG    (DDRD |= (1<<6))
#define LED_ON        (PORTD &= ~(1<<6))
#define LED_OFF        (PORTD |= (1<<6))

#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))

volatile int n;
uint8_t buf[64];

int main(void) {
  CPU_PRESCALE(0);
 
  LED_CONFIG;
  LED_OFF;
 
  // set the F port to act as input pins without a pullup resistor
  PORTF = 0;
  DDRF = 0;
 
  /* ******************* */
  // at 16 MHz the fastest timer will overflow at 16/256 = 62.5 kHz
  // Setup the registers for Normal mode:
 
  // TCCR0A is [COMOA1,0,COM0B1,0,-,-,WGM01,0]
  // compare output match bits are all zero
  // waveform generation mode is zero to select Normal mode
  TCCR0A = 0;
 
  // TCCR0B is [FOC0A,B,-,-,WGM02,CS02,1,0]
  // Force output compare is zero
  // Clock select is 0,0,1 for the non-prescaled clock
  TCCR0B = 1;
 
  // Timer/counter interrupt mask register
  // the TOIE0 bit enables interrupts when the timer overflows
  TIMSK0 = (1<<TOIE0);
 
  // Timer counter register is reset to zero
  TCNT0 = 0;
 
  /* ******************* */
 
  buf[0] = 0xC0;
  buf[1] = 0xB0;
  n = 2;
 
  // USB initialization routine taken from example.c
  usb_init();
  while (!usb_configured());
  _delay_ms(1000);
  // wait for the user to run their terminal emulator program
  // which sets DTR to indicate it is ready to receive.
  while (!(usb_serial_get_control() & USB_SERIAL_DTR)) /* wait */ ;
  // discard anything that was received prior.  Sometimes the
        // operating system or other software will send a modem
        // "AT command", which can still be buffered.
 
  // enable global interrupts
  sei();
 
  while(1) {
    if(n == 64) {
      usb_serial_write(buf, 64);
      usb_serial_flush_output();
      n = 2;
    }
  }
}

ISR(TIMER0_OVF_vect) {
  if(n <= 63) {
    buf[n] = PINF;
    n++;
  }
  else {
    // looks like the main loop didn't manage to write in time and we missed a sample
    LED_ON;
  }
}
 

I used the following script on my computer to stream the data from the virtual serial device into a file (withour having to wait for line buffering):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>

#define BLOCKSIZE 1024

int main(int argc, char **argv)
{
    char buf[BLOCKSIZE];
    int port;
    long n, m, sum=0, dotcount=0;
    struct termios settings;
    FILE *dump;

    if (argc < 3) {
        fprintf(stderr, "Usage:   serial_dump <port> <filename>\n");
        #if #system(linux)
        fprintf(stderr, "Example: serial_dump /dev/ttyACM0 <filename>\n");
        #else
        fprintf(stderr, "Example: serial_dump /dev/cu.usbmodem12341 <filename>\n");
        #endif
        return 1;
    }
   
    // Open the output file
    dump = fopen(argv[2], "w");
    if(!dump) {
      fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
      return 1;
    }
   
    // Open the serial port
    port = open(argv[1], O_RDONLY);
    if (port < 0) {
        fprintf(stderr, "Unable to open %s for reading\n", argv[1]);
        return 1;
    }

    // Configure the port
    tcgetattr(port, &settings);
    cfmakeraw(&settings);
    tcsetattr(port, TCSANOW, &settings);

    m = 0;
    while (1) {
      n = read(port, buf+m, sizeof(buf)-m);
      m += n;
     
      if (n < 1) {
        fprintf(stderr, "error reading from %s\n", argv[1]);
        break;
      }
     
      if (m == sizeof(buf)) {
        fwrite(buf, sizeof(buf), 1, dump);
        fflush(dump);
        m = 0;
      }
    }
   
    close(port);
   
    return 0;
}


And... I just noticed that this throws away the last <BLOCKSIZE bytes of the data without writing them, better fix that for next time.. (although this is not a problem in the way I current use the program)

and as the packets themselves include a "magic" (this may be considered superstitious and I might remove it in future) to detect if any bytes were lots I used the following to convert the dump into something I could open in sigrok:  

#include <stdio.h>

void main(void) {
  int i;
  int b;
 
  i = 0;
  while((b = getchar()) != EOF) {
    if(i == 0) {
      if(b != 0xC0) {
    fprintf(stderr, "ERR");
    return;
      }
    }
    else if(i == 1) {
      if(b != 0xB0) {
    fprintf(stderr, "ERR!");
    return;
      }
    }
    else {
      putchar(b);
      fflush(stdout);
    }
    i++;
    if(i == 64) {
      i = 0;
    }
  }
  fflush(stdout);
}
 


No comments:

Post a Comment