How can I speed up this simple Arduino sketch? - Page 1
Forum section: electronics / general

author: Thane of Cawdor
author_rank: Regular Contributor
timestamp: October 01, 2014, 02:38:20 pm
content: Hi everyone,I am experimenting on a short sketch based mainly off the Adafruit OLED library and I have added a some extra lines to make an oscilloscope-like display. At very low frequencies (sub 10Hz range) the display shows clean waveforms. However, at higher frequencies update rate of the display becomes an issue and the lines merge into each other. Are there any parts of the code which I can modify to speed up the rate at which data is shown on the OLED display?The code:Code:#include <SPI.h>#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>#define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET);#define NUMFLAKES 10#define XPOS 0#define YPOS 1#define DELTAY 2#ifndef cbi#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))#endif#ifndef sbi#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))#endif#define LOGO16_GLCD_HEIGHT 16#define LOGO16_GLCD_WIDTH 16static const unsigned char PROGMEM logo16_glcd_bmp[] ={B00000000, B11000000,B00000001, B11000000,B00000001, B11000000,B00000011, B11100000,B11110011, B11100000,B11111110, B11111000,B01111110, B11111111,B00110011, B10011111,B00011111, B11111100,B00001101, B01110000,B00011011, B10100000,B00111111, B11100000,B00111111, B11110000,B01111100, B11110000,B01110000, B01110000,B00000000, B00110000 };#if (SSD1306_LCDHEIGHT != 64)#error("Height incorrect, please fix Adafruit_SSD1306.h!");#endifint x = 0;int y = 0;int ax = 0;int ay = 64;void setup() {#if FASTADCsbi(ADCSRA,ADPS2);cbi(ADCSRA,ADPS1);cbi(ADCSRA,ADPS0);#endifSerial.begin(9600);// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)display.begin(SSD1306_SWITCHCAPVCC, 0x3c); // initialize with the I2C addr 0x3D (for the 128x64)// init done// Show image buffer on the display hardware.// Since the buffer is intialized with an Adafruit splashscreen// internally, this will display the splashscreen.display.display();delay(500);}void loop() {while (x<=128) {x++;display.drawLine(ax, ay, x, y, WHITE);//(xpos-start,ypos-start,xpos-end,ypos-end)ay = y;ax = x;y = 63-(((analogRead(A0)) / 17)); //inverts signal to show 0V as bottom of screen and scales down analogue inputdisplay.display();}while (x >128) {display.clearDisplay();//refresh and clear display once line reaches OLED widthx = 0;ax = 0;}}Thanks

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: October 01, 2014, 02:56:37 pm
content: I am guessing display.display() takes a while, and that it blats the whole image.edit: bad code removed.yes separate out the acquisitions from the display as someone mentioned below.

author: mikerj
author_rank: Super Contributor
timestamp: October 01, 2014, 03:05:54 pm
content: You don't really want to be reading the ADC and updating the display at the same time. Split the function into two operations:1) Grab 128 samples from the ADC and store into a buffer. Store the raw sample value rather than scaling on the fly.2) Apply any scaling to the sample values and send the data to the display.This way your sample rate is not dependent on the time taken to draw lines on the screen, and the nasty divide by 17 in the loop. Instead the lengthy processing tasks will simply determine the refresh rate of the display.The fastest sample rate will be achievable in a tight loop as above, but for slower/variable sample rates consider using a timer to trigger the ADC and loading the buffer in the ADC interrupt.

author: Thane of Cawdor
author_rank: Regular Contributor
timestamp: October 01, 2014, 03:26:47 pm
content: Thanks for the responses,QuoteI haven't used the Arduino environment for too long and how would you store values in a buffer? Is there a specific way to do this?

author: mikerj
author_rank: Super Contributor
timestamp: October 01, 2014, 04:52:16 pm
content: Something like this. This is completely untested, probably loads of syntax errors!Code:#include <stdint.h>#define ADC_BUFF_LEN 128static uint16_t adcBuffer;void loop() {// Read ADC samples into bufferfor(x=0;x<ADC_BUFF_LEN;x++) {adcBuffer = analogRead(A0);}display.clearDisplay();// Write samples to the displayfor(x=0;x<ADC_BUFF_LEN;x++) {y = 63-(adcBuffer / 17); //inverts signal to show 0V as bottom of screen and scales down analogue inputif(x==0) // Prevent the first sample drawing a line up the edge of the display{ax = x;ay = y;}display.drawLine(ax, ay, x, y, WHITE); //(xpos-start,ypos-start,xpos-end,ypos-end)// Save current coordinates for next loop iterationay = y;ax = x;}// Refresh displaydisplay.display();}

author: nixfu
author_rank: Unknown
timestamp: October 02, 2014, 12:57:20 am
content: Use a faster processor such as the $19 Teensy 3.1 that runs at 96mhz instead of 8/16mhz of a regular arduino.

author: dannyf
author_rank: Super Contributor
timestamp: October 02, 2014, 01:40:23 am
content: It is not clear to me what your issues are.The title seems to suggest that it is an execution time / speed issue; The body of your message, however, seems to suggest that it is a triggering or time division issue.There are solutions to each of the problems but they are quite different.

author: zapta
author_rank: Super Contributor
timestamp: October 02, 2014, 02:10:23 am
content: You can see near line 157 in the file below a 128 point graph that is drawn 10 times a second. The dispaly is a 128x64 1.3" oled connected via SPI and max speed (4Mhz IIRQ) to a 16Mhz atmega328p (Arduino mini pro).https://github.com/zapta/power-monitors/blob/master/pmon_3v8/arduino/display.cppIt is done using the u8glib. This is an excellent graphics library (also for Arduino) and the developer is very responsive on the Arduino forums (display section). You can ask him what performance to expect from his library.

author: Rick Law
author_rank: Super Contributor
timestamp: October 02, 2014, 02:47:20 am
content: Quote from: dannyf on October 02, 2014, 01:40:23 amFrom working with the TFT screen, I think it may also be contention with screen memory. At high rate, for the TFT screen I used, the screen is fighting with the MCU for memory access.Perhaps the OLED screen has that issue too.

author: zapta
author_rank: Super Contributor
timestamp: October 02, 2014, 03:19:36 am
content: OP, are you using i2c? What clock speed? Typically they are slow. Also, color mode makes things even slower.

author: Thane of Cawdor
author_rank: Regular Contributor
timestamp: October 02, 2014, 03:31:59 am
content: Hi,QuoteIt's an I2C OLED with only one colour (white) and I'm using a clock speed of 16MHz.QuoteI am still quite uncertain, but I was under the impression that the execution time was causing the time division issue.QuoteThanks, I'll try to integrate it into the sketch and test it out

author: Thane of Cawdor
author_rank: Regular Contributor
timestamp: October 02, 2014, 05:08:24 am
content: Quote from: mikerj on October 01, 2014, 04:52:16 pmThat works just fine.Now it's going super fast. I think I'll try out your method of controlling the sample rate by using a timer to trigger the ADC. Would it be possible to use the millis/micros function to do the timing i.e. sample ADC each time a certain value is reached? or would I be better off using delays?Thanks

author: zapta
author_rank: Super Contributor
timestamp: October 02, 2014, 07:58:10 am
content: Quote from: Thane of Cawdor on October 02, 2014, 03:31:59 amOne color is good.Are you sure that your I2C clock is 16Mhz (not to be confuses with the MCU clock)? I would bet it's not.If you have an oscilloscope look at the I2C clock signal, it's possible that the time is spend delivering the data to the display.Edit: I am looking at the datasheet of the ssd1306, max I2C clock speed is 400khz (2.5usec). Page 54 herehttps://www.adafruit.com/datasheets/SSD1306.pdf. With 3 and 4 wire SPI it can go 10Mhz (100ns). That's ~x25 faster communication.

author: mikerj
author_rank: Super Contributor
timestamp: October 02, 2014, 08:04:23 am
content: Simply putting a software delay in the ADC loop will slow down the sample rate, but this is a very crude way of doing things. You are effectively throwing away CPU that could be used doing other things, such as drawing the previous waveform.If you look at the ADCSRB register, you will notice that an ADC conversion can be automatically triggered by a number of events without any software overhead. Most of these events are timer related; either a timer overflow or a output compare match event. You could set up a timer with an output compare function to fire this event at very precise, and easily adjustable time intervals. This would then start an ADC conversion, and when the ADC conversion has completed you could use the ADC interrupt to load the new sample into the buffer. When the buffer is full you can disable the ADC interrupt and raise a flag (just set a variable to '1') so that the main loop knows it's time to update the display.If you want to be clever, you can use two buffers (you have probably heard the term "double buffered" if you have been programming for long). This way you alternate between the buffers being filled by the ADC, so that the display can be updated from one buffer whilst the other buffer is being filled.At some point you will notice that the waveform display isn't very stable because you have no triggering, you simply start sampling at some random point in the waveform. You might want to consider using the analog comparator to provide a trigger function, or this could also be done in the ADC interrupt. Remember to keep your interrupt handlers as short and fast as possible, no divide or floating point operations for instance.To do these things you are probably going to have to step outside of the basic Arduino libraries and start accessing hardware registers directly.

author: Thane of Cawdor
author_rank: Regular Contributor
timestamp: October 02, 2014, 08:24:57 am
content: I haven't been programming for very long and I have a few questions in regard to the info you provided:Firstly, what is the ADCSRB register and how would I look at it?Secondly, how would I check whether the buffer is full? Would I just place a 'flag' at the end of the "for" loop?Thanks again

author: Rerouter
author_rank: Super Contributor
timestamp: October 02, 2014, 08:37:46 am
content: Try addingCode:#define TWI_FREQ_LCD 400000LTWBR = ((CPU_FREQ / TWI_FREQ_LCD) - 16) / 2;This will change the I2C clock rate to 400Khz, with most devices still happy to run at 800Khz, The atmega328 doesnt like running much faster, but i've played on other platforms with sensors which where happy up to 1.6Mhz when they where only rated at 400Khz)

author: TopLoser
author_rank: Unknown
timestamp: October 02, 2014, 08:43:17 am
content: This covers a lot of the points being raised:http://www.instructables.com/id/Girino-Fast-Arduino-Oscilloscope/?ALLSTEPS

author: mikerj
author_rank: Super Contributor
timestamp: October 02, 2014, 03:47:28 pm
content: Quote from: Thane of Cawdor on October 02, 2014, 08:24:57 amThe first thing you need to do is to download thedatasheet for the ATMega devicewhich is used on your Arduino board. Next you need to read all the relevant sections on the peripherals you wish to use, these will explain the register names and how they function. This isn't very exciting, but without a good understanding of the device you are programming your efforts will be doomed.Once you have a plan, you can access special function registers directly from within your Arduino code e.g.Code:ADCSRA |= ADATE; // Set ADATE bit to enable auto triggeringADCSRB |= (ADTS1 | ADTS0); // Select Timer0 Compare Match A to trigger the ADCIf you are filling the buffer from an ADC interrupt then you won't have a 'for' loop. Every time the interrupt fires you would grab the latest ADC value and copy it to the next location in the buffer. This means you will need an index variable which is incremented on every interrupt. You know when you have reached the end of the buffer because the index variable will be larger than the last location in your buffer.This all means you will have to wean yourself off using some of the high level Arduino functions, they are designed for simplicity and ease of use, but not performance. In this case you won't be calling analogRead() any more, since this function triggers an ADC conversion, waits for the conversion to finish and then returns the result. The new scheme has the timer triggering the ADC, and when the conversion is complete the ADC will fire an interrupt for you, providing you have configured it correctly.

author: dannyf
author_rank: Super Contributor
timestamp: October 03, 2014, 08:24:01 pm
content: The fastest adc on this mcu is to run the adc continuously and manage the output in an isr. If you were to use a naked isr, the cost would be your own context management + code to manage the output -> 10 ticks?Otherwise, 20 or so ticks should do. At 20MIPS, that's far faster than what the adc hardware can actually do.

author: dannyf
author_rank: Super Contributor
timestamp: October 03, 2014, 08:24:55 pm
content: I would also left aligned the adc results as 8-bit display resolution is likely more than enough.

author: IconicPCB
author_rank: Super Contributor
timestamp: October 08, 2014, 03:49:45 am
content: Free run the ADC

author: poorchava
author_rank: Super Contributor
timestamp: October 08, 2014, 06:39:36 am
content: IIRC (haven't used AVR's in ages) ADC in simpler AVRs can do up to 200ksps at full resolution, but they will also work in low msps range with reduced resolutions. I remember running one at something like 800ksps, it gave about 7 usable data bits from what i remember.