DorkbotPDX

Windows 10 Preview Build 9860 *finally* fixes USB Serial

Dorkbotpdx.org - Wed, 2014-10-29 16:21

I am happy to report Windows 10 Preview build 9860 fixes the long-standing USB serial bugs, which impact nearly all Arduino compatible boards.

  • The driver loads automatically.  No 3rd party INF is required.
  • The surprise removal bug is finally fixed (was present in build 9841, is fixed in 9860)
  • The seconds-long pause when detecting a new USB device for the very first time appears to be gone.

Windows 10 is finally going to support all class-compliant USB serial (eg, CDC-ACM) as well as Linux and Mac OS-X.  Very exciting.

 

 

Categories: DorkbotPDX, dorkbotpdx.org

Shall We Dance?

DorkbotPDX Flickr Group - Sat, 2014-09-20 13:59

atduskgreg has added a photo to the pool:

Shall We Dance?

CHURCH OF ROBOTRON

Dorkbotpdx.org - Fri, 2014-09-12 11:28

The Church of Robotron is coming to Portland, OR. We will be open the Last Wednesday of September (the 24th) and the First Thursday of October (the 2nd) starting at 7pm both nights at the Diode Gallery (514 NW Couch St) which is across the street from Ground Kontrol. We'll have multiple versions of Robotron 2084 available to train with and we will be triggering physical events in response to game events. One example: lasers when lasers are shot in game. For info about how this is accomplished, check out this older post.

Right now, we have an installation in the window at the gallery which is running 24/7 until October 3rd. It features a fully playable version of Robtron 2084, sermons, and a leaderboard that has pictures of all who attempt to become the mutant savior. Here's a video of it:

The window sensor is a capacitive sensor that was made by Philip Odom. He used the same techniques he taught during the Capacitive Sensing Workshop. Jason Plumb got audio working by using a transducer that turns the window into a speaker. The sign was built by Debbie Wager. Finally, this was all integrated together by the rest of the church.

Come check out the window anytime! Come to our open nights (9/24 & 10/2), check out this post for an idea of what to expect!

Categories: DorkbotPDX, dorkbotpdx.org

Embrace Heart Lighting

Dorkbotpdx.org - Wed, 2014-09-03 03:02

Earlier this summer, I worked on a tiny piece of the Embrace sculpture, for Burning Man 2014.

Inside were 2 hearts, one made here in Portland by Lostmachine Andy & other burners at Flat Rat Studios.  I made electronics to gradually fade 4 incandescent light bulbs in heart beating patterns.

Click "Read more" for technical details and many more wonderful photos (taken by Sarah Taylor)....

Inside the enormous sculpture were two hearts.  The blue on was built by a group in Vancouver, B.C., Canada, and of course this one was built here in Portland, Oregon, USA.


Andy wanted this heart to have a very warm, gentle atmosphere, with warn incandescent bulbs slowly fading to create the heart beat.  These effect turned out quite well.

Here's a quick video, from the first test of the light controller.

The electronics all fit onto this circuit board, which is approximately 4 by 3.5 inches.

The four BT139X Triacs that actually switch the AC voltage are mounted on the bottom side to a heatsink that's meant to dissipate any heat to the metal case.  Originally Andy believed the lights might be 500 watts each, so I was concerned about heat.  In the end, four 60 watt bulbs were used and the Triacs did not get noticeably warm.

Here is a parts placement diagram for building the circuit board.  Two boards were built, the one that ran the project and a spare... just in case!

The PCB cad files are attached below, if anyone wants to make more of these boards.

The AC switching circuitry was basically Fairchild Semiconductor's recommended circuit for the MOC3023 optical isolator, which allows a Teensy 2.0 board to safely control the AC voltage.  Four copies of this circuit were built on the board.

This circuit requires the Teensy 2.0 to know the AC voltage timing, so it can trigger the Triac at the right moment.  Triggering early in the AC waveform causes the Triac to conduct near the full AC voltage for maximum brightness.  Triggering later reduces the brightness.

To get the AC timing, I built this special power supply onto the board.

The Teensy 2.0 receives pulses on pins 5 and 6 as the AC waveform cycles positive and negative.

One caveat is this approach depends on the AC voltage being a sine wave.  The AC voltage was one of the first questions I asked Andy, and he was told Burning Man would supply a true sine wave AC voltage.  When he got out there, it turned out the power was actually a "modified sine wave", which really isn't anything like a sine wave.  This circuit didn't work well.  Fortunately, they were able to run the lighting from a small generator that produced a true sine wave.

With the AC timing arriving on pins 5 and 6, and 4 pins able to trigger Triacs, and 3 pins connected to analog voltages for changing speed, brightness and pattern, the only other major piece of this technology puzzle is the software.

In this code, loop() tracks the changes in the waveform on pins 5 & 6, and it fires the Triacs at their programmed times.  120 times per second (each AC half cycle), the recompute_levels() function runs, which reads the analog controls and changes the Triac time targets, which loop() uses to actually control the voltage outputs.

Here's all the code:

 

void setup() { pinMode(0, INPUT_PULLUP); // unused pinMode(1, INPUT_PULLUP); // unused pinMode(2, INPUT_PULLUP); // unused pinMode(3, INPUT_PULLUP); // unused pinMode(4, INPUT_PULLUP); // unused pinMode(5, INPUT); // Phase A pinMode(6, INPUT); // Phase B pinMode(7, INPUT_PULLUP); // unused pinMode(8, INPUT_PULLUP); // unused pinMode(9, INPUT_PULLUP); // unused pinMode(10, INPUT_PULLUP); // unused digitalWrite(11, LOW); pinMode(11, OUTPUT); // LED digitalWrite(12, HIGH); pinMode(12, OUTPUT); // trigger4, low=trigger digitalWrite(13, HIGH); pinMode(13, OUTPUT); // trigger3, low=trigger digitalWrite(14, HIGH); pinMode(14, OUTPUT); // trigger2, low=trigger digitalWrite(15, HIGH); pinMode(15, OUTPUT); // trigger1, low=trigger pinMode(16, INPUT_PULLUP); // unused pinMode(17, INPUT_PULLUP); // unused pinMode(18, INPUT_PULLUP); // unused analogRead(19); // pot #3 analogRead(20); // pot #2 analogRead(21); // pot #1 pinMode(22, INPUT_PULLUP); // unused pinMode(23, INPUT_PULLUP); // unused pinMode(24, INPUT_PULLUP); // unused } uint8_t pot1=0, pot2=0, pot3=0; uint8_t level1=100, level2=128, level3=0, level4=250; uint8_t phase_to_level(uint16_t phase) { uint16_t amplitude; // 10923 = 32768 / 3 // 0 to 10922 = increasing: 0 -> 32767 // 10923 to 21845 = decreasing: 32767 -> 0 // 21846 to 32768 = increasing: 0 -> 32767 // 32769 to 43691 = decreasing: 32767 -> 0 // 43692 to 65535 = resting: 0 if (phase < 10923) { amplitude = phase * 3; } else if (phase < 21845) { phase = phase - 10923; phase = 10922 - phase; amplitude = phase * 3; } else if (phase < 32768) { phase = phase - 21846; amplitude = phase * 3; } else if (phase < 43691) { phase = phase - 32769; phase = 10922 - phase; amplitude = phase * 3; } else { amplitude = 0; } //amplitude = (phase < 32768) ? phase : 65535 - phase; amplitude >>= 6; // range 0 to 511 amplitude *= (pot2 + 84) / 6; // amplitude += 6000 + pot2 * 8; // minimum brightness return (amplitude < 32768) ? amplitude >> 7 : 255; } void recompute_levels() { static uint16_t phase=0; static uint8_t n=0; analog_update(); //Serial.print("pot: "); //Serial.print(pot1); //Serial.print(", "); //Serial.print(pot2); //Serial.print(", "); //Serial.print(pot3); phase += (((uint16_t)pot1 * 83) >> 5) + 170; //Serial.print(", phase: "); //Serial.print(phase); if (pot3 < 128) { level1 = phase_to_level(phase); level2 = level1; level3 = phase_to_level(phase + pot3 * 52); level4 = level3; } else { uint16_t n = (pot3 - 127) * 26; level1 = phase_to_level(phase); level2 = phase_to_level(phase + 6604 - n); level3 = phase_to_level(phase + 6604); level4 = phase_to_level(phase + 6604 + n); } //Serial.print(", levels: "); //Serial.print(level1); //Serial.print(", "); //Serial.print(level2); //Serial.print(", "); //Serial.print(level3); //Serial.print(", "); //Serial.print(level4); //Serial.println(); } void loop() { uint8_t a, b, prev_a=0, prev_b=0, state=255, triggered=0; uint32_t usec, abegin, bbegin, alen, blen; uint16_t atrig1, atrig2, atrig3, atrig4; uint16_t btrig1, btrig2, btrig3, btrig4; bool any; while (1) { // read the phase voltage and keep track of AC waveform timing a = digitalRead(5); b = digitalRead(6); if (a && !prev_a) { // begin phase A usec = micros(); if (state == 0) { state = 1; abegin = usec; triggered = 0; Serial.print("A"); Serial.println(usec); } else if (state == 255) { state = 11; abegin = usec; } else { state = 255; } } if (!a && prev_a) { // end phase A usec = micros(); if (state == 1) { state = 2; alen = usec - abegin; Serial.print("a"); Serial.print(usec); Serial.print(","); Serial.println(alen); if (alen < 12000) { // compute trigger offsets for next A phase recompute_levels(); atrig1 = level1 ? ((256 - level1) * alen) >> 8 : 30000; atrig2 = level2 ? ((256 - level2) * alen) >> 8 : 30000; atrig3 = level3 ? ((256 - level3) * alen) >> 8 : 30000; atrig4 = level4 ? ((256 - level4) * alen) >> 8 : 30000; } else { state = 255; } } else if (state == 11) { state = 12; alen = usec - abegin; } else { state = 255; } } if (b && !prev_b) { // begin phase B usec = micros(); if (state == 2) { state = 3; bbegin = usec; triggered = 0; Serial.print("B"); Serial.println(usec); } else if (state == 12) { state = 13; bbegin = usec; } else { state = 255; } } if (!b && prev_b) { // end phase B usec = micros(); if (state == 3) { state = 0; blen = usec - bbegin; Serial.print("b"); Serial.print(usec); Serial.print(","); Serial.println(blen); if (blen < 12000) { // compute trigger offsets for next B phase recompute_levels(); btrig1 = level1 ? ((256 - level1) * blen) >> 8 : 30000; btrig2 = level2 ? ((256 - level2) * blen) >> 8 : 30000; btrig3 = level3 ? ((256 - level3) * blen) >> 8 : 30000; btrig4 = level4 ? ((256 - level4) * blen) >> 8 : 30000; } else { state = 255; } } else if (state == 13) { state = 0; blen = usec - bbegin; } else { state = 255; } } prev_a = a; prev_b = b; // trigger triacs at the right moments if (state == 1) { usec = micros(); any = false; if (!(triggered & 1) && usec - abegin >= atrig1) { digitalWrite(15, LOW); triggered |= 1; any = true; //Serial.println("trig1(a)"); } if (!(triggered & 2) && usec - abegin >= atrig2) { digitalWrite(14, LOW); triggered |= 2; any = true; //Serial.println("trig2(a)"); } if (!(triggered & 4) && usec - abegin >= atrig3) { digitalWrite(13, LOW); triggered |= 4; any = true; //Serial.println("trig3(a)"); } if (!(triggered & 8) && usec - abegin >= atrig4) { digitalWrite(12, LOW); triggered |= 8; any = true; //Serial.println("trig4(a)"); } if (any) { delayMicroseconds(25); digitalWrite(15, HIGH); digitalWrite(14, HIGH); digitalWrite(13, HIGH); digitalWrite(12, HIGH); } } else if (state == 3) { usec = micros(); any = false; if (!(triggered & 1) && usec - bbegin >= btrig1) { digitalWrite(15, LOW); triggered |= 1; any = true; //Serial.println("trig1(b)"); } if (!(triggered & 2) && usec - bbegin >= btrig2) { digitalWrite(14, LOW); triggered |= 2; any = true; //Serial.println("trig2(b)"); } if (!(triggered & 4) && usec - bbegin >= btrig3) { digitalWrite(13, LOW); triggered |= 4; any = true; //Serial.println("trig3(b)"); } if (!(triggered & 8) && usec - bbegin >= btrig4) { digitalWrite(12, LOW); triggered |= 8; any = true; //Serial.println("trig4(b)"); } if (any) { delayMicroseconds(25); digitalWrite(15, HIGH); digitalWrite(14, HIGH); digitalWrite(13, HIGH); digitalWrite(12, HIGH); } } } } #define ADMUX_POT1  0x60 #define ADMUX_POT2  0x61 #define ADMUX_POT3  0x64 void analog_update() { static uint8_t count=0; switch (count) {   case 0: // start conversion on pot #1 ADMUX = ADMUX_POT1; ADCSRA |= (1<<ADSC); count = 1; return;   case 1: // read conversion on pot #1 if (ADCSRA & (1<<ADSC)) return; pot1 = ADCH; ADMUX = ADMUX_POT2; count = 2; return;   case 2: // start conversion on pot #2 ADMUX = ADMUX_POT2; ADCSRA |= (1<<ADSC); count = 3; return;   case 3: // read conversion on pot #2 if (ADCSRA & (1<<ADSC)) return; pot2 = ADCH; ADMUX = ADMUX_POT3; count = 4; return;   case 4: // start conversion on pot #3 ADMUX = ADMUX_POT3; ADCSRA |= (1<<ADSC); count = 5; return;   case 5: // read conversion on pot #3 if (ADCSRA & (1<<ADSC)) return; pot3 = ADCH; ADMUX = ADMUX_POT1; count = 0; return;   default: count = 0; } }


Update: Here's a great time-lapse video where you can see the slow, gradual incandescent light fading as a rapid heart beat.  Skip forward to about 0:36 to see it quickly.

 

 

 

AttachmentSize embraceheart_pcb_files.zip18.06 KB
Categories: DorkbotPDX, dorkbotpdx.org

Node-RED Hacking - Audio Library Front-End

Dorkbotpdx.org - Wed, 2014-08-20 12:43

When I started the Audio library, a nice GUI (like Puredata or Max/MSP) seemed an impossibly distant dream.  Then, in this forum thread, I learned of the open source Node-RED project.

Over the last few days I've been coming up to speed on modern Javascript tools like jQuery and D3, to hack Node-RED into a GUI front-end.  Much work still needs to be done before this is usable, but I'm pretty excited about the possibilities!

Update: a live demo page is now available.  :)

Categories: DorkbotPDX, dorkbotpdx.org

Display & SPI Optimization

Dorkbotpdx.org - Wed, 2014-08-13 18:58

Recently I've been working on an optimized ILI9341 display library, to take advantage of Teensy 3.1's more capable SPI hardware.  Here's a quick video demo, so you can see how much of a difference it makes.

In the transition from 8 to 32 bit microcontrollers, on-chip SPI ports usually gain more sophisticated features.  Special programming is needed to fully levergage these more powerful features.  Merely recompiling code designed for simple SPI hardware on 8 bit hardware rarely acheives the best performance.  As you can see in the video, optimizing for these features makes a pretty dramatic improvement.

Click "Read more" for the all the technical details...

Fast SPI Clock & 32 Bit ARM

Some of the speed increase comes from the simple fact that Teensy 3.1 is based on a Freescale Kinetis chip with 32 bit ARM Cortex-M4 processor, which is significantly faster than the Atmel 8 bit AVR on Arduino Uno.  Uno's maximum SPI clock speed is 8 MHz, whereas Teensy's SPI clock can go up to 24 MHz.

However, faster clocks, without the special optimizations described in the rest of this article, only provide modest speed increase.  Only special code to fully leverage the more sophisticated SPI hardware, together with a faster CPU, can give the massive speed increase shown in the video.

  SPI FIFO Buffering

The 4 word FIFO in Teensy 3.1's SPI port is the key to improving overall SPI throughput.  Without the FIFO, software must wait for the SPI hardware to finish transmitting before it can write new data.  In principle, the software could begin working on the next byte while the priot byte transmits, but in practice that is very difficult to achieve in the software design.

When writing to the SPI port on 8 bit AVR, the code waits for each byte to be fully transmitted on the MOSI pin, and it returns the bits received on the MISO pin.  This results in very simple and easily maintainable source code, but it results in long gaps between bytes on the SPI bus.

Here are the SPI signals for the Arduino Uno.  Even though the SPI port uses an 8 MHz clock, less than half of the available bus time is actually used, due to significant software overhead.

On Teensy 3.1, using a Freescale Kinetis K20, the SPI port has a 4 word FIFO.  In most cases, 4 words is enough buffering to allow for the software overhead to compute more data to occur while previously written words are still transmitting.

As you can see in these waveforms, only a small gap is present between each word, limited only by the SPI port's timing.  The SPI bus is nearly fully utilized, and of course the clock speed is higher (this screenshot is on a 4X faster time scale).

Actually using the SPI FIFO is a task more easily said than actually done.  Several approaches were attempted throughout the development of these display optimizations.

Often when writing to hardware registers, a status bit or flag is read to check if the hardware is ready to receive data, and then then the actual data is written.  However, that approach adds significant overhead when the hardware is ready, with the FIFO empty and the SPI bus idle.

A "write first, ask questions later" technique used in this display optimization always leaves at least 1 word of space available in the FIFO, which allows new data to always be written as quickly as possible.  Then the FIFO is checked for full status and the code waits until at least 1 word is free.

void writecommand_cont(uint8_t c) __attribute__((always_inline)) { // write first SPI0.PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; // ask questions later waitFifoNotFull(); }

Using only 3 of the 4 words in the FIFO turns out to be a good trade-off, because it always slightly accelerates the commonly occuring case of a new drawing operation writing its first data to the empty FIFO.  3 words of buffering is usually enough to sustain maximum speed data flow.

A challange to use the SPI FIFO for this application, writing as rapidly as possible to MOSI while disregard all incoming data from MISO, is properly balancing read with writes, so extra data isn't left in the receive FIFO.  I must confess, it took a few iteration on this latest, most optimized code to get it right.  It's quite a bit more difficult that the simple but slow approach where you always write and then read a single byte.

  16 Bit SPI Transfers

The SPI port requires a small idle time between words, even when another word is waiting in the FIFO.  Words up to 16 bits are supported.  When 8 bytes need to be sent, they can be combined into a 16 bit word to avoid the idle time.

In this screenshot, 5 bytes are transmitted, but only 3 SPI idle times are needed because 4 of them are send using two 16 bit writes.

Much of the display data is 16 bits, such as X and Y coordinates and 5/6/5 color values.  The 16 bit words occupy only a single entry in the SPI FIFO, so 16 bit writes also entend the time allowed for software to generate more data without the SPI bus going idle.

  Hardware Control of Register Address & Chip Select

On 8 bit AVR processors, the chip select signal needs to be created by manipulating a GPIO pin.  For example, the AVR code running on Arduino Uno in the video demo uses this:

void Adafruit_ILI9341::writecommand(uint8_t c) { *dcport &= ~dcpinmask; *csport &= ~cspinmask; spiwrite(c); *csport |= cspinmask; }

These extra writes to GPIO registers add overhead.  They also require waiting for all 8 bits to full transmit, which greatly increases the difficulty of attempting to do any work while the SPI port is sending a byte.  For complex libraries, like color graphical displays, structured programming and maintainable code usually a preferred over "spagetthi code" which might try to perform different tasks during the very short time needed to transmit 1 byte.

Fortunately, Teensy 3.1's SPI port can control up to 5 "chip select" lines.  Two of them are used to control the chip select and data/command address line.  The result is virtually zero extra overhead to manipulate these 2 pins.

Each word in the SPI FIFO also has 5 bits for the 5 possible signals.  This allows the display software to write commands and data into the FIFO, without waiting.  At the SPI port uses the FIFO contents, it automatically controls both the chip select and the data/command address line.  As you can see in this screenshot, in most cases command and data are transmitted to the display with only the minimum SPI idle time between each word.

However, controlling these signals does come with a cost in software complexity.  Each write to the SPI port must also have a bit that tells the SPI peripheral whether to continue asserting the signals, or to de-assert them when the write is completed.  This means the last write in a group must be done differently, requiring loops to be restructured so the last iteration calls the non-continue SPI write.

 

Display Window Addressing

Other optimizations have been added to the code, which might help on 8 bit AVR processors, but are dramatically more effective with the faster 32 bit ARM processor and FIFO-based SPI hardware.

For example, there is the simple diagonal line drawing code.

for (; x0<=x1; x0++) { if (steep) { drawPixel(y0, x0, color); } else { drawPixel(x0, y0, color); } err -= dy; if (err < 0) { y0 += ystep; err += dx; } }

The drawPixel() function requires 11 bytes of SPI communication to set up the ILI9341 address window, then 2 bytes to actually write the color to the pixel.

This optimized version combines groups of horizontally or vertically adjacent pixels into a single operation, which requires 11 bytes for the entire line, and then 2 bytes per pixel.  Any line at an angle other than 45 degrees involves at least some 2+ pixel segments.  Because the ARM processor is fast and can do all this 16 bit integer math in single-cycle operations, and because the SPI FIFO is capable of buffering typically 5 or 6 bytes of prior output, this extra work to avoid the 11 byte address window setup rarely results in SPI idle time.

int16_t xbegin = x0; if (steep) { for (; x0<=x1; x0++) { err -= dy; if (err < 0) { int16_t len = x0 - xbegin; if (len) { VLine(y0, xbegin, len + 1, color); } else { Pixel(y0, x0, color); } xbegin = x0 + 1; y0 += ystep; err += dx; } } if (x0 > xbegin + 1) { VLine(y0, xbegin, x0 - xbegin, color); }

Another optimization uses here is inline coding of the Pixel(), VLine() and HLine() functions.  These are designed to continue form write, keeping the signals asserted and making best possible use of the FIFO, until the entire line is drawn.  Use of these special inline functions in drawLine() and other functions does increase the compiled code size, typically by about 5K to 8K.  On Teensy 3.1, where the flash memory is 256K, an extra 5K to 8K is usually a good trade-off for a significant speed boost.  But on Arduino Uno, where only 30K of flash is available for code and nearly 20K is already used, these types of optimizations that increase code size aren't usually viewed favorably.

 

Future Work on Large Fonts

In a future version, I hope to implement arbitrary size bitmap fonts, with support for fast drawing of very large characters.  The existing library only supports a single 5x7 font, with simple scaling, which looks quite blocky when scaled up to easily readable sizes on these small displays.

A large 50 to 90 pixel bitmap will look wonderful on these displays.  Here is a lengthy message with my plans for supporting fast & large bitmap fonts.  I'm really hoping someone with an interest in Python or other scripting languages might get involved in the project, to convert fonts into a special list-of-blocks format.

 

Credit Where Credit Is Due

All of this work is based on the open source libraries published by Adafruit Industries.  Limor Fried and Kevin Townsend have put a tremendous amount of work into Adafruit's many display libraries.  I highly recommend buying Adafruit products to support their efforts.

Peter Loveday wrote the earliest optimizations for these libraries on Teensy 3.0.  My work on these optimizations continued on the path Peter started.  Kurt Eckhardt was the first to port these optimizations to the ILI9341 chip.  I've since redesigned much of the code.

 

 

Categories: DorkbotPDX, dorkbotpdx.org

SPI Transactions in Arduino

Dorkbotpdx.org - Wed, 2014-07-30 11:05

For the last several weeks, I've been working on SPI transactions for Arduino's SPI library, to solve conflicts that sometimes occur between multiple SPI devices when using SPI from interrupts and/or different SPI settings.

To explain, a picture is worth 1000 words.  In this screenshot, loop() repetitively sends 2 bytes, where green is its chip select and red is the SPI clock.  Blue is the interrupt signal (rising edge) from a wireless module.  In this test, the interrupt happens at just the worst moment, during the first byte while loop() is using the SPI bus!

Click "Read mode" for lots more detail.....

Without transactions, the wireless lib interrupt would immediately assert (active low) the yellow chip select while the green is still active low, then begin sending its data with both devices listening!

With transactions (shown above), this interrupt is masked until the green access completes.  Not shown is the fact other unrelated interrupts remain enabled, so timing sensitive libs like Servo & SoftwareSerial aren't delayed, only interrupts using the SPI library.  SPI transactions also manage SPI settings, so each device is always accessed with its own settings, as shown here with the fast clock during green and slower clock during yellow.

Here's a more involved test case with Adafruit CC3000 and the SD library.

Hopefully soon this new SPI code will become part of Arduino's officially published SPI library and as SPI-based libs update to use the new functions, strange conflicts between SPI devices will become a thing of the past.

This new transaction support is being done in collaboration with the Arduino developers.  In fact, Matthijs Kooijman really deserves credit for the SPISettings portion of this work, as that part and the AVR implemention of it was his work.  Mikael Patel contributed many valuable insights, based on his COSA project, and Cristian Maglie (Arduino's technical lead) created benchmarks to study the performance impact these new functions might have.  Nantonos also contributed, by writing detailed documentation for these new SPI functions.

This work adds 3 new functions to the SPI library.

SPI.beginTransaction(SPISettings); SPI.endTransaction(); SPI.usingInterrupt(number);

In a netshell, SPI.beginTransaction() protects your SPI access from other interrupt-based libraries, and guarantees correct setting while you use the SPI bus.  SPI.endTransaction() tells the library when you're done using the SPI bus, and SPI.usingInterrupt() informs the SPI library if you will be using SPI from a function through attachInterrupt.

The new SPISettings is a special data type, just for describing SPI clock, data order and format.  For fixed settings, you can use beginTransaction(SPISettings(clock, order, format)), and the compiler will automatically inline your fixed settings with the most optimal code.  For user controlled settings, you can create a variable of SPISetting type and assign it based on user choice, which you don't know in advance.  This allows a very efficient beginTransaction(), because the non-const settings are converted to an efficient form ahead of time.

For example, you might use these new functions this way, for fixed settings:

int readStuff(void) { SPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0)); // gain control of SPI bus digitalWrite(10, LOW); // assert chip select SPI.transfer(0x74); // send 16 bit command SPI.transfer(0xA2); byte b1 = SPI.transfer(0); // read 16 bits of data byte b2 = SPI.transfer(0); digitalWrite(10, HIGH); // deassert chip select SPI.endTransaction(); // release the SPI bus return (int16_t)((b1 << 8) | b2); }

This approach generates the most efficient code, because the SPI settings are fixed.  SPISettings compiles to optimal code, thanks to Matthijs Kooijman's nice work!

The other awesome feature of SPISetting is hardware independence.  Any clock speed can be specified as a normal 32 bit integer.  There's no need for "dividers" that require knowing your board's clock rate.  Matthijs's code inside SPISettings efficiently converts those integers to the dividers used by the hardware.  The clock speed you give to SPISettings is the maximum speed your SPI device can use, not the actual speed your Arduino compatible board can create.  The SPISettings code automatically converts the max clock to the fastest clock your board can produce, which doesn't exceed the SPI device's capability.  As Arduino grows as a platform, onto more capable hardware, this approach is meant to allow SPI-based libraries to automatically use new faster SPI speeds.

For non-const settings, on libraries that allow the user to set the clock speed or other settings, you can create a SPISettings variable to hold the settings.  For example:

SPISettings mySettings; void useClockSpeed(unsigned long clock) { mySettings = SPISettings(clock, MSBFIRST, SPI_MODE3); }; void writeTwoBytes(byte a, byte b) { SPI.beginTransaction(mySettings); // gain control of SPI bus digitalWrite(10, LOW); // assert chip select SPI.transfer(a); // send 16 bit command SPI.transfer(b); digitalWrite(10, HIGH); // deassert chip select SPI.endTransaction(); // release the SPI bus }

The simplest way to use SPI transactions involves SPI.beginTransaction() right before asserting chip select, and SPI.endTransaction() right after releasing it.  But other approaches are possible.  For example, my SPI transaction patch for the Ethernet library implements transactions at the socket level.  In the SD library initialization, 80 clocks are sent with chip select high to prep the SD card, as another example where transactions to not necessarily correspond to chip selects.  This design is meant to be flexible, and easy to add to the dozens of existing SPI-based libraries.

The new SPI.h header defines a symbol SPI_HAS_TRANSACTION, to allow library authors to easily add SPI.beginTransaction() and SPI.endTransaction() inside #ifdef checks, for libraries supporting a wide range of Arduino versions.

The one other new function is SPI.usingInterrupt(), which informs the SPI library you will be using SPI functions from an interrupt.  It takes an integer input, which is the same number you use with attachInterrupt().

Newer versions of Arduino have a digitalPinToInterrupt() function, which is useful for converting Arduino pin numbers into their interrupt numbers, and it can tell you whether a pin has interrupt capability.  Here's how it would be used:

void configureInterruptPin(byte pin) { int intNum = digitalPinToInterrupt(pin); if (intNum != NOT_AN_INTERRUPT) { SPI.usingInterrupt(intNum); attachInterrupt(intNum, myInterruptFunction, RISING); } }

The main idea is a call from your SPI-based library using interrupts causes OTHER libraries to temporarily mask the interrupt your library uses, so your interrupt won't conflict with their SPI activity and the SPI bus will be free when your interrupt function runs.


Developing and testing this function SPI sharing functionality has been a long and difficult path.  Since April, the API was discussed in great detail before Cristian Maglie (Arduino's technical lead and the man who ultimately decides what contributions Arduino will accept) ultimately decided on this approach, using a hybrid of my original proposal and Matthijs's SPISettings.  All contributions to a very widely used system like Arduino involve some controversy... there's *always* someone who doesn't like change or wants things done differently.  Luckily, after about 6 weeks of discussion, a clear path was chosen (by Cristian).

I should also mention how rare and insidious these SPI conflicts are.  Even with my intentional test case, rapidly re-reading a file from the SD card while the Adafruit CC3000 library is likely to interrupt, the data would often be read without error.  The SPI port is very fast and the AVR processor is quite slow, so even in a worst case, the window of opportunity for conflicts is small.  Many "normal" programs might see errors very rarely.  Sketches written in very simple ways, where you wait for all activity on one device to finish before using another, are unlikely to ever have conflicts.  The rare nature of these problems has led many people to question the need for this work, but I can assure you (or you could run the test code yourself) these conflicts are real and eventually cause data error or even complete lockup if the interrupt strikes are just the wrong moment.

Hopefully soon, Arduino will publish a new version with this updated SPI library, and over time, the many SPI-based Arduino libraries will gradually update to use these functions.  I want Arduino to be a very solid platform, not just for people using my own Teensy boards, but for all Arduino compatible products... which is why I've put so much work into this SPI library update over the last several weeks.

 

 

 

Categories: DorkbotPDX, dorkbotpdx.org

RFM69 Wireless & SPI Bus Sharing (feedback wanted...)

Dorkbotpdx.org - Sun, 2014-07-27 11:19

Recently I've been working to improve the Arduino SPI library, to better support multiple SPI devices with different settings, and SPI devices requiring interrupts.

Today I discovered a new problem while testing the HopeRF RFM69 wireless module.

Click "Read more" for details and the workaround I found....

First, this article is meant to publically document what I've found and invite conversation with anyone else who might have experienced similar issues.  It's very difficult to know if this problem is caused by buggy software, faulty hardware, or radio interference.  Please comment below, or on this forum thread, if you have any insight or opinions.

In a nutshell, I've been working on new Arduino SPI library functions to allow SPI devices with different settings and usage of attachInterrupt, where the interrupt routine uses SPI.transfer(), to hopefully work together.  With the SPI library from Arduino 1.0.5 & 1.5.7, usage from within an interrupt routine can occur while the main program is using SPI.transfer with another device's chip select active, and of course each device might need very different SPI settings.

As part of this testing, I've been working with the Ethernet library using a WIZ820io module and the RadioHead library with a RFM69W wireless module.  The Ethernet library used with Teensy has a patch to allow either W5100 or W5200 chips, so the normally unsupported WIZ820io works fine.  In my testing, I've patched both libraries to use the new SPI.beginTransaction() and SPI.endTransaction() functions, and RadioHead uses SPI.usingInterrupt(), to let those 2 functions know which interrupt needs to be masked while the Ethernet library is using SPI.

So a caveat is that I've working with 3 patched libraries.  There's a lot of uncertainty here.

My test program uses a Teensy 3.1 to send a request to another board running RadioHead's "rf69_server" example, and also a HTTP request to fetch a web page.  Then it tries to receive both of them.  I'll attach the code below.

The RFM69 is unable to receive while the Ethernet library polls the WIZ820io.  I spent hours carefully checking the waveforms on my oscilloscope.  At first I thought this must be a bug in my code.  But it's easy to see the 2 chip selects never assert simultaeously, as they might if SPI.beginTransaction didn't mask the interrupt while Ethetnet uses SPI.  It's easy to see the other device is sending the reply, because its LED blinks when it transmits on its RFM69.  They're sitting right next to each other on my desk (the first photo) and with a line uncommented in the code to cause the Ethernet polling to wait 20 ms, the radio reply is always received correctly.

Finally, in an act of desparation, I build this little hack to force the SCK signal low at the RFM69 when its NSS chip select is idle (high).

Amazingly, this works!  It allows the RFM69 to receive, while the Ethenet module is polling.

I'm still not ready to conclude the RFM69 has a hardware bug, where activity at the SCK input, which ought to be ignored while NSS is high, might be causing it to miss incoming radio data.

It's entirely possible the communication could be creating radio interference.  My little adaptor board has a ground plane between the RFM69 and the digital signals (I'm a little paranoid of messing up RF stuff).

It's also possible my software has a bug, even though the waveforms appear to be fine on a scope.  I might have missed something.

Anyway, here's a quick schematic for that little circuit.

I also created a new adaptor board with this circuit.

Here are my patched copies of these libraries:

https://github.com/PaulStoffregen/SPI

https://github.com/PaulStoffregen/Ethernet

https://github.com/PaulStoffregen/RadioHead

At this point, the big question is whether I've made a mistake somewhere (entirely likely), or if the RFM69 module really has some sort of issue where SPI bus usage by other devices, with the RFM69's NSS signal high, causes trouble with radio reception?

If you've used the RFM69 or similar HopeRF modules on a SPI bus with a lot of other activity, please reply or comment on the forum thread.  I'd really like to hear about your experiences.

AttachmentSize rf69_client_and_ethernet.zip1.66 KB
Categories: DorkbotPDX, dorkbotpdx.org

Standalone SPI 7-Segment Display

Dorkbotpdx.org - Sat, 2014-07-26 10:03

I had some spare 4-digit 7-segment LED displays and some AT90USB82s, and I'd always intended to do something with them.  This was probably the easiest thing!  It's just the AT90 driving the display, with a(t least) 4 wires controlling it: Vcc, GND, MOSI and SCK.  (I haven't written the code yet, but my plan is to make the display accepts characters via SPI and then spends the rest of the time displaying them).

The board has footprints for a 16MHz crystal and USB connector, so you could make it a USB-enabled 7-segment display as well.  I stuffed those parts on my test board, but I'm not sure whether the USB actually works.  You can power the display from USB, at least, although the video shows it being powered over SPI (which is the same connection I use to flash code).

(Some time passes...)

Ha!  It turns out Paul's code for Teensy USB Serial (AT90USB1286) runs on the AT90USB82 also.  So I just plugged it in, and now the 7-Seg display shows up as a serial port.  When you type digits, it scrolls them in from the right.  Next up: escape codes to control brightness, dots, colon and apostrophe.  Plus I have to add characters (I only have hex digits now).  Hee hee, this was almost too easy!

(More time passes...)

The SPI code was really easy too.  I spent more time writing a test driver for an ATtiny13A than I did configuring & enabling the AT90USB82 SPI.  There's just a few things you have to set up for slave mode, then you just look in the data register to see if anything arrived.  Easy peasy.

Categories: DorkbotPDX, dorkbotpdx.org

[dorkbotpdx-announce] Fwd: Flux move out tonight & tomorrow: score some stuff

dorkbotpdx-announce - Wed, 2014-07-23 11:49
This is a bummer, but grab some furniture and trinkets! ---------- Forwarded message ---------- From: Molly Danielsson Attention Friends (& sweet Trolls) of Flux! We are closing due to insufficient funds. If you thought Flux was awesome and fun, please come help us move ou
Categories: DorkbotPDX, Mailing Lists

Power Playground

Dorkbotpdx.org - Sun, 2014-07-13 23:05

Here's the beginnings of a circuit for playing with what I think of as power electronics -- particularly inductors and batteries.  Design goal is modest voltage (6-25V), modest current (1-5A), modest power (5-10W).  I'd like to be able to characterize inductors and transformers, learn about magnetic saturation, and charge & discharge batteries.

AttachmentSize PowerPlayground.pdf27.22 KB
Categories: DorkbotPDX, dorkbotpdx.org

DorkbotPDX Capacitive Sensing Workshop

DorkbotPDX Flickr Group - Mon, 2014-06-30 10:02

bzztbomb has added a photo to the pool:

DorkbotPDX Capacitive Sensing Workshop

DorkbotPDX Workshop for 6/2014 given by Philip Odom at Flux Hackerspace in Downtown Portland. We draw sensors with pencil and triggered LED lights and MIDI notes.

dorkbotpdx.org/capacitive_sensing_workshop_2014

DorkbotPDX Capacitive Sensing Workshop

DorkbotPDX Flickr Group - Mon, 2014-06-30 10:02

bzztbomb has added a photo to the pool:

DorkbotPDX Capacitive Sensing Workshop

DorkbotPDX Workshop for 6/2014 given by Philip Odom at Flux Hackerspace in Downtown Portland. We draw sensors with pencil and triggered LED lights and MIDI notes.

dorkbotpdx.org/capacitive_sensing_workshop_2014

DorkbotPDX Capacitive Sensing Workshop

DorkbotPDX Flickr Group - Mon, 2014-06-30 10:02

bzztbomb has added a photo to the pool:

DorkbotPDX Capacitive Sensing Workshop

DorkbotPDX Workshop for 6/2014 given by Philip Odom at Flux Hackerspace in Downtown Portland. We draw sensors with pencil and triggered LED lights and MIDI notes.

dorkbotpdx.org/capacitive_sensing_workshop_2014

Re: [dorkbotpdx-blabber] OSHPark is hiring - Board depaneler

dorkbotpdx-discuss - Thu, 2013-06-20 12:00
DOH! I did it too... Please ignore my previous post, it was ment to be sent
directly to James...

Jason

On Tue, Jun 18, 2013 at 12:07 PM, Jason Barnett

Categories: DorkbotPDX, Mailing Lists

Re: [dorkbotpdx-blabber] OSHPark is hiring - Board depaneler

dorkbotpdx-discuss - Thu, 2013-06-20 12:00
James,
I mentioned this job to my wife, Nobuko, and she is interested in it. She
came with me from Japan and thus has no work history since we came to the
states and is trying to get back into the workforce. She is now a US
citizen so will have no problem with that aspect of employment.
While living in Japan, she worked as a receptionist for a respected lawyer
Categories: DorkbotPDX, Mailing Lists

Re: [dorkbotpdx-blabber] Anyone with CAN experience?

dorkbotpdx-discuss - Thu, 2013-06-20 12:00
Thanks! The Smartboard has Ethernet already. I could use just about any
wifi router/bridge to connect the device through Wifi. It's an option that
I'm definitely considering.

______________________________ _________________
dorkbotpdx-blabber mailing list
dorkbotpdx-blab...@dorkbot.org
[link]

Categories: DorkbotPDX, Mailing Lists

Re: [dorkbotpdx-blabber] Anyone with CAN experience?

dorkbotpdx-discuss - Thu, 2013-06-20 12:00
whoops, here's the product
[link]

--
-Nathan
______________________________ _________________
dorkbotpdx-blabber mailing list
dorkbotpdx-blab...@dorkbot.org
[link]

Categories: DorkbotPDX, Mailing Lists

Re: [dorkbotpdx-blabber] Anyone with CAN experience?

dorkbotpdx-discuss - Thu, 2013-06-20 12:00
If you don't need it to be going out via bluetooth and can tolerate
wifi, this is around $25-30 and has a UART port on it, so you could
interface it with your smartboard to send out the data that way. This
driver file says:

[link]

Categories: DorkbotPDX, Mailing Lists

[dorkbotpdx-blabber] Ignore Joseph Baker's Last Post !!!

dorkbotpdx-discuss - Thu, 2013-06-20 12:00
Fellow Dorkbot-istas :

Please ignore my previous post. It was intended as a confidential
response to Laen.

Having said that, if you need a bit of help with a project and think I
could contribute, I would be glad to spend 1/2 or a full hour learning
about what you're working on.

Hope to see you tonight.

Categories: DorkbotPDX, Mailing Lists
Syndicate content