20150329.jpg

 

The first chapter is finished.
I started with a defect turntable. It looked good at the start but didn't turn.

It hasn't changed on the outside, but the inside is revamped with latest technology. And it spins!

 

Photo 05-02-15 22 23 55.jpg

 

In each of the previous 6 posts I have touched one isolated part of the table.

I covered the motor drive part, the speed detection part, and the firmware for each of these modules.

In this closure post we'll bring the things together and build the firmware that controls the table and keeps it up to speed.

 

The Firmware

 

The Arduino UNO that's one of the parts for the challenge has the control duty. The motor control and sensor capture blocks were already written for  the UNO.

In the final firmware design, they appear as the two top blocks. That shouldn't be a surprise, because they are the main touchpoints with the table's mechanics.

And it's those mechanics that we want to control.

design.jpg

 

The Feedback And Control Block

 

To make this design do something real, we'll have to tie the two blocks together.

We need feedback from the speed sensor to know how we have to adjust the motor.

We tell the speed control how fast our motor should spin.

The control block has the single duty to learn from the sensor block how fast the actual speed is, and adjust that speed via the motor drive block if necessary.

The control block does that in a neternal control loop.

 

Desired speed, hysteresis, loop frequency

 

There are three attributes and parameters that define the behavior of the control block.

  • desired speed: we tell the controller what speed it should aim for. This is the only attribute we control from the outside. The controller does all the management to achieve that goal autonomously.
  • loop frequency: this parameter tells the controller how often it should validate speed and adjust if needed.
  • hysteresis: this parameter tells the controller how much play area it has. We define the upper and lower acceptable deviations from the ideal speed.

 

The two parameters define how smooth the controller can handle its job to guard the speed. Playing with the table and listening to it is key while fine-tuning them.

To help with the parameterisation, I added a 3-LED display that shows the controller status:

  • SLOW: the table is spinning below its lower limit and the controller is speeding it up
  • OK: we're operating within bounds. The controller keeps the motor pace.
  • FAST: we're going too fast. The controller is slowing down the motor.

 

In the video below (8 seconds) you see this in operation.

I have tuned the control parameters bad on purpose for this video - and turned down the DC supply close to the lowest limit - so that you see something happening.

In well-tuned mode, the green led is on virtually all the time. A well tuned loop is boring to look at. There are more things happening when the device is outside its comfort zone.

 

 

Supporting Modules

 

There are two modules to support the turntable drive mechanism:

 

A user interface, where you can adapt the behavior and ask statistics. This simple serial monitor based front end has only three functions.

  • You can set the desired speed. When you enter a speed, the info is sent to the controller. From now on, the table will aim to stay on that new speed until it is power cycled (you could call this a calibration).
  • You can store the current speed in EEPROM. Once you've set the correct speed and the table is stable, you can tell it to save this setting. This is persistent. When you power up the table the next time, it will read back your stored setting at start-up.
  • There is a status function. The firmware tells the status of the three main blocks: the desired speed of the control block, the measured speed of the sensor, and the current PWM duty cycle of the motor drive block.

 

The second utility module is the persistence interface. It communicates with the ATMega EEPROM. It stores and reads the desired speed value. Without this tiny block you would have to set the desired speed each time.

 

The Code

 

Because this is the end of a chapter, and a whole functional block is finished, I'm publishing the full firmware code.

It isn't hard to recognize the blocks and methods from the architectural drawing at the start of this article.

 

 

 

/*


element14 enchanted objects
enchanted player
turntable speed control
motor pwm steering
motor speed counting


Includes most of the standard example ReadASCIIString
created 13 Apr 2012
by Tom Igoe


Includes Timer1 library
http://playground.arduino.cc/code/timer1


Includes avdweb Frequency / period counter for the Arduino
http://www.avdweb.nl/arduino/hardware-interfacing/frequency-period-counter.html


*/




// includes for PWM
#include <TimerOne.h>


// includes for sensor
#include <FreqPeriodCounter.h>


// includes for eeprom
#include <EEPROM.h>

// constants for PWM
const int iPwmPin = 9; // PWM only pin 9 or 10
const int iPwmEnablePin = 11; // motor on/off
int iPwmDuty = 400; // 705; // exact turntable speed at 10 volt


// variables and constant for sensor
const byte iSensorPin = 3;
const byte iSensoCounterInterrupt = 1; // = pin 3
FreqPeriodCounter sensorCounter(iSensorPin, micros);


// variables and constants for speed adjust
#define HYSTERESIS 10
int iPeriod = 0;
#define LED_SLOW 5
#define LED_OK 6
#define LED_FAST 7


// variableds and constants for EEPROM
#define EEPROM_DUTY_ADDR 0




// function declarations PWM
void pwmSetup();
void pwmChangeDuty(int duty);
// function declarations sensor
void sensoCounterISR();
void sensorSetup();
long  sensorPeriod();
// functions for speed adjust
void speedSetup();
void speedUp();
void speedDown();
void speedAdjust(int period);
// function declarations eeprom
void eepromWritePeriod(int period);
int eepromReadPeriod();


// ========== PWM code begin ==========


void pwmSetup() {
  pinMode(iPwmPin, OUTPUT);
  pinMode(iPwmEnablePin, OUTPUT);
  Timer1.initialize();
  Timer1.pwm(iPwmPin, iPwmDuty, 30); // duty cycle [10 bit], period [us] <8388480
  digitalWrite(iPwmEnablePin, HIGH); // enable OUT1
}


void pwmChangeDuty(int duty) {
  Timer1.setPwmDuty(iPwmPin, duty);
  iPwmDuty = duty;
}


// ========== PWM code end ==========




// ========== sensor code begin ==========
void sensoCounterISR() {
  sensorCounter.poll();
}


void sensorSetup() {
  attachInterrupt(iSensoCounterInterrupt, sensoCounterISR, CHANGE);
}


long  sensorPeriod() {
  return sensorCounter.period;
}
// ========== sensor code end ==========


// ======= speed adjustment begin =======
void speedSetup() {
  pinMode(LED_SLOW, OUTPUT);
  pinMode(LED_OK, OUTPUT);
  pinMode(LED_FAST, OUTPUT);
  iPeriod = eepromReadPeriod();
}


void speedUp() {
  pwmChangeDuty(iPwmDuty+1);
}


void speedDown() {
  pwmChangeDuty(iPwmDuty-1);
}


void speedAdjust(int period) {
  if (iPeriod + HYSTERESIS < period) {// speed up
    digitalWrite(LED_SLOW, HIGH);
    digitalWrite(LED_OK, LOW);
    digitalWrite(LED_FAST, LOW);
    if (iPwmDuty < 1023 ) {
      speedUp();
    } else {
      Serial.println( "*** can't speed up, maximum reached" );
    }
  }


  if (iPeriod - HYSTERESIS > period) {// speed down
    digitalWrite(LED_SLOW, LOW);
    digitalWrite(LED_OK, LOW);
    digitalWrite(LED_FAST, HIGH);
    if (iPwmDuty > 400 ) {
      speedDown();
    } else {
      Serial.println( "*** can't speed down, minimum reached" );
    }
  }


  if ((iPeriod - HYSTERESIS <  period) && (period < iPeriod + HYSTERESIS)) { // speed ok
    digitalWrite(LED_SLOW, LOW);
    digitalWrite(LED_OK, HIGH);
    digitalWrite(LED_FAST, LOW);
  }
}


// ======= speed adjustment end =======


// ========== eeprom storage begin ==============


void eepromWritePeriod(int period) {
  if(eepromReadPeriod() != period) { // prevent writing when not needed
    EEPROM.write(EEPROM_DUTY_ADDR, period >> 8);
    EEPROM.write(EEPROM_DUTY_ADDR + 1, period & 0xff);
  }
}


int eepromReadPeriod() {
  int lRet = EEPROM.read(EEPROM_DUTY_ADDR);
  lRet = lRet << 8;
  lRet |= EEPROM.read(EEPROM_DUTY_ADDR + 1);
  return (lRet);
}


// ========== eeprom storage end ==============


// ========== ARDUINO code begin ==========


void setup(void) {
  Serial.begin(9600);
  sensorSetup();
  speedSetup();
  pwmSetup();
  Serial.println("Ready for input...");
}

void loop(void) {

  int period = (int)(sensorPeriod()/100); // this is enough precision, want to keep to int


  while (Serial.available() > 0) {

    int iDesiredPeriod = Serial.parseInt();
    if (iDesiredPeriod > 10) {
      iPeriod = iDesiredPeriod;
     }


    if (iDesiredPeriod == 10) { // cmd 10 = store
      eepromWritePeriod(period);
      Serial.print("**** Stored period: ");
      Serial.println(period);
      period = eepromReadPeriod();
      Serial.print("**** Read back period: ");
      Serial.println(period);
     }

    if (iDesiredPeriod == 9) { // cmd 9 = stats
      Serial.println("**** Stats ****");
      Serial.print("**** Desired period: ");
      Serial.println(eepromReadPeriod());
      Serial.print("**** Measured period: ");
      Serial.println(period);
      Serial.print("**** PWM Duty Cycle: ");
      Serial.println(iPwmDuty);
     }

  }


  speedAdjust(period);
  delay(20);

}

// ========== ARDUINO code end ==========








 

Enough practical design. The table is spinning as it should now. From now on I'll focus on enchantement.

I'm also leaving my own comfort zone behind now. For the next steps I have no clue yet what they will be and how I'm going to build things.

 

End of Chapter 1

 

Photo 05-02-15 22 23 36.jpg

 

 

 

 

Table of Contents
Chapter 1: Fix the turntable
1: Perpetuum Ebner Musical 1
2: A Time to Kill and a Time to Heal
3: Preparation for Motor Drive
4: Motor control with Infineon Motor Shield and Arduino UNO
5: Turntable speed sample testbed with Arduino UNO
6: Turntable Speed Sensor design
7: Control Theory - End of Chapter 1
Chapter 2: First Enchantments
8: Digital Light Organ Enchantment
9: Autonomous Servo Lift
10: SMD Time - Solder the IR Speed Sensor PCB
11: Yelp - who can Help me to Compile and Run my First SAMA5D4 C Program
12: Son et Lumiere - End of Chapter 2
Chapter 3: Taming the Board
13: Breakthrough - Run my own C++ Program on the SAMA5D4
14: Digital Light Organ Input Buffer
15: SAMA5D4 Blinky
16: Scope Creep
17: Audio Sampling with 16-bit ADC ADS8343
18: Sending Files to SAMA5D4 over USB
19: Port my Light Organ from Arduino to SAMA5D4
20: Fast Fourier Transform on the SAMA5D4 - End of Chapter 3
Epilogue: Reaching for the Clouds
21: Right-Sizing my Plans
22: My Own C++ Buffered Sampler on the SAMA5D4
Interlude
23: Building In the Motorized Light Organ
24: Up to the Clouds with Yún
25: Publish or Perish
26: Turntable Finished
Stretch & Boni
Bonus 1a: Remote Light Organ with WiFI pt. 1
Bonus 1b: Remote Light Organ with WiFI pt. 2
Grande Finale: Paho MQTT Client on the SAMA5D4
Related blog
Vintage Turntable repair: Can I fix a Perpetuum Ebner from 1958
Review 1: Atmel SMART SAMA5D4 Xplained Ultra Unboxing and First Steps
Review 2: Atmel SMART SAMA5D4 Xplained Ultra - Building the Libraries from Source
Review 3: Digital Continuous Rotation (360°) Servo Part 1
Review 4: Digital Continuous Rotation (360°) Servo Part 2
Review 5: Atmel SMART SAMA5D4 Xplained Ultra - TCP/IP running
Review 6: Atmel SMART SAMA5D4 Xplained Ultra - LINUX Distro with SSH support
poem
Enchanted Objects: Let's work together to tame the ATMEL SMART SAMA5D4 Xplained Ultra kit
17 bis: Off South...
Review 7: Atmel SMART SAMA5D4 Xplained Ultra - C++ ADC Example on Linux
Review 8: Atmel SMART SAMA5D4 Xplained Ultra - Product Review
Review 9a: Atmel SMART SAMA5D4 Xplained Ultra - Set up ADC Buffer with Hardware Trigger Part 1
Review 9b: Atmel SMART SAMA5D4 Xplained Ultra - Set up ADC Buffer with Hardware Trigger Part 2
Review 10: Atmel SMART SAMA5D4 Xplained Ultra - New Content on AT91.com
1958 Turntable from the Black Forest - Summary of the Enchanted Player Story