I've been working on a project where I need to control servo motors based on data saved on an SD card. So far it is going well, however I'm having issues with controlling the timing and speed of the servo motors movements. I'll explain what I'm trying to accomplish and some example code.
I'm using an Arduino Mega with an SD module attached to it. On the SD card I have four different .txt files. Each file contains 30 integer values, each line contains a single integer and is terminated with a (,). This is merely test data so I can sweep through a range of angles to check I'm reading and converting the values just fine. But when I try to slow down the servos using timers, delays etc. it speeds through the code as if they were there. In my case the code looks like this:
string dataRead =""; // String object to read bytes from
unsigned long int motorTime = 250; // Refresh time of the motor (250 ms)
unsigned long int lastMotor = (long) micros();
while (entry.available()) { // While there are bytes in the file to be read
dataRead = entry.readStringUntil(',');
while((long) micros() - lastMotor <= (motorTime * 1000)); // Do nothing until refresh time has elapsed
myServo.write(dataRead.toInt());
lastMotor = (long) micros();
}
The data is read fine and the motor moves according to the data, however the timing code just appears to be negated for some reason. I suspect it is because all sorts of hardware features are being enabled and disabled underneath all the layers of abstraction in the Arduino IDE and the delay is being negated for some reason.
Has anyone had experience of this? Any tips for driving a servo at a set speed? My alternative solution is to load the data into an array; but I don't want to run the risk of burning through all of the RAM and cause other problems.
Thanks in advance!
I fixed it in the end. I disabled interrupts for when I was reading the data and that messed with the timer functions such as micros() and millis(); they rely on interrupts to keep track of the elapsed time.
It probably makes more sense to detach the interrupt service routines rather than disabling them by default.
Related
I am coding an offline, battery-powered esp32 to take periodic sensor readings and store them until a hotspot is found, in which it connects and pushes the data elsewhere. I am relatively new to esp32 and ask for suggestions on the best way to do this.
I was thinking of storing the reading and DateTime in SPIFFS memory and running a webserver that starts when a network is found, checking every minute or so. Since it is battery-powered, I would also like to deep sleep the board to save power. Does the setup() function run again when the board comes out of deep sleep or would I need to have my connectToWiFi function inside the loop?
Is this viable? And are there any better routes to take? I've seen things on asynchronous servers and using the esp32 as an access point that could maybe work. Is it best to download the file through a web server or send the file line by line through a free online database?
Deep sleep on the ESP32 is almost the equivalent of being power cycled - the CPU restarts, and any dynamic memory will have lost its contents. An Arduino program will enter setup() after deep sleep and will have to completely reinitialize everything the program needs to run.
There is a very small area (8Kbytes) of static memory associated with the real time clock (RTC) which is retained during deep sleep. You can directly reference variables stored there using a special decorator (RTC_DATA_ATTR) when you declare the variable.
For instance, you could use a variable stored in this area to count the number of times the CPU has slept and woken up.
RTC_DATA_ATTR uint64_t sleep_counter = 0;
void setup() {
sleep_counter++;
Serial.begin(115200);
Serial.print("ESP32 has woken up ");
Serial.print(sleep_counter);
Serial.println(" times");
}
Beware that it's generally not safe to store objects in this area - you don't necessarily know whether they've allocated memory that won't persist during deep sleep. So storing a String in this memory won't work. Also storing a struct with pointers generally won't work as the pointers won't point to storage in this area.
Also beware that if the ESP32 loses power, RTC_DATA_ATTR will be wiped out.
The RTC static RAM also has the advantage of not costing as much power to write to as SPIFFS.
If you need more storage than this, SPIFFS is certainly an option. Beware that ESP32's generally use cheap NAND flash memory which is rated for a maximum of maybe 100,000 writes.
SPIFFS performs wear-leveling, which will help avoid writing to the same location in flash over and over again, but eventually it will still wear out. This isn't a problem for most projects but suppose you're writing to SPIFFS once a minute for two years - that's over a million writes. So if you're looking for persistent storage that's frequently written to over a very long time you might want to use a better quality of flash storage like an external SD card.
If an SD card is not an option (beware you don’t pull out the SD while writing!) I would write to SPIFFS or a direct write with esp_partition_write().
For the latter: if you use predefined structs with you sensor data (plus time etc) and you start the partition with a small table with the startvalue which has to be updated next time until the other (a mini fat) it’s easy to retrieve data (no fuzz with reading lines). Keep in mind that every time you wipe the flash the wear counts for that whole block! So if you accept old data to be ignored but present, this could dramatically reduce wear.
For example say you write:
Struct:
Uint8_t day;
Uint8_t month;
Uint8_t year; //year minus 2000, max 256
Uint8_t hour;
Uint8_t minutes;
Uint8_t seconds;
Uint8_t sensorMSB;
Uint8_t sensorLSB;
That’s 8 bytes.
The first struct (call it the mini fat):
Uint8_t firstToProcessMSB;
Uint8_t firstToProcessLSB;
Uint8_t amountToProcessMSB;
Uint8_t amountToProcessLSB;
Uint8_t ID0;
Uint8_t ID1;
Uint8_t ID2;
Uint8_t ID3;
Also eight bytes. For the ID you can use some values to recognize a well written partition. But that’s up to you. It’s only a suggestion!
In an 65.536 byte sized partition you can add 8192 (-1) bytes before you have to erase. Max 100.000 times…
When your device makes contact you read out the first bytes. Check if it’s ok, read the start byte. With fseek you step 8 bytes every hop to the end position and read all values in one time until you reached the end. If succesfull, you change the startposition to the end + 1 and only erase when things go tricky. (Not enough space)
It’s wise to do before the longest suspected amount of time to run out. Otherwise you will also lose data. Or you just make the partition bigger.
But if in this case you could write every minute for 5 days.
I am using attiny microcontroller and atmel studio. And I am using the millis function in my project.
Code related to the millis function:
And I am using this classic code:
starttimex = millis();
endtimex = starttimex;
while ((endtimex - starttimex)<=60000)
{
endtimex = millis();
// Action
}
I don't want the millis() function to reset after 50 days.
I have to use uint64_t instead of unsigned long.
My questions:
Does this cause any trouble? Does this situation have a disadvantage? One of the disadvantages is memory size. I know this. But, I don't know other disadvantage.
I don't understand variable of timer0_overflow_count in the image. Is there a need for this for millis?
Should I do uint64_t for all of the unsigned long variables in the image?
Thanks
If your interval (60000 in your sample code) does not exceed the range of unsigned long, you're fine.
The millis() function does not reset, it simply rolls over :)
(endtimex - starttimex) in unsigned arithmetics calculates fine, even during a rollover.
I don't understand variable of "timer0_overflow_count" in image. Is there a need for this for millis?
The overflow count is used to keep track of the number of millis even when the period of the timer is not an exact multiple of milliseconds. For example, imagine if the timer went off every 1.5ms, see how that would work?
Does this cause any trouble?Does this situation have a disadvantage?
You can use any type you want for the millis counter, but keep in mind that interrupts are off whenever that variable is updated or read, so going from 32 to 64 bits means that interrupts will be off for significantly longer, which could impact other interrupts running on the system.
I have steppers stepping during an interrupt timer at 50 and have all my code working between the interrupts until I tried reading serial commands more than one character long.
I'm getting dropped bytes so my strings are missing a letter every 4-5 chars. I researched all day to try and figure out a solution but have come up with nothing. If I don't use an interrupt my stepper stops for 2 seconds reading a one char serial input as a string.
My goal is to have a remote control app sending speed commands. I need help working this problem out.
https://sourceforge.net/p/open-slider/code/ci/master/tree/OpenSliderFirmware/
String incomingString = "";
if (Serial.available() > 0) {
incomingString = Serial.readString();
Serial.println(incomingString);
}
Using Accelstepper library
interrupt:
//Interrupt Timer1
void ISR_stepperManager() {
Slide.runSpeed();
Xaxis.runSpeed();
Yaxis.runSpeed();
}
Quick answer: you don't if the interrupt timer is cutting in too often.
I resolved the problem by using a variable interrupt timer and a step multiplier. Basically the steps are called every time the timer interrupts instead of checking millis inside the interrupt function. This solved many issues. The speed of the stepper is now controlled by the interrupt timer. This gave me more free cycles to fully read the incoming serial without corruption and improved efficiency. Calling more steps per cycle when doing over 4k steps/s also improved efficiency requiring less cycles for a high rate of steps.
The serial is processed one char per cycle to prevent blocking.
Overall, if you are using serial and an interrupt timer, any interrupt happening < 100us you should be cautious how much code you are running during the interrupt. It will cause issues with incoming serial and user inputs. A few lines of code in a 25us timer interrupt, incoming serial will not function.
i'm not sure if it will help to your problem, but i saw along the time that the String type is not safe to use when other things need to happened.
i prefer to use char array and read one char at a time.
while(Serial.available())
{
data[x] = Serial.read();
x++;
}
i'm finding it much more reliable.
hope it's help!
The Arduino A/D converter takes about 0.1ms according to the manual. Actually, my tests show that on an Uno I can execute about 7700 per second in a loop.
Unfortunately analogRead waits while the reading is being performed, making it difficult to get anything done.
I wish to interleave computation with a series of A/D conversions. Is there any way of initiating the analogRead, then checking the timing and getting the completed value later? If this needs to be low-level and non-portable to other versions, I can deal with that.
Looking for a solution that would allow sampling all the channels on an Arduino on a regular basis, then sending data via SPI or I2C. I am willing to consider interrupts, but the sampling must remain extremely periodic.
Yes, you can start an ADC conversion without waiting for it to complete. Instead of using analogRead, check out Nick Gammon's example here, in the "Read Without Blocking" section.
To achieve a regular sample rate, you can either:
1) Let it operate in free-running mode, where it takes samples as fast as it can, or
2) Use a timer ISR to start the ADC, or
3) Use millis() to start a conversion periodically (a common "polling" solution). Be sure to step to the next conversion time by adding to the previously calculated conversion time, not by adding to the current time:
uint32_t last_conversion_time;
void setup()
{
...
last_conversion_time = millis();
}
void loop()
{
if (millis() - last_conversion_time >= ADC_INTERVAL) {
<start a new conversion here>
// Assume we got here as calculated, even if there
// were small delays
last_conversion_time += ADC_INTERVAL; // not millis()+ADC_INTERVAL!
// If there are other delays in your program > ADC_INTERVAL,
// you won't get back in time, and your samples will not
// be regularly-spaced.
Regardless of how you start the conversion periodically, you can either poll for completion or attach an ISR to be called when it is complete.
Be sure to use the volatile keyword for variables which are shared between the ISR and loop.
The function of the program I am writing is to stream incoming analog data from a sensor to a program on my computer across the USB port. For a little fun, I've decided to add a button to the program that will turn on/off a lamp. The lamp will be connected to a relay, which is connected to the arduino. I know how to go about programming it, but I want to know if this will interrupt the sensor data transfer?
I will get the current state of the light (HIGH(1) or LOW(0)) from the arduino when the button is pressed, then write to the arduino (HIGH(1) or LOW(0)) depending on the current state. I have a 5 second delay between each loop of the arduino program, for reasons related to the sensor output; however, I think I'm going to have to change this so that when the button is pressed, it is not missed by the arduino loop, or is that not possible?
I thought I read somewhere that you can't transmit/receive streaming data on the same serial line...in which case, I will need the Mega.
You have to remember and think of the Arduino as a single threaded device. While it is doing anything it is not able to do anything else. Period! In regard to the serial port however, the buffer will still accept incoming data on RX, however if an overflow situation occurs whilst blocked, management is impossible.
See the following taken directly from the Arduino reference
Certain things do go on while the delay() function is controlling the Atmega chip however, because the delay function does not disable interrupts. Serial communication that appears at the RX pin is recorded, PWM (analogWrite) values and pin states are maintained, and interrupts will work as they should. Reference
Now in saying that when you are setting the delay to 5 seconds between loops ( delay(5000) ) you are essentially blocking it from doing anything else almost full stop.
The Arduino framework exposes a a counter ( millis() ) that basically runs from the moment of boot for roughly 50 days in increments of one (1) millisecond. See Arduino - millis()
In your application you would define (remember) what loop you were due to run & when the said loop had finished so to not allow the other loop to run until the millis() counter was a defined amount more than your count. (Remember to define the count as a long)
Then what you do is move your loops out into separate functions that will only execute if the if statement return true...
for example...
long interval = 5000; // Define interval outside of the main loop
long previousCount = 0; // Used to store milli count when finished
int loopPosition = 1;
void loop()
{
if ((long)millis() - previousCount >= 5000 )
// This if statement will only return true every 5 seconds (5000ms)
{
if (loopPosition == 1)
{
function_One();
previousCount = millis(); // Redefine previousCount to now
loopPosition++; // Increment loop position
}
else if (loopPosition == 2)
{
function_Two();
previousCount = millis();
loopPosition--; // Decrement loop position
}
}
// Do Anything Here You Want
// - While ever the if statement above returns false
// the loop will move to this without hesitation so
// you can do things like monitor a pin low / high scenario.
}
void function_One()
{
// Do Your First Loop
}
void function_Two()
{
// Do Your Second Loop
}
The above will stop any delay you are using from blocking awesomeness, and to more of a point, almost make delay obsolete if implemented correctly under the right scenarios.
In regard to your serial data comment, like i said at the top of this article, the Arduino can only do one thing at a time. Transmitting and receiving at exactly the same time is impossible even with the Mega. In saying that a board like the 'Uno' for example is only capable of one serial interface, where as the 'Mega' is capable of four.
Best of luck....
NB- For a beginner reading this, the following tutorial / example covers what i have above in fairly simple terms and is a great building block for further awesomeness! Arduino - Blink Without Delay