<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Technoblogy</title>
<link>http://www.technoblogy.com/show?KVM</link>
<description><![CDATA[Electronics and microcontroller projects]]></description>
<pubDate>Wed, 01 Jul 2026 23:00:00 GMT</pubDate>
<lastBuildDate>Wed, 01 Jul 2026 23:00:00 GMT</lastBuildDate>
<language>en</language>
<image>
<url>http://www.technoblogy.com/pictures/KVM/icon-72.png</url>
<title>Technoblogy</title>
<link>http://www.technoblogy.com/show?KVM</link>
<width>72</width>
<height>72</height>
</image>
<generator>CL-HTTP RSS</generator>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<item>
<title>Digital Signal Generator</title>
<description><![CDATA[<p>This is a wide-range digital signal generator that generates a crystal accurate square-wave of from 2Hz to approximately 33.5MHz in precise steps of 2Hz. You can enter an exact frequency using a numeric keypad, or adjust the frequency continuously using a rotary encoder, and the frequency is displayed on an OLED display:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/digitalsignalgenerator.jpg" alt="DigitalSignalGenerator.jpg" width="680" height="390" /></p>
<p style="text-align: center;"><em>The Digital Signal Generator, based on an AVR16EB28, generates a square wave of from&nbsp;2Hz<br /> to approximately 33.5MHz in precise steps of 2Hz.</em></p>
<p>The project takes advantage of the new Numerical Controller Oscillator (NCO) mode available in the timer/counter TCF in the AVR EB-series microcontrollers. It uses an AVR16EB28, a crystal, an OLED display, a rotary encoder, a keypad, and a small number of other components.</p>
<h3 id="introduction">Introduction</h3>
<p>When Microchip released their AVR EB-series chips at the end of 2023 I was intrigued by one particular feature: the&nbsp;timer/counter&nbsp;TCF that works differently from all previous AVR&nbsp;timer/counters. Typically&nbsp;timer/counters divide the clock by a divisor in a compare register. This gives a range of frequencies with unequal steps. For example, with divisors of 1, 2, 3, 4 &hellip; a 20MHz clock gives you 20MHz, 10MHz, 6.67MHz, 5MHz &hellip; where the steps are getting progressively smaller.</p>
<p>The TCF timer/counter, by comparison, provides a mode called Numerical Controller Oscillator (NCO) which adds a value from a register to the count value on each clock cycle, and takes the output from the overflow <sup id="cite_ref1"><a href="#cite_note1">[1]</a></sup>. For values of 1, 2, 3, 4 &hellip; and a 24-bit counter a 20MHz clock gives you frequencies of 1.192Hz, 2.384Hz, 3.576Hz, 4.768Hz &hellip; where the steps are equal.</p>
<h4 id="phaselocked-loop-pll">Phase-Locked Loop (PLL)</h4>
<p>Another feature of the AVR EB series is a PLL (Phase-Locked Loop) that can be used to multiply a clock of 2.2MHz to 5.5MHz by a factor of 8 or 16 to drive the TCF timer/counter with up to 80MHz, way beyond the processor's maximum 20MHz clock rate. These two features of the AVR EB series led to the idea of this simple wide-range digital signal generator.</p>
<h4 id="crystal">Crystal</h4>
<p>I wanted to generate frequencies with round numbers of Hz, and better accuracy than the processor's built-in oscillator, so I decided to use a crystal clock. I calculated that the value of crystal clock that would be needed to give round-number frequencies is 2<sup>22</sup>Hz or 4.194304MHz, and I was pleased to find that this is a commonly available value.</p>
<p>The NCO oscillator can either produce an asymmetrical square-wave output with a pulse width of one clock cycle, or a symmetrical square wave of half the frequency; I chose the second option.</p>
<h4 id="the-frequency-range">The frequency range</h4>
<p>In this mode the output frequency <em>f</em>&nbsp;&nbsp;in Hz is given by:</p>
<div class="eqn"><em>f</em>&nbsp;&nbsp;=&nbsp;</div>
<div class="eqn"><span class="num"><em>clk</em> &times; <em>inc</em></span><span class="den">2<sup>24</sup> &times; 2</span></div>
<p>where <em>clk</em> is the clock input to the NCO and <em>inc</em> is the increment value in the compare register. With a crystal of 2<sup>22</sup>Hz and a x16 PLL this becomes:</p>
<div class="eqn"><em>f</em>&nbsp;&nbsp;=&nbsp;</div>
<div class="eqn"><span class="num">2<sup>22</sup> &times; 16 &times; <em>inc</em></span><span class="den">2<sup>24</sup> x 2</span></div>
<div class="eqn">&nbsp;=&nbsp;2 &times; <em>inc</em></div>
<p>Because <em>inc</em> can take the values 1 to 2<sup>24</sup>&ndash;1 the result is a range of 2Hz to 33554430Hz in steps of exactly 2Hz. For alternative options see <a href="#further-suggestions">Further suggestions</a> below.</p>
<h4 id="operation">Operation</h4>
<p>The Digital Signal Generator works as follows: you enter the frequency you want, in Hz, on the numeric keypad as a sequence of up to eight digits, and press *. The display then shows the actual frequency that will be generated, and generates the square waves on the outputs. You can then adjust the frequency up or down in steps of 2Hz using the rotary encoder. Pressing # clears the current input.</p>
<p>The waveform outputs are taken from pins 1 and 28 on the AVR16EB14. For example, here are the two waveforms generated for a frequency of 100000Hz:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/digitalsignaloutputs.gif" alt="DigitalSignalOutputs.gif" width="220" height="165" /></p>
<h4 id="jitter">Jitter</h4>
<p>I should mention one characteristic of the NCO which is that the output will have jitter when the value being added to the counter isn't an exact divisor of the maximum count. For example, with a 24-bit counter and an increment value of 3, which is not an exact divisor of 2<sup>24</sup>, a 20MHz clock will give a square wave with a wavelength alternating between 0.419 secs and 0.210 secs. However, this is the worst case, and at higher frequencies the jitter rapidly becomes a negligible proportion of the wavelength. Note that, despite the jitter, the average frequency of the output is precise, and so even at low frequencies the jitter is not a problem for applications such as timing or clock generation.</p>
<h3 id="the-circuit">The circuit</h3>
<p>Here's the circuit of the Digital Signal Generator:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/digitalsignalgenerator.gif" alt="DigitalSignalGenerator.gif" width="581" height="423" /></p>
<p style="text-align: center;"><em>The circuit of the Digital Signal Generator, based on an AVR16EB28.</em></p>
<h4 id="the-processor">The processor</h4>
<p>I initially thought about designing the circuit based on a 14-pin AVR16EB14, but this has the limitation that the timer/counter waveform output WO0 is on the same pin as the EXTCLK external clock pin needed for the input to the PLL. A 20-pin package would solve this problem, but I went to the 28-pin AVR16EB28 because it's available in a DIP package, and so could be fitted on the breadboard without needing a breakout board. The extra I/O pins might come in useful in a future expansion. You could of course also use the AVR32EB28.</p>
<h4 id="the-display">The display</h4>
<p>For the display I used an I2C 128x32 0.91" OLED display, available at low cost from a variety of vendors on AliExpress <sup id="cite_ref2"><a href="#cite_note2">[2]</a></sup>. Note that the display is 3.3V only, so don't power the circuit from 5V.</p>
<h4 id="numeric-keypad">Numeric keypad</h4>
<p>For the numeric keypad I used the PCB keypad I designed in an earlier article; see <a class="auto" href="http://www.technoblogy.com/show?41BI">One-Input Numeric Keypad</a>. It&nbsp;fits neatly on the breadboard, and connects to a single analogue input, PD6. Alternatively you could build the keypad on a piece of Veroboard.</p>
<h4 id="the-crystal">The crystal</h4>
<p>The crystal I used is a&nbsp;4.194304MHz&nbsp;through-hole type with a 0.2" lead spacing, an accuracy of 20ppm, and a load capacitance of 18pF&nbsp;<sup id="cite_ref3"><a href="#cite_note3">[3]</a></sup>.&nbsp;To calculate the capacitor values I used the formula C = 2(C<sub>L</sub>&nbsp;- C<sub>S</sub>), where C<sub>L</sub>&nbsp;is the published load capacitance 18pF, and C<sub>S</sub>&nbsp;is the stray capacitance. On a breadboard there is probably close to 18pF of stray capacitance, so additional capacitors are probably not necessary, but if you're designing a PCB version of the circuit you should leave space for the capacitors.</p>
<p>Unlike the earlier ATmega processors the new AVR series don't support an external high-frequency crystal, so it's necessary to provide an external crystal oscillator with its output connected to the EXTCLK input. One possible crystal oscillator design uses an inverter across the crystal:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/crystaloscillator2.gif" alt="CrystalOscillator2.gif" width="185" height="163" /></p>
<p>To avoid the need for an external inverter I used the Event System to construct an inverter as follows:</p>
<ul>
<li>Define PC2 as an event output, EVOUTC.</li>
<li>Define PC3 as an event generator on channel 2</li>
<li>Configure PC3 to invert the input.</li>
</ul>
<p>The crystal is connected between PC3 and PC2, and the inverter output PC2 is then connected to the EXTCLK input on PA0.</p>
<h4 id="rotary-encoder">Rotary encoder</h4>
<p>For the rotary encoder I used a Bourns rotary encoder with 16 pulses per rotation <sup id="cite_ref4"><a href="#cite_note4">[4]</a></sup> because the wire terminations fit neatly into a breadboard, but you could use any suitable rotary encoder, such as Adafruit's which includes a knob&nbsp;<sup id="cite_ref5"><a href="#cite_note5">[5]</a></sup>.</p>
<h3 id="construction">Construction</h3>
<p>I built the whole circuit on a 360 hole breadboard <sup id="cite_ref6"><a href="#cite_note6">[6]</a></sup>, using pre-cut colour-coded jumper wires to make the interconnections easier <sup id="cite_ref7"><a href="#cite_note7">[7]</a></sup>.</p>
<p>I constructed a right-angle adapter from a 4-way pin header to mount the display horizontally, while plugging it into four consecutive breadboard columns; see the photograph at the start of the article.</p>
<h3 id="the-program">The program</h3>
<p>Here's a description of the main parts of the program:</p>
<h4 id="crystal-oscillator">Crystal oscillator</h4>
<p>The routine <strong>SetupCrystal()</strong>&nbsp;uses the Event System to configure PC3 and PC2 as an inverter, with the crystal connected across it:</p>
<pre>void SetupCrystal () {
  PORTC.EVGENCTRLA = PORT_EVGEN0SEL_PIN3_gc;              // Use PC3 as EVGEN0
  EVSYS.CHANNEL2 = EVSYS_CHANNEL_PORTC_EV0_gc;            // Link EVGEN0 to CHANNEL2
  EVSYS.USEREVSYSEVOUTC = EVSYS_USER_CHANNEL2_gc;         // Link CHANNEL2 to PC2
  PORTC.PIN3CTRL = PORT_INVEN_bm;                         // Invert input PC3
}</pre>
<p>The output on PC2 is connected to the input of the Phase-Locked Loop at EXTCLK on PA0.</p>
<h4 id="phaselocked-loop-pll1">Phase-Locked Loop (PLL)</h4>
<p>The routine <strong>SetupPLL()</strong> sets up the PLL to multiply the frequency on EXTCLK by 16:</p>
<pre>void SetupPLL () {
  uint8_t pllctrla = CLKCTRL_SOURCE_EXTCLK_gc | CLKCTRL_MULFAC_16X_gc;
  _PROTECTED_WRITE(CLKCTRL.PLLCTRLA, pllctrla);           // EXTCLK and x16
}</pre>
<p>The built-in macro <strong>_PROTECTED_WRITE()</strong> must be used because the Clock Controller registers are protected from being changed by a runaway program using Configuration&nbsp;Change Protection.</p>
<h4 id="numerically-controlled-oscillator-nco">Numerically Controlled Oscillator (NCO)</h4>
<p>The next step is to set up the NCO in the routine <strong>SetupNCO()</strong>. I found the Microchip application note TB3341 <sup id="cite_ref8"><a href="#cite_note8">[8]</a></sup> helpful in writing this part of the program:</p>
<pre>void SetupNCO () {
  PORTMUX.TCFROUTEA = PORTMUX_TCF0_ALT1_gc;               // WO0,WO1 on PA6,PA7

  // Enable WO0 and WO1 and invert WO1 polarity
  while((TCF0.STATUS &amp; TCF_CTRLABUSY_bm) != 0);
  TCF0.CTRLC = TCF_WO0EN_bm | TCF_WO1EN_bm | TCF_WO1POL_bm;
  
  // Configure NCO Pulse-Frequency mode
  TCF0.CTRLB = TCF_CLKSEL_PLL_gc | TCF_WGMODE_NCOFDC_gc;  // Use PLL as clock
}</pre>
<p>This first selects the alternative positions for the waveform outputs WO0 and WO1 because the default positions conflict with EXTCLK.</p>
<p>It then enables the waveform outputs and inverts WO1, to give two out of phase square waves.</p>
<p>Finally it selects NCO&nbsp;Pulse-Frequency mode, which gives a symmetrical square wave, and specifies the PLL as the clock source.</p>
<h4 id="setting-the-frequency">Setting the frequency</h4>
<p>The function <strong>SetFrequency()</strong> is called each time the frequency changes:</p>
<pre>void SetFrequency () {
  while((TCF0.STATUS &amp; TCF_CMP0BUSY_bm) != 0);
  TCF0.CMP = Input&gt;&gt;1;
}</pre>
<h4 id="starting-and-stopping-the-nco">Starting and stopping the NCO</h4>
<p>Finally, these two functions are called to start and stop the square-wave output:</p>
<pre>void StartNCO () {
  while((TCF0.STATUS &amp; TCF_CTRLABUSY_bm) != 0);
  TCF0.CTRLA |= TCF_ENABLE_bm; 
}

void StopNCO () {
  while((TCF0.STATUS &amp; TCF_CTRLABUSY_bm) != 0);
  TCF0.CTRLA &amp;= ~TCF_ENABLE_bm;
}</pre>
<h4 id="numeric-keypad2">Numeric keypad</h4>
<p>The numeric keypad is read by the function <strong>ReadKeypad()</strong>, which returns the number of the key, or -1 if no key is being pressed. For more details see&nbsp;<a class="auto" href="http://www.technoblogy.com/show?41BI">One-Input Numeric Keypad</a>.</p>
<h4 id="display-driver">Display driver</h4>
<p>The functions for driving the 128x32 OLED display are based on earlier projects. The frequency is plotted using double-sized characters, using smoothing as described in&nbsp;<a class="auto" href="http://www.technoblogy.com/show?3AJ7">Smooth Big Text</a>.</p>
<h4 id="rotary-encoder3">Rotary encoder</h4>
<p>The rotary encoder uses a pin-change interrupt to update the frequency, based on my earlier project&nbsp;<a class="auto" href="http://www.technoblogy.com/show?1YHJ">Bounce-Free Rotary Encoder</a>.</p>
<h4 id="main-loop">Main loop</h4>
<p>Finally the main loop polls the numeric keypad and updates the display until * is pressed, and then sets the output frequency to the entered value.</p>
<h3 id="installing-the-program">Installing the program</h3>
<p>First install Spence Konde's DxCore from GitHub: see <a href="https://github.com/SpenceKonde/DxCore/blob/master/Installation.md" target="_blank">DxCore - Installation</a>. I used version 1.6.2.</p>
<p>Then, in the Arduino IDE:</p>
<ul>
<li>Choose the&nbsp;<strong>AVR EB-series (no bootloader)</strong>&nbsp;option under the&nbsp;<strong>DxCore</strong>&nbsp;heading on the&nbsp;<strong>Board</strong> menu.</li>
<li>Check that the subsequent options are set as follows (ignore any other options):</li>
</ul>
<p style="padding-left: 18px;"><strong>Board: "</strong><strong>AVR EB-series (no bootloader)</strong><strong>"</strong><br /><strong>Chip: "AVR16EB28"<br />Clock Speed: "20 MHz internal"<br /></strong></p>
<p style="padding-left: 18px;">You can leave the other options at their defaults, the first option on each submenu.</p>
<ul>
<li>Connect a UPDI programmer to the&nbsp;<strong>UPDI</strong>,<strong> GND</strong>, and<strong> VDD&nbsp;</strong>pins.</li>
</ul>
<p>The recommended way to upload the program is to use a 3.3V Serial board, such as the SparkFun FTDI Basic board&nbsp;<sup id="cite_ref9"><a href="#cite_note9">[9]</a></sup>&nbsp;connected with&nbsp;a Schottky diode as follows. You can substitute a 4.7k&Omega; resistor for the Schottky diode:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/serialupdiprogschottky3v.gif" alt="SerialUPDIProgSchottky3V.gif" width="395" height="147" /></p>
<ul>
<li>Set&nbsp;<strong>Programmer</strong>&nbsp;to the&nbsp;<strong>"SerialUPDI - Normal: 230400 baud (recommended)"</strong> option.</li>
<li>Select the USB port corresponding to the USB to Serial board in the <strong>Port</strong> menu.</li>
</ul>
<ul>
<li>Select <strong>Burn Bootloader</strong> to reset the fuses (in case they were changed for an earlier project).</li>
<li>Click <strong>Upload</strong> to upload the program to the AVR16EB28.</li>
</ul>
<h3 id="resources">Resources</h3>
<p>Here's the program for the Digital Signal Generator:&nbsp;<a href="http://www.technoblogy.com/list?5QQI">Digital Signal Generator Program</a>.</p>
<p>Get details of the One-Input Numeric Keypad here:&nbsp;<a class="auto" href="http://www.technoblogy.com/show?41BI">One-Input Numeric Keypad</a>.</p>
<h3 id="further-suggestions">Further suggestions</h3>
<ul>
<li>If you prefer an output adjustable in steps of exactly 1Hz you can use the 4.194304MHz crystal divided by 2 to give a range of 1Hz to 16777215Hz in steps of 1Hz, with a symmetrical square-wave output. Note that the AVR EB-series Errata states that in NCO mode the prescaler in TCF0 CTRLA is non-functional, but you can divide the input to the PLL by 2 instead.</li>
<li>If you want a higher maximum frequency you can change the NCO mode from NCOFDC (fixed duty-cycle) to NCOPF (pulse-frequency) to give a range of 4Hz to 67108860Hz in steps of 4Hz with an asymmetrical square-wave output one clock period wide.</li>
<li>If your rotary encoder includes a switch you could add a pin-change interrupt on PD2 to toggle between these options, or to change the frequency step size of the rotary encoder.</li>
</ul>
<h3 id="updates">Updates</h3>
<p>7th July 2026: Fixed a problem that meant that the Digital Signal Generator Program source code was password protected.</p><hr>
<ol>
<li id="cite_note1"><a href="#cite_ref1">^</a> This is like the phase accumulator used in DDS (Direct Digital Synthesis), except that the TCF timer/counter only has a one-bit output.</li>
<li id="cite_note2"><a href="#cite_ref2">^</a> <a href="https://www.aliexpress.com/item/1005005653454828.html" target="_blank">0.91" 128x32 OLED Display Module White</a> on AliExpress.</li>
<li id="cite_note3"><a href="#cite_ref3">^</a> <a href="https://uk.farnell.com/qantek-technology-corporation/qcl4-194304f18b23b/crystal-4-194304mhz-18pf-hc-49us/dp/2508442" target="_blank">QCL4.194304F18B23B</a> on Farnell.</li>
<li id="cite_note4"><a href="#cite_ref4">^</a> <a href="https://halltronics.com/product/motion-control-sensors/HT73119/3315y-001-016l" target="_blank">Bourns Incremental Encoder</a> on Halltronics.</li>
<li id="cite_note5"><a href="#cite_ref5">^</a> <a href="https://www.adafruit.com/product/377" target="_blank">Rotary Encoder + Extras</a> on Adafruit.</li>
<li id="cite_note6"><a href="#cite_ref6">^</a> <a href="https://www.rapidonline.com/Catalogue/Product/34-0666" target="_blank">AD-100 Advanced Solderless Breadboard</a> on Rapid Electronics.</li>
<li id="cite_note7"><a href="#cite_ref7">^</a> <a href="https://thepihut.com/products/jumper-wire-kit-140-piece" target="_blank">Jumper Wire Kit</a> on The Pi Hut.</li>
<li id="cite_note8"><a href="#cite_ref8">^</a> <a href="https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ApplicationNotes/ApplicationNotes/TB3341-Getting-Started-with-TCF-TechBrief-DS90003341.pdf" target="_blank">Getting Started with Timer/Counter Type F (TCF)</a> on Microchip.</li>
<li id="cite_note9"><a href="#cite_ref9">^</a> <a href="https://www.sparkfun.com/sparkfun-ftdi-basic-breakout-3-3v.html" target="_blank">SparkFun FTDI Basic Breakout - 3.3V</a>&nbsp;on Sparkfun.</li>
</ol>
]]></description>
<pubDate>Wed, 01 Jul 2026 23:00:00 GMT</pubDate>
<guid isPermaLink="true">http://www.technoblogy.com/show?5Q7F</guid>
</item>
<item>
<title>One-Input Numeric Keypad</title>
<description><![CDATA[<p>This is a 12-key numeric keypad for use in a range of microcontroller projects. It has the advantage that you only need one analogue input to interface it:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/numerickeypad.jpg" alt="NumericKeypad.jpg" width="300" height="350" /></p>
<p style="text-align: center;"><em>The 12-key Numeric Keypad that interfaces to a single analogue input.</em></p>
<p>It's on a small 1.2 x 1.6" (30 x 40mm) PCB, and I've also built a demo circuit to show how to use it.</p>
<h3 id="introduction">Introduction</h3>
<p>Microcontroller projects often need a numeric keypad, and a typical approach is to arrange the keys in a matrix of rows and columns; so for a 3 x 4 numeric keypad you might need to scan seven I/O pins.</p>
<p>This keypad works differently; each key generates a different analogue voltage, and you only need a single analogue input to interface it. I wrote about the idea some time ago in the article&nbsp;<a class="auto" href="http://www.technoblogy.com/show?NGM">One Input Keypad Interface</a>, and I've incorporated keypads using this approach in several projects, such as&nbsp;<a class="auto" href="http://www.technoblogy.com/show?2AON">Walkie-Textie Wireless Communicator</a>&nbsp;and&nbsp;<a class="auto" href="http://www.technoblogy.com/show?5Q7F">Digital Signal Generator</a>, but I've never published a PCB to allow you to create a stand-alone keypad. However, recently I wanted to prototype a project on a breadboard using a keypad, and I wished I had a PCB version. So here it is.</p>
<h3 id="the-circuit">The circuit</h3>
<p>This is the circuit of the keypad:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/oneinputkeypad.gif" alt="OneInputKeypad.gif" width="303" height="415" /></p>
<p style="text-align: center;"><em>The circuit of the One-Input Numeric Keypad, which uses a network of six resistors.</em></p>
<p>The buttons are widely available 6 x 6mm tactile buttons&nbsp;<sup id="cite_ref1"><a href="#cite_note1">[1]</a></sup>.&nbsp;It uses a network of six 0805 surface-mount E6 resistors, arranged as a voltage divider, so each keypress generates a different voltage. It's also designed to draw no current unless a key is pressed. The resistor values are calculated to give the best spread of voltages in the available range from GND to VDD.</p>
<p>The '*' and '#' keys generate the values 10 and 11 respectively; they are useful for functions such as Enter and Delete. The surface-mount resistors are mounted on the back of the board, and the three connections are brought out to a five-way pin header at the top of the board (only three of the pins are used):</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/keypadback.jpg" alt="KeypadBack.jpg" width="300" height="350" /></p>
<p style="text-align: center;"><em>The back of the One-Input Numeric Keypad PCB showing the six 0805 surface-mount resistors.</em></p>
<h3 id="the-program">The program</h3>
<p>Here's the <strong>ReadKeypad()</strong> function that interrogates the keypad:</p>
<pre>const int Keypad = PIN_PC0;
const int SmallestGap = 40;
int Values[] =  {1023,680,640,590,547,507,464,411,351,273,180,133, 0,-100};
int Buttons[] = {  -1, 11,  9,  6,  3,  0, 10,  8,  7,  5,  4,  2, 1};

int ReadKeypad () {
  int val, lastval=0, count = 0;
  do {
    val = analogRead(Keypad);
    if (abs(val-lastval)&lt;2) count++;
    else { lastval = val; count = 0; }
  } while (count &lt; 3);
  int i = 0;
  val = val - SmallestGap/2;
  while (val &lt; Values[i]) { i++; }
  return Buttons[i - 1];
}</pre>
<p>The constant&nbsp;<strong>Keypad</strong>&nbsp;specifies the analogue input that the keypad is connected to. The program uses two arrays:&nbsp;<strong>Values[]</strong>&nbsp;stores the analogue values given by each pin, in descending order, and&nbsp;<strong>Buttons[]</strong>&nbsp;stores the corresponding button for each voltage. The&nbsp;<strong>ReadKeypad()</strong>&nbsp;routine reads the analogue input, and then checks the value against each value in&nbsp;<strong>Values[]</strong>&nbsp;until it's larger; it then returns the corresponding button from&nbsp;<strong>Buttons[]</strong>. If no key is pressed it returns -1.</p>
<h3 id="demo-circuit">Demo circuit</h3>
<p>I built a simple circuit to demonstrate the keypad by displaying the key pressed on a seven-segment LED display:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/keypaddemo.jpg" alt="KeypadDemo.jpg" width="680" height="360" /></p>
<p style="text-align: center;"><em>The demonstration circuit for the One-Input Numeric Keypad, based on an AVR32EB14,<br /> displays the key pressed on a seven-segment LED display.</em></p>
<p>I built the demo circuit on a 360 hole breadboard <sup id="cite_ref2"><a href="#cite_note2">[2]</a></sup>, using pre-cut colour-coded jumper wires to make the interconnections easier <sup id="cite_ref3"><a href="#cite_note3">[3]</a></sup>. I used an AVR32EB14 in a TSSOP-14 package, mounted on a breakout board <sup id="cite_ref4"><a href="#cite_note4">[4]</a></sup>&nbsp;and a 0.56" single-digit seven-segment display <sup id="cite_ref5"><a href="#cite_note5">[5]</a></sup>. The display is driven directly from the eight I/O pins PA0 to PA1, PC2 to PC3, and PD4 to PD7, with a 220&Omega; current-limiting resistor from the common cathode to GND. PC0 is used for the analogue keypad input.</p>
<h3 id="resources">Resources</h3>
<p>Get Eagle and Gerber files for the keypad:&nbsp;<a href="https://github.com/technoblogy/one-input-numeric-keypad" target="_blank">https://github.com/technoblogy/one-input-numeric-keypad</a>.</p>
<p>Or order PCBs from OSH Park here:&nbsp;<a href="https://oshpark.com/shared_projects/QgtlGBee" target="_blank">One-Input Numeric Keypad</a>.</p>
<p>Here's the demo program:&nbsp;<a href="http://www.technoblogy.com/list?5QMR">One-Input Numeric Keypad Demo</a>.</p><hr>
<ol>
<li id="cite_note1"><a href="#cite_ref1">^</a> <a href="https://uk.farnell.com/alcoswitch-te-connectivity/fsm4jh/switch-spno-0-05a/dp/1555982" target="_blank">FSM4JH Through-Hole Tactile Switch</a> on Farnell.</li>
<li id="cite_note2"><a href="#cite_ref2">^</a> <a href="https://www.rapidonline.com/Catalogue/Product/34-0666" target="_blank">AD-100 Advanced Solderless Breadboard</a> on Rapid Electronics.</li>
<li id="cite_note3"><a href="#cite_ref3">^</a> <a href="https://thepihut.com/products/jumper-wire-kit-140-piece" target="_blank">Jumper Wire Kit</a> on The Pi Hut.</li>
<li id="cite_note4"><a href="#cite_ref4">^</a> <a href="https://www.adafruit.com/product/1210" target="_blank">SMT Breakout PCB for SOIC-14 or TSSOP-14</a> on Adafruit.</li>
<li id="cite_note5"><a href="#cite_ref5">^</a> <a href="https://www.switchelectronics.co.uk/products/white-0-56-seven-segment-display-cathode-led" target="_blank">0.56" Seven Segment Display</a> on Switch Electronics.</li>
</ol>
]]></description>
<pubDate>Sun, 28 Jun 2026 23:00:00 GMT</pubDate>
<guid isPermaLink="true">http://www.technoblogy.com/show?41BI</guid>
</item>
<item>
<title>IR NeoPixel Controller</title>
<description><![CDATA[<p>This is an IR NeoPixel Controller that lets you control a set of one or more high-power RGB LEDs with an infrared remote control:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/irneopixelremote.jpg" alt="IRNeoPixelRemote.jpg" width="720" height="570" /></p>
<p style="text-align: center;"><em>The IR NeoPixel Controller, based on an AVR64DD14, controlling a high-power NeoPixel RGB LED.</em></p>
<p>It lets you control the hue, saturation, and brightness of each RGB LED independently, or alternatively set them all to the same settings. You can also switch off and on all the LEDs, or animate the LEDs with slowly-changing hue. It's based on an AVR64DD14 and a TSOP38238 IR receiver.</p>
<h3 id="introduction">Introduction</h3>
<p>I recently bought some smart colour bulbs, thinking it would be nice to be able to adjust the colour of the illumination in my living room. However it turned out that I also needed to buy a compatible smart hub, install a control app, create an account, and control the bulbs using my smartphone.</p>
<p>This is fine if you want to automate your home, but for a single light it was far more complicated than I really wanted. I therefore thought about building my own solution based on a NeoPixel interface, using an infrared remote control to control the brightness and colour of the LEDs. The advantage of using NeoPixel LEDs over dumb high-power RGB LEDs is that you can chain multiple lights together with just three wires.</p>
<p>This IR NeoPixel Controller will support up to nine lights, a number I chose because the IR remote provides keys 0 to 9. However, the main constraint will be how much power your power supply can handle. A typical USB-C output can provide 5V at 1.5A, which is enough for two or possibly three high-power 3W LEDs.</p>
<p>The IR NeoPixel Controller lets you control the hue, saturation, and brightness of each RGB LED independently, or alternatively set them all to the same settings. You can also switch off and on all the lights, or animate the lights with slowly-changing hue. The settings are stored in EEPROM, so if you disconnect and reconnect the power the LEDs remember their previous settings.</p>
<h3 id="components">Components</h3>
<p>The circuit consists of three components: the NeoPixel LEDs, the NeoPixel driver, and the IR remote control receiver.</p>
<h4 id="highpower-neopixel-leds">High-power NeoPixel LEDs</h4>
<p>For the LEDs I chose Adafruit's 3 watt chainable NeoPixel LEDs, that use WS2811 NeoPixel-protocol controllers to drive a high-power RGB LED mounted on an aluminium heatsink <sup id="cite_ref1"><a href="#cite_note1">[1]</a></sup>. To use these set the #<strong>define</strong> at the start of the program to <strong>RGB</strong>.</p>
<p>Alternatively you can use the 4 watt RGBW versions <sup id="cite_ref2"><a href="#cite_note2">[2]</a></sup>, which are available in warm white, natural white, or cool white. To use these set the <strong>#define</strong> at the start of the program to <strong>RGBW:</strong></p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/irneopixel4.jpg" alt="IRNeoPixel4.jpg" width="720" height="520" /></p>
<p style="text-align: center;"><em>The IR NeoPixel Controller controlling a high-power NeoPixel RGBW LED.</em></p>
<p><strong>Warning!</strong> These high-power NeoPixel LEDs are extremely bright, so when testing the IR NeoPixel Controller I covered them with translucent bottle tops. When using it for room illumination I recommend using a reflector to direct it at the ceiling, or put it into a translucent globe light fitting <sup id="cite_ref3"><a href="#cite_note3">[3]</a></sup>:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/rgbglobe.jpg" alt="RGBGlobe.jpg" width="602" height="338" /></p>
<p style="text-align: center;"><em>A high-power NeoPixel RGB LED in a translucent globe light fitting.</em></p>
<p>The high-power LEDs should be mounted on a heatsink to avoid overheating at maximum brightness. I used a square 40 x 40 x 11mm black <sup id="cite_ref4"><a href="#cite_note4">[4]</a></sup> or silver <sup id="cite_ref5"><a href="#cite_note5">[5]</a></sup> finned heatsink.</p>
<p>Note that although the high-power modules use the same connectors as Adafruit's LED strips, the polarity of the +5V and GND connections is reversed, so check before plugging them in!</p>
<p>This circuit will also drive standard&nbsp;WS2812 or WS2812B&nbsp;NeoPixel LEDs daisy-chained together, but these are each only about 0.3 watts. However, because perception of brightness is non linear they will look only about a third of the brightness of the high-power LEDs.</p>
<h4 id="neopixel-driver">NeoPixel driver</h4>
<p>To drive the LED modules I used my NeoPixel driver based on an AVR64DD14: <a class="auto" href="http://www.technoblogy.com/show?5CKL">NeoPixel Driver Program 3</a>. This uses a Timer/Counter in the AVR processor to generate the waveforms needed by the NeoPixel protocol, and the SPI peripheral to output them as a serial stream, rather than the software approach used by many other NeoPixel interfaces.</p>
<h4 id="ir-remote-control-receiver">IR remote control receiver</h4>
<p>The IR remote control receiver&nbsp;is designed to work with remote controls that use the NEC protocol, like many low cost infrared remotes available from the Far East. The remote control I used is available from Adafruit <sup id="cite_ref6"><a href="#cite_note6">[6]</a></sup>, or their distributors such as The Pi Hut in the UK&nbsp;<sup id="cite_ref7"><a href="#cite_note7">[7]</a></sup>. The NEC protocol is also used by the Apple Remote&nbsp;<sup id="cite_ref8"><a href="#cite_note8">[8]</a></sup>, so if you have one of those you could use it. If you want to use a different remote control you can find its key codes as described below: <a href="#finding-codes-for-ir-remote">Finding the codes for an IR remote</a>.</p>
<h3 id="using-the-ir-neopixel-controller">Using the IR NeoPixel Controller</h3>
<p>The following diagram shows the function of each key on the Adafruit remote control:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/adafruitremoteannotated.jpg" alt="AdafruitRemoteAnnotated.jpg" width="356" height="356" /></p>
<p>Initially all the LEDs will be off, at minimum brightness, the saturation will be at maximum, and the remote control will control the first LED.</p>
<ul>
<li>To control an individual LED press the corresponding number key, 1 to 9. To control all the LEDs press 0.</li>
<li>To control the brightness press or hold down ▲ or ▼.</li>
<li>To change the hue (colour) press or hold down ◀ or ▶. The hue goes in the sequence: red, yellow, green, cyan, blue, magenta, and back to red.</li>
<li>To change the saturation press or hold down <strong>VOL-</strong> or <strong>VOL+</strong>. Reducing the saturation dilutes the colour with white.</li>
<li>To turn off all the LEDs press <strong>STOP</strong>. Press it again to revert to their previous settings.</li>
</ul>
<p>The settings are saved automatically every 10 seconds.</p>
<h3 id="the-circuit">The circuit</h3>
<p>Here's the circuit of the IR NeoPixel Controller:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/irneopixel.gif" alt="IRNeoPixel.gif" width="516" height="266" /></p>
<p style="text-align: center;"><em>Circuit for the IR NeoPixel Controller.</em></p>
<h4 id="infrared-receiver">Infrared receiver</h4>
<p>The circuit uses the popular TSOP38238 38kHz infrared receiver module in a 3-pin through-hole package, and available from Adafruit <sup id="cite_ref9"><a href="#cite_note9">[9]</a></sup>&nbsp;or most component suppliers.</p>
<p>Because I am planning to build a surface mount version of the circuit on a PCB I also tested the circuit with a surface-mount equivalent, the TSOP36138, mounted on a SOIC-12 breakout board <sup id="cite_ref10"><a href="#cite_note10">[10]</a></sup> (it wouldn't fit on an SOIC-8 breakout board):</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/irneopixelsmd.jpg" alt="IRNeoPixelSMD.jpg" width="625" height="400" /></p>
<p style="text-align: center;"><em>A version of the circuit using a&nbsp;TSOP36138 surface-mount infrared receiver module</em><br /><em> mounted on a breadboard.</em></p>
<h4 id="microcontroller">Microcontroller</h4>
<p>The project is controlled by a 14-pin AVR processor, the AVR64DD14. This has just enough I/O pins to perform all the functions, including an error light, provided you configure PF6 as an input to receive the IR signals from the IR receiver.</p>
<p>The program is small enough to fit on an AVR32DD14 or even an AVR16DD14, although I haven't tested them.</p>
<h4 id="error-light">Error light</h4>
<p>The error light flashes if a complex pattern cannot be evaluated in the 20ms between updates of the NeoPixels, but given the small number of LEDs in this application this will probably never happen, so I've also made it flash once to show that the IR remote control has set maximum or minimum brightness or saturation.</p>
<h3 id="construction">Construction</h3>
<p>I built the circuit on a mini breadboard, available from SparkFun <sup id="cite_ref11"><a href="#cite_note11">[11]</a></sup> or The Pi Hut in the UK <sup id="cite_ref12"><a href="#cite_note12">[12]</a></sup>. Pre-cut colour-coded jumper wires make the interconnections easier <sup id="cite_ref13"><a href="#cite_note13">[13]</a></sup>.&nbsp;</p>
<p>The AVR64DD14 isn't available in a dual in-line package, so I mounted it on an SOIC-14 breakout board <sup id="cite_ref14"><a href="#cite_note14">[14]</a></sup>.</p>
<h3 id="the-program">The program</h3>
<h4 id="ir-remote-control-format">IR remote control format</h4>
<p>The NEC format was originally developed by the consumer electronics firm NEC. The data is encoded using pulse distance encoding. Each bit starts with 562.5&micro;s of carrier. A zero has a total width of 1.125ms before the start of the next bit, and a one has a total width of 2.25ms before the next bit:</p>
<p><img src="http://www.technoblogy.com/pictures/kvm/ircodes4.png" alt="IRCodes4.png" width="279" height="104" /></p>
<p>The carrier pulse consists of 21 cycles at 38kHz. The pulses usually have a mark/space ratio of 1:4, to reduce the current consumption:</p>
<p><img src="http://www.technoblogy.com/pictures/kvm/ircodes7.png" alt="IRCodes7.png" width="554" height="89" /></p>
<p>Each code sequence starts with a 9ms pulse, known as the AGC pulse. This is followed by a 4.5ms silence:</p>
<p><img src="http://www.technoblogy.com/pictures/kvm/ircodes5.png" alt="IRCodes5.png" width="630" height="79" /></p>
<p>The data consists of 32 bits: a command field consisting of an 8-bit id and an 8-bit key code, and a 16-bit address field:</p>
<p><img src="http://www.technoblogy.com/pictures/kvm/ircodes12.gif" alt="IRCodes12.gif" width="632" height="104" /></p>
<p>The bits are transmitted low-order bit first and high-order bit last. Note that there needs to be one extra pulse at the end to terminate the last bit.</p>
<p>The address identifies the equipment being controlled, and the key identifies the key pressed.</p>
<p>In the original version of the NEC protocol the 8-bit id was just an inverted version of the key. The Apple Remote uses the full 16-bits of the address and command fields; the address is 0x87EE, and the id is used to allow a remote to be paired to a particular product.</p>
<h4 id="ir-receiver">IR receiver</h4>
<p>The IR remote control receiver code is based on my earlier&nbsp;<a class="auto" href="http://www.technoblogy.com/show?V6F">IR Remote Control Receiver</a>, which is designed to work with the NEC protocol.</p>
<p>The timing of each component of the NEC protocol can conveniently be expressed in multiples of 112.5&micro;s. The multiples are defined by the following constants:</p>
<pre>const int ZeroBit = 10;                                   // 10 * 112.5us = 1.125ms
const int OneBit  = 20;                                   // 20 * 112.5us = 2.25ms
const int RepeatPulse = 100;                              // 100 * 112.5us = 11.25ms
const int AGCPulse = 120;                                 // 120 * 112.5us = 13.5ms
</pre>
<p>I used Timer/Counter TCB to do the timing. First, it is configured to count in units of&nbsp;112.5&micro;s:</p>
<pre>  TCB0.CCMP = 2700-1;                                     // 112.5us
  TCB0.CTRLA = TCB_CLKSEL_DIV1_gc | TCB_ENABLE_bm;        // Divide timer by 1
  TCB0.CTRLB = 0;                                         // Periodic Interrupt mode
  TCB0.INTCTRL = TCB_CAPT_bm;                             // Overflow interrupt&nbsp;</pre>
<p>Note that this assumes a 24MHz clock, and you will need to change the divisor for a different clock rate.</p>
<p>Next, the TCB interrupt service routine simply counts interrupts in the global variable <strong>Ticks</strong>:</p>
<pre>ISR(TCB0_INT_vect) {
  TCB0.INTFLAGS = TCB_CAPT_bm;                            // Clear interrupt flag
  if (Ticks &lt; 255) Ticks++;
}</pre>
<p>The output from the IR receiver goes to input PF6, and a pin-change interrupt is set on this pin:</p>
<pre>&nbsp; PORTF.PIN6CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc;// Interrupt falling edge</pre>
<p>The PORTF interrupt service routine is called on each falling edge of the IR input:</p>
<pre>ISR (PORTF_PORT_vect) {
  static uint32_t RecdData;
  // Waiting for AGC pulse and gap
  int Time = Ticks;
  if (NextBit == -1) {                                    // Waiting for AGC pulse
    if ((Time &gt; AGCPulse-10) &amp;&amp; (Time &lt;= AGCPulse+10)) {  // Got AGC pulse
      RecdData = 0; NextBit = 0;
    } else if ((Time &gt; RepeatPulse-10) &amp;&amp; (Time &lt;= RepeatPulse+10)) {
      ProcessCode(RecdData, true);                        // Repeat code
    }
  } else {                                                // Data bit
    if ((Time &gt; OneBit+5) || (Time == 255)) NextBit = -1; // Fail - restart
    else {
      if (Time &gt; OneBit-5) {                              // Bit = '1'
        RecdData = RecdData | ((unsigned long) 1&lt;&lt;NextBit);
      }
      NextBit++;
      if (NextBit == 32) {                                // Got complete code
        ProcessCode(RecdData, false);                     // Normal code                     
        NextBit = -1;                                     // Go back to waiting
      }
    }
  }
  Ticks = 0;                                              // Clear counter
  PORTF.INTFLAGS = PIN6_bm;                               // Clear interrupt flag
}</pre>
<p>This uses the global variable <strong>NextBit</strong> to keep track of the progress of the decoding. Initially <strong>NextBit</strong> is set to -1, to indicate that the routine is waiting for the AGC start pulse. As the bits are received they are shifted into <strong>RecdData</strong>, and <strong>NextBit</strong> is incremented. When&nbsp;<strong>NextBit</strong>&nbsp;reaches 32 the complete IR code has been received, and the routine calls <strong>ProcessCode()</strong> to perform the required action.</p>
<p>The routine checks that the length of the AGC pulse and gap is within 10 ticks of the required 13.5ms, and if so sets <strong>NextBit</strong> to 0 to start reading the code. It also checks whether the AGC pulse and gap is within 10 ticks of the 11.25ms width of a repeat code, and if so calls&nbsp;<strong>ProcessCode()</strong> with the same code and the second parameter set to <strong>true</strong>.<strong></strong></p>
<p>For each bit the routine checks its length. If it is within 5 ticks of 2.25ms it is taken as a 1-bit. If shorter than this it is taken as a 0-bit. If the bit is too long, or <strong>Time</strong> = 255, it indicates that the code hasn't been received correctly, so the processing of the code is abandoned and the routine returns to the waiting state.</p>
<h4 id="processing-the-ir-remote-key-codes">Processing the IR remote key codes</h4>
<p>The IR remote key presses are processed by the routine <strong>ProcessCode()</strong>, which takes the 32-bit code and a flag to specify whether it is a repeat code; ie the key is being held down.</p>
<p>The code is first separated into the address, key, and id components, and the address is checked to ensure that the code is from the Adafruit remote:</p>
<pre>void ProcessCode (uint32_t code, bool repeat) {
  uint16_t address = code &amp; 0xffff;
  uint8_t key = code &gt;&gt; 16 &amp; 0xff;
  uint8_t id = code &gt;&gt; 24 &amp; 0xff;
  
  if (address != IRAddress) return;                        // Check for remote
  uint8_t n = State.LED;
  
  switch (key) {
    case UP:    if (State.Control[n][VALUE] &lt;= 253) State.Control[n][VALUE]+=2;
                else Error(true); break;
    ...
  }
}</pre>
<p>The routine then uses a <strong>switch</strong> statement to perform the appropriate action for each key. In most cases this involves incrementing or decrementing one of the elements of the array&nbsp;<strong>State.Control[][]</strong>. The first array index is the number of the LED, from 1 to 9, or 0 to set all LEDs to the same value. The second array index is the parameter being controlled: hue, saturation, or value (brightness). For simplicity I've only shown the processing for the UP key, which increases the brightness.</p>
<p>An earlier version of the program used a&nbsp;<strong>NeedsSaving</strong> flag to indicate whether the data had changed and needed to be saved to EPROM, but I realised that this was unnecessary because the Arduino EEPROM library <strong>EEPROM.update()</strong> function, and its wrapper <strong>EEPROM.put()</strong>, only write to the EEPROM if the value to be written is different from what's already there, so there's no wear on the EEPROM if the data hasn't changed.</p>
<h4 id="neopixel-driver1">NeoPixel driver</h4>
<p>The NeoPixel driver code is identical to the program described in my earlier project&nbsp;<a class="auto" href="http://www.technoblogy.com/show?5CC0">A NeoPixel Driver using AVR Hardware [3]</a>. The main change is that the number of channels is specified by a global variable <strong>Channels</strong>, to allow for RGBW or GRBW NeoPixels.</p>
<p>Actually, there is one other change. In my first prototype I found that the NeoPixels sometimes flickered when operating the IR remote. I tracked the problem down to the pin-change interrupt interfering with the timing of the SPI0 interrupt. The solution is to give the SPI0 interrupt priority by defining it as a level 1 high priority vector, with the statement:</p>
<pre>&nbsp; CPUINT.LVL1VEC = 20; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// Give SPI0_INT priority</pre>
<p>The value 20 is the SPI0_INT vector number, which you can find in the datasheet Interrupt Vector Mapping table.</p>
<h4 id="pattern-functions">Pattern functions</h4>
<p>My earlier&nbsp;<a class="auto" href="http://www.technoblogy.com/show?5DCO">Light Lab</a>&nbsp;project provided 22 alternative patterns which included both static and animated displays that you could control with three potentiometers, and it supported strips of up 160 LEDs. To achieve this the pattern functions used fixed-point arithmetic, because floating point wasn't fast enough to support that number of LEDs.</p>
<p>This IR Controlled NeoPixel application has more modest ambitions: it only allows up to nine LEDs, and there is essentially one pattern which allows you to adjust the hue, saturation, and brightness of each or all LEDs with the remote control. The pattern functions are written using floating-point calculations, which is fast enough with this number of LEDs, and this makes them simpler than their equivalents in Light Lab.</p>
<p>For example, here's the main <strong>hsv()</strong> function for RGB or GRB displays:</p>
<pre>float hsv (int k, float h, float s, float v) {
  return clamp(2.0 - (3.0 * tri(h - k/3.0))) * v * s + v * (1 - s);
}</pre>
<p>It takes an integer channel number k, from 0 to 2 for red, green, and blue, and floating-point values of hue, saturation, and value (brightness), of 0.0 to 1.0. It returns the intensity of that channel, from 0.0 to 1.0.</p>
<p>It uses the functions <strong>fract()</strong>, fractional part, <strong>clamp()</strong>, which clamps its parameter to the range 0.0 to 1.0, and <strong>dist()</strong>, which&nbsp;returns the the shortest distance around a circle between two points on a circle of circumference&nbsp;1.0.&nbsp;</p>
<p>For RGBW or GRBW LEDs the additional channel number 3 indicates the white channel, and the function becomes:</p>
<pre>float hsv (int k, float h, float s, float v) {
  if (k == 3) return v * (1 - s);                         // White channel
  else return clamp(2.0 - (3.0 * tri(h - k/3.0))) * v * s;
}</pre>
<p>For an explanation of how these <strong>hsv()</strong> functions work see <a class="auto" href="http://www.technoblogy.com/show?3Z49">A Single-Line HSV Function</a>.</p>
<h4 id="saving-the-state-in-eeprom">Saving the state in EEPROM</h4>
<p>After setting the LEDs to the colour settings you want to use, if you switch off and on again they are restores to the same settings. To do this the settings are saved in a struct called <strong>State</strong>:</p>
<pre>struct {
  uint8_t flag = NumPixels;                               // Check if changed
  uint8_t Control[NumPixels+1][3];                        // Control values
  uint8_t LED = 1;                                        // 1 to 9 or 0 = all
  bool Animate = false;                                   // Changing display
  bool On = true;                                         // Switch LEDs on/off
} State;</pre>
<p>On startup the settings are loaded from EEPROM with the following statement in <strong>setup()</strong>:</p>
<pre>  if (EEPROM.read(0) == NumPixels) EEPROM.get(0, State);  // Only read if valid</pre>
<p>The first time you run the program, or if the number of LEDs, <strong>NumPixels</strong>, has changed, the settings are initialised to their defaults by calling <strong>ResetState()</strong>.</p>
<p>The settings are saved automatically every 10 seconds in the <strong>Refresh()</strong> loop.</p>
<h3 id="installing-the-program">Installing the program</h3>
<p>First install Spence Konde's DxCore from GitHub: see <a href="https://github.com/SpenceKonde/DxCore/blob/master/Installation.md" target="_blank">DxCore - Installation</a>. I used version 1.5.11 <sup id="cite_ref15"><a href="#cite_note15">[15]</a></sup>.</p>
<p>Then, in the Arduino IDE:</p>
<ul>
<li>Choose the&nbsp;<strong>AVR DD-series (no bootloader)</strong>&nbsp;option under the&nbsp;<strong>DxCore</strong>&nbsp;heading on the&nbsp;<strong>Board</strong> menu.</li>
<li>Check that the subsequent options are set as follows (ignore any other options):</li>
</ul>
<p style="padding-left: 18px;"><strong>Board: "</strong><strong>AVR DD-series (no bootloader)</strong><strong>"</strong><br /><strong>Chip: "AVR64DD14"<br />Clock Speed: "24 MHz internal"<br />Reset and UPDI: "PF6: Input &amp; PF7: UPDI (no reset pin, safe.)"&nbsp;</strong></p>
<p style="padding-left: 18px;">You can leave the other options at their defaults, the first option on each submenu.</p>
<ul>
<li>Connect a UPDI programmer to the&nbsp;<strong>UPDI</strong>,<strong> GND</strong>, and<strong> VDD&nbsp;</strong>pins.</li>
</ul>
<p>To program the processor the recommended option is to use a 5V or 3.3V USB to Serial board, such as the SparkFun FTDI Basic board&nbsp;<sup id="cite_ref16"><a href="#cite_note16">[16]</a></sup>, or a USB to Serial cable&nbsp;<sup id="cite_ref17"><a href="#cite_note17">[17]</a></sup>, connected with&nbsp;a Schottky diode as follows. You can substitute a 4.7k&Omega; resistor for the Schottky diode:</p>
<p class="center"><img src="http://www.technoblogy.com/pictures/kvm/serialupdiprogschottky.gif" alt="SerialUPDIProgSchottky.gif" width="395" height="147" /></p>
<ul>
<li>Set&nbsp;<strong>Programmer</strong>&nbsp;to the&nbsp;<strong>"SerialUPDI - Normal: 230400 baud (recommended)"</strong> option.</li>
<li>Select the USB port corresponding to the USB to Serial board in the <strong>Port</strong> menu.</li>
</ul>
<ul>
<li>Select <strong>Burn Bootloader</strong> to configure the fuse to make PF6 an input.</li>
<li>Click <strong>Upload</strong> to upload the program to the AVR64DD14.</li>
</ul>
<p>If the IR remote control doesn't work, check that you remembered to set the <strong>Reset and UPDI</strong> option to make PF6 an input.</p>
<h3 id="finding-codes-for-ir-remote">Finding the codes for an IR remote</h3>
<p>The following procedure allows you to use the IR NeoPixel Controller to display the address and key codes for any NEC-protocol IR remote control.</p>
<p>Connect a NeoPixel strip with at least 8 LEDs to the +5V, Data, and GND pins. I used an Adafruit eight NeoPixel strip <sup id="cite_ref18"><a href="#cite_note18">[18]</a></sup>.</p>
<p>Then modify the IR NeoPixel Controller program as follows:</p>
<ul>
<li>Set the&nbsp;<strong>#define FORMAT</strong>&nbsp;line to the NeoPixel format appropriate to the type of strip you are using.</li>
<li>Comment out the following line in the <strong>ProcessCode()</strong> function, as shown, to respond to any address:</li>
</ul>
<pre style="padding-left: 18px;">// if (address != 0xbf00) return;</pre>
<ul>
<li>Replace the <strong>switch</strong>&nbsp;block below it by the lines:</li>
</ul>
<pre style="padding-left: 18px;">ResetState();
for (int i=0; i&lt;8; i++) State.Control[i+1][VALUE] = (key&gt;&gt;i &amp; 1) * 16;</pre>
<ul>
<li>Upload the program.</li>
</ul>
<p>Now when you press any key on the remote control, its code will be displayed in binary on the eight LEDs.</p>
<h4 id="finding-the-remote-control-address">Finding the remote control address</h4>
<ul>
<li>To display the bottom eight bits of the remote control address when you press a key, change the second line to:</li>
</ul>
<pre style="padding-left: 18px;">for (int i=0; i&lt;8; i++) State.Control[i+1][VALUE] = (address&gt;&gt;i &amp; 1) * 16;</pre>
<ul>
<li><span style="font-family: 'Lucida Grande', 'Trebuchet MS', 'Bitstream Vera Sans', Verdana, Helvetica, sans-serif; font-size: 14px; white-space: normal;">To display the top eight bits&nbsp;of the remote control address change the line to:</span></li>
</ul>
<pre style="padding-left: 18px;">for (int i=0; i&lt;8; i++) State.Control[i+1][VALUE] = (address&gt;&gt;(i+8) &amp; 1) * 16;</pre>
<h3 id="resources">Resources</h3>
<p>Here's the IR NeoPixel Controller program:&nbsp;<a href="http://www.technoblogy.com/list?53LD">IR NeoPixel Controller Program</a></p><hr>
<ol>
<li id="cite_note1"><a href="#cite_ref1">^</a> <a href="https://www.adafruit.com/product/4544" target="_blank">Ultra Bright 3 Watt Chainable NeoPixel LED </a>on Adafruit.</li>
<li id="cite_note2"><a href="#cite_ref2">^</a> <a href="https://www.adafruit.com/product/5408" target="_blank">Ultra Bright 4 Watt RGBW NeoPixel LED</a> on Adafruit.</li>
<li id="cite_note3"><a href="#cite_ref3">^</a> <a href="https://www.lightbulbs-direct.com/firstlight-sphere-traditional-style-15cm-flush-ceiling-light-in-white-and-opal-glass/" target="_blank">Firstlight Sphere 15cm Flush Ceiling Light</a> on Lightbulbs Direct.</li>
<li id="cite_note4"><a href="#cite_ref4">^</a> <a href="https://thepihut.com/products/aluminium-heatsink-for-nema-17-stepper-motors-black" target="_blank">Aluminium Heatsink - Black</a> on The Pi Hut.</li>
<li id="cite_note5"><a href="#cite_ref5">^</a> <a href="https://thepihut.com/products/aluminium-heatsink-for-nema-17-stepper-motors-silver" target="_blank">Aluminium Heatsink - Silver</a> on The Pi Hut.</li>
<li id="cite_note6"><a href="#cite_ref6">^</a> <a href="https://www.adafruit.com/product/389" target="_blank">Mini Remote Control</a>&nbsp;on Adafruit.</li>
<li id="cite_note7"><a href="#cite_ref7">^</a> <a href="https://thepihut.com/products/mini-remote-control" target="_blank">Mini IR Remote Control</a> on The Pi Hut.</li>
<li id="cite_note8"><a href="#cite_ref8">^</a> <a href="http://en.wikipedia.org/wiki/Apple_Remote" target="_blank">Apple Remote</a>&nbsp;on Wikipedia.</li>
<li id="cite_note9"><a href="#cite_ref9">^</a> <a href="https://www.adafruit.com/product/157" target="_blank">IR (Infrared) Receiver Sensor</a> on Adafruit.</li>
<li id="cite_note10"><a href="#cite_ref10">^</a> <a href="https://www.adafruit.com/product/1211" target="_blank">SMT Breakout PCB for SOIC-12 or TSSOP-12</a> on Adafruit.</li>
<li id="cite_note11"><a href="#cite_ref11">^</a> <a href="https://www.sparkfun.com/breadboard-mini-modular-white.html" target="_blank">Breadboard - Mini Modular (White)</a> on SparkFun.</li>
<li id="cite_note12"><a href="#cite_ref12">^</a> <a href="https://thepihut.com/products/mini-breadboard-white" target="_blank">Mini Breadboard - White</a> on The Pi Hut.</li>
<li id="cite_note13"><a href="#cite_ref13">^</a> <a href="https://thepihut.com/products/jumper-wire-kit-140-piece" target="_blank">Jumper Wire Kit</a> on The Pi Hut.</li>
<li id="cite_note14"><a href="#cite_ref14">^</a> <a href="https://www.adafruit.com/product/1210" target="_blank">SMT Breakout PCB for SOIC-14 or TSSOP-14</a> on Adafruit.</li>
<li id="cite_note15"><a href="#cite_ref15">^</a> There is a problem programming the AVR64DD14 with DxCore 1.6.2.</li>
<li id="cite_note16"><a href="#cite_ref16">^</a> <a href="https://www.sparkfun.com/products/9716" target="_blank">SparkFun FTDI Basic Breakout - 5V</a>&nbsp;on Sparkfun.</li>
<li id="cite_note17"><a href="#cite_ref17">^</a> <a href="https://www.adafruit.com/product/70" target="_blank">FTDI Serial TTL-232 USB Cable</a>&nbsp;on Adafruit.</li>
<li id="cite_note18"><a href="#cite_ref18">^</a> <a href="https://www.adafruit.com/product/1426" target="_blank">NeoPixel Stick - 8 x 5050 RGB LED</a> on Adafruit.</li>
</ol>
]]></description>
<pubDate>Mon, 01 Jun 2026 23:00:00 GMT</pubDate>
<guid isPermaLink="true">http://www.technoblogy.com/show?5H4W</guid>
</item>
</channel>
</rss>
