Use Qwiic I2C and SOMRT1061 for Rapid Prototyping

Closeup of the SOMRT1061 Dev Kit running the example program on a Qwiic OLED Display, with a keypad and 3D printed case

Who needs breadboards? With the new SOMRT1061 development board’s Qwiic connector, adding off-the-shelf I2C devices and libraries to your prototypes has never been easier!

You might not have noticed it in our recent NNDK release notes, but we’ve been hard at work retooling our I2C library to make use of the friendly Wire interface commonly used by other microcontroller platforms. Why? Because with the release of our new SOMRT1061 development kit and its Qwiic I2C connector, it’s now possible to buy a device from a popular website like SparkFun and have it running on your NetBurner device in minutes! Your next ARM-powered Industrial IoT project can come together faster than ever with a wide assortment of affordable, reusable, plug-and-play modules.

i.MX ARM Dev Kit Featured in this Article

SOMRT1061 200 IR system-on-module breakout board mounted to a fully featured development board with RS232, Ethernet, USB, MicroSD, and power connectors

Or, learn more about NetBurner IoT.

The Project

What’s the best way to demonstrate these new capabilities? Let’s keep it as simple as possible: this tutorial combines our Earthquake WebClient example that makes use of our elegant ParsedJsonDataSet library, with new Qwiic I2C examples for an OLED Display and a 12-button Keypad, both from SparkFun. A little bit of glue code to push the data where we want it, a few inputs and outputs, and we’re done!

Code in this blog post and associated GitHub repository are copyright NetBurner, Inc, and covered by the NetBurner license: see the NNDK license that came with your product for more details. Some included files are provided by SparkFun and have their own copyright notices. Arduino is a trademark of Arduino SA, but this tutorial contains no Arduino code: the included Arduino.h file enables compatibility with common libraries only, and is named as such only to enable that compatibility. The NetBurner logo included in this project’s 3D files is a trademark of NetBurner.

Getting Started with Qwiic

Qwiic is an open standard created by SparkFun to easily connect I2C devices without breadboards or soldering.

In this example, we’ll use their 1.3″ Qwiic OLED Display and 12-button Qwiic Keypad, but you can theoretically use any Qwiic product in their catalogue. If we don’t have an example for it, our examples can be used as a guide for implementing the product’s library in your NetBurner project. We’ve done our best to make the Wire object as close as possible to other platforms, and make a compatibility layer for the most common objects and functions found in Arduino.h. Advanced components may require more work, like audio codecs, radios, or sensors, but the foundation is here!

3D Printed Project Box

To really bring a proof-of-concept home in a nice way, we’ve also put together a 3D-printable enclosure for our SOMRT1061 dev kit that exposes all the ports, plus cutouts for the screen and keypad we’ll be using. It’s made in the highly-customizable OpenSCAD with the help of the YAPP project box library, so we hope you’ll find it useful in a variety of ways! The cutouts for the header pins are ready to use, just commented out in favor of the screen and keypad cutouts we’ll be using for this post. Please use and modify to your heart’s content!

Bill of Materials

For the 3D printed enclosure:

  • The enclosure itself (see the Readme on GitHub)
  • Qty 8: Heat-set M3 inserts for plastic
  • Qty 4: M3x4 socket-cap screws
  • Qty 3: M3x8 socket-cap screws
  • Qty 9: 1-inch long cut sections of clear 1.75mm plastic filament, and clear glue (optional)
  • If printing with the default OpenSCAD settings with the outputWifi option set to true (for the optional NBEXP-WIFI-INIR WiFi expansion module):
    • Qty 1: M3x20 socket-cap screw
    • Qty 1: the 11mm printed cylindrical spacer in the 3D file
  • Otherwise, print and generate an STL with outputWifi set to false and use:
    • Qty 1: an additional M3x8 socket-cap screw
    • No spacer needed
  • Soldering iron or other heated tip for the M3 inserts
  • Appropriate hex key or bit for the M3 screws

The Code

Please download the code from our GitHub repository and copy-paste the files onto a new NetBurner project created with NBEclipse and configured with your SOMRT1061’s IP address.

To summarize, the real glue holding this project together is a compatibility layer that implements common APIs that these Qwiic and other Wire I2C devices expect. So for example we have a file called Arduino.h which just contains thin wrappers over standard NetBurner function calls:

				
					// Arduino.h
// NetBurner "Arduino Compatibility" Shim

#ifndef _NB_ARDUINO
#define _NB_ARDUINO

#include <nbstring.h>
#include <stdio.h>
#include <string.h>
#include <serial.h>
#include <nbprintfinternal.h>
#include <stdarg.h>
#include <math.h>

typedef bool boolean;
typedef uint8_t byte;

#define PI 3.1415926535

static inline void delay(int x) 
{
	if (x > (1000/TICKS_PER_SECOND)) { OSTimeDly(x/TICKS_PER_SECOND+1); } 
	 else for (volatile int i = 0; i < x*100000; i++);
}
static inline int min(const int a, const int b) {
	return a < b ? a : b;
}
static inline int max(const int a, const int b) {
	return a > b ? a : b;
}
static inline long map(long x, long in_min, long in_max, long out_min, long out_max)
{
	long outD = (out_max - out_min);
	long inR = (x - in_min);
	long inD = (in_max - in_min);
	return outD*inR/inD+out_min;
}
static inline unsigned long millis() {
	return Secs*1000;
}

class String : public NBString
{
public:
		String(){};

		/**
		 * @brief Construct a new NBString object from an existing NBString object
		 *
		 * @param str   Existing NBString object
		 */
		String(const NBString &str) :NBString(str) {};
		

		/**
		 * @brief Construct a new NBString object from a substring of an existing NBString object.
		 *
		 * @param str   Existing NBString object
		 * @param pos   Starting position to copy
		 * @param len   Ending position to copy
		 */
		String(const NBString &str, size_t pos, size_t len = npos) :NBString(str,pos,len) {};

		/**
		 * @brief Construct a NBString object from a character string (null terminated array of characters)
		 *
		 * @param s   String to initialize object
		 */
		String(const char *s) :NBString(s) {};
		

		/**
		 * @brief Construct a NBString object from a character string up to the specified amount
		 *
		 * @param s     String to initialize object
		 * @param n     Number of characters to copy
		 */
		String(const char *s, size_t n) : NBString(s,n){};

		String(uint32_t v) {siprintf("%lu",v); };
		String(uint16_t v) {siprintf("%u",v); };
		String(uint8_t v) {siprintf("%u",v); };
		String(int32_t v) {siprintf("%ld",v); };
		String(int16_t v) {siprintf("%d",v); };

};


#define HEX (16)
#define DEC (10)
#define OCT (8)
#define BIN (2)


class Print
{
size_t  handle_unum(uint32_t v, int base)
{
NBString s;
switch(base)
{
case 16: s.siprintf("%lX",v); break;
case 10: s.siprintf("%ld",v); break; 
case 8: s.siprintf("%lo",v); break; 
case 2: s.siprintf("%b",v); break; 
default:return 0;
}
return print((const NBString)s); // (uint8_t *)s.c_str(),s.length()
}

size_t  handle_inum(int32_t v, int base)
{
if(v<0)
{
return write((uint8_t)'-')+handle_unum((uint32_t)-v,base); 
}
return handle_unum((uint32_t)v,base);
}

public:
virtual size_t write(uint8_t c)=0; // Pure virtual, must be implemented precisely by children
virtual size_t write(const uint8_t *buffer, size_t size=0){ size_t i=0; while(i<size) { write((uint8_t)buffer[i]); i++; } return i; };
virtual size_t write(const char *str) {if(str) {return write((uint8_t *)str,strlen(str));} return 0; };

inline size_t print(const char *c) {if(c) return write(c); else return 0;};
inline size_t print(const NBString & s){ return write(s.c_str());}
inline size_t print(char c) { return write((uint8_t *)&c,1);};
inline size_t print(uint32_t i,int base=10){ return handle_unum(i,base);}
inline size_t print(int32_t i,int base=10){ return handle_inum(i,base);} 
inline size_t print(int i,int base=10){ return handle_inum((int32_t)i,base);};  
inline size_t print(unsigned int i,int base=10){ return handle_unum((uint32_t)i,base);}; 
inline size_t print(unsigned char i,int base=10){ return handle_unum((uint32_t)i,base);}; 
inline size_t print(double d, int wid=2) {NBString s; s.sprintf("%0.*f",wid,d); return write((uint8_t *)s.c_str(),s.length()); };

inline size_t print(float f, int wid=2){ return print((double)f,wid);};
inline size_t println(const char *c) {int rv=print(c); rv+=write((uint8_t *)"\r\n",2); return rv;};
inline size_t println(const NBString & s) {int rv=print(s); rv+=write((uint8_t *)"\r\n",2); return rv;}; 
inline size_t println(char c) {int rv=print(c); rv+=write((uint8_t *)"\r\n",2); return rv;}; 
inline size_t println(int i,int base =10) {int rv=print(i,base); rv+=write((uint8_t *)"\r\n",2); return rv;}; 
inline size_t println(unsigned int i,int base=10) {int rv=print(i,base); rv+=write((uint8_t *)"\r\n",2); return rv;};  
inline size_t println(unsigned char i,int base=10){int rv=print(i,base); rv+=write((uint8_t *)"\r\n",2); return rv;};  
inline size_t println(unsigned long i,int base=10){int rv=print(i,base); rv+=write((uint8_t *)"\r\n",2); return rv;};  
inline size_t println(long i,int base=10) {int rv=print(i,base); rv+=write((uint8_t *)"\r\n",2); return rv;};  
inline size_t println(double d,int wid=2){int rv=print(d,wid); rv+=write((uint8_t *)"\r\n",2); return rv;}; 
inline size_t println(float f, int wid=2) {int rv=print(f,wid); rv+=write((uint8_t *)"\r\n",2); return rv;}; 
inline size_t println() {return write((uint8_t *)"\r\n",2);}; 
};


class  FDprint : public Print
{
int m_fd;
public:
virtual size_t write(uint8_t c) { char o[1]; o[0] = c; return ::write(m_fd,(const char*)o, 1);};
FDprint(int fd) {m_fd=fd; };
};

#define 	pgm_read_byte_near(address_short)   *((uint8_t*)(address_short))
#define 	pgm_read_word_near(address_short)   *((uint16_t*)(address_short))
#define 	pgm_read_dword_near(address_short)   *((uint32_t*)(address_short))
#define 	pgm_read_float_near(address_short)   *((float *)(address_short))
 
class SerialIf : public FDprint
{
public:
inline int begin(int b){SerialClose(0); return SimpleOpenSerial(0, b);};
SerialIf() : FDprint(0) {};
};

extern SerialIf Serial;

#endif


				
			

And a few more header files to implement some other expected variables:

				
					// src/avr/pgmspace.h
// shim for ARM
#define PROGMEM
#define pgm_read_byte * 
				
			
				
					// src/pgmspace.h
// shim for non-ARM
#define PROGMEM
#define pgm_read_byte * 
				
			

And believe it or not, with those files in place it’s now easy to copy-paste I2C Wire code examples into our project! SparkFun’s qwiic_* cpp and h files can live next to our main.cpp file in the src folder, and resources like qw_* headers, bitmaps, and fonts can live in the src/res subfolder.

Obviously there’s a lot to a complete program, so review the complete code on GitHub, but here’s a short overview of the functions necessary to initialize and use the I2C Wire interface and Qwiic devices; this program simply displays keypad characters on the OLED display:

				
					// main.cpp
#include <init.h>
#include <nbrtos.h>
#include <Wire.h>
#include <SparkFun_Qwiic_Keypad_Arduino_Library.h>
#include <SparkFun_Qwiic_OLED.h>
#include "res/qwiic_resdef.h"
// ...snip...
SerialIf Serial;
TwoWire Wire;
Qwiic1in3OLED myOLED;
QwiicFont *pFont;
KEYPAD key;
// ...snip...
void UserMain(void *pd)
{
    init();
    Wire.begin();
    myOLED.begin();
    key.begin();
    
    while (1) {
        uint8_t rv=key.getButton();
        key.updateFIFO();
        if (rv>0) {
            myOLED.erase();
            myOLED.setCursor(0, 0);
            myOLED.print((char)rv);
            myOLED.display();
        }
        OSTimeDly(1);
    }
}
				
			

Flashing the Complete Program

Once you have all the code from GitHub copied, compiled, and uploaded to your SOMRT1061, your serial terminal should look something like this:
Primary network interface configured for DHCP
MAC Address = 00:03:f4:12:34:56
Type "A" to abort boot
Application: QwiicBlogExample
NNDK Revision: 3.5.2
Time Set
Running OLED Earthquake example
OLED begin success.
Keypad begin success.
Keypad is connected.
Got 4 quakes:
0 ago - 4.9 4 km E of Limones, Panama
1 ago - 5.6 13 km SE of Manaca Civil, Panama
2 ago - 5.7 5 km SSE of Puerto Armuelles, Panama
4 ago - 4.6 70 km SW of Cañete, Chile
Don’t forget to connect up your Qwiic keypad and screen! Qwiic (and I2C) are usually daisy chainable in any orientation, so multiple devices can be connected in a row:
Photo of a SOMRT dev kit with QWIIC wires connecting a keypad and screen

3D Printing

Time to make a nice project box for your prototype!

Check out the 3d-printed-enclosure subfolder on GitHub for the printable files. You can try loading the STL into a slicer directly, but we suggest opening the SCAD file in OpenSCAD and poking around first. There are also test-sample files available, which you can use as calibration examples to test your printer and OpenSCAD settings on a smaller model before copying those settings to the larger model, saving time and plastic.

Case Assembly

Here are all of the necessary parts laid out:

Photo of all parts required for assembly, including electronic components and 3d printed pieces

After a successful print that passes a fit check, we start by installing the heat-set inserts with a soldering iron or other hot tip set to about 300 degrees Celsius. (PLA plastic melts at about 210 degrees C, but we also need to heat up the brass insert in a timely manner.) The taller posts can be installed with a regular-tipped iron. Shorter posts may benefit from the tip applied sideways to avoid hitting the plastic underneath, or by using a specialized tip for the purpose:

The cut pieces of clear filament are inserted into the light tubes and optionally secured with a dot of clear glue:

Photo of short pieces of clear 3D printing filament being inserted into the light tubes of the enclosure lid

The button posts can also be installed through the hollow flanges labeled RST and IRQ. The button caps snap-fit onto the ends of the posts, so if the fit is slightly off they can be sanded or glued to attach securely, or reprinted with different settings based on your printer:

Photo of plastic button caps being inserted onto the button posts mounted through the enclosure lid

The 3D printed straps can then be screwed in place with short M3 screws to hold the Qwiic keypad and screen:

Photo of the enclosure lid with Qwiic keypad and screen screwed to the inside of the lid with the help of 3d printed straps

Almost done! Connect another Qwiic wire between the lid components and the Dev Board. The cable can either be connected through the outside and routed in through the WiFi slot, or it can be looped back around directly inside the enclosure base using the extra space in the Qwiic cutout:

Photo of the enclosure lid and base with Qwiic wire connecting the two halves

If using the WiFi module, it can now be installed above the Qwiic connector. The 11mm 3D printed spacer can be used to support the space between both circuit boards. Note that the WiFi module takes extra power, so it may be a good time to check if your computer’s USB port provides enough power, and switch ports or use an external charger if not:

Photo of the enclosure lid and base with WiFi module and spacer installed

And we’re ready to put it all together! Fold the top half on top of the bottom half, routing the Qwiic wire underneath the WiFi module as you go. You can place the long M3 screw underneath to hold things in place if you want. The two halves should fit neatly together with no forcing or pinched cables:

Photo of the enclosure lid and base attached together, with the Qwiic wire routed outside and then back inside, and the WiFi module poking out of its slot

Finally the bottom screws can be secured:

Photo of the bottom of the Qwiic Blog enclosure, showing an M3 screw being secured into the bottom screw holes

And you’re done! Plug in power and Ethernet, and the most recent data from USGS.gov will appear. Press 8 to scroll “down”, 2 to scroll “up”, and # to get new data:

Photo of the assembled enclosure running the Qwiic blog example, with USB and Ethernet cables connected and text on the screen.

Wrap-up

Not only is the SOMRT1061 an exciting new platform with tons of capability packed into a small package, we’ve also loaded the SOMRT1061 Development Kit full of ways to quickly and easily bring your ideas to life. Whether it’s the convenient Qwiic I2C system, onboard A2D, easy WiFi header, USB OTG ports, RTC, multiple CAN and UARTs, the fun RGB LED, or the flexible develop-prototype-production SOMRT1061 form factor, we hope NetBurner’s new products empower you like never before.

If you have any questions or feedback about this blog post or our products, please check out our Forums or contact us at [email protected] or [email protected] !

Share this post

Subscribe to our Newsletter

Get monthly updates from our Learn Blog with the latest in IoT and Embedded technology news, trends, tutorial and best practices. Or just opt in for product change notifications.

Leave a Reply