/* atari_1020.cpp Part of Grbl_ESP32 copyright (c) 2018 - Bart Dring This file was modified for use on the ESP32 CPU. Do not use this with Grbl for atMega328P Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Grbl is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Grbl. If not, see . -------------------------------------------------------------- This contains all the special features required to control an Atari 1010 Pen Plotter */ #include "grbl.h" #ifdef ATARI_1020 #define HOMING_PHASE_FULL_APPROACH 0 // move to right end #define HOMING_PHASE_CHECK 1 // check reed switch #define HOMING_PHASE_RETRACT 2 // retract #define HOMING_PHASE_SHORT_APPROACH 3 // retract static TaskHandle_t solenoidSyncTaskHandle = 0; static TaskHandle_t atariHomingTaskHandle = 0; uint16_t solenoid_pull_count; bool atari_homing = false; uint8_t homing_phase = HOMING_PHASE_FULL_APPROACH; uint8_t current_tool; void machine_init() { solenoid_pull_count = 0; // initialize grbl_send(CLIENT_SERIAL, "[MSG:Atari 1020 Solenoid]\r\n"); // setup PWM channel ledcSetup(SOLENOID_CHANNEL_NUM, SOLENOID_PWM_FREQ, SOLENOID_PWM_RES_BITS); ledcAttachPin(SOLENOID_PEN_PIN, SOLENOID_CHANNEL_NUM); pinMode(SOLENOID_DIRECTION_PIN, OUTPUT); // this sets the direction of the solenoid current pinMode(REED_SW_PIN, INPUT_PULLUP); // external pullup required // setup a task that will calculate solenoid position xTaskCreatePinnedToCore( solenoidSyncTask, // task "solenoidSyncTask", // name for task 4096, // size of task stack NULL, // parameters 1, // priority &solenoidSyncTaskHandle, 0 // core ); // setup a task that will do the custom homing sequence xTaskCreatePinnedToCore( atari_home_task, // task "atari_home_task", // name for task 4096, // size of task stack NULL, // parameters 1, // priority &atariHomingTaskHandle, 0 // core ); } // this task tracks the Z position and sets the solenoid void solenoidSyncTask(void *pvParameters) { int32_t current_position[N_AXIS]; // copy of current location float m_pos[N_AXIS]; // machine position in mm TickType_t xLastWakeTime; const TickType_t xSolenoidFrequency = SOLENOID_TASK_FREQ; // in ticks (typically ms) xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. while(true) { // don't ever return from this or the task dies memcpy(current_position,sys_position,sizeof(sys_position)); // get current position in step system_convert_array_steps_to_mpos(m_pos,current_position); // convert to millimeters calc_solenoid(m_pos[Z_AXIS]); // calculate kinematics and move the servos vTaskDelayUntil(&xLastWakeTime, xSolenoidFrequency); } } // to do...have this return a true or false. This could be used by the normal homing feature to // continue with regular homing after setup // return true if this completes homing bool user_defined_homing() { // create and start a task to do the special homing homing_phase = HOMING_PHASE_FULL_APPROACH; atari_homing = true; return true; // this does it...skip the rest of mc_homing_cycle(...) } /* Do a custom homing routine. A task is used because it needs to wait until until idle after each move. 1) Do a full travel move to the right. OK to stall if the pen started closer 2) Check for pen 1 3) If fail Retract 4) move to right end 5) Check... ....repeat up to 12 times to try to find pen one TODO can the retract, move back be 1 phase rather than 2? */ void atari_home_task(void *pvParameters) { uint8_t homing_attempt = 0; // how many times have we tried to home TickType_t xLastWakeTime; const TickType_t xHomingTaskFrequency = 100; // in ticks (typically ms) .... need to make sure there is enough time to get out of idle char gcode_line[20]; while(true) { // this task will only last as long as it is homing if (atari_homing) { // must be in idle or alarm state if (sys.state == STATE_IDLE) { switch(homing_phase) { case HOMING_PHASE_FULL_APPROACH: // a full width move to insure it hits left end inputBuffer.push("G90G0Z1\r"); // lift the pen sprintf(gcode_line, "G91G0X%3.2f\r", -ATARI_PAPER_WIDTH + ATARI_HOME_POS - 3.0); // plus a little extra inputBuffer.push(gcode_line); homing_attempt = 1; homing_phase = HOMING_PHASE_CHECK; break; case HOMING_PHASE_CHECK: // check the limits switch if (digitalRead(REED_SW_PIN) == 0) { // see if reed switch is grounded inputBuffer.push("G4P0.1\n"); // dramtic pause sys_position[X_AXIS] = ATARI_HOME_POS * settings.steps_per_mm[X_AXIS]; sys_position[Y_AXIS] = 0.0; sys_position[Z_AXIS] = 1.0 * settings.steps_per_mm[Y_AXIS]; gc_sync_position(); plan_sync_position(); sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls inputBuffer.push(gcode_line); current_tool = 1; // local copy for reference...until actual M6 change gc_state.tool = current_tool; atari_homing = false; // done with homing sequence } else { homing_phase = HOMING_PHASE_RETRACT; homing_attempt++; } break; case HOMING_PHASE_RETRACT: sprintf(gcode_line, "G0X%3.2f\r", -ATARI_HOME_POS); inputBuffer.push(gcode_line); sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); inputBuffer.push(gcode_line); homing_phase = HOMING_PHASE_CHECK; break; default: grbl_sendf(CLIENT_SERIAL, "[MSG:Homing phase error %d]\r\n", homing_phase); atari_homing = false;; // kills task break; } if (homing_attempt > ATARI_HOMING_ATTEMPTS) { // try all positions plus 1 grbl_send(CLIENT_SERIAL, "[MSG: Atari homing failed]\r\n"); inputBuffer.push("G90\r"); atari_homing = false;; } } } vTaskDelayUntil(&xLastWakeTime, xHomingTaskFrequency); } } // calculate and set the PWM value for the servo void calc_solenoid(float penZ) { bool isPenUp; static bool previousPenState = false; uint32_t solenoid_pen_pulse_len; // duty cycle of solenoid isPenUp = ( (penZ > 0) || (sys.state == STATE_ALARM) ); // is pen above Z0 or is there an alarm // if the state has not change, we only count down to the pull time if (previousPenState == isPenUp) { // if state is unchanged if (solenoid_pull_count > 0) { solenoid_pull_count--; solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // stay at full power while counting down } else { solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_HOLD; // pull in delay has expired so lower duty cycle } } else { // pen direction has changed solenoid_pen_pulse_len = SOLENOID_PULSE_LEN_PULL; // go to full power solenoid_pull_count = SOLENOID_PULL_DURATION; // set the time to count down } previousPenState = isPenUp; // save the prev state digitalWrite(SOLENOID_DIRECTION_PIN, isPenUp); // skip setting value if it is unchanged if (ledcRead(SOLENOID_CHANNEL_NUM) == solenoid_pen_pulse_len) return; // update the PWM value // ledcWrite appears to have issues with interrupts, so make this a critical section portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; portENTER_CRITICAL(&myMutex); ledcWrite(SOLENOID_CHANNEL_NUM, solenoid_pen_pulse_len); portEXIT_CRITICAL(&myMutex); } /* A tool (pen) change is done by bumping the carriage against the right edge 3 times per position change. Pen 1-4 is valid range. */ void user_tool_change(uint8_t new_tool) { uint8_t move_count; char gcode_line[20]; protocol_buffer_synchronize(); // wait for all previous moves to complete if ((new_tool < 1) || (new_tool > MAX_PEN_NUMBER)) { grbl_sendf(CLIENT_ALL, "[MSG: Requested Pen#%d is out of 1-4 range]\r\n", new_tool); return; } if (new_tool == current_tool) return; if (new_tool > current_tool) { move_count = BUMPS_PER_PEN_CHANGE * (new_tool - current_tool); } else { move_count = BUMPS_PER_PEN_CHANGE * ((MAX_PEN_NUMBER - current_tool) + new_tool); } sprintf(gcode_line, "G0Z%3.2f\r", ATARI_TOOL_CHANGE_Z); // go to tool change height inputBuffer.push(gcode_line); for (uint8_t i = 0; i < move_count; i++) { sprintf(gcode_line, "G0X%3.2f\r", ATARI_HOME_POS); // inputBuffer.push(gcode_line); inputBuffer.push("G0X0\r"); } current_tool = new_tool; grbl_sendf(CLIENT_ALL, "[MSG: Change to Pen#%d]\r\n", current_tool); } // move from current tool to next tool.... void atari_next_pen() { if (current_tool < MAX_PEN_NUMBER) { gc_state.tool = current_tool + 1; } else { gc_state.tool = 1; } user_tool_change(gc_state.tool); } // Polar coaster has macro buttons, this handles those button pushes. void user_defined_macro(uint8_t index) { char gcode_line[20]; switch (index) { #ifdef MACRO_BUTTON_0_PIN case CONTROL_PIN_INDEX_MACRO_0: grbl_send(CLIENT_SERIAL, "[MSG: Pen Switch]\r\n"); inputBuffer.push("$H\r"); break; #endif #ifdef MACRO_BUTTON_1_PIN case CONTROL_PIN_INDEX_MACRO_1: grbl_send(CLIENT_SERIAL, "[MSG: Color Switch]\r\n"); atari_next_pen(); sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // alway return to right side to reduce home travel stalls inputBuffer.push(gcode_line); break; #endif #ifdef MACRO_BUTTON_2_PIN case CONTROL_PIN_INDEX_MACRO_2: // feed out some paper and reset the Y 0 grbl_send(CLIENT_SERIAL, "[MSG: Paper Switch]\r\n"); inputBuffer.push("G0Y-25\r"); inputBuffer.push("G4P0.1\r"); // sync...forces wait for planner to clear sys_position[Y_AXIS] = 0.0; // reset the Y position gc_sync_position(); plan_sync_position(); break; #endif default: grbl_sendf(CLIENT_SERIAL, "[MSG: Unknown Switch %d]\r\n", index); break; } } void user_m30() { char gcode_line[20]; sprintf(gcode_line, "G90G0X%3.2f\r", ATARI_PAPER_WIDTH); // inputBuffer.push(gcode_line); } #endif