Compare commits

...

2 Commits

Author SHA1 Message Date
Thierry Fleury
0f8b70dbc8 Test gcode 2025-09-05 19:15:22 +02:00
Thierry Fleury
e7d8304234 wip 2025-09-05 14:04:57 +02:00
15 changed files with 286 additions and 44 deletions

25
lib/gcode/gcode.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#ifndef _GCODE_H_
#define _GCODE_H_
#include <Arduino.h>
struct MachineState {
double x;
double y;
double z;
int moveSpeed;
int cutSpeed;
};
class GCodeHandler
{
private:
Stream* _gcodeStream;
MachineState _currentState;
public:
GCodeHandler(Stream* gcodeStream);
bool readStep(MachineState* step);
};
#endif

View File

@@ -0,0 +1,79 @@
#include <gcode.h>
GCodeHandler::GCodeHandler(Stream* gcodeStream)
{
_gcodeStream = gcodeStream;
// Init state
_currentState.x = 0;
_currentState.y = 0;
_currentState.z = 0;
_currentState.moveSpeed = 0;
_currentState.cutSpeed = 0;
}
bool GCodeHandler::readStep(MachineState* step)
{
MachineState newState = _currentState;
bool hasCommand = false;
for(;;) {
switch (_gcodeStream->read())
{
case 'G':
switch (_gcodeStream->parseInt())
{
case 0:
hasCommand = true;
newState.moveSpeed = 120;
break;
case 1:
hasCommand = true;
newState.moveSpeed = 80;
break;
default:
break;
}
break;
case 'M':
switch (_gcodeStream->parseInt())
{
case 2:
case 30:
// Program end
return false;
default:
break;
}
break;
case 'X':
hasCommand = true;
newState.x = _gcodeStream->parseFloat();
break;
case 'Y':
hasCommand = true;
newState.y = _gcodeStream->parseFloat();
break;
case 'Z':
hasCommand = true;
newState.z = _gcodeStream->parseFloat();
break;
case '(':
_gcodeStream->readStringUntil(')');
break;
case ';':
_gcodeStream->readStringUntil('\n');
break;
case '\n':
if (hasCommand) {
*step = newState;
_currentState = newState;
return true;
}
break;
case ' ':
break;
default:
break;
}
}
}

View File

@@ -15,7 +15,6 @@ public:
geo_pos GetPosition(); geo_pos GetPosition();
float GetMotionHeading(); float GetMotionHeading();
float GetMotionSpeed(); float GetMotionSpeed();
~GnssServiceClass();
}; };

View File

@@ -59,7 +59,3 @@ float GnssServiceClass::GetMotionSpeed() {
//gnss->getPVT(); //gnss->getPVT();
return gnss->getGroundSpeed() / 1000.; return gnss->getGroundSpeed() / 1000.;
} }
GnssServiceClass::~GnssServiceClass()
{
}

View File

@@ -40,7 +40,6 @@ public:
void SetMoveSpeed(MoveSpeed speed); void SetMoveSpeed(MoveSpeed speed);
MoveSpeed GetMoveSpeed(); MoveSpeed GetMoveSpeed();
void UpdateMotors(); void UpdateMotors();
~MotorsControllerClass();
}; };
void setup_motors( void setup_motors(

View File

@@ -97,7 +97,3 @@ void MotorsControllerClass::UpdateMotors() {
log_w("Unable to obtain semaphore"); log_w("Unable to obtain semaphore");
} }
} }
MotorsControllerClass::~MotorsControllerClass()
{
}

View File

@@ -0,0 +1,93 @@
#include <Arduino.h>
#include <USB.h>
#include <USBMSC.h>
#include "wear_levelling.h"
USBMSC msc;
const esp_partition_t* partition;
wl_handle_t wl_handle = WL_INVALID_HANDLE;
bool mscEnabled = false;
static int32_t onWrite(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize){
wl_erase_range(wl_handle, (size_t)((lba * CONFIG_WL_SECTOR_SIZE) + offset), (size_t)bufsize);
wl_write(wl_handle, (size_t)((lba * CONFIG_WL_SECTOR_SIZE) + offset), (const void*) buffer, (size_t)bufsize);
return bufsize;
}
static int32_t onRead(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize){
wl_read(wl_handle, (size_t)((lba * CONFIG_WL_SECTOR_SIZE) + offset), (void*)buffer, (size_t)bufsize);
return bufsize;
}
static bool onStartStop(uint8_t power_condition, bool start, bool load_eject) {
log_i("MSC START/STOP: power: %u, start: %u, eject: %u\n", power_condition, start, load_eject);
return true;
}
static void enableMsc()
{
if (mscEnabled)
return;
log_i("Turn on MSC");
wl_mount(partition, &wl_handle);
msc.vendorID("ESP32");
msc.productID("USB_MSC");
msc.productRevision("1.0");
msc.onRead(onRead);
msc.onWrite(onWrite);
msc.onStartStop(onStartStop);
msc.mediaPresent(true);
msc.begin(partition->size/CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE);
mscEnabled = true;
}
static void disbaleMsc()
{
if (!mscEnabled)
return;
log_i("Turn off MSC");
msc.end();
log_i("Unmount WL partition");
wl_unmount(wl_handle);
wl_handle = WL_INVALID_HANDLE;
mscEnabled = false;
}
static void usbEventCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
if (event_base == ARDUINO_USB_EVENTS) {
arduino_usb_event_data_t *data = (arduino_usb_event_data_t *)event_data;
switch (event_id) {
case ARDUINO_USB_STARTED_EVENT:
log_i("USB PLUGGED");
enableMsc();
break;
case ARDUINO_USB_STOPPED_EVENT:
log_i("USB UNPLUGGED");
disbaleMsc();
break;
case ARDUINO_USB_SUSPEND_EVENT:
log_i("USB SUSPENDED: remote_wakeup_en: %u\n", data->suspend.remote_wakeup_en);
disbaleMsc();
break;
case ARDUINO_USB_RESUME_EVENT:
log_i("USB RESUMED");
enableMsc();
break;
default: break;
}
}
}
void setup_shared_msc()
{
partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
USB.onEvent(usbEventCallback);
USB.begin();
}

View File

@@ -0,0 +1,7 @@
#pragma once
#ifndef _SHARED_MSC_H_
#define _SHARED_MSC_H_
void setup_shared_msc();
#endif

View File

@@ -37,7 +37,6 @@ public:
/// @param speed desired speed (0 - 255) /// @param speed desired speed (0 - 255)
/// @param timeout (ms) /// @param timeout (ms)
void GoTo(geo_pos &pos, u_int8_t speed, uint timeout); void GoTo(geo_pos &pos, u_int8_t speed, uint timeout);
~TripControllerClass();
}; };
extern TripControllerClass* TripController; extern TripControllerClass* TripController;

View File

@@ -4,6 +4,8 @@
#include <motors.h> #include <motors.h>
#include <trip.h> #include <trip.h>
#define ROTATE_MIN_SPEED 12
struct _rotate_args { struct _rotate_args {
double heading; double heading;
uint timeout; uint timeout;
@@ -59,8 +61,8 @@ void TripControllerClass::RotateToHeading(double heading, uint timeout) {
while ((delta < -10 || delta > 10 || abs(delta) < abs(lastDelta)) && millis() <= startTime + timeout) while ((delta < -10 || delta > 10 || abs(delta) < abs(lastDelta)) && millis() <= startTime + timeout)
{ {
MoveSpeed move = { MoveSpeed move = {
.left = (int16_t)(delta > 0 ? 10 + delta / 2 : -10 + delta / 2), .left = (int16_t)(delta > 0 ? ROTATE_MIN_SPEED + delta / 2 : -ROTATE_MIN_SPEED + delta / 2),
.right = (int16_t)(delta > 0 ? -10 - delta / 2 : 10 - delta / 2) .right = (int16_t)(delta > 0 ? -ROTATE_MIN_SPEED - delta / 2 : ROTATE_MIN_SPEED - delta / 2)
}; };
MotorsController->SetMoveSpeed(move); MotorsController->SetMoveSpeed(move);
delay(100); delay(100);
@@ -136,8 +138,4 @@ void TripControllerClass::_gotoTask(void * param) {
vTaskDelete(NULL); vTaskDelete(NULL);
} }
TripControllerClass::~TripControllerClass()
{
}
TripControllerClass* TripController = new TripControllerClass(); TripControllerClass* TripController = new TripControllerClass();

View File

@@ -3,6 +3,8 @@
#define _WORK_H_ #define _WORK_H_
#include <Arduino.h> #include <Arduino.h>
#include <FS.h>
#include <gcode.h>
#include <geo-util.h> #include <geo-util.h>
typedef struct { typedef struct {
@@ -19,7 +21,6 @@ private:
double mmPerDegreeLng; double mmPerDegreeLng;
public: public:
WorkZone(geo_pos origin); WorkZone(geo_pos origin);
geo_pos ToGeoPos(double x, double y);
geo_pos ToGeoPos(work_pos workPos); geo_pos ToGeoPos(work_pos workPos);
work_pos ToWorkPos(geo_pos geoPos); work_pos ToWorkPos(geo_pos geoPos);
~WorkZone(); ~WorkZone();
@@ -29,13 +30,16 @@ class WorkControllerClass
{ {
private: private:
TaskHandle_t _currentTask; TaskHandle_t _currentTask;
File* _workFile;
GCodeHandler* _gcode;
static void _workTask(void *); static void _workTask(void *);
void _openWorkFile();
void _closeWorkFile();
public: public:
WorkControllerClass(); WorkControllerClass();
bool IsWorking(); bool IsWorking();
void StartWork(); void StartWork();
void StopWork(); void StopWork();
~WorkControllerClass();
}; };
extern WorkControllerClass* WorkController; extern WorkControllerClass* WorkController;

View File

@@ -3,14 +3,19 @@
#include <gnss.h> #include <gnss.h>
#include <motors.h> #include <motors.h>
#include <trip.h> #include <trip.h>
#include <gcode.h>
#include <FFat.h>
#define WORK_FILE_NAME "/work.gcode"
struct _workArgs { struct _workArgs {
}; };
WorkControllerClass::WorkControllerClass(/* args */) WorkControllerClass::WorkControllerClass(/* args */)
{ {
_currentTask = nullptr; _currentTask = nullptr;
_workFile = nullptr;
} }
bool WorkControllerClass::IsWorking() bool WorkControllerClass::IsWorking()
@@ -33,41 +38,78 @@ void WorkControllerClass::StopWork()
if (_currentTask != nullptr) { if (_currentTask != nullptr) {
vTaskDelete(_currentTask); vTaskDelete(_currentTask);
_currentTask = nullptr; _currentTask = nullptr;
_closeWorkFile();
log_i("Work stopped !"); log_i("Work stopped !");
} }
MotorsController->SetCutSpeed(0); MotorsController->SetCutSpeed(0);
MotorsController->SetMoveSpeed({0 , 0}); MotorsController->SetMoveSpeed({0 , 0});
} }
void WorkControllerClass::_workTask(void *) void WorkControllerClass::_workTask(void *param)
{ {
_workArgs* args = (_workArgs*)param;
log_i("Work starting"); log_i("Work starting");
WorkController->_openWorkFile();
geo_pos origin = GnssService->GetPosition(); geo_pos origin = GnssService->GetPosition();
WorkZone workZone(origin); WorkZone workZone(origin);
double track[][2] = {
{ 0, 1000 },
{ 1000, 1000 },
{ 1000, -1000 },
{ -1000, -1000 },
{ -1000, 1000 },
{ 1000, 0 },
{ 0, 0 },
};
geo_pos target; geo_pos target;
for(int i = 0; i < sizeof(track) / sizeof(track[0]); i++) MachineState state;
while (WorkController->_gcode->readStep(&state))
{ {
target = workZone.ToGeoPos(0, 2000); target = workZone.ToGeoPos({ .x = state.x, .y = state.y});
TripController->GoTo(target, 100, 120000); TripController->GoTo(target, state.moveSpeed, 120000);
} }
MotorsController->SetCutSpeed(0);
MotorsController->SetMoveSpeed({0, 0});
log_i("Work completed !"); log_i("Work completed !");
WorkController->_closeWorkFile();
WorkController->_currentTask = nullptr; WorkController->_currentTask = nullptr;
vTaskDelete(NULL); vTaskDelete(NULL);
} }
WorkControllerClass::~WorkControllerClass() void WorkControllerClass::_openWorkFile()
{ {
if (_workFile == nullptr) {
if (!FFat.begin()) {
log_w("FFAT is not formated");
if (FFat.format()) {
log_i("FFAT formated");
}
if (!FFat.begin()) {
log_e("Failed to format FFAT");
return;
}
}
log_d("FFAT mounted");
File workFile;
if (!FFat.exists(WORK_FILE_NAME)) {
workFile = FFat.open(WORK_FILE_NAME, FILE_WRITE, true);
log_i("%s file created", WORK_FILE_NAME);
}
else {
workFile = FFat.open(WORK_FILE_NAME, FILE_READ, false);
log_d("%s file opened", WORK_FILE_NAME);
}
_workFile = &workFile;
_gcode = new GCodeHandler(_workFile);
}
}
void WorkControllerClass::_closeWorkFile()
{
if (_workFile != nullptr) {
delete _gcode;
_gcode = NULL;
_workFile->close();
_workFile = nullptr;
FFat.end();
log_i("%s file closed", WORK_FILE_NAME);
}
} }
WorkControllerClass* WorkController = new WorkControllerClass(); WorkControllerClass* WorkController = new WorkControllerClass();

View File

@@ -8,18 +8,13 @@ WorkZone::WorkZone(geo_pos origin)
this->mmPerDegreeLng = 40074155890 * cos(DEG_TO_RAD * originLat) / 360; this->mmPerDegreeLng = 40074155890 * cos(DEG_TO_RAD * originLat) / 360;
} }
geo_pos WorkZone::ToGeoPos(double x, double y)
{
geo_pos result = {
.lat = originLat + y / mmPerDegreeLat,
.lng = originLng + x / mmPerDegreeLng
};
return result;
}
geo_pos WorkZone::ToGeoPos(work_pos workPos) geo_pos WorkZone::ToGeoPos(work_pos workPos)
{ {
return ToGeoPos(workPos.x, workPos.y); geo_pos result = {
.lat = originLat + workPos.y / mmPerDegreeLat,
.lng = originLng + workPos.x / mmPerDegreeLng
};
return result;
} }
work_pos WorkZone::ToWorkPos(geo_pos geoPos) work_pos WorkZone::ToWorkPos(geo_pos geoPos)

8
sampleWork.gcode Normal file
View File

@@ -0,0 +1,8 @@
G0 Y1000
G1 X1000
G1 Y-1000
G1 X-1000
G1 Y1000
G1 X0
G0 Y0
M30

View File

@@ -4,10 +4,12 @@
#include "compass.h" #include "compass.h"
#include "gnss.h" #include "gnss.h"
#include "motors.h" #include "motors.h"
#include "shared-msc.h"
#include "trip.h" #include "trip.h"
#include "work.h" #include "work.h"
void setup() { void setup() {
setup_shared_msc();
setup_ble(); setup_ble();
setup_motors( setup_motors(
MB_LEFT_DIR_PIN, MB_LEFT_DIR_PIN,