Feed on
Posts
Comments

Following from Part 6 we moved to the ATtiny44A, using voltage dividers/level shifters to communicate with the SRAM when the ATtiny44A was at 5 volts. In this part we will add some code to calculate the baseline reading when no load is connected and modify the way our USB data transfer works by not emulating keyboard any more so we can transfer the data faster.

Here’s how the breadboards look at the moment.

Calculate baseline reading

Now that I’ve tested that everything works well we can make it calculate the baseline reading when we press the button. Most of the code needed, we can take from the SATVL.

volatile boolean f_pin = 0; // Pin change variable
int baseline = 0; // x * 0.001074 = ~x mV baseline
...
// Pin change interrupt
ISR(PCINT0_vect) {
  f_pin = 1; // Set global button pressed (or released) flag
}

Just our global variables and our ISR above.

int usbConnected = PINA & (1<<usbminusPin);
if (usbConnected) { // Send data from EEPROM to USB
  ...
}
else {
  f_pin = 0;
  setup_watchdog(T500MS);
  system_sleep();
  turnOffwatchdog();

  cbi(GIMSK,PCIE0); // Disable pin change interrupt
  DDRA |= (1<<PA5); // SPI DO output

  if (f_pin == 0) { // Button held down, record the baseline reading
    ...
    // Update the baseline
    baseline = (adcHighbyte << 8) | adcLowbyte; // Blink LED to confirm

    blinkLed(3, T128MS);
  }

  // Button let go, start logging
  else if (f_pin == 1) {
    ...
  }
}
sbi(GIMSK,PCIE0); // Enable pin change interrupt

Once button is pressed or held, we sleep for 500ms and when we wake up we disable the pin change interrupt just so no more buttons presses change anything. If the button was held down we update the baseline variable and blink 3 times otherwise if the button was pushed and released we start logging. We re-enable the pin change interrupt once we are done.

When we want to start logging, we have to firstly plug the load voltage in, hold the button down for 1 second to record the baseline, plug in the load and then press the button to start logging.

Update the USB data transfer methods

So when you have 4096 readings being transferred via the emulated keyboard, it does take a long time and it also doesn’t let you use the computer either. I was looking around for another way to transfer the data and I found a nice tutorial at codeandlife.com where they used a program to communicate with the ATtiny.

Makefile issues

I decided to try this out on my ATtiny85 first and everything seemed to work well however when trying this out with the ATtiny44A it didn’t work; I tried changing bits and pieces everywhere without success. Eventually I had a look at the usbdrv folder and noticed that the usbdrv.o file was dated a few days ago. I deleted this file, recompiled and then everything worked!

To ensure this doesn’t happen again, in the makefile I have added the below which will wipe all compiled .o and .lst files from the usbdrv folder.

VUSBDIR = usbdrv

clean_list :
@echo
@echo $(MSG_CLEANING)
...
$(REMOVE) $(SRC:%.c=$(VUSBDIR)/*.o)
$(REMOVE) $(SRC:%.c=$(VUSBDIR)/*.lst)

Changes to V-USB code

First we set up the global variables we need.

boolean inusbTransfer = true;
uint16_t readCount = 0;
uint16_t endCount = 4096;

// V-USB Transfer variables
#define USB_TRANSFER 1
static uchar replyBuf[16];
static uchar *nextDigit;
static uchar dataReceived = 0, dataLength = 0; // for USB_DATA_IN

USB_TRANSFER is the data that our computer program will send to the ATtiny to ask for more data to send, replyBuf is our buffer and nextDigit is our pointer.

// Convert a number to ASCII and add it to the replyBuf variable (it will only between 0 to 125)
void numberToascii(int number) {
  uchar diviser = 100;
  if ((number / diviser) != 0) {
    *++nextDigit = (number / diviser) + 48;
    *++nextDigit = ((number % diviser) / 10) + 48;
  }
  else if ((number / 10) != 0){
    *++nextDigit = (number / 10) + 48;
  }
  *++nextDigit = (number % 10) + 48;
}

The function above converts a number to ASCII and adds it to our buffer.

// This gets called when custom control message is received
USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) {
  usbRequest_t *rq = (void *)data; // cast data to correct type

  if (rq->bRequest == USB_TRANSFER) { // custom command is in the bRequest field
    nextDigit = &replyBuf[-1];
    if (readCount < endCount) {
      int checkforZero = sendnextDigit(readCount);
      if (checkforZero == true) { // End if 0
        inusbTransfer = false;
      }
      readCount = readCount + 2;
    }
    else {
      inusbTransfer = false;
    }
    usbMsgPtr = replyBuf;
    return sizeof(replyBuf);
  }
  return 0;
}

// This gets called when data is sent from PC to the device
USB_PUBLIC uchar usbFunctionWrite(uchar *data, uchar len) {
  uchar i;

  for(i = 0; dataReceived < dataLength && i < len; i++, dataReceived++)
    replyBuf[dataReceived] = data[i];

  return (dataReceived == dataLength); // 1 if we received it all, 0 if not
}

// Transfers the EEPROM data to USB.
void transferData (void) {

  // Reset variables
  inusbTransfer = true;
  readCount = 0;

  while (inusbTransfer == true) {
    usbPoll();
  }
}

The functions above are the bulk of the data transfer process. We still run the transferdata function like we used to however in the while loop all we do is poll the USB. When our computer program runs it sends USB_TRANSFER (1) to the ATtiny at which time it runs usbFunctionSetup to see if the data received matches anything we are looking for and that’s where we moved the call to the sendnextDigit function which send the next reading from the SRAM.

When our computer program grabs this reading, it will once again send the USB_TRANSFER (1) and so on. If you look at it as a whole, it’s not too complex at all.

Data extracting program

I will be skipping most of the code which handles the inner workings of the USB as no changes needed to be made to that.

usb_dev_handle *handle = NULL;
int nBytes = 0;
char buffer[256];

if (argc < 2) {
  printf("Usage:\n");
  printf("sclextract.exe <filename>\n");
  exit(1);
}

FILE *pFile = fopen(argv[1], "wb");
handle = usbOpenDevice(0x16C0, "insidegadgets.com", 0x05DC, "SACL");

if (handle == NULL) {
  fprintf(stderr, "Could not find USB device!\n");
  exit(1);
}

We have our USB handle, nBytes which counts the number of bytes we have received and our buffer to store what we receive from the ATtiny. One of our arguments is the filename where we will write the data to and we open that file to write to. You can see that we specify the USB device to open which is – insidegadgets.com SATCL, the name can be changed in the usbconfig.h file.

printf("Transferring data...\n");

nBytes = 1;
while (nBytes > 0) {
  nBytes = usb_control_msg(handle,
  USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
  USB_TRANSFER, 0, 0, (char *)buffer, sizeof(buffer), 5000);

  if (nBytes > 0) {
    printf("%s", buffer);
    fwrite(buffer , 1 , strlen(buffer) , pFile );
  }
}

if (nBytes < 0)
  printf("Transfer complete\n");

usb_close(handle);
fclose (pFile);

We begin the transfer process in which we send USB_TRANSFER (1), wait back from the response, count the number of bytes received and store the data received to the buffer. If there were bytes received we print them out and write to the file, otherwise we exit.

So that wraps up the Standalone Current Logger and you can download SACL v0.4 here. All in all it was an interesting project and I think so far it’s been the biggest project I’ve put together which is mostly because of the opamp.

Improvements which can be made include:

  • Include the functionality to set logging times like the SATVL
  • Include the functionality to check for the SRAM size like the SATVL
  • Perhaps applying averaging so every 10 samples are averaged into 1
  • Use a dedicated ADC chip
  • Do more testing with the opamp to see if we can get more accuracy than 0.1mA

Leave a Reply