Feed on
Posts
Comments

Today I’ll be showing how we can integrate the Standalone Temperature Logger (SATL) into the V-USB Test program we have. It’s taken me a little while to find my way around V-USB and its kinks.

Schematic

The schematic has been updated to include the USB port. Another change is that when the LED turns on it also turns on the thermistor, before we used to do this with the dataPin.

Cleaning up the V-USB code

The first thing I’ve done is copy over our setup.c file from the SATL WinAVR to our project and then move everything related to V-USB setup or procedures (except the sending of keys) to our setup.c file which now leaves us with just two functions in main.c (evaluateADC and main).

Next I would like to cut down on the main’s USB setup code, lets make a new function called startUSB() and place all the code below in it.

uchar   i;
uchar   calibrationValue;

calibrationValue = eeprom_read_byte(0); /* calibration value from last time */
if(calibrationValue != 0xff){
  OSCCAL = calibrationValue;
}
odDebugInit();
usbDeviceDisconnect();
for(i=0;i<20;i++){  /* 300 ms disconnect */
  _delay_ms(15);
}
usbDeviceConnect();
DDRB |= 1 << BIT_LED;   /* output for LED */
PORTB |= 1 << BIT_KEY;  /* pull-up on key input */
wdt_enable(WDTO_1S);
timerInit();
adcInit();
usbInit();
sei();

Now for things we can get rid of:

  • We can remove the variable i and the loop of _delay_ms(15). It says it’s delaying for 300ms so lets just put that in instead
  • We take out the calibrationValue part as there is no real use for this. The only thing I can think of is that it might save a few milliseconds in load time but that’s not important for this project
  • We can take out odDebugInit() as no debugging is needed and remove DDRB and PORTB references
  • Remove wdt_enable(WDTO_1S), really this isn’t needed at all. It’s odd that they enable the watchdog timer and then keep having to reset it in the main USB loop, why not just disable it all together?
  • Take out timerInit(), adcInit() and their functions as we’ll do these in our setup function. Also remove the sei().
// Start USB communications
void startUSB(void) {
  usbDeviceDisconnect();
  _delay_ms(300); // 300 ms disconnect
  usbDeviceConnect();
  usbInit();
}

Ah, now we have something a lot neater.

void usbEventResetReady(void) {
  calibrateOscillator();
  eeprom_write_byte(0, OSCCAL);   /* store the calibrated value in EEPROM */
}

We remove the EEPROM write reference as it’s no longer needed.

sbi(TCCR1, CS12); // Insidegadgets fix
sbi(TCCR1, CS11);
sbi(TCCR1, CS10);
sbi(TCCR1, PWM1A);

Now for a general clean up of the setup.c file – as we don’t use Timer1 in our project we can just remove it from our setup function.

DDRB &= ~((1<<ledPin) | (1<<dataPin)); // Set outputs to inputs to save some power
...
DDRB |= ((1<<ledPin) | (1<<dataPin)); // Set those outputs back to outputs

Lastly we remove any references to the dataPin as it doesn’t exist anymore in this version of the SATL.

Changing the USB transferring data functions

We need to change the evaluateADC function to read from the EEPROM. Lets break it up into 2 functions, one will read the EEPROM and convert the values read to HID values and the other will transfer the data.

// Send the next digit from the EEPROM address
int sendnextDigit(int eepromAddress) {
  uchar digit;

  int value = (eeprom_read_byte((uint8_t*) eepromAddress) - 130)

  if (value == 0) {
    return 1;
  }

  value -= 130; // Minus 130 from value to get real temperature

We just read from the EEPROM here. If the value read from the EEPROM is 0 then we know that the end of the temperature logging has been reached. (If you remember when logging temperature we log the temperature and then write 0 to the next EEPROM address in case we stop logging temperature or the battery cuts out, etc our fail safe).

  // Check if value is less than 0, if so convert it to positive number and include the negative sign at the end
  boolean isNegative = false;
  if (value < 0) {
    value = value - (value * 2);
    isNegative = true;
  }
  nextDigit = &valueBuffer[sizeof(valueBuffer)];
  *--nextDigit = 0xff;
  *--nextDigit = 0;
  *--nextDigit = KEY_RETURN;

  // Break up the number and convert to USB HID values
  do{
    digit = value % 10;
    value /= 10;
    *--nextDigit = 0;
    if (digit == 0){
      *--nextDigit = KEY_0;
    }
    else{
      *--nextDigit = KEY_1 - 1 + digit;
    }
  } while (value != 0);

  if (isNegative == true) {
    *--nextDigit = 0;
    *--nextDigit = KEY_MINUS;
  }

  return 0;
}

We actually re-use the original EasyLogger example code because it works. Basically it reads one digit at a time of our value (e.g 123) and breaks it up from the right hand to left hand, so it gets broken up to 3, 2, 1. Because we are going reverse with the pointer the 321 becomes 123. You’ll also notice that 0 is always placed between numbers (*–nextDigit = 0), I’m assuming that this is a convention needed in the HID interface.

Because the code from EasyLogger doesn’t handle negative numbers, we firstly check if the value is a negative number. If it is we convert it to a positive number and then include the negative sign at the end of converting the number. You can find which number corresponds to which key by looking at page 53 of the HID Usage Tables PDF.

Now to the transfer of data.

void transferData(void) {
  boolean inusbTransfer = true;
  int readCount = 1;
  while (inusbTransfer == true) {
    usbPoll();
    if(usbInterruptIsReady() && nextDigit != NULL){ // We can send another digit
      buildReport();
      usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
      if(*++nextDigit == 0xff) { // This was terminator character
        nextDigit = NULL;
      }
    }

We re-use the EasyLogger code once again, we have a while loop to exit the USB transfer once it’s complete.

    if (nextDigit == NULL) {
      if (readCount < 512) {
        int checkforZero = sendnextDigit(readCount);
        if (checkforZero == true) { // End if 0
          inusbTransfer = false;
        }
        readCount++;
      }
      else {
        inusbTransfer = false;
      }
    }
  }
}

Now we just check if nextDigit is equal to NULL which means it’s empty so go ahead and load the next data to send. We check that we aren’t going over the 512 addresses in the ATtiny85 EEPROM and then also check if the value 1 is received back from the sendnextDigit function, if so we exit the USB transfer.

So why did we not just stick this all into the if (*++nextDigit == 0xff) {  …  } section? It’s because that section is only executed once data has been sent at least once before. When we start the USB transfer no data is sent yet so nextDigit is NULL but since no data is sent we’ll never reach that section of code because the if statement checks nextDigit != NULL.

Modifications to the main file
int thermistorPin = 3; // Analog input 3
#define ledPin PB0 // Output
#define buttonPin PB4 // Input
#define usbminusPin PB1 // USB D-

#define F_CPU 16500000

#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <math.h>
#include <util/delay.h>
#include "usbdrv/usbdrv.h"
#include "usbdrv/oddebug.h"
#include "setup.c"
...

We’ll go ahead and copy our entire main.c file from the SATL_WinAVR to our current project and merge the includes. Notice that we’ll keep the F_CPU at 16.5MHz. We have 2 new pins: usbminusanalogPin and usbminusPin, they will be used to detect the presence of the USB connection.

#define usbDeviceDisconnect()   (USBDDR |= (1<<USBMINUS))

What I’ve been able to find is that when we start the USB the first call is to the usbDeviceDisconnect() function which actually sets the USB minus pin as an input as shown above. Potentially this means that we should be able to check whether the USB is connected to our board.

int main(void) {
  setup();

  DDRB |= (1<<ledPin); // Set Outputs
  DDRB &= ~((1<<buttonPin) | (1<<usbminusPin)); // Set Inputs

  // Pin interrupt setup
  sbi(GIMSK,PCIE); // Enable pin change interrupt
  sbi(PCMSK,PCINT4); // Apply interrupt to button pin

We have our main function now and you can see we have added the usbminusPin as an input.

while(1) {
// Wait for input
...
// First test we are connected via USB by checking if the D- pin is high
int usbConnected = PINB & (1<<usbminusPin);
if (usbConnected) { // Send data from EEPROM to USB
  PORTB |= (1<<ledPin);
  startUSB();
  transferData();
  PORTB &= ~(1<<ledPin);
}
else {
// Button was pressed
...

Now instead of testing if we are connected to the Arduino when the button is pressed, we just test if we are connected to the PC. Because of the zener diode and resistors in between, the voltage can vary slightly between 2.5V to 3.6V however when reading the pin, it’s enough voltage to change it to be high. If a USB connection is detected when the button is pressed we turn on the LED, call the 2 USB functions and then turn off the LED.

// Log Temperature
if (functionSelect == 1) {
  PORTB |= (1<<ledPin); // Turn on LED which also turns on the thermistor
  int tempValue = (int) Thermistor(analogRead(thermistorPin));
  PORTB &= ~(1<<ledPin); // OFF

Last thing we do is take out all references to the dataPin. In the place where the thermistor used to turn on from the dataPin, we change it to be the ledPin and that’s all the work that needs to be done.

Working out the USB Kinks

Before we actually test the whole system I wanted to make sure the USB section really worked so I made the USB code always print an incrementing number to the PC.

I found two issues, first the counter would start at about 20 or so. Second after about a few increments it would either sort of freeze (the LED stayed on showing it was stuck in the sending data to PC code) or would repeat a single digit of the number repeatedly.

My work around for the first issue was to actually send the PC empty data for about 3-4 seconds as this seems to correct it.

void startUSB(void) {
...
for (int emptyData = 0; emptyData < 700; emptyData++) {
  usbPoll();
  if(usbInterruptIsReady()){
    reportBuffer[0] = 0;
    reportBuffer[1] = 0;
    usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
  }
  _delay_ms(5);
}
...

So we just add it in the startUSB function, fill in some of the reportBuffer with zeros and always have it send that to the PC if the USB interrupt is ready.

For my second issue, after lots of testing it appears that you can’t have any other interrupts on whilst you are sending data via USB. It makes sense because USB timing is precise but what I didn’t realise is that I had Timer0 switched on in order to use the millis() function, ah!

if (usbConnected) { // Send data from EEPROM to USB
  cbi(TIMSK, TOIE0); // Turn off Timer0
  PORTB |= (1<<ledPin);
  startUSB();
  transferData();
  PORTB &= ~(1<<ledPin);
  sbi(TIMSK, TOIE0); // Turn on Timer0
}

All we need to do is use cbi and sbi to turn off Timer0, run the USB code and then turn it back on. We turned on Timer0 in the setup function in setup.c. Now we are really done!

Download the source here: SATL_VUSB_v2.0_Beta_2

Test run

Firstly we connect it to the USB and remove the USB D- pin cable so that it’s just using the 5V power but not connected as a USB device. We then set it to log some temperature and then re-connect the USB D- pin, press the button and it transfers the data to the PC. It all works well 🙂

Leave a Reply