Closed oaklandGman closed 3 years ago
Thanks for the detailed and informative report. Till now, I haven't used FastAccelStepper on esp32 with webserver/websocket and too haven't run into this problem.
Two things:
pcnt_counter_clear()
has been replaced by a preprocessor directive. I assume, that this library call is not with IRAM_ATTR
equipped and due to this can cause the cache issue popping up. Could you please give it a try ?Here is my code ... you should be able to see the motor getting movement commands, then make a request to /hello or /head and esp will panic. The code block to move the stepper can be in a task, or in loop(), the crash remains the same.
I'll give the latest commit a try and report back! Thanks for the response.
#include <Arduino.h>
#include <ArduinoOTA.h>
#include <SPIFFS.h>
#include <FastAccelStepper.h>
#include <ESPAsyncWebServer.h>
#include <ESPmDNS.h>
#include <SPIFFSEditor.h>
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
AsyncEventSource events("/events");
const char* ssid = "xxx";
const char* password = "xxx";
const char* PARAM_MESSAGE = "Machine Control";
const char * hostName = "machine";
const char* http_username = "xxx";
const char* http_password = "xxx";
// IO pin assignments
const int BIG_MOTOR_STEP = 19;
const int BIG_MOTOR_DIR = 21;
const int BIG_MOTOR_SLEEP = 22;
const int SMALL_MOTOR_STEP = 25;
const int SMALL_MOTOR_DIR = 23;
const int SMALL_MOTOR_SLEEP = 26;
const int EMERGENCY_STOP_PIN = 35; //define the IO pin the emergency stop switch is connected to
const int LIMIT_SW1 = 33;
const int LIMIT_SW2 = 27;
// Speed settings
const unsigned int BIG_MOTOR_HZ = 9000;
const unsigned int BIG_MOTOR_ACCEL = 40000;
const unsigned int SMALL_MOTOR_HZ = 200;
const unsigned int SMALL_MOTOR_ACCEL = 4294967295;
unsigned int bigmotorSpeed = 100;
unsigned int smallmotorSpeed = SMALL_MOTOR_HZ;
unsigned int bigmotorMove = 50;
bool bigmotorDir = true;
TaskHandle_t taskStepper;
TaskHandle_t taskOTA;
FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *big_motor = NULL;
FastAccelStepper *small_motor = NULL;
void runStepper(void * parameter) {
unsigned long previousMillis = 0;
unsigned long currentMillis = millis();
uint localMove = 0;
for (;;) { // loop forever
currentMillis = millis();
if (currentMillis - previousMillis >= 500) {
previousMillis = millis();
if (!big_motor->isMotorRunning()) { // test to see if motor is done moving
Serial.println("Big motor done!");
big_motor->setSpeedInHz(bigmotorSpeed);
bigmotorDir = bigmotorDir ^ 1; // flip direction
if (bigmotorDir) {
localMove = bigmotorMove;
} else {
bigmotorSpeed = bigmotorSpeed + 100; // increase speed
if (bigmotorSpeed > BIG_MOTOR_HZ) { // reset speed
bigmotorSpeed = 100;
}
localMove = bigmotorMove * (-1);
bigmotorMove = bigmotorMove + 50; // increase travel
if (bigmotorMove > 1000) { // reset travel
bigmotorMove = 50;
}
}
big_motor->move(localMove);
Serial.printf("Big motor speed %u, moving %i steps\n", bigmotorSpeed, localMove);
} else {
Serial.println("M");
}
// if (!small_motor->isMotorRunning()) {
// // Serial.println("Small motor done!");
// Serial.println("Small motor moving 1000 steps.");
// small_motor->move(2000);
// } else {
// Serial.println("L");
// }
}
delay(5);
}
}
void runOTA(void * parameter) {
}
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
//client connected
Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
client->printf("Hello Client %u :)", client->id());
client->ping();
} else if(type == WS_EVT_DISCONNECT){
Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if(type == WS_EVT_ERROR){
//error was received from the other end
Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
}
}
void setup(){
Serial.begin(115200);
Serial.println("Booting");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Hello World");
});
ArduinoOTA
.onStart([]() {
String type;
// big_motor.stop();
// small_motor.stop();
big_motor->forceStopAndNewPosition(0);
small_motor->forceStopAndNewPosition(0);
digitalWrite(BIG_MOTOR_SLEEP, LOW); // turn off driver
digitalWrite(SMALL_MOTOR_SLEEP, LOW); // turn off driver
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.setHostname(hostName);
ArduinoOTA.begin();
MDNS.addService("http","tcp",80);
SPIFFS.begin();
ws.onEvent(onWsEvent);
server.addHandler(&ws);
events.onConnect([](AsyncEventSourceClient *client){
client->send("hello!",NULL,millis(),1000);
});
server.addHandler(&events);
server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password));
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
server.onNotFound([](AsyncWebServerRequest *request){
Serial.printf("NOT_FOUND: ");
if(request->method() == HTTP_GET)
Serial.printf("GET");
else if(request->method() == HTTP_POST)
Serial.printf("POST");
else if(request->method() == HTTP_DELETE)
Serial.printf("DELETE");
else if(request->method() == HTTP_PUT)
Serial.printf("PUT");
else if(request->method() == HTTP_PATCH)
Serial.printf("PATCH");
else if(request->method() == HTTP_HEAD)
Serial.printf("HEAD");
else if(request->method() == HTTP_OPTIONS)
Serial.printf("OPTIONS");
else
Serial.printf("UNKNOWN");
Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());
if(request->contentLength()){
Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength());
}
int headers = request->headers();
int i;
for(i=0;i<headers;i++){
AsyncWebHeader* h = request->getHeader(i);
Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
int params = request->params();
for(i=0;i<params;i++){
AsyncWebParameter* p = request->getParam(i);
if(p->isFile()){
Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
} else if(p->isPost()){
Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
} else {
Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
}
}
request->send(404);
});
server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index)
Serial.printf("UploadStart: %s\n", filename.c_str());
Serial.printf("%s", (const char*)data);
if(final)
Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len);
});
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
if(!index)
Serial.printf("BodyStart: %u\n", total);
Serial.printf("%s", (const char*)data);
if(index + len == total)
Serial.printf("BodyEnd: %u\n", total);
});
server.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
engine.init();
big_motor = engine.stepperConnectToPin(BIG_MOTOR_STEP);
small_motor = engine.stepperConnectToPin(SMALL_MOTOR_STEP);
if (big_motor) { // set up drive motor
big_motor->setDirectionPin(BIG_MOTOR_DIR);
big_motor->setEnablePin(BIG_MOTOR_SLEEP);
big_motor->setAutoEnable(false);
big_motor->setSpeedInHz(BIG_MOTOR_HZ);
big_motor->setAcceleration(BIG_MOTOR_ACCEL);
}
if (small_motor) { // set up small motor
small_motor->setDirectionPin(SMALL_MOTOR_DIR);
small_motor->setEnablePin(SMALL_MOTOR_SLEEP);
small_motor->setAutoEnable(true);
small_motor->setSpeedInHz(SMALL_MOTOR_HZ);
small_motor->setAcceleration(SMALL_MOTOR_ACCEL);
}
digitalWrite(SMALL_MOTOR_SLEEP, LOW); // turn off driver
// small_motor->move(1000); // test motor
// big_motor->move(1000); // test motor
xTaskCreatePinnedToCore(
runStepper, /* Function to implement the task */
"taskStepper", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
0, /* Priority of the task */
&taskStepper, /* Task handle. */
0); /* Core where the task should run */
// xTaskCreatePinnedToCore(
// runOTA, /* Function to implement the task */
// "taskOTA", /* Name of the task */
// 10000, /* Stack size in words */
// NULL, /* Task input parameter */
// 0, /* Priority of the task */
// &taskOTA, /* Task handle. */
// 0); /* Core where the task should run */
}
void loop(){
ArduinoOTA.handle();
ws.cleanupClients(); // clean up any disconnected ws clients
delay(10);
}
Your last commit looks like it solved the issue, I can talk to the webserver and the motor commands keep updating (via serial output), not sure if the webserver is stealing time from the ISR, I don't have the board plugged into the motors right now.
Maybe I was premature in celebration. It appears if the request to the webserver takes too long, I still get a panic. I was trying to open an app called SPIFFSEDIT and poof... I don't plan to use this feature while the motors are running, so I'll need to code up a way to shut down the motors, before the esp freaks out and has a panic. My eventual use case is just monitoring motor speed and position via websockets, and sending slight corrections from a basic user interface.
Guru Meditation Error: Core 1 panic'ed (Cache disabled but cached memory region accessed)
Core 1 register dump:
PC : 0x40080ff3 PS : 0x00060034 A0 : 0x800811ad A1 : 0x3ffbe730
A2 : 0x3ffc1788 A3 : 0x3ffc17e4 A4 : 0x3ff5e000 A5 : 0x3ffd4f20
A6 : 0x00000001 A7 : 0x3ffd5638 A8 : 0xbad00bad A9 : 0x00000001
A10 : 0x0000000a A11 : 0x3ffd5070 A12 : 0x80154dfa A13 : 0x3ffd5020
A14 : 0x3f403d30 A15 : 0x3ffd0c98 SAR : 0x00000015 EXCCAUSE: 0x00000007
EXCVADDR: 0x00000000 LBEG : 0x4000c2e0 LEND : 0x4000c2f6 LCOUNT : 0x00000000
Core 1 was running in ISR context:
EPC1 : 0x40092806 EPC2 : 0x00000000 EPC3 : 0x00000000 EPC4 : 0x40080ff3
Backtrace: 0x40080ff3:0x3ffbe730 0x400811aa:0x3ffbe750 0x40081249:0x3ffbe770 0x400851ca:0x3ffbe790 0x400824f5:0x3ffbe7b0 0x40084a89:0x3ffbe7d0 0x40092803:0x3ffd4fe0 0x40093099:0x3ffd5000 0x4008753c:0x3ffd5020 0x40154df7:0x3ffd5090 0x400f5e45:0x3ffd50c0 0x400f607f:0x3ffd50f0 0x400f75f3:0x3ffd5120 0x400f916e:0x3ffd5160 0x400f5a30:0x3ffd51a0 0x400f4738:0x3ffd51d0 0x40120585:0x3ffd5230 0x4000bcc5:0x3ffd5250 0x40184d41:0x3ffd5270 0x40176915:0x3ffd5290 0x40175e04:0x3ffd5310 0x40175e2b:0x3ffd5340 0x400d5dda:0x3ffd5360 0x400d9ac1:0x3ffd5470 0x400d9b85:0x3ffd54b0 0x400d9de9:0x3ffd54f0 0x40175255:0x3ffd5510 0x401752d1:0x3ffd5550 0x40175976:0x3ffd5570 0x40088ed1:0x3ffd55a0
PC: 0x40080ff3: apply_command(StepperQueue*, queue_entry const*) at .pio\libdeps\pc\FastAccelStepper@src-a564200d5afe0cb1a1a376e0516aa23f\src\StepperISR_esp32.cpp line 100
EXCVADDR: 0x00000000
Decoding stack results
0x40080ff3: apply_command(StepperQueue*, queue_entry const*) at .pio\libdeps\pc\FastAccelStepper@src-a564200d5afe0cb1a1a376e0516aa23f\src\StepperISR_esp32.cpp line 100
0x400811aa: what_is_next(StepperQueue*) at .pio\libdeps\pc\FastAccelStepper@src-a564200d5afe0cb1a1a376e0516aa23f\src\StepperISR_esp32.cpp line 191
0x40081249: pcnt_isr_service(void*) at .pio\libdeps\pc\FastAccelStepper@src-a564200d5afe0cb1a1a376e0516aa23f\src\StepperISR_esp32.cpp line 206
0x400851ca: pcnt_intr_service at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/driver/pcnt.c line 303
0x400824f5: shared_intr_isr at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp32/intr_alloc.c line 504
0x40092803: esp_rom_spiflash_read_data at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spi_flash/spi_flash_rom_patch.c line 221
0x40093099: esp_rom_spiflash_read at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spi_flash/spi_flash_rom_patch.c line 587
0x4008753c: spi_flash_read at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spi_flash/flash_ops.c line 567
0x40154df7: esp_partition_read at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spi_flash/partition.c line 242
0x400f5e45: spiffs_api_read at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spiffs/spiffs_api.c line 36
0x400f607f: spiffs_phys_rd at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spiffs/spiffs/src/spiffs_cache.c line 161
0x400f75f3: spiffs_obj_lu_find_entry_visitor at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spiffs/spiffs/src/spiffs_nucleus.c line 166
0x400f916e: spiffs_object_find_object_index_header_by_name at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spiffs/spiffs/src/spiffs_nucleus.c line 1694
0x400f5a30: SPIFFS_stat at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spiffs/spiffs/src/spiffs_hydrogen.c line 765
0x400f4738: vfs_spiffs_stat at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/spiffs/esp_spiffs.c line 536
0x40120585: esp_vfs_stat at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/vfs/vfs.c line 492
0x40184d41: stat at ../../../.././newlib/libc/syscalls/sysstat.c line 12
0x40176915: VFSImpl::open(char const*, char const*) at C:\users\glmcl\.platformio\packages\framework-arduinoespressif32\libraries\FS\src\vfs_api.cpp line 41
0x40175e04: fs::FS::open(char const*, char const*) at C:\users\glmcl\.platformio\packages\framework-arduinoespressif32\libraries\FS\src\FS.cpp line 191
0x40175e2b: fs::FS::open(String const&, char const*) at C:\users\glmcl\.platformio\packages\framework-arduinoespressif32\libraries\FS\src\FS.cpp line 182
0x400d5dda: SPIFFSEditor::handleRequest(AsyncWebServerRequest*) at .pio\libdeps\pc\ESP Async WebServer\src\SPIFFSEditor.cpp line 443
0x400d9ac1: AsyncWebServerRequest::_parseLine() at .pio\libdeps\pc\ESP Async WebServer\src\WebRequest.cpp line 582
0x400d9b85: AsyncWebServerRequest::_onData(void*, unsigned int) at .pio\libdeps\pc\ESP Async WebServer\src\WebRequest.cpp line 123
0x400d9de9: std::_Function_handler >::_M_invoke(const std::_Any_data &, void *&&, AsyncClient *&&, void *&&, unsigned int &&) at .pio\libdeps\pc\ESP Async WebServer\src\WebRequest.cpp line 76
0x40175255: AsyncClient::_recv(tcp_pcb*, pbuf*, signed char) at c:\users\glmcl\.platformio\packages\toolchain-xtensa32\xtensa-esp32-elf\include\c++\5.2.0/functional line 2271
0x401752d1: AsyncClient::_s_recv(void*, tcp_pcb*, pbuf*, signed char) at .pio\libdeps\pc\AsyncTCP\src\AsyncTCP.cpp line 1191
0x40175976: _async_service_task(void*) at .pio\libdeps\pc\AsyncTCP\src\AsyncTCP.cpp line 159
0x40088ed1: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143
Thanks for the code. Has taken me a while to get it compiled in ArduinoIDE due to missing libraries. That's why I prefer platformio. After successful compilation, the big motor has not run, so I changed big_motor->setAutoEnable(false);
to big_motor->setAutoEnable(true);
. Then the stepper has run well. With this setup web access works, but OTA has reliably sent the esp32 into reset. Even I have removed the ISR-incompatible library code, the ISR has still accessed flash for data from a const table. After moving this table into RAM, OTA runs through. Just the motor is stuttering while OTA is running. This stuttering I have not further investigated. I guess, if the StepperTask is not on Core0 should be better.
This version with the table moved into RAM is the just released 0.22.1
Please try
After some more thoughts: that your app code is running in core0 is not the issue for the stuttering motor. Perhaps, if ota is managed from core0, then is better. But as it accesses the flash, perhaps the cyclical (4ms) Stepper Manager Task gets delayed, which may be unavoidable.
If that’s the only remaining issue, then just stop the motor on OTA start.
Thanks for helping me sort this out!
Testing 0.22.1 now, running good, even the massive "spiffsedit" app (url is /edit) opens without a panic.
Sorry about the libraries in my code, I should have posted my platform.ini (doh!). The whole project is here on github now, just fyi https://github.com/oaklandGman/stepdrv04
I have tried stopping the stepper motors during ota, they don't need to keep running during a firmware update. Also trying to shut down the webserver, and suspend the task sending commands to the stepper library.
vTaskSuspend(taskStepper); // shutdown stepper motion task
big_motor->forceStopAndNewPosition(0);
small_motor->forceStopAndNewPosition(0);
ws.textAll("Shutting down for OTA update");
server.end(); // shutdown webserver
digitalWrite(BIG_MOTOR_SLEEP, LOW); // turn off driver
digitalWrite(SMALL_MOTOR_SLEEP, LOW); // turn off driver
Happy to see, that it now works for you.
Thanks for sharing your code. Always interesting to read. Something new is onsenui. Will look into this, if it suits my purposes.
As there is nothing left to do for FastAccelStepper, this issue can be closed.
I have a very basic sketch, a simple webserver and websocket server, currently hosting a static page, but eventually there will be more. In loop() I'm polling the stepper every 500ms, to see if it's moving, pretty much just running the motor back and forth continuously, different amounts and different speeds.
The ESP32 runs the stepper just fine, until I talk to the webserver, or connect via websocket, then the ESP reboots. I've tried pinning my stepper code to core 0, core 1, or leaving it in loop, no change.
Here's the code I'm using to move the motor, via freertos pinned task
From what I'm reading, it has to do with accessing data / routines from outside the ISR? Whoosh over my head,
Here's the full panic and stack dump decode.