New Air Velocity Sensors for Data Center Thermal Management

Posifa Technologies today announced its new PAV1000 series of air velocity sensors for data center thermal management and HVAC applications such as filter monitoring . Measuring air velocity at 7 m/s (PAV1005V) and 15 m/s (PAV1015V), the devices offer extremely fast response times of 125 ms typical in a compact, robust package.

The PAV1000 series was designed as an economical, high-performance air velocity sensor for thermal management and filter monitoring in even the most space-constrained locations, such as rack enclosures with air cooling in data centers . Unlike thermistor-based solutions, the Posifa MEMS sensor core is minimally affected by ambient temperature changes and provides instant real-time feedback on proper air flow at critical locations .

Air velocity data perfect ly complement s temperature information to provide the best insight into thermal efficiency. By i dentifying areas that require unusually high air flow to maintain stable temperatures , layouts can be modified to reduce cooling demands f or increased energy savings. In HVAC applications , air velocity measurements can be used to determine when filter s need to be replace d. This ensures hardware is adequately protected during heavy use , while allowing customers with light usage requirements to benefit from extended change intervals.

The devices feature Posifa’s third-generation thermal flow die, which uses a pair of thermopiles to detect changes in temperature gradient caused by mass flow. The result is excellent repeatability of 1 % FS and accuracy of 5 % FS. The solid-state thermal isolation structure on the sensor die eliminates the need for a surface cavity or fragile membrane used in competing technologies, making the sensor resistant to clogging and pressure shock.

The PAV1000 series offers an output voltage range from 0 VDC to 4.5 VDC, supply voltage from 5 VDC to 5.5 VDC, and supply current of 20 mA. The devices operate over a -25 ° C to +85 ° C temperature range.

Samples and production quantities of the PAV1000 series air velocity sensors are available now. Further information is available at https://posifatech.com/air-velocity-sensors/ .

Banana Pi BPI-M4 SBC Features Realtek Quadcore Soc

This year SinoVoip revealed Banana Pi BPI-M4 – a mid-range  SBC that runs Android 8.1 or Linux on a quad -A53 Realtek RTD1395 SoC featuring HDMI, M.2, WiFi/BT, 40-pin GPIO, PoE support, and 5x USB ports. SinoVoip is well-known for its Allwinner SoC based single board computers and the Banana Pi line of product. But this BPI-M4 SBC sports a quad-core, Cortex-A53 based Realtek RTD1395. Their earlier BPI-W2 router board was also powered by a Realtek quadcore SoC.

Banana Pi BPI-M4 SBC

The Banana Pi BPI-M4 is said to have a Raspberry Pi layout, despite being a bit larger in both dimensions with a footprint of 92 x 60mm. Unlike the RPi SBCs, there’s an option for 2GB in addition to 1GB RAM, and it’s DDR4. You also get 8GB to 64GB eMMC and a microSD slot. The clock rate of RTD1395 SoC is not listed but the Cortex-A53 foundation and Mali 470 MP4 GPU would make it more powerful than the BPI-M2 series of boards like BPI-M2 Berry, BPI-M2 Magic, and BPI-M2 Zero. The 1.8GHz  octa-core BPI-M3, with PowerVR SGX544MP1 GPU, might outperform it though this is not confirmed yet.

Banana Pi BPI-M4, front view

 

Banana Pi BPI-M4, back view

The Ethernet speed of BPI-M4 is 10/100Mbps, and there’s a Power-over-Ethernet (PoE) connector that supports the official Raspberry Pi PoE HAT. The YouTube video below shows the SBC booting Android 8.1.1 via PoE power. SinoVoip also states it will run full Linux with a new Realtek SoC, which may take a while. The Banana Pi BPI-M4’s HDMI port is limited to 1080p. This SBC also lacks the support for H.265. There are 4x USB host ports on BPI-M4. You also get a USB Type-C port that offers another power alternative. The board ships with WiFi-ac and BT 4.2. A cellular modem can also be attached using the M.2 E-Key slot. This 5V board has power and boot buttons.

The key specifications are:

  • Processor:
    • Realtek RTD1395 (4x Cortex-A53)
    • ARM Mali-470 MP4 GPU
  • Memory/Storage:
    • RAM — 1GB or 2GB DDR4 RAM
    • 8GB eMMC expandable to up to 64GB
    • MicroSD slot for up to 256GB
  • Wireless:
    • 802.11b/g/n/ac WiFi with Bluetooth 4.2 (RTL8821 module)
    • antenna connector
    • M.2 (see farther below)
  • Networking:
    • 10/100 Ethernet port
    • PoE connector for RPi PoE HAT
  • Media I/O:
    • HDMI port with support for 1080p and multi-channel audio
    • 3.5mm audio jack
  • Other I/O:
    • 4x USB 2.0 host ports
    • USB 2.0 Type-C port
    • Debug UART
  • Expansion:
    • 40-pin, RPi 3-compatible expansion header with 28x GPIO, UART, I2C, power, SPI (or PWM), etc.
    • M.2 E-key (PCIe 2.0 and USB 2.0)
  • Other features:
    • 2x LEDs
    • reset, power, install, UBoot buttons
    • boot select switch
  • Power: 5V/2A DC jack (or USB Type-C or PoE)
  • Weight: 48 g
  • Dimensions: 92 x 60mm
  • Operating system:
    • Android 8 1 1
    • Linux

Pricing for the Banana Pi BPI-M4 is not yet available on the official product page.

(update 14/06/2019)

The Banana Pi BPI-M4 is available on AliExpress for $38 (plus $5.26 to the U.S.) with 1GB RAM and 8GB eMMC. More information may be found on the BPI-M4 AliExpress page and the wiki.

Newport GW6200 Networking Single Board Computer

High Performance Single Board Computer for Networking with 2x Mini-PCIe Sockets, 2x Gigabit Ethernet Ports, and USB 2.0/3.0

The GW6200 is a member of Gateworks 6th generation Newport family of single board computers targeted for a wide range of indoor and outdoor networking applications. The single board computer features the Cavium OcteonTX™ Dual Core ARMv8 SoC processor operating at 800MHz, 1GByte of DDR4 DRAM, and 8GBytes of eMMC System Flash. Two Mini-PCIe expansion sockets support a combination of 802.11abgn/ac wireless radios, LTE/4G/3G CDMA/GSM cellular modems, mSATA drives and other PCI Express peripherals. Additional peripherals include two Gigabit Ethernet ports and two USB 2.0/3.0 ports. The Gateworks System Controller provides embedded features such as real time clock, voltage and temperature monitor, fan control and monitor, serial EEPROM, and advanced power management with programmable board shut-down and wake-up for remote sensor applications. A wide-range DC input power supply provides up to 10W to the Mini-PCIe sockets for supporting the latest high-power radios and up to 10W to the USB 2.0/3.0 jacks for powering external devices. Power is applied through 2-pin connector or an Ethernet jack with either 802.3at or Passive Power over Ethernet. Both OpenWrt and Ubuntu Linux Board Support Packages are supported.

Features

  • Cavium OcteonTXTM Dual Core ARMv8 800MHz SoC Processor
  • 1GByte DDR4 SDRAM Memory and 8GBytes eMMC Flash Memory Standard
  • Micro-SD™ Flash Expansion Socket
  • Two High-Power Mini-PCIe Sockets
  • Socket 1: PCIe or USB3.0, USB2.0, SIM Socket
  • Socket 2: PCIe or mSATA, USB2.0
  • Two Gigabit Ethernet Ports
  • Two USB 2.0/3.0 Ports up to 5Gbps Super Speed
  • Two RS232 or One RS232/422/485 Serial Port
  • Digital I/O, SPI, and I2C Expansion
  • Real Time Clock with Battery Backup
  • Voltage and Temperature Monitor
  • Serial Configuration EEPROM
  • Programmable Watchdog Timer
  • Programmable Fan Controller with Tachometer Support
  • Programmable Shut-Down and Wake-Up
  • Tamper Switch Support
  • Optional Ublox ZOE-MQ8 GNSS GPS Receiver with PPS Support
  • Optional Microchip MCP25625 2.0B CAN Bus Controller
  • 2-Pin Connector Input Voltage Range 8 to 60V
  • Ethernet Port Passive PoE Input Voltage Range 10 to 60V
  • Ethernet Port 802.3at PoE Input Voltage Range 37 to 57V
  • Input Voltage Reverse and Transient Protection
  • Typical Operating Power 5W@25C
  • -40C to +85C Operating Temperature
  • Form Factor Compatible with GW5200 Ventana Media SBC
  • OpenWrt and Ubuntu Board Support Packages
  • 1 Year Warranty

Block Diagram

more information and support material can be found on Gateworks Newport Family Support  page and product page.

USB to -5V Negative Voltage DC-DC switching Converter

Now days USB power source is very common and available in many devices and power banks. This DC-DC converter provide -5V negative voltage from +5V USB power source. This converter can be used in a wide range of industrial automation control equipment, sensors, isolated operational amplifiers and test & measurement equipment that require bipolar supply voltages.

The Max735 is CMOS, inverting switch-mode regulator with internal power MOSFETs. It operates from 4V to 6.2V input supply, and provides 5V 200mA load current. The MAX735 employ a high-performance current mode pulse- width modulation scheme to provide tight output-voltage regulation and low sub harmonic noise. The fixed frequency oscillator is factory trimmed to 160Khz, allowing for easy noise filtering.

USB to -5V Negative Voltage DC-DC switching Converter – [Link]

USB to +/-5V Voltage DC-DC switching Converter

Now days USB power source is very common and available in many devices and power banks. This DC-DC converter provide -5V negative voltage from +5V USB power source. This converter can be used in a wide range of industrial automation control equipment, sensors, isolated operational amplifiers and test & measurement equipment that require bipolar supply voltages.

The Max735 is CMOS, inverting switch-mode regulator with internal power MOSFETs. It operates from 4V to 6.2V input supply, and provides 5V 200mA load current. The MAX735 employ a high-performance current mode pulse- width modulation scheme to provide tight output-voltage regulation and low sub harmonic noise. The fixed frequency oscillator is factory trimmed to 160Khz, allowing for easy noise filtering.

Features

  • Supply Input USB +5V (4V to 6.2V Range)
  • PCB Directly Mount on USB Power Source (PCB USB Connector)
  • Header Connectors for output
  • Operating Frequency 160 KHz
  • Provides -5V Negative Supply
  • Maximum Load current 200mA
  • PCB dimensions: 43.89 x 22.70mm

Schematic

Parts List

Connections

Photos

MAX735 Datasheet

Video

Using Interrupts with Arduino

We can say that an Interrupt is an automatic transfer of software execution in response to a hardware event that is asynchronous with the current software execution. As it’s name says, interrupts, “interrupt” the normal program flow to perform an action (run another code block, function, etc.) and return back to the normal program flow when the action is completed. Interrupts can be established for events such as a counter’s number change, a pin changing state (from low to high or high to low), serial communication receiving information, or the Analog to Digital having established a conversion, etc., depending on what is supported by the microcontroller you are working with. For today’s tutorial, we will focus on interrupts as a result of pins changing state and how these can be used on the Arduino.

To monitor their digital pins for changes, microcontrollers by default use a technique generally referred to as “polling“, which means they constantly check the digital pins to know if any change has occurred. This usually takes a lot of the microcontroller’s resources and reduces its response time which may not be a problem in most applications but could be important in time-critical ones. One of the solutions usually adopted in scenarios where all the negatives of polling need to be eradicated is the use of external Interrupts. Interrupts provide the microcontrollers with a way of multitasking such that a particular digital pin does not need to be continually polled, as the pin would by itself signal the microcontroller and immediately pause the operations when a change occurs. Asides in time-critical applications, where the microcontroller needs increased response rate, Interrupts are widely used in power saving applications for all kind of device, from smartphones and their power button to your PC and the spacebar and the application goes on.

Using Interrupts with Arduino – [Link]

0.8μm smartphone image sensor packs 64 megapixels

Samsung has expanded its 0.8μm pixel image sensor lineup with the 64-megapixel Isocell Bright GW1 and the 48Mp Isocell Bright GM2.

Isocell Bright GW1 is a 64Mp image sensor that features the highest resolution in Samsung’s 0.8μm-pixel image sensor lineup. With pixel-merging Tetracell technology and remosaic algorithm, GW1 can produce bright 16Mp images in low-light environments and highly-detailed 64Mp shots in brighter settings. To take pictures resembling the way the human eye perceives its surroundings in a mixed light environment, GW1 supports real-time high dynamic range (HDR) of up to 100-decibels (dB) that provides richer hues. In comparison, the dynamic range of a conventional image sensor is at around 60dB, while that of the human eye is typically considered to be around 120dB.

GW1 is equipped with a Dual Conversion Gain (DCG) that converts the received light into an electric signal according to the illumination of the environment. This allows the sensor to optimize its full well capacity (FWC), utilizing the collected light more efficiently especially in bright environments. Sharper results can be delivered through Super PD, a high-performance phase detection auto-focus technology, and full HD recording at 480 frames-per-second (fps) is supported for smooth cinematic slow motion videos.

Isocell Bright GM2 is a 48Mp image sensor that also adopts Tetracell technology in low-light environments and a remosaic algorithm in well-lit settings, bringing highly-detailed pictures with natural and vivid colors. GM2, like GW1, adopts DCG as well for added performance and Super PD for fast autofocus. Samsung Isocell Bright GW1 and GM2 are currently sampling and are expected to be in mass production in the second half of this year.

Samsung – www.samsung.com

Arduino Menu Tutorial with a Rotary Encoder and a Nokia 5110 LCD display

A Rotary Encoder
A Rotary Encoder

In our last tutorial, we examined how to create a menu for your Arduino project on a Nokia 5110 LCD, with push buttons to navigate through it. In today’s tutorial will build a modified version of it as we will use a rotary encoder (in place of the push buttons) for menu navigation.

A rotary encoder is an electro-mechanical device which converts the angular position or rotation of its shaft or axle to an analog or digital signal. They are used in several systems where precision and feedback in terms of rotational motion or angular position are required. Another good feature of the rotary encoder which is handy for this tutorial is that they come with buttons attached, so it can be clicked by pressing the knob and this is recognized by the Arduino just as it will if it were any other button or switch.

A menu is one of the easiest ways through which users can interact with devices with diverse features. From Smartphones to PCs and even TVs, it is used in almost every electronic device with a screen and navigating through it is done usually by pressing certain buttons to move up/down, right/left and make selections. However, in some recent smart devices, either for aesthetics or for an improved form factor, a knob-like approach is employed for navigation. For today’s tutorial, we will explain how to create a menu with the same Knob style control, using a rotary encoder.

Arduino Menu Tutorial with a Rotary Encoder and a Nokia 5110 LCD display – [Link]

Menu on Nokia 5110 LCD display with Arduino

Nokia 5110 LCD Display

We have published quite a number of tutorials using different displays with the Arduino, with the most recent being the tutorial on displaying graphics on all kind of displays with Arduino. For today’s tutorial, we will look into achieving more with displays by implementing a menu based system with the Nokia 5110 LCD display and the Arduino. The menu is one of the easiest and most intuitive ways through which users interact with products that require navigation. From mobile phone to PCs, its applications are endless. Today we will explore how to add this cool feature to your Arduino project.

At the heart of today’s project is the Nokia 5110 LCD Display. The Nokia 5110 LCD is one of the most popular LCD display among makers. It was originally developed for use as a screen for cell phones and was used in lots of mobile phones during the 90’s. The display uses a low power CMOS LCD controller/driver, the PCD8544, which drives the 84×48px graphics display. In a normal state, the display consumes about 6 to 7mA which makes it quite ideal for low power devices. We have published quite a number of tutorials on this display that might help you understand how to drive such a display.

Menu on Nokia 5110 LCD display with Arduino – [Link]

Arduino Menu Tutorial with a Rotary Encoder and a Nokia 5110 LCD display

In our last tutorial, we examined how to create a menu for your Arduino project on a Nokia 5110 LCD, with push buttons to navigate through it. In today’s tutorial, we will build a modified version of it which will make use a rotary encoder (in place of the push buttons) for menu navigation.

A Rotary Encoder
A Rotary Encoder

A menu is one of the easiest ways through which users can interact with devices with diverse features. From Smartphones to PCs and even TVs, it is used in almost every electronic device with a screen and navigating through it is done usually by pressing certain buttons to move up/down, right/left and make selections. However, in some recent smart devices, either for aesthetics or for an improved form factor, a knob-like approach is employed for navigation. For today’s tutorial, we will explain how to create a menu with the same Knob style control, using a rotary encoder.

Rotary Encoders are used in several systems where precision and feedback in terms of rotational motion or angular position are required. By turning the shaft to the right or left, we either get an increase or decrease in value (depending on the configuration). One of the major advantages of rotary encoders is the fact that their rotation is limitless. If the maximum position, (which is 20 for the particular rotary encoder used in this tutorial) is reached, the device starts the position counting all over again while the value attached to the position continues to increase/decrease with every turn of the knob in the same direction. Another good feature which will be handy for this tutorial is that they come with buttons attached, so it can be clicked by pressing the knob and is recognized by the Arduino just as any other button or switch.

Required Components

The following components are required to build this project;

  1. Arduino Uno
  2. Rotary Encoder
  3. Nokia 5110 LCD
  4. Small Breadboard
  5. Jumper Wires
  6. Power Bank

As usual, the exact components used for this tutorial can be purchased via the links attached to each of them.

Schematics

The schematics for today’s project is almost similar to the one of the last tutorial. We will take out the three push-buttons and add a rotary encoder. Connect the components as shown in the schematics below.

Schematics

The rotary encoder used is an analog device, and for that reason all its three output pins are connected to analog pins on the Arduino.

To make the connections easier to follow, a pin map between the components is provided below.

LCD – Arduino

Reset - D3
pin2 - D4
pin3 - D5
pin4 - D11
pin5 - D13
VCC - 3.3v
Backlight - D7
GND - GND

Rotary Encoder – Arduino

GND - GND
VCC - VCC
SW - A2
DT - A1
CLK - A0

You can check out our previous tutorials on the Nokia 5110 LCD Display and the Rotary Encoder to better understand how they work.

Go over the schematics once again to ensure everything is as it should be then let’s proceed to write the code.

Code

The code for this version of the tutorial is a little bit more complex compared to that of the last tutorial and you may need to replicate it yourself before you really understand the full scope of it.

To simplify/reduce the amount of work we need to do, we will use four libraries. Two of the libraries; the Adafruit GFX library and the Nokia 5110 LCD library, will be used to interact with the display while the other two; the Encoder Library and the TimerOne library, will reduce the amount of code we write to interact with the rotary encoder. Each of the libraries can be downloaded via the links attached to them or installed via the Arduino Library Manager.

To briefly explain the code;

As usual, we start by including the libraries that will be used for the project.

//////////////////////////////////////////////
  //       Arduino Rotary Encoder Menu        //
 //                 v1.0                     //
//           http://www.educ8s.tv           //
/////////////////////////////////////////////

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <ClickEncoder.h>
#include <TimerOne.h>

Next, we create variables to hold the menus alongside an object of the click encoder and the LCD library.

int menuitem = 1;
int frame = 1;
int page = 1;
int lastMenuItem = 1;

String menuItem1 = "Contrast";
String menuItem2 = "Volume";
String menuItem3 = "Language";
String menuItem4 = "Difficulty";
String menuItem5 = "Light: ON";
String menuItem6 = "Reset";

boolean backlight = true;
int contrast=60;
int volume = 50;

String language[3] = { "EN", "ES", "EL" };
int selectedLanguage = 0;

String difficulty[2] = { "EASY", "HARD" };
int selectedDifficulty = 0;

boolean up = false;
boolean down = false;
boolean middle = false;
ClickEncoder *encoder;
int16_t last, value;

Adafruit_PCD8544 display = Adafruit_PCD8544( 5, 4, 3);//This only works with recent Adafruit Nokia 5110 library. Ensure yours is up to date

Next, we write the void setup() function. We start by declaring the pin to which the LCD’s backlight is connected (D7) as output and then proceed to create an encoder object before initializing the display and the rotary encoder, setting it to start at zero. We also create an interrupt routine which will be used to detect button press within any of the menu pages.

void setup() {

  pinMode(7,OUTPUT);
  turnBacklightOn();
  
  encoder = new ClickEncoder(A1, A0, A2);
  encoder->setAccelerationEnabled(false);
   
  display.begin();      
  display.clearDisplay(); 
  setContrast();  

  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerIsr); 
  
  last = encoder->getValue();
}

Up next is the void loop() function. This function is the most complex part of the whole code. The function basically creates the menu and uses the variables created initially to keep track of the previous, current and next state so when the rotary encoder is turned, it knows the right menu to display. The menu selection part of the code is also handled by a routine within the void loop() function.

We start by creating the menu using the drawMenu() function.

void loop() {

  drawMenu();

Next, we call the readRotaryEncoder function to obtain the control inputs from the rotary encoder.

readRotaryEncoder();

 ClickEncoder::Button b = encoder->getButton();
 if (b != ClickEncoder::Open) {
 switch (b) {
    case ClickEncoder::Clicked:
       middle=true;
      break;
  }
}

Next, Data from the encoder is fed into a series of if-else statements which checks if the encoder was clicked or turned in a particular direction and which of the screens is currently being displayed to determine what action is to be done next. For instance, the first if statement checks if the menu is currently on page 1 and if the encoder is turned to the right (indicates up). If this is the case, it then checks the position of the menu cursor and adjusts it accordingly.

  
  if (up && page == 1 ) {
     
    up = false;
    if(menuitem==2 && frame ==2)
    {
      frame--;
    }

     if(menuitem==4 && frame ==4)
    {
      frame--;
    }
      if(menuitem==3 && frame ==3)
    {
      frame--;
    }
    lastMenuItem = menuitem;
    menuitem--;
    if (menuitem==0)
    {
      menuitem=1;
    } 
  }else if (up && page == 2 && menuitem==1 ) {
    up = false;
    contrast--;
    setContrast();
  }
  else if (up && page == 2 && menuitem==2 ) {
    up = false;
    volume--;
  }
  else if (up && page == 2 && menuitem==3 ) {
    up = false;
    selectedLanguage--;
    if(selectedLanguage == -1)
    {
      selectedLanguage = 2;
    }
  }
    else if (up && page == 2 && menuitem==4 ) {
    up = false;
    selectedDifficulty--;
    if(selectedDifficulty == -1)
    {
      selectedDifficulty = 1;
    }
  }

  if (down && page == 1) //We have turned the Rotary Encoder Clockwise
  {

    down = false;
    if(menuitem==3 && lastMenuItem == 2)
    {
      frame ++;
    }else  if(menuitem==4 && lastMenuItem == 3)
    {
      frame ++;
    }
     else  if(menuitem==5 && lastMenuItem == 4 && frame!=4)
    {
      frame ++;
    }
    lastMenuItem = menuitem;
    menuitem++;  
    if (menuitem==7) 
    {
      menuitem--;
    }
  
  }else if (down && page == 2 && menuitem==1) {
    down = false;
    contrast++;
    setContrast();
  }
  else if (down && page == 2 && menuitem==2) {
    down = false;
    volume++;
  }
   else if (down && page == 2 && menuitem==3 ) {
    down = false;
    selectedLanguage++;
    if(selectedLanguage == 3)
    {
      selectedLanguage = 0;
    }
  }
  else if (down && page == 2 && menuitem==4 ) {
    down = false;
    selectedDifficulty++;
    if(selectedDifficulty == 2)
    {
      selectedDifficulty = 0;
    }
  }
  
  if (middle) //Middle Button is Pressed
  {
    middle = false;
   
    if (page == 1 && menuitem==5) // Backlight Control 
    {
      if (backlight) 
      {
        backlight = false;
        menuItem5 = "Light: OFF";
        turnBacklightOff();
        }
      else 
      {
        backlight = true; 
        menuItem5 = "Light: ON";
        turnBacklightOn();
       }
    }

    if(page == 1 && menuitem ==6)// Reset
    {
      resetDefaults();
    }


    else if (page == 1 && menuitem<=4) {
      page=2;
     }
      else if (page == 2) 
     {
      page=1; 
     }
   }   
  }

The remaining pieces of code are the functions used within the loop including the drawmenu() function and the function to read the rotary encoder.

  void drawMenu()
  {
    
  if (page==1) 
  {    
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(BLACK, WHITE);
    display.setCursor(15, 0);
    display.print("MAIN MENU");
    display.drawFastHLine(0,10,83,BLACK);

    if(menuitem==1 && frame ==1)
    {   
      displayMenuItem(menuItem1, 15,true);
      displayMenuItem(menuItem2, 25,false);
      displayMenuItem(menuItem3, 35,false);
    }
    else if(menuitem == 2 && frame == 1)
    {
      displayMenuItem(menuItem1, 15,false);
      displayMenuItem(menuItem2, 25,true);
      displayMenuItem(menuItem3, 35,false);
    }
    else if(menuitem == 3 && frame == 1)
    {
      displayMenuItem(menuItem1, 15,false);
      displayMenuItem(menuItem2, 25,false);
      displayMenuItem(menuItem3, 35,true);
    }
     else if(menuitem == 4 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,false);
      displayMenuItem(menuItem3, 25,false);
      displayMenuItem(menuItem4, 35,true);
    }

      else if(menuitem == 3 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,false);
      displayMenuItem(menuItem3, 25,true);
      displayMenuItem(menuItem4, 35,false);
    }
    else if(menuitem == 2 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,true);
      displayMenuItem(menuItem3, 25,false);
      displayMenuItem(menuItem4, 35,false);
    }
    
    else if(menuitem == 5 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,false);
      displayMenuItem(menuItem4, 25,false);
      displayMenuItem(menuItem5, 35,true);
    }

    else if(menuitem == 6 && frame == 4)
    {
      displayMenuItem(menuItem4, 15,false);
      displayMenuItem(menuItem5, 25,false);
      displayMenuItem(menuItem6, 35,true);
    }
    
      else if(menuitem == 5 && frame == 4)
    {
      displayMenuItem(menuItem4, 15,false);
      displayMenuItem(menuItem5, 25,true);
      displayMenuItem(menuItem6, 35,false);
    }
      else if(menuitem == 4 && frame == 4)
    {
      displayMenuItem(menuItem4, 15,true);
      displayMenuItem(menuItem5, 25,false);
      displayMenuItem(menuItem6, 35,false);
    }
    else if(menuitem == 3 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,true);
      displayMenuItem(menuItem4, 25,false);
      displayMenuItem(menuItem5, 35,false);
    }
        else if(menuitem == 2 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,true);
      displayMenuItem(menuItem3, 25,false);
      displayMenuItem(menuItem4, 35,false);
    }
    else if(menuitem == 4 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,false);
      displayMenuItem(menuItem4, 25,true);
      displayMenuItem(menuItem5, 35,false);
    }   
    display.display();
  }
  else if (page==2 && menuitem == 1) 
  {    
   displayIntMenuPage(menuItem1, contrast);
  }

  else if (page==2 && menuitem == 2) 
  {
   displayIntMenuPage(menuItem2, volume);
  }
   else if (page==2 && menuitem == 3) 
  {
   displayStringMenuPage(menuItem3, language[selectedLanguage]);
  }
  else if (page==2 && menuitem == 4) 
  {
   displayStringMenuPage(menuItem4, difficulty[selectedDifficulty]);
  }
  else if (page==2 && menuitem == 4) 
  {
   displayStringMenuPage(menuItem4, difficulty[selectedDifficulty]);
  }
  
  }

The remaining piece of code are the functions used to perform different tasks like reading of the potentiometer, drawing of the menu, etc.

 void resetDefaults()
  {
    contrast = 60;
    volume = 50;
    selectedLanguage = 0;
    selectedDifficulty = 0;
    setContrast();
    backlight = true;
    menuItem5 = "Light: ON";
    turnBacklightOn();
  }

  void setContrast()
  {
    display.setContrast(contrast);
    display.display();
  }

  void turnBacklightOn()
  {
    digitalWrite(7,LOW);
  }

    void turnBacklightOff()
  {
    digitalWrite(7,HIGH);
  }

  void timerIsr() {
  encoder->service();
}

void displayIntMenuPage(String menuItem, int value)
{
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(BLACK, WHITE);
    display.setCursor(15, 0);
    display.print(menuItem);
    display.drawFastHLine(0,10,83,BLACK);
    display.setCursor(5, 15);
    display.print("Value");
    display.setTextSize(2);
    display.setCursor(5, 25);
    display.print(value);
    display.setTextSize(2);
    display.display();
}

void displayStringMenuPage(String menuItem, String value)
{
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(BLACK, WHITE);
    display.setCursor(15, 0);
    display.print(menuItem);
    display.drawFastHLine(0,10,83,BLACK);
    display.setCursor(5, 15);
    display.print("Value");
    display.setTextSize(2);
    display.setCursor(5, 25);
    display.print(value);
    display.setTextSize(2);
    display.display();
}

void displayMenuItem(String item, int position, boolean selected)
{
    if(selected)
    {
      display.setTextColor(WHITE, BLACK);
    }else
    {
      display.setTextColor(BLACK, WHITE);
    }
    display.setCursor(0, position);
    display.print(">"+item);
}

void readRotaryEncoder()
{
  value += encoder->getValue();
  
  if (value/2 > last) {
    last = value/2;
    down = true;
    delay(150);
  }else   if (value/2 < last) {
    last = value/2;
    up = true;
    delay(150);
  }
}

The complete code for the project is provided below and also attached under the download section of this tutorial.

   //////////////////////////////////////////////
  //       Arduino Rotary Encoder Menu        //
 //                 v1.0                     //
//           http://www.educ8s.tv           //
/////////////////////////////////////////////

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <ClickEncoder.h>
#include <TimerOne.h>

int menuitem = 1;
int frame = 1;
int page = 1;
int lastMenuItem = 1;

String menuItem1 = "Contrast";
String menuItem2 = "Volume";
String menuItem3 = "Language";
String menuItem4 = "Difficulty";
String menuItem5 = "Light: ON";
String menuItem6 = "Reset";

boolean backlight = true;
int contrast=60;
int volume = 50;

String language[3] = { "EN", "ES", "EL" };
int selectedLanguage = 0;

String difficulty[2] = { "EASY", "HARD" };
int selectedDifficulty = 0;

boolean up = false;
boolean down = false;
boolean middle = false;

ClickEncoder *encoder;
int16_t last, value;

Adafruit_PCD8544 display = Adafruit_PCD8544( 5, 4, 3); //Download the latest Adafruit Library in order to use this constructor

void setup() {

  pinMode(7,OUTPUT);
  turnBacklightOn();
  
  encoder = new ClickEncoder(A1, A0, A2);
  encoder->setAccelerationEnabled(false);
   
  display.begin();      
  display.clearDisplay(); 
  setContrast();  

  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerIsr); 
  
  last = encoder->getValue();
}

void loop() {

  drawMenu();

  readRotaryEncoder();

   ClickEncoder::Button b = encoder->getButton();
   if (b != ClickEncoder::Open) {
   switch (b) {
      case ClickEncoder::Clicked:
         middle=true;
        break;
    }
  }    
  
  if (up && page == 1 ) {
     
    up = false;
    if(menuitem==2 && frame ==2)
    {
      frame--;
    }

     if(menuitem==4 && frame ==4)
    {
      frame--;
    }
      if(menuitem==3 && frame ==3)
    {
      frame--;
    }
    lastMenuItem = menuitem;
    menuitem--;
    if (menuitem==0)
    {
      menuitem=1;
    } 
  }else if (up && page == 2 && menuitem==1 ) {
    up = false;
    contrast--;
    setContrast();
  }
  else if (up && page == 2 && menuitem==2 ) {
    up = false;
    volume--;
  }
  else if (up && page == 2 && menuitem==3 ) {
    up = false;
    selectedLanguage--;
    if(selectedLanguage == -1)
    {
      selectedLanguage = 2;
    }
  }
    else if (up && page == 2 && menuitem==4 ) {
    up = false;
    selectedDifficulty--;
    if(selectedDifficulty == -1)
    {
      selectedDifficulty = 1;
    }
  }

  if (down && page == 1) //We have turned the Rotary Encoder Clockwise
  {

    down = false;
    if(menuitem==3 && lastMenuItem == 2)
    {
      frame ++;
    }else  if(menuitem==4 && lastMenuItem == 3)
    {
      frame ++;
    }
     else  if(menuitem==5 && lastMenuItem == 4 && frame!=4)
    {
      frame ++;
    }
    lastMenuItem = menuitem;
    menuitem++;  
    if (menuitem==7) 
    {
      menuitem--;
    }
  
  }else if (down && page == 2 && menuitem==1) {
    down = false;
    contrast++;
    setContrast();
  }
  else if (down && page == 2 && menuitem==2) {
    down = false;
    volume++;
  }
   else if (down && page == 2 && menuitem==3 ) {
    down = false;
    selectedLanguage++;
    if(selectedLanguage == 3)
    {
      selectedLanguage = 0;
    }
  }
  else if (down && page == 2 && menuitem==4 ) {
    down = false;
    selectedDifficulty++;
    if(selectedDifficulty == 2)
    {
      selectedDifficulty = 0;
    }
  }
  
  if (middle) //Middle Button is Pressed
  {
    middle = false;
   
    if (page == 1 && menuitem==5) // Backlight Control 
    {
      if (backlight) 
      {
        backlight = false;
        menuItem5 = "Light: OFF";
        turnBacklightOff();
        }
      else 
      {
        backlight = true; 
        menuItem5 = "Light: ON";
        turnBacklightOn();
       }
    }

    if(page == 1 && menuitem ==6)// Reset
    {
      resetDefaults();
    }


    else if (page == 1 && menuitem<=4) {
      page=2;
     }
      else if (page == 2) 
     {
      page=1; 
     }
   }   
  }
  
  void drawMenu()
  {
    
  if (page==1) 
  {    
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(BLACK, WHITE);
    display.setCursor(15, 0);
    display.print("MAIN MENU");
    display.drawFastHLine(0,10,83,BLACK);

    if(menuitem==1 && frame ==1)
    {   
      displayMenuItem(menuItem1, 15,true);
      displayMenuItem(menuItem2, 25,false);
      displayMenuItem(menuItem3, 35,false);
    }
    else if(menuitem == 2 && frame == 1)
    {
      displayMenuItem(menuItem1, 15,false);
      displayMenuItem(menuItem2, 25,true);
      displayMenuItem(menuItem3, 35,false);
    }
    else if(menuitem == 3 && frame == 1)
    {
      displayMenuItem(menuItem1, 15,false);
      displayMenuItem(menuItem2, 25,false);
      displayMenuItem(menuItem3, 35,true);
    }
     else if(menuitem == 4 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,false);
      displayMenuItem(menuItem3, 25,false);
      displayMenuItem(menuItem4, 35,true);
    }

      else if(menuitem == 3 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,false);
      displayMenuItem(menuItem3, 25,true);
      displayMenuItem(menuItem4, 35,false);
    }
    else if(menuitem == 2 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,true);
      displayMenuItem(menuItem3, 25,false);
      displayMenuItem(menuItem4, 35,false);
    }
    
    else if(menuitem == 5 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,false);
      displayMenuItem(menuItem4, 25,false);
      displayMenuItem(menuItem5, 35,true);
    }

    else if(menuitem == 6 && frame == 4)
    {
      displayMenuItem(menuItem4, 15,false);
      displayMenuItem(menuItem5, 25,false);
      displayMenuItem(menuItem6, 35,true);
    }
    
      else if(menuitem == 5 && frame == 4)
    {
      displayMenuItem(menuItem4, 15,false);
      displayMenuItem(menuItem5, 25,true);
      displayMenuItem(menuItem6, 35,false);
    }
      else if(menuitem == 4 && frame == 4)
    {
      displayMenuItem(menuItem4, 15,true);
      displayMenuItem(menuItem5, 25,false);
      displayMenuItem(menuItem6, 35,false);
    }
    else if(menuitem == 3 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,true);
      displayMenuItem(menuItem4, 25,false);
      displayMenuItem(menuItem5, 35,false);
    }
        else if(menuitem == 2 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,true);
      displayMenuItem(menuItem3, 25,false);
      displayMenuItem(menuItem4, 35,false);
    }
    else if(menuitem == 4 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,false);
      displayMenuItem(menuItem4, 25,true);
      displayMenuItem(menuItem5, 35,false);
    }   
    display.display();
  }
  else if (page==2 && menuitem == 1) 
  {    
   displayIntMenuPage(menuItem1, contrast);
  }

  else if (page==2 && menuitem == 2) 
  {
   displayIntMenuPage(menuItem2, volume);
  }
   else if (page==2 && menuitem == 3) 
  {
   displayStringMenuPage(menuItem3, language[selectedLanguage]);
  }
  else if (page==2 && menuitem == 4) 
  {
   displayStringMenuPage(menuItem4, difficulty[selectedDifficulty]);
  }
  else if (page==2 && menuitem == 4) 
  {
   displayStringMenuPage(menuItem4, difficulty[selectedDifficulty]);
  }
  
  }

  void resetDefaults()
  {
    contrast = 60;
    volume = 50;
    selectedLanguage = 0;
    selectedDifficulty = 0;
    setContrast();
    backlight = true;
    menuItem5 = "Light: ON";
    turnBacklightOn();
  }

  void setContrast()
  {
    display.setContrast(contrast);
    display.display();
  }

  void turnBacklightOn()
  {
    digitalWrite(7,LOW);
  }

    void turnBacklightOff()
  {
    digitalWrite(7,HIGH);
  }

  void timerIsr() {
  encoder->service();
}

void displayIntMenuPage(String menuItem, int value)
{
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(BLACK, WHITE);
    display.setCursor(15, 0);
    display.print(menuItem);
    display.drawFastHLine(0,10,83,BLACK);
    display.setCursor(5, 15);
    display.print("Value");
    display.setTextSize(2);
    display.setCursor(5, 25);
    display.print(value);
    display.setTextSize(2);
    display.display();
}

void displayStringMenuPage(String menuItem, String value)
{
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(BLACK, WHITE);
    display.setCursor(15, 0);
    display.print(menuItem);
    display.drawFastHLine(0,10,83,BLACK);
    display.setCursor(5, 15);
    display.print("Value");
    display.setTextSize(2);
    display.setCursor(5, 25);
    display.print(value);
    display.setTextSize(2);
    display.display();
}

void displayMenuItem(String item, int position, boolean selected)
{
    if(selected)
    {
      display.setTextColor(WHITE, BLACK);
    }else
    {
      display.setTextColor(BLACK, WHITE);
    }
    display.setCursor(0, position);
    display.print(">"+item);
}

void readRotaryEncoder()
{
  value += encoder->getValue();
  
  if (value/2 > last) {
    last = value/2;
    down = true;
    delay(150);
  }else   if (value/2 < last) {
    last = value/2;
    up = true;
    delay(150);
  }
}


Demo

Verify your connections, by comparing with the schematics above, to ascertain that everything is as it should be.  With that done, connect your board to the computer and upload the sketch to it. You should see the screen come on with the menu displayed. Try to turn the knob in different directions to navigate the menu and use the click feature of the knob to select an option.

Demo

That’s it for today’s tutorial, thanks for reading and building along. What project will you add this cool feature to? Share via the comment section below. Also, reach out via the comment section if you have any question.

Till next time!

The video version of this tutorial is available on Youtube.

TOP PCB Companies