Introduction: Controllable Chaotic Circuit System

Background: What is Chaos?

Chaos is often mistaken for noise in a system; however, it differs in several key respects. Chaos is deterministic; the future state of the system is entirely determined by the past states of the system. Another characteristic of chaos is its noticeable sensitivity to initial conditions - trajectories that start out very close to each other will diverge exponentially as time passes. In practice, this sensitivity makes it is nearly impossible to predict the future states of a chaotic system as any measurement error in the initial conditions will ultimately render the predictions invalid!

Chaotic systems are also nonlinear! Without a nonlinear element in the system, the trajectory of the system will either approach a steady state value, or oscillate regularly rather than irregularly. Chaotic systems are aperiodic and therefore have low autocorrelation compared to non-chaotic systems. Though a chaotic system may approach a steady state oscillation asymptotically, it will never reach it without intervention. The autocorrelation characteristics make distinguishing chaos from noise in an unknown system particularly difficult, since both schemes do not resemble themselves on a regular, periodic basis unless the offset is zero.

This instructable will describe how to build a controllable Chua chaos circuit system which displays the output of the circuit, can be tuned to various chaotic states and rotated on an XY display. No special components are required to build this circuit! We hope you have a fun time building this circuit and be sure to send any questions to Liam Neath!

Before we really get started, let us outline the parts you will need:

Power

  • 5V from USB 1 x bipolar DC voltage converter (IA0509S)

Chua circuit core

  • 2 x TL072 op amp
  • 2 x 220Ω Res
  • 2 x 22kΩ Res
  • 1 x 2.2kΩ Res
  • 1 x 3.3kΩ Res
  • 2 x 10Ω Res
  • 1 x 1.2kΩ Res
  • 1 x 47Ω Res
  • 1 x 100kΩ Res
  • 1 x 18nF Cap
  • 8 x 100nF Cap
  • 1 x 10nF Cap
  • 1 x white LED
  • 1 x 220Ω resistor (to limit current to the LED)
  • 1 x CdS cell

Signal Conditioner

  • 2 x TL072 op amps
  • 4 x 2.2kΩ Res
  • 2 x 10kΩ Res
  • 2 x 3.3kΩ Res
  • 1 x LT1167 instrumentation amplifier
  • 1 x 330Ω Resistor (initially we used 1.3kΩ but to achieve a higher gain, to avoid clipping, we switched to 330Ω on our second version)
  • 6 x 100nF Cap

Display

  • 1 x Teensy 3.6 Board
  • 1 x CRT display
  • 3 x BNC connectors

Rotation Unit

  • 3 x rotary encoders
  • 12 x 10kΩ resistors
  • 6 x .1 uF capacitors

Miscellaneous

  • Jumper Wires
  • Wire Cutters and Strippers
  • Soldering Iron
  • Lead Free Solder
  • Solder Flux (We all make mistakes!)
  • Multimeter
  • Multimeter probes
  • Power Source

Step 1: Understanding How Everything Is Connected

Where to Probe

This is a high level depiction of Chua’s circuit made with Eagle. The double scrolls can be observed on an oscilloscope by probing nodes Vc1 and Vc2 and plotting the signals against one another! The section of the schematic labelled Chaos Core is where all the chaos is happening and can be described as the heart of our circuit.

Signal Conditioning

You may notice that we have placed signal conditioning buffers between the Chaos Core and the microcontroller. This section of the circuit attenuates the signal such that the signal voltage seen by the microcontroller lies between 0 V and 3.3 V. This is necessary as the microcontroller used, a Teensy 3.6, cannot handle voltages above 3.3 V.

Digital Core

The Teensy digital core is the brain of our circuit, it receives input from the chaos core, modifies it, and then sends it out to be displayed on a CRT scope. It uses PWM to control the CdS cell that forms part of the middle resistor in the chaos core to make the chaos core cycle through 4 different modes (also called regimes). The regimes are: fixed point, limit cycle, single scroll, and double scroll. We wrote code to analyze the outputs of the Chua circuit and determine which regime it is in. The Teensy can then adjust the CdS cell to either maintain that state or move to the next regime. You may wonder why we are using this LED-CdS cell combination. We were unable to find a digital potentiometer that was 9 V tolerant and provided 0 - 5 kOhm resistance. This clever idea was suggested to us by Professor Kovacs and we are very grateful for his input throughout the project!

Power Supply

The power supply circuitry runs off a 5V USB from a computer. This alone can power the circuit, including the chaos core, the digital core, and rotary encoders.

Step 2: Building Core Chua Circuit

Follow the schematic above to construct the main body of the chaos circuit. As shown, the core is an implementation of the high level Chua’s circuit. The components to the left of the RSENSE resistor form a “gyrator”, which is an op amp circuit that acts like an inductor. As suggested at chaoscircuits.com (Siderskiy, Valentin. See works cited.), this eliminates the need for an actual inductor which, at the inductance required by the circuit, would be expensive and difficult to manage.

The middle resistor in the high level Chua’s circuit is implemented by R14, R33, and PH1 (a photoresistor). This allows the value of the middle resistor to be adjusted by using the Teensy to PWM the LED, which modulates the value of PH1.

The two op amp circuits to the right of Vc1 implement Chua’s diode, which is a nonlinear resistor. This provides the necessary nonlinearity for chaos.

In order to ensure clean scrolls and proper functionality, the 18nF capacitor in the gyrator and the 10nF capacitor between VC1 and GND both need values within 5% of the rated value.

Above is a photo of the prototype model, fabricated on two pieces of perfboard. The green piece of perfboard is the chua circuit, while the blue piece contains the three buffer circuits which adjust the three output signals for the teensy. The top horizontal red lead on the green circuit is Vcc+ (+9V), the white lead with one end next to the Vcc+ is Vcc- (-9 V), and the rightmost orange lead is analog ground. The other leftmost orange wire is Vc1, and the rightmost red vertical wire is Vc2.

The blue perfboard is the signal conditioning circuit. It works to alter the signal from the green board to something the Teensy can handle. Then it sends its output to the green board. This output is unconnected to anything else on the green board. It just makes sending the output to the Teensy a little neater than if the output went directly from the blue board to the Teensy in our box enclosure.

Ph1: The tuning resistance

When the circuit is in a double scrolled chaotic state, the resistance across the variable resistor block (a 1.247kΩ resistor in series with a CdS cell of 0.4kΩ) is 1.7kΩ in total.

Below are the CdS Cell Values at each chaotic state:

  • Point 0.7kΩ
  • Single Scroll 0.6kΩ
  • Double Scroll 0.4kΩ
  • Over saturated 0.2kΩ

The key parameter controlling the region of operation of the chaos core is the collective value of the resistor and CdS Block. We need to vary this resistance from 1.449k Ohms to 1.949k Ohms. This is accomplished using a PWM signal to drive an LED which is coupled to a CdS photoresistor. The PWM frequency is very high (375 kHz), jumping between 0 and 3.3 V on a 0 to 36% duty cycle.

The CdS cell response to varying light levels is very slow: 20 ms rise time and 30 ms decay time. Therefore the CdS cell averages the incoming light intensity and produces a constant resistance output. Our implementation uses a white LED with a 220Ω resistor in series, which is fixed to the top of the CdS cell. The combined assembly was wrapped in heat shrink tubing to keep out ambient light.

The CdS cell, white LED, and 220Ω resistor are shown in the image at the top of this step for your convenience.

Step 3: Connecting the Chua Circuit to the Teensy - Signal Conditioning

Following the above schematic labeled "Buffers", build two buffers. The grounding on this part of the circuit is critical as it shifts from the +-9V analog region to the 0-3.3V digital realm. GND in this diagram corresponds to GND on the Teensy and -9V in the analog region. The op-amp should be fed +9V and -9V (digital GND) through pins 8 and 4 respectively.

The purpose of these buffers is to scale and center the X and Y voltage outputs from the core chua circuit so that these outputs are compatible with the teensy 3.6. Use the figure below to then connect the two buffers you just built to the core chua circuit you constructed in the previous step by making connections between the two spots labeled VC1 and the two spots labeled VC2. This buffer circuit has a gain of 0.18. The instrumentation op amp, which adjusts the measured voltage on the current sense resistor has a gain of 39, set by the 1.3kΩ resistor Rg.

Following the above schematic labeled "Instrumentation Amplifier", make these connections to the LT1167 instrumentation amplifier. The purpose of adding this instrumentation amplifier to the core circuit is to measure the voltage difference across the 10Ω resistor (shown below), which is proportional to the current across that resistor. Therefore this amplifier outputs the current value to the teensy. Following the chaos core figure above, connect V_H and VC2 labels on the instrumentation amplifier to the points labeled analogously in the chaos core.

The teensy digital core is also shown in a figure above. Make connections between labeled points in this diagram and points matching labels in the above diagrams (as done in previous steps) to connect the teensy to the chua core via the parts we just constructed in this step.

Note that the code at present does not actually utilize the current signal for displaying the double scrolls but it is provided in order to easily swap between it and one of the other voltage signals to view a slightly different shape of the double scrolls.

Step 4: Connecting to the CRT Display

The X-Y input of the display are connected directly to the teensy DAC pins. To achieve this, make connections between DAC_X, DAC_Y, and DAC_Z points labeled on the figure above with the matching labeled points on the figure of the teensy above.

The Teensy has the capability to drive a strong signal through several feet of coaxial cable so don't worry about any attenuation!

Analog interface

The teensy ADC is capable of digitizing 0 - 3.3V signals. Thus a resistor divider (with a gain of 0.183 constructed with a 3.3 kΩ resistor and two 10 kΩ resistors) is used to rescale the signal so teensy can read them.

Step 5: Powering the System

The chaos core we implemented requires a +/- 9V supply. All analog pins on the teensy accept or produce a 0-3.3V signal. As the output of the chaos circuit we implemented is on the order of 0-18V, buffers were required to maintain a clean signal and to scale down the output to a range acceptable for the teensy. All power was provided to the circuit over USB. This feat was achieved using a small bipolar DC voltage converter.

The isolated DC/DC converter module which provides the +/-9V is connected to 5V USB. The most negative terminal of this converter is connected to the Teensy's 0V. Thus when referenced to the Teensy's 0V, the chaos core is powered by 0V, 9V, and 18V with all analog grounds connected to 9V.

The figure above (labeled "Power Supply and Decoupling") depicts how to set up these voltage references to power the system.

Step 6: Making Your Circuit Smart!

You have finished building your analog circuitry, it is now time to add some smarts to it! The brain of your project is the microcontroller at the center of the system. We used a Teensy 3.6; however, there are multiple microcontrollers on the market such as: Arduinos, Raspberry Pis and MicroPythons. Although this code was written for the Teensy 3.6, it can easily be used for other microcontrollers with little to no modification.

All the code which we will discuss in the following pages was written to accomplish two tasks: to modify the chaotic state that the circuit is currently in and to rotate the chaotic scrolls based on the position of two knobs. We will first discuss modifying the chaotic state and complete our discussion with digitization and rotation of the chaotic scrolls.

All code is contained within the instructable; however, you may pull from this GitHub repo for a complete folder!

At the end of this section your microcontroller should be sampling two points in the chaotic circuit, performing a rotation based on user input and ensuring that the circuit finds and remains in a chaotic state!

Modifying the Chaotic State

As previously discussed, the CdS cell provides the user with a method to modify the chaotic state of the circuit. This modification is accomplished by programmatically changing the resistance of the cell which in turn results from modifying the PWM signal going to the LED. If you are confused about how or where this is accomplished, look back a few pages to the section titled: Step 2: Building the Core Chua Circuit, specifically in the subsection Ph1: The tuning resistance.

Now that we are up to speed, let us dive in to understanding how we are going to change the component’s resistance. The brightness of the LED can be modified by manipulating how much time the LED is on or off. In order to turn the LED on or off, we must have a positive voltage drop across the LED. Hence, the signal driving the LED determines whether the LED is on or off. Here are a few relations between the driving signal and the LED’s brightness:

  • If the signal manipulating the LED is always high (say +3.3 V) then the LED will be at it’s brightest.
  • On the other hand, if said signal is always low (say 0 V) then the LED will be at the lowest level of brightness (simply off).
  • In the case where the signal is low half of the time and high the rest, the LED will roughly be half as bright as its maximum brightness!

We are able to accomplish the signal-brightness relations above by utilizing Pulsed Width Modulation (PWM). Here is a short explanation of PWM: The Teensy, like most microcontrollers, is a digital system. Its output is either high or low, not in between. So, to get an intermediate output, we pulse the output, turning it on and off very fast, faster than the LED can respond. This makes it so the LED listening to the output sees some value between totally off and totally on. Arduino provides the function analogWrite(pin, pwmDuty) which lets us accomplish this. Using this function, you can set the duty cycle (the fraction of the period where the signal is on) quite easily!

You may be wondering what values pwmDuty can take on. By default, values range from 0 (completely off signal - 0% duty cycle) to 255 (completely on signal - 100% duty cycle). However, this level of granularity proved too rough for our purposes. One can increase this range, thus increasing the level of control on the LED’s brightness, by using analogWriteResolution(). This is another standard Arduino function which allows you to modify the range of values utilized by analogWrite. In addition, you can also modify the frequency of the PWM, or how short the period of turning on and off is using analogWriteFrequency(). By increasing the write frequency of the PWM signal, you are able to match the quality of the produced PWM signal to the increased resolution - allowing you to get a smoother PWM signal.

Using analogWrite() and analogWriteResolution(), you can write a short program to control the chaotic circuit you built in the previous sections! Firstly, outline all the constants needed in order to iterate over a range of duty cycle values and define variables which will be used in the setup() and loop() functions:

uint16_t pwmDuty = 0; // duty cycle of the PWM signal controlled the LED

uint16_t pwmPin = A8; // Microcontroller pin connected to the LED

uint16_t pwmDutyStart = 0; // starting value of the duty cycle

uint16_t pwmDutyEnd = 4096; // ending value of the duty cycle

uint16_t timeDelayMilliSecs = 100; // time needed to wait at each duty cycle value

uint16_t dutyStepSize = 5; // increment value after each analogWrite()

Next, initialize the serial connection and use analogWrite() and analogWriteResolution():

/* setup()

* ----------------------------

* serial connection is initalized and the PWM writefrequency

* and resolution are tweaked for better performance.

*/

void setup() {

Serial.begin(9600);

analogWriteResolution(12); // number of bits in resolution

analogWriteFrequency(pwmPin, 375000); // smoother pwm signal

Serial.println("running mode_sweep. . . ");

analogReference(0);

}

You can now write the main loop which will run continuously while the microcontroller is powered:

/* loop()

* --------------------------

* beginning at the value provided by pwmDutyStart, writes said

* pwmDutyValue to the pwmPin and increments the value by dutyStepSize.

* Resets when pwmDuty > pwmDutyEnd.

*/

void loop() {

pwmDuty = pwmDutyStart;

// loop through all values betwwen pwmDutyStart and pwmDutyEnd

while (pwmDuty < pwmDutyEnd){

Serial.println("the PWM Duty is now: ");

Serial.println(pwmDuty); analogWrite(pwmPin, pwmDuty);

delay(timeDelayMilliSecs);

pwmDuty += dutyStepSize; // increment the pwmDuty;

}

Serial.println("completed cycling through all pwmDuty values!");

Serial.println("pausing for 100 milliseconds then redoing process");

delay(100);

}

Copy the snippets of code above into an empty Arduino sketch and load the program to your Teensy! You may also obtain the unspliced code from the appendix (Appendix 1A).

Probe the output lines of the Chaos Core using your oscilloscope (the Chaos Core is explained in Section 2) and you should observe the waveforms displayed in the 4-image figure above after you have loaded the code onto the Teensy!

Installing Necessary Libraries
Before you go any further there are a few necessary libraries that you must install. Please complete the following steps. Don’t worry about understanding how each library relates to one another, everything will be explained later!

  1. Add this FFT library, provided by Enrique Condes , to your Arduino IDE
  2. Add this rotary encoder library, provided by 0XPIT, to your Arduino IDE
  3. Create a folder named RotateCalc and add the following scripts, found at Appendix 1C, to it:
    1. RotationUtil.cpp and RotationUtil.h
    2. RotateCalc.cpp and RotateCalc.h
  4. Add the folder previously created to your Arduino IDE
  5. Repeat steps 3 and 4 using the following scripts, found at Appendix 1D, and a newly created folder named UserRotate: UserRotate.h and UserRotate.cpp

Self Tuning Algorithm

We have demonstrated the ability to set the chaotic state of the circuit by using analogWrite(). Given this ability and a key characteristic of chaotic circuits, one can write a simple algorithm to find and remain in a particular chaotic state. The following paragraphs will briefly discuss the theory behind this algorithm and explain the implementation.

The behavior of the chaos circuit is highly sensitive to circuit parameters. This means that normal component variation produces circuits which demonstrate chaotic behaviour for different CdS Cell Resistance values. Furthermore, the frequency spectrum, produced in each chaotic state, is distinct from the frequency spectrums in other chaotic states. Thus one can determine the chaotic state by observing the frequency spectrum(s) of the input signal(s)!

Before we discuss the algorithm's implementation, it is fitting to outline the analog to digital and digital to analog conversions which are operating at a higher level and are supplying the algorithm with data.

The self tuning algorithm needs to ‘read’ the signal at the chaos circuit’s probe locations. In order to do so, an Analog-To-Digital Converter (ADC) is used on the pins connected to the probe locations. The Teensy 3.6 has two internal ADCs, allowing us to simultaneously digitize both analog signals. We utilized Pedvide’s ADC library to take advantage of the Teensy 3.6’s extremely fast ADCs. The following function initializes the ADC struct which will later provide the algorithm with data.

/* setupADCs()

* -----------------------------------------------------------

* contains setup code required to condition the ADCs to work

* at a specific speed and resolution

*/

void setupADCs() { // ensure that pinMode is set

pinMode (LED_BUILTIN,OUTPUT); pinMode(readPinOne,INPUT);

pinMode(readPinTwo, INPUT);

// setup for ADC0

adc->setAveraging(1);

adc->setResolution(16);

adc->setConversionSpeed(ADC_HIGH_SPEED);

adc->setSamplingSpeed(ADC_HIGH_SPEED);

// setup for ADC 1

adc->setAveraging(1);

adc->setResolution(16,ADC_1);

adc->setConversionSpeed(ADC_HIGH_SPEED,ADC_1);

adc->setSamplingSpeed(ADC_HIGH_SPEED,ADC_1);

// begin synchronized reads!

adc->startSynchronizedContinuous(readPinOne, readPinTwo);

}

Once the ADC has been initialized, the values can be retrieved in a simultaneous manner by calling the function readSynchronizedContinuous(). However, there is further processing on the provided value which must be done:

readResult = adc->readSynchronizedContinuous();

readResult.result_adc0 = (uint16_t)readResult.result_adc0;

readResult.result_adc1 = (uint16_t)readResult.result_adc1;

float tempRead1 = (readResult.result_adc0*1.0/ adc->getMaxValue(ADC_0)) *4050.0;

float tempRead2 = (readResult.result_adc1*1.0/adc->getMaxValue(ADC_1)) *4050.0;

We cast the values stored in the readResult structure to uint16_t so that values larger than 1.5V are not interpreted as negative and then scale those values between 0 and 4050. A scale between 0 and 4050 is used as our use of analogWrite() later will require values between 0 and 4096!

tempRead1 and tempRead2 provide the data needed by the algorithm. Either value can be supplied to the algorithm as each signal has distinct frequency spectrums for each chaotic state. The following snippets of code will utilize tempRead1.

tempRead1 is a sample of the analog signal you are probing. To determine the regime of the circuit, we need to take the Fourier Transform of the signal and look at the low frequency components. If we do this with all of the signals in tempRead1, then unwanted high frequency components would dominate the produced fourier transform because of the way the Teensy is built. In order to accurately obtain the lower frequency (500 Hz - 5 kHz) signals, we downsample. The script below essentially takes every 20th sample instead.

sampleCounter += 1;

if (sampleCounter % 20 == 0){

if (sample20 < samples){

vReal[sample20] = double (tempRead1);

vImag[sample20] = 0; sample20 += 1;

}

}

sampleCounter counts the number of samples which we have obtained. Once this value is a multiple of 20, we add the value in tempRead1 to the array of samples, given that there is space, then increment the array location counter (sample20). vReal and vImag refer to the real and imaginary parts of the signal and are arrays of doubles. These arrays are used in the compute_fourier function, which holds the implementation of our simple algorithm. This function is attached below:

/* void compute_fourier()

* -------------------------------------------------------------

* Once called, compute_fourier ensures that the array of samples

* is full. Once this assertion is true, the function computes the

* Fourier transform and uses the binPower of the lower bins to

* increment or decrement the pwmDuty value.

*/

void compute_fourier() {

if (sample20 == samples) {

double binPower = fourier(vReal, vImag);

sample20 = 0; // reset the counter in the array

if (binPower < bin_power_chaos_min) {

pwmDuty += 100;

} else if (binPower > bin_power_chaos_max) { // reset the pwmDuty if we are above

pwmDuty = 100; // the chaotic state for two attractors } if (pwmDuty > maxAnalogWriteValue) pwmDuty = 100; // saftey check to make sure pwmDuty

analogWrite(pwmPin, pwmDuty); // is not larger than highest value

}

}

}

The algorithm above is only 14 lines; however, it utilizes a powerful concept - each chaotic state has a well defined frequency spectrum. Understanding this short function begins with observing the pictures attached below. The pictures show the chaotic state of the circuit and the frequency spectrum of a probed signal:

The control algorithm is based off the empirical observation that there is significantly more power in the lower parts of the spectrum of the Chua circuit’s outputs when the system is in the double-scroll state than when the system is in other states. Thus, the sum of the power in the lower parts of the frequency spectrum provide a good indication of whether or not the circuit is producing double scrolls. Hence, compute_fourier() looks at the sum of normalized frequency components (bin power) and compares this value to a specific cut-off (empirically determined by sweeping pwdmDuty and recording the power in the lower part of the spectrum), and incrementing the pwmDuty if this value is lower than the minimum bin power attributed to double scrolls.

You may be asking how the binPower value is obtained! This is done in the fourier() function which is outlined below:

/* double fourier(double vRealIn [samples], double vImagIn [samples])

* -------------------------------------------------------------------

* computes the fourier transform using data stored in two arrays of

* doubles. Once the transform has been computed, normalizes the frequency

* components and returns a sum of the lowest maxBinToSum normalized

* frequency components (bin power)

*/

double fourier(double vRealIn [samples], double vImagIn [samples]) {

FFT.Windowing(vRealIn, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); // Weigh data FFT.Compute(vRealIn, vImagIn, samples, FFT_FORWARD); // Compute FFT FFT.ComplexToMagnitude(vRealIn, vImagIn, samples); // Compute magnitudes

vRealIn[0] = 0; // zero out the DC component double maxValue = 0;

for (int i = 0; i < samples ; i++) { // find the largest component

if (vRealIn[i] > maxValue) {

maxValue = vRealIn[i];

}

}

double binSum = 0; // normalize by largest component and add to

for (int i = 0; i < maxBinToSum; i++) { // a sum of lower bin powers

binSum += vRealIn[i] / maxValue;

}

return binSum;

}

At the core of this function is an FFT library written by Enrique Condes. FFT is an instantiation of his class and can be likened to a preprogrammed calculator - providing output based on a predefined set of equations and variable input. After doing further processing using the RotateCalc object, which is discussed in the next section, the digitized signal is transferred back to the analog world using the Digital-to-Analog converters (DACs). The section of code which does this is attached below:

int valuesRead[3] = {tempRead1, tempRead2 , 0};

int result [3];

calculator.fetchRotationResult(valuesRead, result);

analogWrite(xOutputPin, (int)(result[0] * xScaleFactor + xOffSet));

analogWrite(yOutputPin, (int)(result[1] * yScaleFactor + yOffSet));

Copy and paste the code in Appendix 1B to see your system start in a state of no chaos and watch as it find its way to double chaotic scrolls!

Step 7: Adding User Interaction

User Defined Rotation

The Teensy 3.6 is pretty fast microcontroller, which means that we can do a lot more than computing the Fourier Transform every few seconds! One feature, which is hinted at in in the circuit’s schematic, is the user’s ability to rotate the chaotic scrolls. We will look at the two libraries which were written to handle this and then finish our discussion with the usage of these libraries in chaos_findrotate.ino.

Any rotation requires a targeted matrix transformation to occur on each point sampled by the Teensy. RotationUtil.cpp contains the implementation of this logic, which was adopted from webpage discussing 3D Rotation. Glen Murray does a great job explaining the math and you should visit his site if you are curious!

Instead of diving into the nitty details of RotationUtil, we will simply look at RotateCalc.h which utilizes two RotationUtil objects. RotateCalc.h is attached below:

#ifndef RotateCalc_h

#define RotateCalc_h

#include "RotationUtil.h"class RotateCalc {

public:

/* RotateCalc(int axisCoords[3][3], int angle[2])

----------------------------------------------

creates a RotateCalc object which utilizes three

RotationUtil objects to rotate a provided point about three axes.

*/

RotateCalc(int axisCoords[2][3],int angle [2]);

/* void fetchRotationResult(int inputCoord[3], int result[3])

----------------------------------------------

returns the coordinate result of rotating the coordinate specified

by inputCoord around each internal axis of rotation

*/

void fetchRotationResult(int inputCoord[3], int result[3]); /

* void fetchRotatorAngles(float angles [2]);

----------------------------------------------

places the angles of rotation which are being used by each calculator

*/

void fetchRotatorAngles(float angles [2]);

/*

void updateRotatorAngle(int number, int angle);

----------------------------------------------

updates the angle of rotation which is being used by

the calculator specified by number, which ranges from 0 to 1.

*/

void updateRotatorAngle(int number, int angle);

private:

RotationUtil _rotatorOne; // two rotator objects which are used

RotationUtil _rotatorTwo; // to effect the rotation!

}; #endif

The most important section of this .h file are the lines describing the constructor and fetchRotationResult(). To instantiate your own RotateCalc object you need to do provide the axes of rotation and angles of rotation. Following this, you must use fetchRotationResult() to receive the transformed version of the coordinate you pass to the function. Feel free to play around with this library! The source files are attached in the appendix.

Now that we have established the numerical aspect of user rotation, it is now appropriate to discuss how we obtain user information in order to rotate the chaos scrolls. This is done using rotary encoders which rely on the principle of quadrature encoding in order to encode information.

The schematic, labeled Figure 1 above, serves as a brief reminder of the circuit topology around the rotary encoders.

In order to communicate with the rotary encoders, we wrote a small class, named UserRotate, which wrapped the ClickEncoder library developed by 0XPIT. The motivation behind using his library comes from two important features of ClickEncoder. Firstly, ClickEncoder is timer based, allowing any I/O pin on the Teensy to be used with the rotary encoders. Secondly, ClickEncoder supports push button clicking on encoders. This means that future extensions could involve a preprogrammed rotation firing once an encoder is clicked. The current UserRotate.h does not have this capability; however, we foresee adding this in the near future!

For your reference, the UserRotate.h file is outlined below:

#ifndef UserRotate_h

#define UserRotate_h#include class UserRotate {

public:

/*

UserRotate(int aWire, int bWire, char* name);

--------------------------------------

Constructor for the UserRotate class, one must specify the pins connected

to the squarewave pulses and supply a name for debugging purposes.

*/

UserRotate(int aWire, int bWire, char* name);

/*

void timerIsr()

--------------------------------------

Wraps the interrupt needed by the ClickEncoder.h library and

MUST be called in client code.

*/

void timerIsr();

/*

updateValue()

--------------------------------------

Provides the most recent value for the encoder angle. Returns -1 if

there has been no value change since the last time updateValue() was called.

*/

int updateValue();

/*

void setResolutionPerClick(int16_ val)

--------------------------------------

Allows the client to set the value multiplier for each click. For example,

a resolution of 5 indicates that each click from the encoder

corresponds to 5 degrees.

*/

void setResolutionPerClick(int16_t val);

private:

ClickEncoder *_encoder; // internal ClickEncoder object

int _wireA;

int _wireB;

char _name [50]; // name restricted to 50 characters

int16_t _counter; // internal counter

int16_t _last;

int16_t _value;

int16_t _resolutionPerClick; // multiplier for each encoder turn

};

#endif

One of the key points in using this library is the fact that an interrupt handler must be installed in order to service the rotary encoder. This is done in the setup stage of chaos_findrotate.ino by using the built in Timer1 library:

Timer1.initialize(1000);Timer1.attachInterrupt(handlerWrap);

handlerWrap is just a simple function which calls the interrupt handler for each UserRotate object.

/* handlerWrap()

* --------------------------------------------------------

* calls the interrupt handler of each UserRotate object

* so that all rotary encoders can be serviced when Timer1

* fires */

void handlerWrap() {

rotateInput.timerIsr();

rotateInput2.timerIsr();

}

With an avenue to obtain the user’s directives (degrees from UserRotate objects) and the means to arrive at a coordinate transformation, it is not appropriate to put everything together!

Given that you have defined the necessary constants such as the ones below:

// coordinate transformation constants and necessary objects

int allCoordinates[2][3] = {{1, 0, 0}, {0, 1, 0}};

int angles [2] = {70, 0};

RotateCalc calculator = RotateCalc(allCoordinates, angles);

char xRotatorName [] = "xRotator-25_24";

char yRotatorName [] = "yRotator-12_11";

UserRotate xRotateInput = UserRotate(25, 24, xRotatorName);

UserRotate yRotateInput = UserRotate(12, 11, yRotatorName);

You can now check for changes from the rotary encoders using:

int currentCount = xRotateInput.updateValue();

if (currentCount != -1) {

calculator.updateRotatorAngle(0, currentCount);

}

currentCount = yRotateInput.updateValue();

if (currentCount != -1) {

calculator.updateRotatorAngle(1, currentCount);

}

And compute the transformation by doing:

int valuesRead[3] = {tempRead1, tempRead2 , 0};

int result [3];

calculator.fetchRotationResult(valuesRead, result);

You may find the compute script in Appendix 1B. Feel free to copy it into an empty Arduino sketch and play around with your rotary encoders!

Step 8: Results and Conclusion

Now you have controllable chaotic circuit and a way to display it! It is important to note that chaotic circuits are sensitive in nature so achieving the desired result will take careful executions of the steps we have laid out above and a good deal of debugging. We hope you enjoy using your chaos circuit! Here are some photos of our final product.

The attached video displays the output when the code is looping through the possible values for the LED voltage in the CdS cell.

Thanks for reading and if you have any questions be sure to reach out to Liam!

Contributors:

  • Logan Herrera
  • Sarah Radzihovsky
  • Tyler Conklin
  • Sam Johnson
  • Andy Vu
  • Jonathan Wilson
  • Liam Neath
  • Eric Kauderer-Abrams
  • Kirill Vladimir Safin

Step 9: Appendix 1A: Chaotic Mode Sweeping Mode_sweep.ino

/************************************************************************/

/* */

/* mode_sweep.ino -- Chaotic States Demonstration */

/* */

/************************************************************************/

/* Author: Liam Neath */

/* Contact: liamhneath@gmail.com */

/************************************************************************/

/*

This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

*/

#include

#include

#include "arduinoFFT.h"

#include

#include

#include

#include

// FFT constants and global variables#define SCL_INDEX 0x00#define SCL_TIME 0x01#define SCL_FREQUENCY 0x02const uint16_t samples = 128; //This value MUST ALWAYS be a power of 2const uint16_t maxBinToSum = 8;double samplingFrequency = 20000;double vReal[samples];double vImag[samples];

uint16_t pwmDuty = 0; // duty cycle of the PWM signal controlled the LEDuint16_t pwmPin = A8; // Microcontroller pin connected to the LED uint16_t pwmDutyStart = 0; // starting value of the duty cycleuint16_t pwmDutyEnd = 4096; // ending value of the duty cycleuint16_t timeDelayMilliSecs = 100; // time needed to wait at each duty cycle valueuint16_t dutyStepSize = 5; // increment value after each analogWrite()

/* setup() * ---------------------------- * serial connection is initalized and the PWM writefrequency * and resolution are tweaked for better performance. */void setup(){ Serial.begin(9600); analogWriteResolution(12); analogWriteFrequency(pwmPin, 375000); // smoother pwm signal Serial.println("running mode_sweep. . . "); analogReference(0); }

/* loop() * -------------------------- * beginning at the value provided by pwmDutyStart, writes said * pwmDutyValue to the pwmPin and increments the value by dutyStepSize. * Resets when pwmDuty > pwmDutyEnd. */void loop() { pwmDuty = pwmDutyStart; // loop through all values betwwen pwmDutyStart and pwmDutyEnd while (pwmDuty < pwmDutyEnd){ Serial.println("the PWM Duty is now: "); Serial.println(pwmDuty); analogWrite(pwmPin, pwmDuty); delay(timeDelayMilliSecs); pwmDuty += dutyStepSize; // increment the pwmDuty; } Serial.println("completed cycling through all pwmDuty values!"); Serial.println("pausing for 100 milliseconds then redoing process"); delay(100); }

Step 10: Appendix 1B: Self-Locating and Chaos Rotating Chaos_findrotate.ino

/************************************************************************/

/* chaos_findrotate.ino -- Chaotic State Finder and Rotator */

/* */

/************************************************************************/

/* Author: Liam Neath */

/* Contact: liamhneath@gmail.com */

/************************************************************************/

/*

This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

*/

#include

#include

#include "arduinoFFT.h"

#include

#include

#include

#include

// FFT constants and global variables

#define SCL_INDEX 0x00

#define SCL_TIME 0x01

#define SCL_FREQUENCY 0x02

const uint16_t samples = 128; //This value MUST ALWAYS be a power of 2

const uint16_t maxBinToSum = 8;

double samplingFrequency = 20000;

double vReal[samples];

double vImag[samples];

uint16_t sampleCounter = 0;

uint16_t sample20 = 0;uint16_t pwmDuty = 0;

uint16_t maxAnalogWriteValue = 4096;long time_old = 0;

long interval_for_calc = 2000;

double bin_power_chaos_min = 1.3;

double bin_power_chaos_max = 1.7;

arduinoFFT FFT = arduinoFFT(); /* Create global FFT object */

// ADC constants and global variables

const int readPinOne = A9;

const int readPinTwo = A3;

const int pwmPin = A8;

ADC *adc = new ADC();

ADC::Sync_result readResult;

elapsedMicros time;

// DAC Constants and global variables

const int xOutputPin = A22;

const int yOutputPin = A21;

const int xOffSet = -2200;

const int yOffSet = -5000;

const float xScaleFactor = 1.8;

const float yScaleFactor = 3.0;

// coordinate transformation constants and necessary

// objects

int allCoordinates[2][3] = {{1, 0, 0}, {0, 1, 0}};int angles [2] = {0, 0};

RotateCalc calculator = RotateCalc(allCoordinates, angles);

char xRotatorName [] = "xRotator-25_24";

char yRotatorName [] = "yRotator-12_11";

UserRotate xRotateInput = UserRotate(25, 24, xRotatorName);

UserRotate yRotateInput = UserRotate(12, 11, yRotatorName);

/* handlerWrap()

* -------------------------------------------------------- *

* calls the interrupt handler of each UserRotate object

* so that all rotary encoders can be serviced when Timer1

* fires

*/

void handlerWrap() {

xRotateInput.timerIsr();

yRotateInput.timerIsr();

}

/* isFourierNeeded()

* -------------------------------------------------------- *

* determines whether or not sufficeint time has elapsed so that

* the fourier transform can be alculated and the pwmDuty adjusted

* to alter the chaotic state of the circuit

*/

uint16_t isFourierNeeded() {

unsigned long currMillis = millis();

if ((currMillis - time_old) > interval_for_calc) {

time_old = currMillis; return 1;

}

return 0;

}

/* double fourier(double vRealIn [samples], double vImagIn [samples])

* -------------------------------------------------------------------

* computes the fourier transform using data stored in two arrays of

* doubles. Once the transform has been computed, normalizes the frequency

* components and returns a sum of the lowest maxBinToSum normalized

* frequency components (bin power)

*/

double fourier(double vRealIn [samples], double vImagIn [samples]) {

FFT.Windowing(vRealIn, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); // Weigh data FFT.Compute(vRealIn, vImagIn, samples, FFT_FORWARD); // Compute FFT FFT.ComplexToMagnitude(vRealIn, vImagIn, samples); // Compute magnitudes

vRealIn[0] = 0; // zero out the DC component

double maxValue = 0;

for (int i = 0; i < samples ; i++) { // find the largest component

if (vRealIn[i] > maxValue) {

maxValue = vRealIn[i];

}

}

double binSum = 0; // normalize by largest component and add to

for (int i = 0; i < maxBinToSum; i++) { // a sum of lower bin powers

binSum += vRealIn[i] / maxValue;

}

return binSum;

}

/* compute_fourier()

* -------------------------------------------------------------

* Once called, compute_fourier ensures that the array of samples is full.

* Once this assertion is true, the function computes the

* Fourier transform and uses the binPower of the lower bins to

* increment or decrement the pwmDuty value.

*/

void compute_fourier() {

if (sample20 == samples) {

double binPower = fourier(vReal, vImag);

sample20 = 0; // reset the counter in the array

if (binPower < bin_power_chaos_min) {

pwmDuty += 100;

} else if (binPower > bin_power_chaos_max) { // reset the pwmDuty if we are above

pwmDuty = 100; // the chaotic state for two attractors

}

if (pwmDuty > maxAnalogWriteValue)

pwmDuty = 100; // saftey check to make sure pwmDuty

analogWrite(pwmPin, pwmDuty); // is not larger than highest value

}

}

/* setupADCs()

* ----------------------------------------------------------- *

* contains setup code required to condition the ADCs to work

* at a specific speed and resolution

*/

void setupADCs() {

// ensure that pinMode is set

pinMode (LED_BUILTIN, OUTPUT);

pinMode(readPinOne, INPUT);

pinMode(readPinTwo, INPUT);

// setup for ADC0

adc->setAveraging(1);

adc->setResolution(16);

adc->setConversionSpeed(ADC_HIGH_SPEED);

adc->setSamplingSpeed(ADC_HIGH_SPEED);

// setup for ADC 1

adc->setAveraging(1);

adc->setResolution(16, ADC_1);

adc->setConversionSpeed(ADC_HIGH_SPEED, ADC_1);

adc->setSamplingSpeed(ADC_HIGH_SPEED, ADC_1);

// begin synchronized reads!

adc->startSynchronizedContinuous(readPinOne, readPinTwo);

}

void setup() {

sampleCounter = 0;

sample20 = 0;

pinMode(pwmPin, OUTPUT);

analogWriteFrequency(pwmPin, 375000);

analogWriteResolution(12);

analogReference(0);

analogWrite(pwmPin, pwmDuty);

Timer1.initialize(1000);

Timer1.attachInterrupt(handlerWrap);

calculator.updateRotatorAngle(0, 0);

setupADCs();

delay(100);

}

void loop() {

int currentCount = xRotateInput.updateValue();

if (currentCount != -1) {

calculator.updateRotatorAngle(0, currentCount);

}

currentCount = yRotateInput.updateValue();

if (currentCount != -1) {

calculator.updateRotatorAngle(1, currentCount);

}

if (isFourierNeeded() == 1)

compute_fourier();

readResult = adc->readSynchronizedContinuous();

readResult.result_adc0 = (uint16_t)readResult.result_adc0;

readResult.result_adc1 = (uint16_t)readResult.result_adc1;

int tempRead1 = (readResult.result_adc0 * 1.0 / adc->getMaxValue(ADC_0)) * 4050;

int tempRead2 = (readResult.result_adc1 * 1.0 / adc->getMaxValue(ADC_1)) * 4050;

sampleCounter += 1;

if (sampleCounter % 20 == 0) {

if (sample20 < samples) {

vReal[sample20] = double (tempRead1); // fourier library requires doubles

vImag[sample20] = 0; sample20 += 1;

}

}

int valuesRead[3] = {tempRead1, tempRead2 , 0};

int result [3];

calculator.fetchRotationResult(valuesRead, result);

analogWrite(xOutputPin, (int)(result[0] * xScaleFactor + xOffSet));

analogWrite(yOutputPin, (int)(result[1] * yScaleFactor + yOffSet));

}

Step 11: Appendix 1C: Rotation Calculator Scripts - .cpp and .h Files

RotateCalc.h

#ifndef RotateCalc_h

#define RotateCalc_h

#include "RotationUtil.h"

class RotateCalc{

public:

/* RotateCalc(int axisCoords[3][3], int angle[2])

----------------------------------------------

creates a RotateCalc object which utilizes three RotationUtil

objects to rotate a provided point about three axes.

*/

RotateCalc(int axisCoords[2][3],int angle [2]);

/* void fetchRotationResult(int inputCoord[3], int result[3])

----------------------------------------------

returns the coordinate result of rotating the coordinate specified

by inputCoord around each internal axis of rotation

*/

void fetchRotationResult(int inputCoord[3], int result[3]);

/* void fetchRotatorAngles(float angles [2]);

----------------------------------------------

places the angles of rotation which are being used by each calculator

*/

void fetchRotatorAngles(float angles [2]);

/* void updateRotatorAngle(int number, int angle);

----------------------------------------------

updates the angle of rotation which is being used by the calculator

specified by number, which ranges from 0 to 1.

*/

void updateRotatorAngle(int number, int angle);

private:

RotationUtil _rotatorOne; // two rotator objects which are used

RotationUtil _rotatorTwo; // to effect the rotation!

};

#endif

RotateCalc.cpp

#include "Arduino.h"

#include "RotateCalc.h"

const float deg_to_rad = 57.2958;

RotateCalc::RotateCalc(int axisCoords[2][3], int angle [2]) {

int firstArray [3] = {axisCoords[0][0], axisCoords[0][1], axisCoords[0][2]};

int secondArray[3] = {axisCoords[1][0], axisCoords[1][1], axisCoords[1][2]}; _rotatorOne.setAxisCoordinates(firstArray);

_rotatorOne.setAngleOfRotation(angle[0]);

_rotatorTwo.setAxisCoordinates(secondArray);

_rotatorTwo.setAngleOfRotation(angle[1]);

}

void RotateCalc::fetchRotationResult(int inputCoord [3], int result[3]){

int tempResult1[3];

_rotatorOne.getRotationResult(inputCoord, tempResult1);

_rotatorTwo.getRotationResult(tempResult1, result);

}

void RotateCalc::updateRotatorAngle(int number, int angle){

if (number == 0) {

_rotatorOne.setAngleOfRotation(angle);

} else if (number == 1) {

_rotatorTwo.setAngleOfRotation(angle);

}

}

void RotateCalc::fetchRotatorAngles(float angles [2]) {

angles[0] = _rotatorOne.fetchAngleOfRotation() * deg_to_rad;

angles[1] = _rotatorTwo.fetchAngleOfRotation() * deg_to_rad;

}

RotationUtil.h

#ifndef RotationUtil_h

#define RotationUtil_h

#include "Arduino.h"

class RotationUtil{

public:

/* RotationUtil();

----------------------------------------

empty constructor for a RotationUtil object. By default,

the axis of rotation is set to 1,0,0 and the angle is set

to 90 degrees.

*/

RotationUtil();

/* RotationUtil(int axisCoords[3], int angle)

----------------------------------------

sets the axis of rotation to the coordianates specified

by axisCoords[3] and the angle of rotation (in degrees)

to the angle specified by angle

*/

RotationUtil(int axisCoords[3], int angle);

/* void setAngleOfRotation(int degree);

----------------------------------------

updates the angle of rotation that the calculator is

currently using to the value specified by degree

*/

void setAngleOfRotation(int degree);

/* float fetchAngleOfRotation()

----------------------------------------

returns the angle of rotation that is being used

to arrived at the matrix transformation's result

*/

float fetchAngleOfRotation();

/*

void setAxisCoordinates(int coord[3])

----------------------------------------

sets the axis about which the matix transformation

is currently being computed

*/

void setAxisCoordinates(int coord [3]);

/* void getRotationResult(int inputCoord[3], int result[3])

----------------------------------------

provides the result of rotating the coordinate specified in

inputCoord[3] about the internal axis rotation through the

internal angle of rotation. the transformation result is stored

in the array result[3]

*/

void getRotationResult(int inputCoord[3], int result[3]);

private:

/* void preComputeValues()

----------------------------------------

internal routine to precompute and store sections of the

matrix transformation that are rarely modified.

*/

void preComputeValues();

/* float convertDegToRad(int degree)

----------------------------------------

internal routine to compute the radian equivalent to the

angle specified in degrees.

*/

float convertDegToRad(int degree);

int _axisCoordinates [3]; // internal axis of rotation

float _sineResult; // precomputed sine and cosine

float _cosineResult; // results

float _angleOfRotation;

float _sumsSquared; // common values used by the

float _squareOfSums; // transformation

float _lastProduct;

};

#endif

RotationUtil.cpp

#include "Arduino.h"

#include "RotationUtil.h"

// remove magic numbers

const float pointOfR [3] = {2000,2000,0};

const float deg_to_rad = 57.2958;

// precomputed values of sine and cosine which save time! This

// should be placed in a .h file if RotationUtil.cpp becomes large

const float preComputedSine [73] = {0.00000,0.08716,0.17365,0.25882,0.34202,0.42262,0.50000,0.57358,0.64279,0.70711,0.76604,0.81915,0.86603,0.90631,0.93969,0.96593,0.98481,0.99619,1.00000,0.99619,0.98481,0.96593,0.93969,0.90631,0.86603,0.81915,0.76604,0.70711,0.64279,0.57358,0.50000,0.42262,0.34202,0.25882,0.17365,0.08716,0.00000,-0.08715,-0.17365,-0.25882,-0.34202,-0.42262,-0.50000,-0.57358,-0.64279,-0.70711,-0.76604,-0.81915,-0.86602,-0.90631,-0.93969,-0.96593,-0.98481,-0.99619,-1.00000,-0.99619,-0.98481,-0.96593,-0.93969,-0.90631,-0.86603,-0.81915,-0.76605,-0.70711,-0.64279,-0.57358,-0.50000,-0.42262,-0.34202,-0.25882,-0.17365,-0.08716,-0.00000};

const float preComputedCos [73] = {1.00000,0.99619,0.98481,0.96593,0.93969,0.90631,0.86603,0.81915,0.76604,0.70711,0.64279,0.57358,0.50000,0.42262,0.34202,0.25882,0.17365,0.08716,0.00000,-0.08716,-0.17365,-0.25882,-0.34202,-0.42262,-0.50000,-0.57358,-0.64279,-0.70711,-0.76604,-0.81915,-0.86602,-0.90631,-0.93969,-0.96593,-0.98481,-0.99619,-1.00000,-0.99619,-0.98481,-0.96593,-0.93969,-0.90631,-0.86603,-0.81915,-0.76605,-0.70711,-0.64279,-0.57358,-0.50000,-0.42262,-0.34202,-0.25882,-0.17365,-0.08716,-0.00000,0.08715,0.17365,0.25882,0.34202,0.42262,0.50000,0.57357,0.64279,0.70711,0.76604,0.81915,0.86602,0.90631,0.93969,0.96593,0.98481,0.99619,1.00000};

RotationUtil::RotationUtil() {

int axisCoords [3] = {1,0,0};

int angle = 90;

setAxisCoordinates(axisCoords);

setAngleOfRotation(angle);

}

RotationUtil::RotationUtil(int axisCoords [3], int angle) {

setAxisCoordinates(axisCoords);

setAngleOfRotation(angle);

}

float RotationUtil::convertDegToRad(int degree) {

return (degree/deg_to_rad);

}

void RotationUtil::setAngleOfRotation(int degree) {

_angleOfRotation = convertDegToRad(degree);

_sineResult = preComputedSine[(int)(degree % 360)/5];

_cosineResult = preComputedCos[(int)(degree % 360)/5];

preComputeValues(); // must recompute the hashed values since _lastProduct depends on _sineResult;

}

float RotationUtil::fetchAngleOfRotation() {

return _angleOfRotation;

}

void RotationUtil::setAxisCoordinates(int coord [3]) {

for (int i = 0; i < 3; i ++) {

_axisCoordinates[i] = coord[i];

}

preComputeValues(); // obviously must recompute all the hashed values

}

void RotationUtil::preComputeValues() {

_sumsSquared = ((_axisCoordinates[0] * _axisCoordinates[0]) + (_axisCoordinates[1] * _axisCoordinates[1])

+ (_axisCoordinates[2] * _axisCoordinates[2]));

_squareOfSums = sqrt(_sumsSquared); _

lastProduct = _squareOfSums * _sineResult;

}

// see http://twist-and-shout.appspot.com for an explanation

// of all the multiplication which is occuring!

void RotationUtil::getRotationResult(int inputCoord[3], int result[3] ) {

int constProd = (0 - ( (_axisCoordinates[0] * inputCoord[0]) +

(_axisCoordinates[1] * inputCoord[1]) +

(_axisCoordinates[2] * inputCoord[2])));

float endTerm = 1 - _cosineResult;

for (int i = 0; i < 3; i++) {

int tempResult = 0;

if (i == 0) {

tempResult = (pointOfR[0] * ((_axisCoordinates[1]*_axisCoordinates[1]) + (_axisCoordinates[2] * _axisCoordinates[2])) - _axisCoordinates[0] * ( (pointOfR[1] * _axisCoordinates[1]) + (pointOfR[2] * _axisCoordinates[2]) + constProd))* endTerm + (inputCoord[i] * _cosineResult);

tempResult += (((0 - pointOfR[2]) * _axisCoordinates[1]) + (pointOfR[1] * _axisCoordinates[2]) - (_axisCoordinates[2] * inputCoord[1]) + (_axisCoordinates[1] * inputCoord[2])) * _sineResult ;

}

else if (i == 1) {

tempResult = (pointOfR[1] * ((_axisCoordinates[0]*_axisCoordinates[0]) + (_axisCoordinates[2] * _axisCoordinates[2])) - _axisCoordinates[1] * ( (pointOfR[0] * _axisCoordinates[0]) + (pointOfR[2] * _axisCoordinates[2]) + constProd))* endTerm + (inputCoord[i] * _cosineResult);

tempResult += ((pointOfR[2] * _axisCoordinates[0]) - (pointOfR[0] *_axisCoordinates[2])

+ (_axisCoordinates[2]* inputCoord[0]) - (_axisCoordinates[0] *inputCoord[2])) *_sineResult;

}

else {

tempResult = (pointOfR[2] * ((_axisCoordinates[0]*_axisCoordinates[0]) + (_axisCoordinates[1] * _axisCoordinates[1])) - _axisCoordinates[2] * ( (pointOfR[0] * _axisCoordinates[0]) + (pointOfR[1] * _axisCoordinates[1]) + constProd))* endTerm + (inputCoord[i] * _cosineResult);

tempResult += ( ((0 - pointOfR[1])) *_axisCoordinates[0] + (pointOfR[0]*_axisCoordinates[1])

+ ((0 - _axisCoordinates[1]) * inputCoord[0]) + (_axisCoordinates[0] * inputCoord[1])) * _sineResult;

}

result[i] = tempResult;

}

}

Step 12: Appendix 1D: User Rotation I/O - .cpp and .h Files

UserRotate.cpp

#include "Arduino.h" // for strcpy

#include "UserRotate.h"

void UserRotate::timerIsr() {

_encoder->service();

}

UserRotate::UserRotate(int aWire, int bWire, char* nameOfEncoder) {

strcpy(_name, nameOfEncoder );

_encoder = new ClickEncoder(aWire, bWire, 0); // 0 corresponds to the wire carrying push-button signal

_counter = 0;

_wireA = aWire;

_wireB = bWire;

_last = -1;

_resolutionPerClick = 5; // resolution intialized to 5

}

int UserRotate::updateValue() {

_value += _encoder->getValue();

if (_value != _last) {

if ( _last == 0 && _value == 1) {

_counter -= 1;

if (_counter < -1) { // set a minimum on the degree that we are

_counter = 0; // using

}

} else if (_last == 1 && _value == 2) {

_counter += 1;

}

_last = _value;

return _counter * _resolutionPerClick;

}

return -1; // no change so return -1

}

void UserRotate::setResolutionPerClick(int16_t val) {

_resolutionPerClick = val;

}

UserRotate.h

#ifndef UserRotate_h

#define UserRotate_h

#include

class UserRotate {

public:

/* UserRotate(int aWire, int bWire, char* name);

--------------------------------------

Constructor for the UserRotate class, one must specify the pins

connected to the squarewave pulses and supply a name for debugging purposes.

*/

UserRotate(int aWire, int bWire, char* name);

/* void timerIsr()

-------------------------------------

Wraps the interrupt needed by the ClickEncoder.h library and MUST be called in client code.

*/

void timerIsr();

/* updateValue()

--------------------------------------

Provides the most recent value for the encoder angle. Returns -1 if there has

been no value change since the last time updateValue() was called.

*/

int updateValue();

/* void setResolutionPerClick(int16_ val)

--------------------------------------

Allows the client to set the value multiplier for each click. For example, a

resolution of 5 indicates that each click from the encoder corresponds to 5 degrees.

*/

void setResolutionPerClick(int16_t val);

private:

ClickEncoder *_encoder; // internal ClickEncoder object

int _wireA; int _wireB;

char _name [50]; // name restricted to 50 characters

int16_t _counter; // internal counter

int16_t _last; int16_t _value;

int16_t _resolutionPerClick; // multiplier for each encoder turn

};

#endif