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!
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.
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
Top Comments