top of page

Forum Comments

Broken OpenCatEsp32 Build
In Software
BiBoard Serial Port
In Hardware
BiBoard Serial Port
In Hardware
BiBoard Serial Port
In Hardware
Timothy McCarthy
Jul 02, 2024
Didn't I use printToAllPorts? I meant to. There's a few concerns about the algorithm I presented: Is the text buffer large enough? The snprintf function must not exceed the text buffer size. It returns a negative value if it would. In that case, there's no output. This is a change from the current behavior. I didn't do the calculation of the variable ranges and compare that with how snprintf formats them. I simply used 80 characters. This can be increased if necessary. Is the format correct? The ypr values are floats but is that how they're printed? I was a little "fast and loose" about this. There's a performance penalty for outputting to all the ports rather than just the serial port. I had the suspicion that limitation was intentional because I think the verbose form of the command uses the same function. (I'm skeptical about the verbose command form anyway, but that's me.) I'm also skeptical about why printToAllPorts is a template function. There are only two types used with the function: String and char. String has a constructor that takes a char so the compiler can automatically perform the conversion to String. That would lead to using a const String&. OTOH, if a different type is used the template form fails unless it can be converted to a String due to if (deviceConnected) bleWrite(String(text)); So automatic String conversion is a requirement. I'd let the compiler do the work and I think it results in code savings. I have no evidence for this, however. This begs the question of how expensive String really is.
0
Smooth servo motions
In General Discussions
Timothy McCarthy
Jun 22, 2024
It's difficult to give a meaningful answer to this without additional information from you about your understanding of the "smooth motion" problem, level of programming experience, and servo control experiments you've done. As a result, I have to make a few inferences based upon your question. I'm learning to work with servo motors. You should then be able to directly control any servo of the bot. You can send the command to the servo to tell it what position it should move to. There are a number of Serial Protocol commands that perform this operation and you should be familiar with all of them and able to use them. Since you are reading the source code I assume you have a basic understanding of the C++ language and can write a simple working sketch to control a servo. There are a number of tutorials online that cover this. Smooth Motion The inference here is that you understand that the basic servo commands produce a motion that is somewhat "spazmotic" and undesireable. What is the desired motion? To get an idea, write a test to move a servo from angle 0 to 90 degrees, 1 degree at a time. The add an arbitray delay between each step. Vary the degrees per step from 1 to 5 and see the results. This should give you a clear idea of how to smooth the servo motion. It's dependent upon the number and size of the steps between servo positions and the time between each step, i.e., the speed. Now we can read and better understand the transform template function. For the purpose of comprehension we'll ignore the template nature of transform. That's not to say it's unimportant but we want to focus on the primary behavior of the function. The basic function interface is void transform( T* target , byte angleDataRatio = 1 , float speedRatio = 1 , byte offset = 0 , int period = 0 , int runDelay = 8 ) { where T is nominally == int The target array contains the "final" position angles for the servo motors. The basic usage of the function is // reaction.h // ... else if (T_INDEXED_SEQUENTIAL_ASC == token) { transform(targetFrame, 1, 1); delay(10); } Here, targetFrame is defined as an array of all servo motors int targetFrame[DOF + 1]; and it is initialized with the current position of each servo. The command T_INDEXED_SEQUENTIAL_ASC alters targetFrame with positions for one or more servos. So the call transform(targetFrame, 1, 1); means "Move the servos from their current position to the new position using the default step increment and speed." Note that the call is slightly verbose, due to default argument values, and is equivalent to transform(targetFrame); If we outline the transform function we can eliminate inactive code: void transform( T* target , byte angleDataRatio = 1 , float speedRatio = 1 , byte offset = 0 , int period = 0 , int runDelay = 8 ) { if (0) { /*...*/ } else { /*...*/ } } Only the else clause is active. If we outline that code we see else { int* diff = new int[DOF - offset] , maxDiff = 0; for (byte i = offset; i < DOF; i++) { // calculate position differences ... } int steps = speedRatio > 0 ? int(round(maxDiff / 1.0 /*degreeStep*/ / speedRatio)) : 0; //default speed is 1 degree per step for (int s = 0; s <= steps; s++) { for (byte i = offset; i < DOF; i++) { // translate new postion to PWM signal... } delay((DOF - offset) / 2); } delete[] diff; } The algorithm calculates the angular difference between the current servo position and the target position. This angle defines a segment of a circle that can be divided into descrete steps between the two positions. This is a trigonometric exercise that uses the cosine function to calculate the incremental angle (dutyAng) of each step. The incremental angle is then translated into a PWM signal sent to the servo. Aside: Dr. Li, how would you rate this answer on a scale of 1-3 where • 1 = excellent • 2 = most excellent • 3 = better than I could do. More importantly, Jiarui, how would you rate this answer on scale of 1-3 where • 1 = incomprehensible gibberish • 2 = helpful • 3 = very helpful
1
0
One Fine Day with the calibrate command
In Software
Timothy McCarthy
Jun 10, 2024
Resolution Options I think these are the possible resolutions: 1. Ignore the issue and whistle a happy tune. Nobody is complaining. 2. Document the requirement and behavior and create a guthub issue; defer until refactoring pass. [Recommended] 3. Modify the code. I think there&#39;s an easy fix that maintains the current behavior and is inexpensive. Use a local buffer to extract the offset list before setting the calibration posture and processing the offset list. This will free the newCmd buffer for setting the calibration posture and allow filtering of the offset list. At the time the list extraction begins, we know the token is T_CALIBRATE and the length of the command in the newCmd buffer (cmdLen). The nominal form of the offset list is &lt; offset list &gt; :&#61; &lt; joint idx &gt; DELIMITER &lt; offset &gt; | &lt; joint idx &gt; DELIMITER &lt; offset &gt; &lt; offset list &gt; &lt; joint idx &gt; :&#61; &lt; number &gt; &lt; offset &gt; :&#61; &lt; number &gt; DELIMITER :&#61; SPACE | COMMA } TAB The joint index should be in the range [0, DOF), i.e., zero to one less than DOF. The offset should be in the range [-120, 120] but nominally should be (-15, 15). The offset list size is variable, i.e., the number of elements in the list is unspecified, but nominally should have no more than DOF elements. The order of the elements is unspecified as well. We need to extract only the valid offsets from the list; we can discard invalid list elements. We only need a buffer large enough to hold the offsets for valid joints. Given that, the simplest way I can think of to do this without a vector bool extract_offset_list( char* buffer // source buffer , int8_t* list // offset list , const char* delimeter // token delimeters ) { char* pch{ strtok(buffer, delimeter) }; while (nullptr !&#61; pch) { int8_t idx{ int8_t(atoi(pch)) }; pch &#61; strtok(nullptr, delimeter); int8_t offset{ (nullptr &#61;&#61; pch) ? int8_t(127) : int8_t(atoi(pch)) }; if (0 &lt;&#61; idx &amp;&amp; idx &lt; DOF) { list[idx] &#61; offset; } pch &#61; strtok(nullptr, delimeter); } return true; } usage: char newCmd[]{ &#34;0 1 8 9 10 11 12 13 14 15 15 16&#34; }; int8_t list[DOF]{}; memset(list, 127, sizeof(list)); // default value; (unmodified) const char* delimeter{ &#34; ,\t&#34; }; extract_offset_list(newCmd, list, delimeter); // ...load &#34;calib&#34; posture if necessary // ...apply modified offsets from list The behavior of strtok allows the implementation to handle malformed input, e.g., c0,,1,,8,,9,,... Whether that is desirable is an open question. Input syntax validation can be expensive and is not generally performed in the codebase. The memory needed is stack based, small, and should be available. Further testing is needed since reaction is a critical function. Edit: fix function call name Addendum: I&#39;ve submitted issue #16 on this in the OpenCatEsp32 repository
0
One Fine Day with the calibrate command
In Software
Has anyone tried to create a "rotate in place" gait for Bittle?
In Software
Back at the Vets
In Clinic
Timothy McCarthy
May 26, 2024
(A lookup table; I should have guessed it would be a lookup table.) OK, I can add the lookup table to the test and verify against it. That&#39;s workable but fragile. I take it that the clipping values don&#39;t prevent the collision but reduce the severity. Still, once the position integrity is lost, there&#39;s no safe recovery, is there? You can detect the mismatch but you can&#39;t reaquire the position with any confidence. Testing for angle clipping on every movement isn&#39;t reasonable, nor is adding a delay when you do detect it. Rats! I assume this is what the servo feedback effort tries to address?              Addendum: Delayed Response I initially thought the comment &#34;...will cause some delay...&#34; was inaccurate; a follow-up command was being ignored, not delayed. But I wanted to verify that as well as add the clipping array. I changed the test to detect if the angle is clipped and then wait for the servo to cool down. // defect : LLEG fails to move to test pose. // test : pause for servo cool down time // requires : visual check of LLEG move TEST_F(ftfBittleXProtocol, servo collision) { vector &lt; joint_t&gt; test_pose{ { LHIP, 45 } , { LLEG, 90 } }; ASSERT_TRUE(on_setjointsimul(test_pose)); ASSERT_TRUE(on_verify(test_pose)); angle_t angle{ -90 }; // body collision joint_t joint{ LLEG, angle }; ASSERT_TRUE(on_setjointsimul(joint.idx, joint.angle)); angle &#61; angle_t(angleLimit[LLEG][0]); // clipping bool clipped{ on_verify(joint, angle) }; EXPECT_TRUE(clipped); EXPECT_TRUE(on_setjointsimul(test_pose)); EXPECT_TRUE(on_verify(test_pose)); if (clipped) { cout &lt;&lt; &#34;\nWaiting for servo...\n\n&#34;; sleep_for(milliseconds(2500)); } } The delay time value of 2500 ms was trial-and-error. The results surprised me. First, the servo does preform a delayed movement. I&#39;m not sure what the implications of that are but it seems out of place when it happens. The second surprise is the underlying assumption that a clipped angle is in collision and has triggered the delayed servo response isn&#39;t accurate. Not every clipped angle or collision triggers the delayed response so there are false positives. This results in unnecessary time delays. However, it does seem to prevent the disturbing movements under collision.
0
SOLVED: In Search of Reliable Bidirectional Bluetooth Serial Port Profile (SPP) Communication
In Clinic
Timothy McCarthy
May 13, 2024
"BTconnected is only true within the BiBoard boot session that successful pairing occurs between the laptop and the BiBoard. In all other boot sessions, BTconnected is false so it cannot be used as is written in line 117 of io.h." What makes you say this? Do you have evidence that BTconnected becomes true before a connection is made? The log you give doesn't show that. I assume you've modified the code to output BTconnected at discrete points. The log doesn't show a successful pairing during the boot session; it shows a successful initialization of the Bluetooth object.,i.e., // io.h void blueSspSetup() { PTH("SSP: ", strcat(readLongByBytes(EEPROM_BLE_NAME), "_SSP")); SerialBT.enableSSP(); SerialBT.onConfirmRequest(BTConfirmRequestCallback); SerialBT.onAuthComplete(BTAuthCompleteCallback); SerialBT.begin(strcat(readLongByBytes(EEPROM_BLE_NAME), "_SSP")); //Bluetooth device name Serial.println("The SSP device is started, now you can pair it with Bluetooth!"); } That's not the same as the message that should appear when a successful pairing occurs, i.e., // io.h void BTAuthCompleteCallback(boolean success) { confirmRequestPending = false; if (success) { BTconnected = true; Serial.println("SSP Pairing success!!"); } else { BTconnected = false; Serial.println("SSP Pairing failed, rejected by user!!"); } } "In all other boot sessions, BTconnected is false so it cannot be used as is written in line 117 of io.h." I'm not sure what you mean by "other boot sessions"; there's only one, to my knowledge. The bot only boots once per session. I don't have a working Bluetooth setup, (my phone never sees the BittleX or any other device) so I can't run the test, but the test should be very easy to perform if you have a working Bluetooth setup. Open the Arduino serial monitor and then connect the USB cable to the bot. You should see the boot message appear in the Serial Monitor; specifically, you should see the "The SSP device is started, now you can pair it with Bluetooth!". For completeness, turn the bot battery on. Now, go to your mobile device and connect via Bluetooth to the bot. When you have successfully connected, in the Serial Monitor you should see the message "SSP Pairing success!!" At that point, you know the value of BTconnected is true. I don't know how you disconnect Bluetooth from the box, but if you do, I expect some message in the Serial Monitor to indicate that.
0

Timothy McCarthy

更多動作
bottom of page