Open reyna-ayala opened 4 years ago
Hey Reyna. I am not 100% certain what they are doing in the above example without more context (i.e., how is gyro.getSensor() defined?), but I can answer your question about the usage of void. In C you use the keyword "void" when defining functions to indicate that the function has no inputs and/or outputs depending on the usage.
void myFunc(x, y) {}
specifies a function myFunc with input arguments x & y but no output arguments. On the other hand,
z = myFunc(void) {}
specifies a function myFunc with z as an output argument but no input arguments. There are various reasons why you might want to create a function without input / output arguments. Often times, a function that you are using for plotting stuff (not in the context of arduino obviously) will have no output arguments, because there is nothing to return. The function simply makes a plot. Sometimes (less commonly) a function will have no input arguments, such as if you were to make a function that returned the current time / date. The function doesn't need any input, it is just going to reference the system clock and return the associated value.
The point is, void is used in place of function input / output arguments when such arguments are not necessary for the specific application. In this case, they have no output arguments, because their function simply prints stuff out. Why they have no input arguments is less clear, but it appears as though they are creating the sensor object inside the function, rather then getting it from an external source. So, they don't need to get the sensor as an input argument.
Hope that helps explain the void issue. Again, it is a bit unclear what they are doing without taking a look at the sensor & gyro classes that they have evidently defined somewhere else. Looks like gyro might be an imported module that likely includes the class sensor. But, hard to say.
How have the valve manifolds been controlled in the past using Arduino? @Scharzenberger
Hey Reyna,
Yes, they have. I have a C example that is not specifically for Arduino (it is for my Atmegga88 microcontrollers):
void bang_bang_pressure_control( float p_desired, float p_actual )
{
// Define local variables.
float p_lower;
float p_upper;
// Compute the lower and upper pressure bounds.
p_lower = p_desired - p_threshold;
p_upper = p_desired + p_threshold;
// Determine whether to open or close the valve.
if (p_actual > p_upper) // If the current pressure is above the upper pressure limit...
{
// Close the valve to exhaust air.
PORTB &= ~(1 << 1);
}
else if (p_actual < p_lower) // If the current pressure is below the lower pressure limit...
{
// Open the valve to add air.
PORTB |= (1 << 1);
}
}```
This function takes in a desired pressure float and the actual pressure float (converted to a float after reading the pressure sensor) and then determines how to switch the valve manifold. It has been a while since I reviewed my own code on this. Looks like p_threshold is a global constant.
The main difference between this code excerpt and what you will actually want to do is how you open and close the valves. You can use the PORT setting technique above if you figure out which ports the pins you have connected to the transistor array belong to. This would require looking up the microcontroller on the Arduino and seeing how the pins are labled and then checking how the microcontroller pins are connected to the headers of the Arduino. So... Some effort.
Alternatively, you can just use digitalWrite() with the associated pin. When you want it to open, write it high. When you want it to close, write it low. That would be a simple modification.
It took me a second to find it, but here is an example of some code that Josh (don't think you've met him) was using to collect some data from the BPAs using an arduino. Note that he is also reading a force sensor and writing data to the serial monitor, which is unnecessary for you (although writing to the serial monitor will be helpful for debugging). Here it is
* Use for testing and characterizing BPA's
*
* This program starts at a pre-defined frequency and tests the BPA Muscles using a pressure sensor and a Load cell connected to
* HX711 microcontroller. When the first set-frequency is done being tested, the program moves on
* to test other frequencies up until the desired frequency is tested.
*
* The Serial monitor outputs the data- raw pressure, raw force
* the calibration of the sensor and meter should be done in excel after the raw data has been transfered there because including the calibration in the code slows it down to much
*
*/
//FORCE METER
#include "HX711.h"
// HX711 circuit wiring
const int LOADCELL_DOUT_PIN = 2;
const int LOADCELL_SCK_PIN = 3;
HX711 scale;
//Timing
unsigned long spikeMillis = 0;
unsigned long ResetTime = 0; //Timer for Resetting the Pulsing period
unsigned long DataTime = 0; //Timer for data collection
//Frequency and Pulse Width
// Period = Pulse_width_on + Pulse_width_off
float frequency = 30; //CHANGE: What Frequency to start at? Spike X times per second. This is in Hz (1/sec). ***********************************************************************
float pulse_width = 25; //CHANGE: What is your desired pulse width (in milliseconds) make sure to input in milliseconds. Pulse with will be 10 ms, 15 ms, 20 ms *******************************
float period = (1/frequency)*1000; // This will be the period in milliseconds
//float spiketime = (1/(2*frequency))*1000; //frequency turned to milliseconds. The frequency is half the period.
float data_collection_frequency = 80; //frequency at which to collect data, needs to be at least 2x the spiketime and can't be greater than 80 because of the HX711 microconroller
float data_collection_period = 1/data_collection_frequency * 1000; //spike frequency turned into milliseconds
const int spikePin = 13; //what pin the festo is connected to
int spikeState = LOW;
//int n = 0; //counters used to see how many times the BPA was spiked and compared to what the program was set to
int k = 0; //counters used to see how many times data was collected to ensure at least 2 data collections per spike
unsigned long previousMillis = 0;
//________________________________________________________________________________________________
void setup() {
pinMode(spikePin, OUTPUT);
pinMode(A0,INPUT);
digitalWrite(spikePin, spikeState); //make sure the BPA is not on in the beginning
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); //initialize the force meter
Serial.begin(9600);
Serial.print("Frequency = "); Serial.println(frequency);
Serial.print("Pulse Width = "); Serial.println(pulse_width);
// Serial.print("Test time for each Frequency: "); Serial.println(testTime); Serial.println(" ");
//Serial.print("Start Frequency: "); Serial.println(frequency);
}
//________________________________________________________________________________________________
void loop() {
//Pulsing and timing
unsigned long currentMillis = millis();
//______________________________________________________________________________________________
// if (currentMillis - spikeMillis >= spiketime) { //Spike the bpa at the frequency set
// spikeMillis = currentMillis;
// if (spikeState == HIGH) {
// spikeState = LOW;
// }
// else {
// spikeState = HIGH;
// }
// digitalWrite(spikePin, spikeState);
// n = n + 1;
// }
//______________________________________________________________________________________________
unsigned long dt = currentMillis - previousMillis;
if ((dt > pulse_width) && (spikeState == HIGH))
{
digitalWrite(spikePin, LOW);
spikeState = LOW;
previousMillis = currentMillis;
}
else if ((dt > (period - pulse_width)) && (spikeState == LOW))
{
digitalWrite(spikePin, HIGH);
spikeState = HIGH;
previousMillis = currentMillis;
}
if (currentMillis - DataTime >= data_collection_period){ //collect data to the serial monitor at the frequency set
//***Max frequecny that the HX711 can run at is about 80 Hz, we need at lease 2 readinds per spike, so we need x to be at least 2
DataTime = currentMillis;
float arduino_pressure = analogRead(A0); // pressure reading from arduino
float psi_pressure = (0.1100533983*arduino_pressure)-1.32438491; // Conversion based on calibrating
float kilo_pascal_pressure = psi_pressure * 6894.76; // Conversion rate from google
float arduino_force = scale.read(); // force reading from arduino
float newton_force = (-0.0002215943*arduino_force) + 0.5136191769; // Conversion based on calibrating
Serial.print(kilo_pascal_pressure); // Print the pressure value in kPa
Serial.print(" , "); // space comma space
Serial.print(psi_pressure); // Print the pressure value in psi
Serial.print(" , ");// space comma space
Serial.println(newton_force); // Print the force values in Newtons
// Serial.print(analogRead(A0)); Serial.print(","); //pressure
// Serial.println(scale.read()); //ForceReading
k = k + 1;
}
//______________________________________________________________________________________________
//_______________________________________________________________________________________________
}
Ahh... He is using set pulse widths (I helped him write the code, but forgot about that...), Not exactly what you want. But, he does read using analog read and write using digital write, so it is a good example of that!
*/
//FORCE METER
#include "HX711.h"
// HX711 circuit wiring
const int LOADCELL_DOUT_PIN = 2;
const int LOADCELL_SCK_PIN = 3;
HX711 scale;
//Timing
unsigned long spikeMillis = 0;
unsigned long ResetTime = 0; //Timer for Resetting the Pulsing period
unsigned long DataTime = 0; //Timer for data collection
//Frequency and Pulse Width
// Period = Pulse_width_on + Pulse_width_off
float frequency = 30; //CHANGE: What Frequency to start at? Spike X times per second. This is in Hz (1/sec). ***********************************************************************
float pulse_width = 25; //CHANGE: What is your desired pulse width (in milliseconds) make sure to input in milliseconds. Pulse with will be 10 ms, 15 ms, 20 ms *******************************
float period = (1/frequency)*1000; // This will be the period in milliseconds
//float spiketime = (1/(2*frequency))*1000; //frequency turned to milliseconds. The frequency is half the period.
float data_collection_frequency = 80; //frequency at which to collect data, needs to be at least 2x the spiketime and can't be greater than 80 because of the HX711 microconroller
float data_collection_period = 1/data_collection_frequency * 1000; //spike frequency turned into milliseconds
const int spikePin = 13; //what pin the festo is connected to
int spikeState = LOW;
//int n = 0; //counters used to see how many times the BPA was spiked and compared to what the program was set to
int k = 0; //counters used to see how many times data was collected to ensure at least 2 data collections per spike
unsigned long previousMillis = 0;
//________________________________________________________________________________________________
void setup() {
pinMode(spikePin, OUTPUT);
pinMode(A0,INPUT);
digitalWrite(spikePin, spikeState); //make sure the BPA is not on in the beginning
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); //initialize the force meter
Serial.begin(9600);
Serial.print("Frequency = "); Serial.println(frequency);
Serial.print("Pulse Width = "); Serial.println(pulse_width);
// Serial.print("Test time for each Frequency: "); Serial.println(testTime); Serial.println(" ");
//Serial.print("Start Frequency: "); Serial.println(frequency);
}
//________________________________________________________________________________________________
void loop() {
//Pulsing and timing
unsigned long currentMillis = millis();
//______________________________________________________________________________________________
// if (currentMillis - spikeMillis >= spiketime) { //Spike the bpa at the frequency set
// spikeMillis = currentMillis;
// if (spikeState == HIGH) {
// spikeState = LOW;
// }
// else {
// spikeState = HIGH;
// }
// digitalWrite(spikePin, spikeState);
// n = n + 1;
// }
//______________________________________________________________________________________________
unsigned long dt = currentMillis - previousMillis;
if ((dt > pulse_width) && (spikeState == HIGH))
{
digitalWrite(spikePin, LOW);
spikeState = LOW;
previousMillis = currentMillis;
}
else if ((dt > (period - pulse_width)) && (spikeState == LOW))
{
digitalWrite(spikePin, HIGH);
spikeState = HIGH;
previousMillis = currentMillis;
}
if (currentMillis - DataTime >= data_collection_period){ //collect data to the serial monitor at the frequency set
//*Max frequecny that the HX711 can run at is about 80 Hz, we need at lease 2 readinds per spike, so we need x to be at least 2
DataTime = currentMillis;
float arduino_pressure = analogRead(A0); // pressure reading from arduino
float psi_pressure = (0.1100533983arduino_pressure)-1.32438491; // Conversion based on calibrating
float kilo_pascal_pressure = psi_pressure * 6894.76; // Conversion rate from google
float arduino_force = scale.read(); // force reading from arduino
float newton_force = (-0.0002215943arduino_force) + 0.5136191769; // Conversion based on calibrating
Serial.print(kilo_pascal_pressure); // Print the pressure value in kPa
Serial.print(" , "); // space comma space
Serial.print(psi_pressure); // Print the pressure value in psi
Serial.print(" , ");// space comma space
Serial.println(newton_force); // Print the force values in Newtons
// Serial.print(analogRead(A0)); Serial.print(","); //pressure
// Serial.println(scale.read()); //ForceReading
k = k + 1;
}
//______________________________________________________________________________________________
//_______________________________________________________________________________________________
}
For readability, you can use Markdown syntax in GitHub comment. https://www.markdownguide.org/cheat-sheet/
(This message is in reference to the code that @Scharzenberger wrote for the ATmegga88 microcontroller.)
// Determine whether to open or close the valve. if (p_actual > p_upper) // If the current pressure is above the upper pressure limit... { // Close the valve to exhaust air. PORTB &= ~(1 << 1); }
I'm new to bitwise operators but learning... Why would you use (1<<1)
if it will always just produce 2?
Working backwards, I see that you start with: 0000 0001
which becomes 0000 0010
because of the shift. Then, ~
makes 1111 1101
, which is then compared with the current status of PORTB
for the &
bitwise operator. Why does there need to be a shift? Also, why use the &
operator?
I guess all of my questions could be summarized in one: what does this block of code accomplish mechanically and why does it do that?
(Although I could use the alternative, simpler route in programming, I think it would be nice to also understand this method.)
The bitwise operation will always produce 2, but it is a way to set the PORTB register. From reading this block of code, it looks like a conditional statement to perform some mechanical action that is connected to PORTB. To know exactly what this block of code is doing, we would need to know what is connected to PORTB (probably closing the valve from reading the comment). The symbol &= is a AND bitwise assignment. I think this video will help clear up some of the jargons and show you how registers work. https://www.youtube.com/watch?v=6q1yEb_ukw8
Hey Reyna,
What Jake said is correct. My two cents is as follows:
You are right that 1 << 1 produces 0000 0010.
You are right that ~(1 << 1) produces 1111 1101.
With the Atmega microcontrollers (and probably others with minor differences), PORTA, PORTB, PORTC, and PORTD refer to specific registers that set the value of digital pins on the microcontroller. There are all sorts of registers, all with special names (usually written in all caps). The only way to know what a specific register does is to look it up in the microcontroller's datasheet. Thankfully, there is some commonality between different microcontrollers (such as for the PORT registers), but it is not uncommon for the names to vary slightly.
Registers do all sorts of things on the microcontroller. They can be used to manipulate clock settings, timer settings, communication settings, setting pin values, etc.
The Atmega88 is an 8 bit microcontroller, so all of the registers are 8 bit. As I said before, the PORT registers are responsible for setting the digital pin states. So setting PORTA = 0000 0001 will cause digital pin 1 on port A to be high and the others to be low. Likewise, if you set PORTD = 0101 0010, you are setting the second, fifth, and seventh pins on PORTD to be high, while the others are set low.
In this case, I have pin 2 on PORTB attached to the transistor array (and then to the valve manifold). When it is set high, the valve opens. When it is set low, it closes.
PORTB &= ~(1 << 1) is the same as PORTB = PORTB & ~(1 << 1), which is the same as PORTB = PORTB & (1111 1101). Think about what this means. Since we are using AND, the 0 in position 2 will cause the new value of PORTB to have a 0 in position 2 (it doesn't matter what is already in position two of PORTB, because 1 & 0 = 0 and 0 & 0 = 0, so the result is 0 either way). Likewise, 1s in all of the other positions will cause the new value of PORTB to be the same as whatever the old value was in that same position (this is because 1 & 1 = 1 and 0 & 1 = 0, the & 1 doesn't change anything).
So, the above line of code sets the second pin in PORTB to be low, while leaving all of the other pins alone. i.e., we can switch a single pin without affecting the others. If you wanted to switch the 6th pin, you would use PORTB &= ~(1 << 5) instead.
My understanding is that this is effectively what digitalWrite does for you in the Arduino library. It makes it so that you don't have to think about the bit shifting stuff. You can probably just use digitalWrite for your purposes, but it is always good to know what is going on in the background.
Based on this description, can you see how PORTB |= (1 << 1); does the opposite (setting the 2 pin in PORTB high and leaving the others alone)?
Hope this helps.
Also, that video Jake sent looks excellent!
Had to watch it a few times but the video helped a lot! Thanks!
As far as the code, I'm most excited about creating a manual PWM signal with the valve-manifold's bang-bang control. I found this link: https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
However, I do know that while using a scheduler, delays can be problematic since they prevent the action of other devices. This code seems to be run sequentially instead of in parallel so I assume it will be okay with the delays... But wanted to double check to make sure
Hey Reyna,
I would start without using PWM just to get things working the first time. i.e., just have an if statement that checks the current pressure and decides whether to open or close the valve. That should be relatively simple and produce good enough results to start. That said, PWM (or some variation of it) may be helpful to us when we go to do other control techniques, so I am definitely not against you working on it if the basic if statement approach is already working.
Unfortunately, this application isn't super great for PWM (although we might have to go with it anyway). I say this because PWM works best when the PWM frequency is very high. Typically much faster than the physical system can respond. That way, the physical system does not react so much to the individual pulses, but rather to the average value of the PWM signal (which is set via its duty cycle). This means that PWM signals are usually in the MHz range. The valves we have use a maximum switching frequency of approximately 80-100 Hz. Much less than would be ideal for typical PWM signals. As such, our previous PWM attempts have resulted in very shaking, vibrating behavior. Unfortunately, there is not much we can do about that with bang-bang valves.
As far as your question about the code, what you are suggesting is probably fine. For this application, the arduino will not be doing too much, so we might be able to get away with a couple of short delays. However, it is generally best practice to avoid delays whenever possible, and it is definitely possible to implement PWM without delays.
Check this out:
`//Compute the number of points for the given duty cycle. num_crit = round(duty_cycle*num_total);
//Determine whether to turn the output pin on or off.
if (count >= num_total) //If the count has reached the maximum value...
{
count = 0; //Reset the counter to zero.
dac_data = dac_on_value; //Turn the dac output on.
bDacOn = 1;
}
else if ( bDacOn && (count >= num_crit) ) //If we are in the active part of the cycle...
{
dac_data = 0; //Turn the dac output off.
bDacOn = 0;
}
++count; `
num_total is defined as:
` //Compute the total number of points per cycle.
num_total = freq_dac/freq_pwm;`
where freq_dac is the frequency at which you are taking measurements / issuing commands and freq_pwm is the desired pwm frequency.
That is a code snippet from one of my programs manually creating a PWM wave without delays. The principal is as follows:
Hope this helps.
I'm looking into ways to maximize the frequency of the pwm signal and I figured out a way to receive the current prescaler value by looking at the TCCR1B register and receiving 1's for both CS10 and CS11. However, I'm not sure if the clock select bit description table given below applies...? My guess is that CSn0 simply means the rightmost bit of the TCCR1B byte, CSn1 is one to the left of that, etc. Am I understanding that correctly?
The Arduino PWM pins have already been set up to generate the PWM signal at the desired duty cycle. I would not recommend using a timer interrupt to generate the PWM signal, since interrupts can interfere with other functionality on the Arduino and if you have the interrupt to be too fast, all the other functions of the Arduino would not be able to execute. I have problems with interrupt myself for the balance platform project. The table that you are looking at is to set up the prescaler in setting up a timer interrupt. If you have not seen it, this Youtube video does a great job explaining how to set it up Link.
I am assuming that you are using the PWM signal to send to the Valve controller. You might want to check the Arduino kit that you picked up on Sunday to see if it has any guide on working with PWM signal. If not, you can try this video Link.
@Scharzenberger Can you explain what you mean in your code by the number of points per duty cycle?
Hey Reyna,
Your understanding of the table above is correct. The n in CSn0, CSn1, etc. specifies the timer interrupt whose properties you are setting. e.g., n = 1 means the first timer interrupt, n = 2 means the second, and so forth. So, CS10 is the rightmost bit of the first timer interrupt. The number of timer interrupts you can have exist at the same time depends on the specific microcontroller you are using. I don't know how many that is for the Arduino mega, but probably just a couple.
It has been a bit since I have reviewed this, so forgive me if there are any errors in what I am about to say. The prescaler doesn't (necessarily) have anything to do with PWM generation. It affects timer interrupts by scaling your microcontroller's clock speed. This is helpful if your microcontroller has a very high clock speed compared to the duration of your timer. If too many clock cycles pass during your timer, the associated cycle counter will overflow (resetting to 0) and will not function properly. So, a prescalar should be selected such that, given your current clock speed, you are able to count enough clock cycles for your full timer duration (ideally, as many cycles as possible without overflowing so that you maximize your timer resolution). I should have some code for computing the required prescalar somewhere... Anyway, you will need to set this, but you will always be limited by your microcontoller's clock speed.
With respect to the question you asked about the number of points per duty cycle, I can try to explain things more thoroughly:
A PWM signal is a square wave consisting of 1s and 0s. The duty cycle of the PWM refers to what percentage of the wave consists of 1s. So, a PWM signal with a duty cycle of 0.8 is high (e.g., 1) 80% of the time and low (e.g., 0) 20% of the time. Likewise, a PWM signal with a duty cycle of 35% is high 35% of the time and low 65% of the time.
Note that, microcontrollers (like computers) are digital devices. They run at set clock speeds. i.e., they can only make adjusts to their output values (1s or 0s, in this case) at discrete time instances. Anytime in between these clock cycles, the output is constant. i.e., if a pin is set to be high, it will remain high for the duration of the clock cycle.
Suppose that your microcontroller runs at 1000 Hz (which is slow, but that isn't the point). In my code, I am calling this freq_dac = 1000;. Also, suppose that you want to generate a PWM signal with frequency freq_pwm= 100 Hz. This means that every PWM cycle consists of num_total = freq_dac/freq_pwm = 1000/100 = 10 points. i.e., if your microcontroller is running at 1000 Hz and you want a signal of any type out at 100 Hz, you only have enough time to output 10 values per cycle. This is what I meant by the number of points per PWM / duty cycle.
Continuing this example, what should those 10 values be? Suppose you have a duty cycle of duty_cycle = 0.8. In this case, you want 80% of the points per cycle to be high. So, you want num_crit = round(duty_cycle*num_total) = round(0.8x10) = round(8) = 8 points to be high. Note that you need the round because the result must be an integer.
In my code, I achieve this by counting the number of times I go through the loop. I start by outputting 1s. When I hit the correct number of 1s (e.g., num_crit which is 8 in this example), I stop outputting 1s and switch to outputting 0s until I have outputted the total number of values that I can this cycle (2 more values to get to a total of 10, in this example). Once I have outputted num_total points, I reset the counter and switch back to outputting 1s because it is time to start the next cycle.
Sorry for the long explanation...
Does this answer your question?
One more point:
@jakechung42 I think that using a timer interrupt for this application is actually necessary (if you want to use PWMs at least). This is because the maximum switching frequency of the valve manifold is somewhere in the range 80-100Hz. The default arduino PWM frequency is (understandably) significantly higher than this. I don't remember what the default arduino PWM frequency is, but it is significantly higher because PWM works better at higher frequencies. We just can't use a high frequency signal at for this application.
@Reyna-5970 : As I was saying above, the maximum frequency of the valve manifolds is relatively limited. So the purpose of manually creating the PWM signal would be, in part, to slow the PWM down to about 80-100Hz so that the valve manifold can react to the signal.
@Scharzenberger Okay, I see. I thought that the valve manifold is analog where if you give it a certain voltage, it would close or open., similar to an H-bridge. On the other hand, to control the valve manifold, you want to create a PWM signal in the range of 80-100Hz?
Hey Jake,
Yeah, the valves are just digital on/off. At 24V they turn on, at 0V they turn off. So, one approach would be to send a PWM signal of a specific duty cycle to adjust the amount of time the valve is open on average between 0-100%. But, the valves are slow, so the signal has to be <100Hz or the valves will just get stuck on or off.
void
, however, I am a bit puzzled as to whyvoid
would also be passed as an argument.The link at this location gives a somewhat helpful answer, but I think I'm mostly confused why someone would create a function that could not pass any arguments to the function in the first place. I also have little experience working with non-Arduino C/C++ files so I could be misunderstanding this due to the presence of
main
.