Showing posts with label Benchmark. Show all posts
Showing posts with label Benchmark. Show all posts

Thursday, January 9, 2014

FastPins.h or a way to realize really fast IO pin response on the Arduino

During my investigations for the last post I have discovered that there is massive difference in the execution time between the "Arduino way" and the "AVR-GCC way" of setting an output pin.

The result of method 1

  digitalWrite(13,HIGH);
  digitalWrite(13,LOW);

and method 2

  PORTB |= (1<<5);
  PORTB &= ~(1<<5);

is the same, PIN13 with the LED will be switched on and off, except that the first one generates a high pulse of 3.95µs and the second statement gets that handled in just 170ns (0.17µs) on an Arduino with 16MHz. The two pictures below show a comparison of both methods.

Yellow: Original Arduino style
Blue: AVR-GCC style
Zoom of the previous picture

Lets take a look to the pros and cons of both methods

Method 1:

Pro:
  • Super easy just take the original Arduino pin number and set the pin either HIGH or LOW
  • Works on all Arduinos e.g. PIN13 will always be on the same place of the board (UNO, MEGA, Leonardo) 
Con:
  • Slow, it takes 3.95µs to set a pin twice (LOW --> HIGH --> LOW)

Method 2

Pro:
  • Fast, it only takes 0.17µs (32 times faster) to set a pin twice (LOW --> HIGH --> LOW)
  • It could be set more than one pin at a time as long as they are on the same port
Con:
  • Its ugly and cryptic to type
  • The port and the pin number must be known
  • Port and pin number are varying between different boards e.g. PIN13 (LED) is PORTB Pin5 on the UNO, PORTB Pin7 on the MEGA and PORTC Pin7 On the Leonardo
  • Usage makes the Arduino sketch fixed to one board due to different pin assignment
But I still wanted an easy AND fast way to set the output pins. So I figured out that there is something called "preprocessor macros" and to be exact something called "Variadic Macros" which looked like it could be the solution but I didn't understood the documentation until I found this question at stackoverflow.com which brought me on the right way for my solution to make the AVR-GCC code look nice.

Preprocessor macros are text/code snippets which will replace parts of your written program code during the compile cycle of the code, and if possible the calculations are done in the preprocessor during compiling and not at runtime on the Arduino. For example they can be used to make ugly code parts look more readable, and that was exactly that what I wanted.

The Results

I have prepared an include file for the Arduino IDE so that the macros are included into the IDE and I also have written an keywords.txt to highlight the corresponding functions and keywords. It can be downloaded here. Both files must be copied into a sub folder of the Arduino libraries folder.

Due to the disadvantage that the port and the pin number must be known I can highly recommend the homepage of Alberto Piganti to figure out the correct ports and pin numbers. He has prepared a lot of very good pin out diagrams various kinds of Arduinos etc.

Usage of the Library/Include File

The usage is quite easy and more or less self explaining. The first parameter which is given to the function is always the port. If the function only gets two parameters then it sets the complete port to the second value.

  digitalFastWrite(PD,150);

Sets PORTD to 0b10010110

If the function gets three or more parameters then the first parameter is always the port and the last parameter can either be 1 or 0 (HIGH or LOW). This works with up to 8 pins at the same time. All outputs will be set at the same time no matter if only 1 pin is set or if all pins of a port are set.

  digitalFastWrite(PD,0,HIGH);
  digitalFastWrite(PD,0,LOW);

Creates a HIGH LOW pulse on pin 0 of PORTD

  digitalFastWrite(PD,0,1,HIGH);
  digitalFastWrite(PD,0,1,LOW);

Does the same with pin 0&1 of PORTD

Here are some examples to set multiple pins at the same time.

  digitalFastWrite(PD,0,1,2,HIGH);
  digitalFastWrite(PD,0,1,2,LOW);
  digitalFastWrite(PD,0,1,2,3,HIGH);
  digitalFastWrite(PD,0,1,2,3,LOW);
  digitalFastWrite(PD,0,1,2,3,4,5,6,7,HIGH);
  digitalFastWrite(PD,0,1,2,3,4,5,6,7,LOW);

It is also possible to quickly read an digital input. The input and output registers are at different addresses. For this reason use PINx instead of PORTx or Px. If readed from the address of PORTx the status of the internal pullup resistors is read which could be different form the real input state
The following example reads the value of pin 0 from PORTD and copies it into the variable pinState.

  pinState = digitalFastRead(PIND,0);

Until now it is still necessary to use the pinMode() function to set if the pin is an input or an output. But I'm working on it to create something which will be compatible with the above examples so stay tuned.



For further questions just ask it in the comments and I will try to answer them as soon as possible.

Monday, December 23, 2013

3208Clock Part 4: Get started with the HT1632C display driver

There are several ways to get started with the HT1632C display driver. One attempt, and this was also my first step, is to check on Google if someone has already written a driver or a library for the HT1632C. And yes, there are a lot of them.

At first I was playing around with the Arduino library from Adafruit. After some modifications I was able to write something to the display.

A Picture from one my first tests with the Adafruit lib

I also figured out that an update of the complete display (from the frame buffer into the HT1632C display driver) took 8.3ms and was only done with a clock speed of approx 35kHz. Please keep in mind that the ATmega8 of the 3208Clock is running at only 8MHz.

Yellow: Chip Select (CS)
Blue: Clock (WR)

A closer look also showed that the duration of the clock cycles is not equal during the transmission. The frequency of the clock is increasing which means that the time for one period of the clock cycle decreases during the transmission of a 16bit value from 33.6µs to 24.8µs.

Yellow: Chip Select (CS)
Blue: Clock (WR)
Yellow: Chip Select (CS)
Blue: Clock (WR)

At this point I wanted to know 2 things.
  1. Why is the duration of the clock cycles not constant during the transmission?
  2. Why is there only a clock frequency of approx 35 kHz (Datasheet says on page 4 1MHz is ok) which causes a update time for the entire display of 8.3ms?

Investigations and benchmarks

At first I figured out that HT1632::writeScreen() and HT1632::writedata(uint16_t d, uint8_t bits) are the functions of the Adafruit library which take care for the transmission. To have something which makes it easier to investigate I have prepared some small Arduino sketches where I only implemented the corresponding functions.

writedata(uint16_t d, uint8_t bits)

In this function the bit banging except the selection of the chip (CS) is done.

void writedata(uint16_t d, uint8_t bits)
{
  pinMode(_data, OUTPUT);
  for (uint8_t i=bits; i > 0; i--) {
    digitalWrite(_wr, LOW);
    if (d & _BV(i-1)) { digitalWrite(_data, HIGH); }
    else { digitalWrite(_data, LOW); }
    digitalWrite(_wr, HIGH);
  }
  pinMode(_data, INPUT);
}

 Answer for Question No 1

In the line "if (d & _BV(i-1))" happens the magic which causes that the clock cycles are varying during the transmission.

_BV(i-1) is the same like (1<<(i-1)), i is the iterator of the for-loop which is initialized with the number of bits. In the worst case the the iterator i is initialized with 16 which causes that in the check for the if-statement a "left shift operation" is executed 15 times. Because that i gets smaller every bit it has to do less and less shift operations to check the if-statement. And this causes that the clock cycle is not equal over the transmission of multiple bits.

I have fixed the problem with the clock speed by the following code modification:


void writedata(uint16_t d, uint8_t bits)
{
  uint16_t compareBit = 0;
  // 0x8000 -> set MSB
  compareBit = bits == 16 ? 0x8000 : compareBit |= (1<<(bits-1)); 
  
  pinMode(_data, OUTPUT);
  for (uint8_t i=bits; i > 0; i--)
  {
    digitalWrite(_wr, LOW);
    if (d & compareBit) { digitalWrite(_data, HIGH); } // 1
    else { digitalWrite(_data, LOW); } // 0
    compareBit = compareBit >> 1;
    digitalWrite(_wr, HIGH);
  }
  pinMode(_data, INPUT);
}

At the beginning of the function I have made a new variable for the comparison if the DATA pin has to be set. These are some lines more code but in this case the bit shift is only executed once in the for-loop. I also set the compareBit variable in a clever way because the most data packages are 16bit in this case the variable is preset with 0x8000 which is nothing else than setting the MSB true. Otherwise we have a repetitive shift operation for the amount of bits that have to be sent.


After this modification the frequency of the clock has been increased to approx 41.5kHz and the duration for a complete screen refresh is now 6.9ms this is 83% of the time it took before the modification.


Yellow: Chip Select (CS)
Blue: Clock (WR)
 

Yellow: Chip Select (CS)
Blue: Clock (WR)


Answer for Question No 2

After the initial improvement of the clock speed to 41.5kHz it still felt to slow for me. So I have removed all the Arduino specific stuff from the corresponding functions and replaced it by native "avr gcc" code.


void writedata(uint16_t d, uint8_t bits)
{
  uint16_t compareBit = 0;
  // 0x8000 -> set MSB
  compareBit = bits == 16 ? 0x8000 : compareBit |= (1<<(bits-1));
  
  DDRB |= (1<<DATA);
  for (uint8_t i=bits; i > 0; i--)
  {
    PORTB &= ~(1<<WR);
    if (d & compareBit) { PORTB |= (1<<DATA); } // 1
    else { PORTB &= ~(1<<DATA); } // 0
    compareBit = compareBit >> 1;
    PORTB |= (1<<WR);
  }
  DDRB &= ~(1<<DATA);
}


I was not aware that the speed improvement would be factor 10!


Yellow: Chip Select (CS)
Blue: Clock (WR)

Yellow: Chip Select (CS)
Blue: Clock (WR)

All three sketches can be downloaded from my GitHub repository.
Slow is the original code, medium is the code with the bugfix for the varying clock cycle and fast is the code for with all improvements.