Multiple cog access to a ADC routine issue
DiverBob
Posts: 1,110
I discussed an issue I'm having in the robotics forum but I think I have a better chance for success here.
I am running 3 DC motors with feedback via potentiometers for each motor via an MCP3208, 8 channel ADC chip. Additionally, each motor control routine is in a seperate cog so each motor can run independently from the others. There is an ADC routine in the main cog that takes an average of ten readings and returns the ADC value back to the calling routine. The problem is that each motor cog is calling the ADC routine at the same time and this results in bad data being returned
I got a suggestion to use Spin locks but after reading the manual I don't see how they could help in this instance. Does anyone have some suggestions on how to resolve this problem or is there a way to use locks to provide a more orderly access to the ADC routine?
Thanks for the help!
I am running 3 DC motors with feedback via potentiometers for each motor via an MCP3208, 8 channel ADC chip. Additionally, each motor control routine is in a seperate cog so each motor can run independently from the others. There is an ADC routine in the main cog that takes an average of ten readings and returns the ADC value back to the calling routine. The problem is that each motor cog is calling the ADC routine at the same time and this results in bad data being returned
I got a suggestion to use Spin locks but after reading the manual I don't see how they could help in this instance. Does anyone have some suggestions on how to resolve this problem or is there a way to use locks to provide a more orderly access to the ADC routine?
Thanks for the help!
Comments
I would think a similar approach could work in this case. Jhonny+Duane's idea is valid too. One cog is reading the beJesus out of the ADC and storing the results where any other cog can come and get it. You can then use filtering on the results too. This works fine as long as that SPI bus is dedicated to the ADC.
That was the idea one of the guys in the office had. I could run the ADC routine in a seperate cog, read all 8 inputs and store them in global variables. I already have the motor cogs monitoring variables for determining when to take action. The only disadvantage of this is that I'll have 4 cogs dedicated to the motors and reading ADC values, a cog for floating Point calcs, a cog for serial comms, and one cog for the main loop. This only leaves 1 cog for any other functionality. First time I've come this close to running out of processors!
I'll give this a shot tonight and see how well it works. Should I do anything to 'lock' a global variable just prior to reading it in case the ADC routine is trying to update it while the motor routine is reading the value?
Bob
So long as the whole value you need is stored in a single LONG there is no need. Hub round robin means that only one cog can be reading or writing that LONG at each access window.
I output a PWM to a HB25 to control speed and direction of the motor. A potentiometer provides the feedback mechanism. The code for this receives an angle input. The software then translates the angle to a value. The actual ADC value is compared to the desired position to determine direction. Next it starts the motor at full speed in that direction while watching the changing ADC values. Once it gets close to the desired value, the speed is gradually reduced and the stopped at the desired ADC value. That's the basic operation, a series of repeats while testing for specific conditions.
I'll post my current motor control code when I get back to my other computer so you can see how it works. I didn't get an opportunity to work on the code last night but we are in a winter storm warning for the next two days so I should have lots of time to experiment!
Bob
As I read your post, though, I'm thinking "HB25s are set-and-forget" -- why use a cog at all? I coded a pan/zoom controller for a friend that uses an MCP3204 and two servos (to drive the lens rings). The code is setup to run in a 20ms loop. In the top of the loop I read the ADC four times and do an average, then I drop through and create pulses for the servos using one of the counters. Easy-peasy.
The real question is: Are you connecting a pot to a motor that could be programmed to run past its limit? This could result in damage. What I would suggest is attaching an encoder to the motor shaft. On another project for the same friend, we control three DC motors on a pan/tilt/trolley camera rig. We need two cogs for motors (because we're providing signals to the H-Bridge) and one cog for the encoders. There is another cog that is used for speed and location management. We know from empirical testing the how to convert encoder counts in a given period to speed. We can also use the encoder value for location. In the end that lets the user create a camera movement that we can record and playback.
This is the product.
-- http://cameraturret.com/genesis.htm
This company started doing camera controls with the BASIC Stamp, migrated to the SX (with my help), and is now using the Propeller (also with my help).
I would be interested in seeing the code loop you used for controlling multiple DC motors if possible. Thanks for the good ideas. I've already had some success with putting the ADC routine in its own cog and having it continuously updating the position variables.
It would have been useful to see your code. As that's not available yet, I knocked up this little demo to give you some ideas. This program uses just the main cog. The ADC is read and averaged at the top of the loop, those values are converted to speed/direction values (in microseconds), then the motors are updated. The loop runs every 50ms (20x/second).
Tip: For averaging it's useful to select a loop count that's a power of 2 (I used 4); this lets you divide by using a right shift which is much faster than division.
[Edit] You posted your code while I was posting -- I'll have a look and update my suggestion if warranted.
Question: With the pots tied to the motors you know what the range limits are, correct?
While working on the pan/tilt/trolley project I called on one of my friends here in Hollywood as he had built a lot of motorized camera rigs for Disney. He explained an easy way to accelerate from a starting point and then decelerate into a target point. This works when you have absolute position values (as we did using the encoders, and you should with the pots).
In simple terms the move is divided into four phases:
-- A: Accelerate up to desired max speed, then calculate delta to mid-point
-- B: Run to mid-point of move
-- C: Run to deceleration point (value calculated from end of phase A)
-- Decelerate into target
In phase A of the move the speed is ramped up to the desired maximum. When this is reached, the distance between the current position and the mid-point of the move is calculated. The motor runs to the mid-point (phase . In phase C the motor continues at max speed the same distance as phase B. In phase D the speed is ramped down until the target is reached.
In practice I divided phase D into 3 sections: 80% of the phase D distance ran at the normal deceleration rate; then I slowed the rate for another 15%; the final 5% of the move ran very slowly. By doing this we never had overshoot and we were able to get the pan/tilt/trolley rig to hit marks dead on in multiple passes. The code was fairly simple using a state-machine for each motor, with phase D being divided into three sub-states.
I've attached my servo object for your consideration: it has built in ramping (microseconds change per second of real time) and a method that will return true when a ramping channel is at the target position; this feature would be useful in implementing the trapezoidal move described above.
Forgive me if this is overly simplistic for what you're doing; I'm only offering this up in the hopes of being helpful.
I want to give a go at coding using a single loop like your previous post example and see how well that responds. The acceleration/ deceleration process you described sounds good also, I'm not happy with what I have right now. I even removed the acceleration code to cut down on the complexity.
Thank you, these are all good ideas that allow me to explore some different ways to implement the code.
The CoxaMotorRun cog monitors the global value CoxaAngle. It determines the target ADC value and moves the motor until it gets within a deadzone around the value. The calculation for the targetADC is based on the assumption that the slope of the potentiometer output is linear. The routine GetTarget had some issues and wouldn't work right until I broke out the Y1, Y2, X1 and X2 values and converted them to float outside of the equation. Once I did that everything started working right. Haven't gone back to figure out what was happening there.
Where the speed is manually set to 1300 and 1700 is where I need to add the ramping functions. I spent a few hours searching the forum for examples. I liked the sine wave and S curve approaches to ramping, just haven't figured out how to implement yet. I'll work on that later, I want to use this same layout for the Tibia motor and get both motors running together when sent angle commands. Its getting closer to what I am looking for.