I understand what you're saying and if the devices were extremely fast, then I'd agree. But we're talking cheap servos which are slow. If we imagine your example of multiple heads, consider the difficulty of simulating simultaneity of turning them while adjusting lips, jaw openings, eye rolls and so on.
Even with reading and interpreting one line at a time it'll still be slow, but at least the cumulative effects of all movements will be correct every "tick" microseconds. "tick" will, of course, depend on the number of servos, their speed and the relative distance each has to move.
A superior (although much more difficult) approach would be to manage each pot to collect and store (for each servo) an angular displacement and an elapsed time for that movement. The playback would then read that combination (ID, change in angle, time for change) and then update the position of the servo by moving change/time x loop time. That would be beneficial in keeping all of the movement relatively in synch and cut down on data storage as well. But it'd be hell to implement 🙂
Anything seems possible when you don't know what you're talking about.
- I'd just wrap it up in a simple C++ class for one servo. From then on you simply add servos to your heart's delight and no changes to file format or to the main sketch's logic.
But as I mentioned... there are as many opinions here as there are people. It is totally what makes the most sense to @hinobot and his daughters. Some people think procedurally. Fewer people (I have to admit) thing Object Oriented.
I agree completely about handling it (either way) with a class.
But, as I understand it, hinobot and his daughters aren't even C programmers (yet :)) and I think this project will be more difficult than they imagined in the first place. I don't want to scare them off !
Anything seems possible when you don't know what you're talking about.
I don't have any attachment to the code posted. The original code posted by DroneBot Workshop 2019 works perfect for my needs, one to record, the other to play back. Its just fine, I just need to add the ability to capture 4 pots and 4 servos, and play them back at the same rate recorded.
I have ordered the sd card module so when it arrives I will give it a go and maybe in the meantime figure out how to modify Bill's code to use 4 servos but it may take time and you may have is all working before then 🙂
@robotbuilder Thank you, I got all the servos working with modifying the original code, the rub is when we try to record each channel on the SD card, and then as @bill mentions, there is no time code during the recording, so it unceremoniously goes through the entire movements very fast (on one channel).
You can use millis() for timing.
For play you can read the card and update the next positions in fixed intervals of time.
For record you can read the pots at the same fixed time intervals and save the positions on the card.
unsigned long startMillis; //some global variables available anywhere in the program unsigned long currentMillis; const unsigned long period = 1000; //the value is a number of milliseconds void setup() { Serial.begin(115200); //start Serial in case we need to print debugging info // other setup stuff here startMillis = millis(); //initial start time } void loop() { currentMillis = millis(); //get the current "time" (actually the number of milliseconds since the program started) if (currentMillis - startMillis >= period) //test whether the period has elapsed { // ====================== DO YOU THING HERE ================================ // READ and UPDATE POSITIONS // ============================================================================ startMillis = currentMillis; //IMPORTANT to save the start time. } }
It would probably be better to use a button to signify that you're finished adjusting all the pots (especially when you add more). That would allow you plenty of time to carefully adjust each pot (and therefore servo) to just the right place. When the button is pushed, those positions can be collected, converted to a string and then written out to the SD card very quickly.
Anything seems possible when you don't know what you're talking about.
@will
It would probably be better to use a button to signify that you're finished adjusting all the pots (especially when you add more).
Ways to synchronize movements (a kinetic melody) I think is a major consideration when recording them. Also the ability to edit the movements. Usually with animatronics you have synchronized speech or other lights and sounds.
I probably wouldn't record movements rather generate them from code as my interest is robotics. This could also have sensors to react to anyone that comes into the room to make those eyes and head follow people. Much spookier if the machine reacts to stimuli 🙂
Here is an example from James Button for smoother movements.
It would help to own a 3d printer and be able to design your own parts.
I understand what you're saying and if the devices were extremely fast, then I'd agree. But we're talking cheap servos which are slow.
I'm not sure I understand your point. In my opinion, them being slow REQUIRES you to have a timing value for each for when you read the analog pin connected to each potentiometer and writing it to a file and then writing to the servo to actually move it. If you use one timestamp for potentially dozens of pots/servos, by the time you write the last one it might be significant fractions of a second. When played back they will not be in sync visually.
@hinobot - I haven't looked at Bill's or your code, but I took Will's concern to actually try it out. The following two sketches do a recording / playback.
RECORDING - RalfRec.ino
- You can have as many servos/pots as you want. I only had two servos and only one potentiometer laying around to test with. I used the same pot for both servos in the code below. You'll obviously want to change the second A0 pin. I marked the code with "SERVOS" to indicate the places where you need to change the code for 4 or 50 servos.
- It's not very friendly in that the recorder starts recording as soon as it boots up.
- There is a provision for a stop button to stop the recording.
- It doesn't have any provisions for making multiple recordings.
- Its currently set up to record at about 60 Hz.
- I've never messed with an SD card. I used an ESP8266 MPU and it has a 3MB file system available. I just used that. It can store about 55 minutes of recording as it is set up now. You'll have to convert the file loading over to the SD. It shouldn't be that different as all these file systems are based on the C/C++ versions of file handling.
#include <Servo.h> #include <LittleFS.h> // To change the number of servos, change at the // places marked with "SERVOS". These must be consistent // with those written in Ralf.ino struct Position { u32 timestamp; u8 servo; u8 angle; }; // GLOBAL VARIABLES // File object to access data in recorded file. File _file; // Start Time for playing the recording. u32 _start = 0; // How fast you want to sample const u32 INTERVAL = 17; // 17 milliseconds is ~59 Hz. // Last interval occurred. u32 _last; // Stop recording pin const u8 pinStop = D2; // Your servos... doesn't matter if it's one or a hundred. Totally dependent // on what your MPU can handle hardware wise. const u8 CNT = 2; // SERVOS Servo servos[CNT]; u8 pots[CNT]; // This is number of positions that can be stored. On an ESP8266 having a // file system size of ~3MB, and using our ~59 Hz and having 2 servos, we can // store about 55 minutes of recording. u32 maxPos; u32 wrotePos = 0; void setup() { // So we can debug stuff. Serial.begin(115200); Serial.flush(); Serial.printf("\nRalph Recorder is recording!\n"); // SERVOS // Attach as many pins to your servos as you have. // Make sure the number you attach is consistent with CNT. servos[0].attach(D3); servos[1].attach(D4); // SERVOS // Set the corresponding analog pins for each servo. // Make sure the number you configure is consistent with CNT. // ESP8266 only has one analog pin, have to use the same one. pots[0] = A0; pots[1] = A0; // Open the file system. if (!LittleFS.begin()) { Serial.printf("Unable to begin(), aborting\n"); return; } // Determine how many samples we can store in our file. FSInfo info; LittleFS.info(info); maxPos = info.totalBytes / sizeof(Position); Serial.printf("Can store %u samples\n", maxPos); // Open up file to recorded. Overwrites old file. _file = LittleFS.open("/EyeMovement.rec", "w"); if (!_file) { Serial.printf("Unable to open file for writing, aborting\n"); return; } // Setup switch to stop the recording. pinMode(pinStop, INPUT_PULLUP); // We're just going to start recording on boot-up. You'll want to trigger // the start variable with some button press??? _start = millis(); // The last variable is the time that we took a snapshot of all the servos. _last = _start; } void loop() { u32 now = millis(); if ((wrotePos < maxPos) && (now - _last > INTERVAL)) { // We only get here on the period we've set. // First check to see if the stop button is pressed. if (!digitalRead(pinStop)) { // Stop the recording. Serial.println("Recording stopped."); Serial.printf("Stored %u data points\nApproximately %.1f seconds\n", wrotePos, (float)wrotePos / CNT * INTERVAL / 1000); maxPos = 0; // This keeps it from recording any more. _file.close(); // Close the file... ready for Playing. return; } // Read every pot, write the postion and move the servo. for(u8 i=0; i<CNT; i++) { // Read each pot. int analog = analogRead(pots[i]); // ESP8266 analog pin A0, ranges 0 to 1024. // We want to convert this to 0 to 180 degrees. analog = map(analog, 0, 1024, 0, 180); // Create our position object and write it to the file Position pos = { millis(), i, analog }; _file.write((u8*) &pos, sizeof(pos)); // Decrement how many we can store. wrotePos++; // Write to the servo servos[i].write(analog); } // update our last interval variable. _last = now; } }
PLAYBACK - Ralf.ino
- Again... You can have as many servos/pots as you want. I marked the code with "SERVOS" to indicate the places where you need to change the code for 4 or 50 servos. Obviously, it has to be set up with the same number as the RalfRec version.
- It's not very friendly in that the playback starts as soon as it boots up.
- There is a provision for it to loop the playback. I know it works as it has been driving me crazy replaying the same routine over and over as I've written this.
#include <Servo.h> #include <LittleFS.h> // To change the number of servos, change at the // places marked with "SERVOS". These must be consistent // with those written in RalfRec.ino struct Position { u32 timestamp; u8 servo; u8 angle; }; // GLOBAL VARIABLES // Do we want to repeat the file. bool _repeat = true; // File object to access data in recorded file. File _file; // Current position waiting for time to move. Position _current; // Start Time for playing the recording. u32 _start = 0; // Your servos... doesn't matter if it's one or a hundred. Totally dependent // on what your MPU can handle hardware wise. const u8 CNT = 2; // SERVOS Servo servos[CNT]; void setup() { // So we can debug stuff. Serial.begin(115200); Serial.flush(); Serial.printf("\nRalph is here!\n"); // SERVOS // Attach as many pins to your servos as you have. // Make sure the number you attach is consistent with CNT. servos[0].attach(D3); servos[1].attach(D4); // Open the file system. if (!LittleFS.begin()) { Serial.printf("Unable to begin(), aborting\n"); return; } // Open up pre-recorded _file for the positions. _file = LittleFS.open("/EyeMovement.rec", "r"); if (!_file) { Serial.printf("Unable to open file for reading, aborting\n"); return; } u32 pos = _file.size() / sizeof(Position); Serial.printf("File has %u data points\nApproximately %.1f seconds\n", pos, (float)pos / CNT * 17 / 1000); // Read the first position in the file. readNext(); // We're just going to start playing on boot-up. You'll want to trigger // the start variable with some button press??? _start = millis(); } void loop() { if (!_file) return; // We have no data to process. u32 timeSinceStart = millis() - _start; if (timeSinceStart >= _current.timestamp) { servos[_current.servo].write(_current.angle); readNext(); } } void readNext() { // Read next position data into the "_current" position. // Check to see if the returned amount of data agrees with // the required amount. If it does not agree, its because // the end of the _file has been reached. if (_file.read((u8*)&_current, sizeof(_current)) != sizeof(_current)) { // We've finished reading the file. if (_repeat) { // Rewind the file. _file.seek(0, SeekSet); // Use recursion to read the first entry. readNext(); // Set the start time to the current time. _start = millis(); } else // Close the file. _file.close(); } }
As you'll see, I've commented them pretty well. But... IF you want to try them and IF you have any troubles, let me know.
I would include a video, but I don't have enough bandwidth here, but I think you can see the blurred motion of the servos. 😆
3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, WiFi Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide
@frogandtoad Not likely you will ever see anything written by me, I am barely able to read the code. Maybe modify something very similar but that's about it.
First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, 360, fairly knowledge in PC plus numerous MPU's & MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.
I understand what you're saying and if the devices were extremely fast, then I'd agree. But we're talking cheap servos which are slow.
I'm not sure I understand your point. In my opinion, them being slow REQUIRES you to have a timing value for each for when you read the analog pin connected to each potentiometer and writing it to a file and then writing to the servo to actually move it. If you use one timestamp for potentially dozens of pots/servos, by the time you write the last one it might be significant fractions of a second. When played back they will not be in sync visually.
That was my point. Somebody mentioned Claymation earlier in the thread. There, the creator adjusts all of the parts of the animation that need to move and then takes a snapshot and then moves on to the next frame.
Here, you can set all the servos to their next position and then take a snapshot of them. But during playback you have to wait for all of them to be moved. So the timing has to be based on the playback, and the servo settings have to be set according to the distance they'll have to travel during that time.
Anything seems possible when you don't know what you're talking about.
That was my point. Somebody mentioned Claymation earlier in the thread. There, the creator adjusts all of the parts of the animation that need to move and then takes a snapshot and then moves on to the next frame.
Here, you can set all the servos to their next position and then take a snapshot of them. But during playback you have to wait for all of them to be moved. So the timing has to be based on the playback, and the servo settings have to be set according to the distance they'll have to travel during that time.
Now, I see our differences... I completely agree the sketches I provided will serve poorly in that case. I was the one that made that allusion to Claymation type choreography, but @hinobot came back with this:
We are building a pair of eyes that go in a painting for our haunted house. The eyes remain closed for some time, open, move around for a bit, stare off for a moment, and then close. The idea is that the pattern repeat over and over again. Think of a tape recorder that captures information in real time and then plays it back again.
... that he wants to record in real-time (not step by step like I had guessed). I'm now guessing he and his daughters work together to drive the eyes to their desired choreography. His scenario actually makes the coding problem quite simple. Both of the sketches (Recorder/Playback Sketches). Keeping them in two Sketches also make it easy to study and digest what is going on in the coding. A wise choice on his part. The programs are less than 70 lines if you take out all of the comments and they should be pretty easy to digest if he studies the comments and my logic summary - Logic Summary
Although I only tested with two servos, four should not be a problem and they could make a whole animatronic body with dozens of servos with the same code. With some good practice with coding, they could come up with ways of combining files... say one controlling eyes blinking occasionally and another controlling head movements into one script.
I thought it a fun project in my insomnia last night. I'll probably use it in something someday. Maybe they or someone else will improve on it. 😎
VBR,
Inq
3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, WiFi Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide
but @hinobot came back with this:
We are building a pair of eyes that go in a painting for our haunted house. The eyes remain closed for some time, open, move around for a bit, stare off for a moment, and then close. The idea is that the pattern repeat over and over again. Think of a tape recorder that captures information in real time and then plays it back again.
In that specific case, I think @zander had the best idea. They should adjust the pots to get the desired servo readings with a desired elapsed time, make a note of the time and the ones that changed and so on. However, instead of recording the movements on an SD card, they should just write a sketch that exhibits pauses and executes servo.write commands according to the notes made. That would be totally editable to adjust timing and movement, but it would take up too much space for very long sequences.
Personally, I'm skeptical that three people can cope with 4 pots being manipulated "in real time", I feel certain that somebody will over- or under- adjust their pot and mess up that "take". But then, I'm a life-long cynic so take this with a handful of salt 🙂
Anything seems possible when you don't know what you're talking about.
@inq Thank you so much for the code and layout, we just purchased the ACEIRMC ESP8266 module from Amazon, we will upload your code and see if it works for our needs... thanks!
@inq Thank you so much for the code and layout, we just purchased the ACEIRMC ESP8266 module from Amazon, we will upload your code and see if it works for our needs... thanks!
I sure hope it works out for you. If it doesn't, you know where tech support is. 😉
Years ago I bought a bunch of the shields for the ESP8266 WeMos that I like to use. I put them in a drawer and forgot about them. I just sorted through them and one of them is a SD card shield. I'm using your code sample from your OP as a guide. I'll revise the code and re-post it.
VBR,
Inq
3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, WiFi Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide