A-Axisを制御したい！Arduino Mega2560 + CNC シールド v3.0 の覚書

# 初めに

Arduino UnoとCNCシールドで4軸を動かしたく調べてた備忘録
⇒結局はMega2560とCNCシールドに補助シールドを起こすことで対応

# 使用したもの

ライブラリは下記を使用
https://github.com/mlambm/grbl

# ライブラリの問題点

上記ライブラリで4軸目のA軸も無事に動いたが A軸用のリミットスイッチがない


# 確認した内容
A軸用のリミットスイッチを設けるためソースを見ていくと digitalWrite や analogWrite でなく ポートを直接いじっている模様
太刀打ちできんのでChatGPTに聞くも毎回珍回答ばかりで調べた結果が次の通り

|ATmega328pのピン番号|ポート番号|Arduino Unoのピン番号|CNCシールド v3.0のピン名|備考|
|----|-----|----|--|-|
|1|PC6(RESET/PCINT14) | | | |
|2|PD0(PCINT16)|D0| |RXのため使用しない|
|3|PD1(PCINT17)|D1| |TXのため使用しない|
|4|PD2(PCINT18)|D2|X-STEP||
|5|PD3(PCINT19)|D3|Y-STEP||
|6|PD4(PCINT20)|D4|Z-STEP||
|7|VCC| | |電源電圧|
|8|GND| | |グランド|
|9|PB6(XTAL1/PCINT6)| | |クリスタル1|
|10|PB7(XTAL2/PCINT7)| | |クリスタル2|
|11|PD5(PCINT21)|D5|X-DIR||
|12|PD6(PCINT22)|D6|Y-DIR||
|13|PD7(PCINT23)|D7|Z-DIR||
|14|PB0(PCINT0)|D8|EN|ドライバーの有効・無効|
|15|PB1(PCINT1)|D9|X-EndStop|Limit X-Axis|
|16|PB2(PCINT2)|D10|Y-EndStop|Limit Y-Axis|
|17|PB3(PCINT3)|D11|Z-EndStop|Limit Z-Axis|
|18|PB4(PCINT4)|D12|SpinEnable(A.STP)||
|19|PB5(PCINT5)|D13|SpinDir(A.DIR)| |
|20|AVCC| | | |
|21|AREF| | | |
|22|AGND| | | |
|23|PC0(PCINT8)|D14 / A0|ABORT| |
|24|PC1(PCINT9)|D15 / A1|HOLD| |
|25|PC2(PCINT10)|D16 / A2|RESUME| |
|26|PC3(PCINT11)|D17 / A3|Coolant| |
|27|PC4(PCINT12)|D18 / A4|for I2C(SDA)?| |
|28|PC5(PCINT13)|D19 / A5|for I2C(SCL)?| |

|CNCシールドのピン|説明|
|-|-|
|Step[XYZ]|[XYZ]軸の回転方向|
|Probe| ワークピースの位置を確認するためのセンサー|
|Spindle|主軸（カッター）の回転を制御するためのピン|
|Coolant|冷却液を制御するためのピン|

![schematic](https://blog.protoneer.co.nz/wp-content/uploads/2013/07/Arduino-CNC-Shield-Scematics-V3.XX_.jpg)
![pcb-font](https://i0.wp.com/blog.protoneer.co.nz/wp-content/uploads/2013/07/Arduino-CNC-Shield-V3-PCB-Front.jpg)
![pcb-rear](https://i0.wp.com/blog.protoneer.co.nz/wp-content/uploads/2013/07/Arduino-CNC-Shield-V3-PCB-Back.jpg)
![Arduino Uno](https://i0.wp.com/blog.protoneer.co.nz/wp-content/uploads/2013/01/Grbl_Pin_Layout.png)
[Unoの回路図](https://www.arduino.cc/en/uploads/Main/Arduino_Uno_Rev3-schematic.pdf)

Coolant が使えるのかな
⇒使えないわけではない様だが外部割込みは一つのポートで完結しないとプログラムが大変らしい

# 結論その1
結論Unoだとピンが足りない！

# Mega2560 に鞍替え
では沢山ポートがある Mega2560 でならので何とかなるだろう！と鞍替え
...リミットスイッチがPin Change Interrupt register に属してないといけないから自分の知識では結構難しかった💦

[megaAVR&reg; データシート](https://avr.jp/user/DS/PDF/mega2560.pdf)

これの 13. 外部割り込み に詳細が書かれている
PCIE0, PCIE1, PCIE2は54頁

|Pin Change Interrupt Enable #|PIN|Pin Change Enable Mask #|
|-|-|-|
|PCIE0|PCINT0～PCINT7|PCMSK0|
|PCIE1|PCINT8～PCINT15|PCMSK1|
|PCIE2|PCINT16～PCINT23|PCMSK2|

なるほど
ATmega2560 には Pin Change Interrupt Register は3つあるが PCMSK1 は Mega2560 では使用できない事が判明

# ということで次の様になった

CNCシールドのものを直接使用するのは断念しサブシールドを作成して対応

|Mega2560|ポート|役割|備考|
|-|-|-|-|
|D22|PA0|X-step|
|D24|PA2|Y-step|
|D26|PA4|Z-step|
|D28|PA6|A-step|
|D31|PC6|X-dir|
|D33|PC4|Y-dir|
|D35|PC2|Z-dir|
|D37|PC0|A-dir|
|A8|PK0|X-limit|PCMSK2 PCINT16
|A9|PK1|Y-limit|PCMSK2 PCINT17
|A10|PK2|Z-limit|PCMSK2 PCINT18
|A11|PK3|A-limit|PCMSK2 PCINT19
|D50|PB3|Reset|PCMSK0 PCINT3
|D51|PB2|Feed Hold|PCMSK0 PCINT2
|D52|PB1|Sycle State|PCMSK0 PCINT1
|D53|PB0|Safty Door|PCMSK0 PCINT0

# いじったのは次のファイル

```C:config.h
#define CPU_MAP_ATMEGA2560

#define HOMING_CYCLE_2 ((1<<A_AXIS))
```

```C: limits.c, report.c
N_AXIS3 -> N_AXIS4
```

```C: settings.c
// Returns limit pin mask according to Grbl internal axis indexing.
uint8_t get_limit_pin_mask(uint8_t axis_idx)
{
  if ( axis_idx == X_AXIS ) { return((1<<X_LIMIT_BIT)); }
  if ( axis_idx == Y_AXIS ) { return((1<<Y_LIMIT_BIT)); }
  if ( axis_idx == Z_AXIS ) { return((1<<Z_LIMIT_BIT)); }
  return((1<<A_LIMIT_BIT));
}
```

```c:stepper.c
  #if defCPU_MAP_ATMEGA328P
    uint8_t step_bits[2];  // Stores out_bits output to complete the step pulse delay
  #else
    uint8_t step_bits;  // Stores out_bits output to complete the step pulse delay
  #endif

---

ISR(TIMER1_COMPA_vect)
{        
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
  if (busy) { return; } // The busy-flag is used to avoid reentering this interrupt
  
#ifdef CPU_MAP_ATMEGA328P
  // Set the direction pins a couple of nanoseconds before we step the steppers
  DIRECTION_PORT0 = (DIRECTION_PORT0 & ~DIRECTION_MASK0) | (st.dir_outbits & DIRECTION_MASK0);

  // Then pulse the stepping pins
  #ifdef STEP_PULSE_DELAY
    st.step_bits[0] = (STEP_PORT0 & ~STEP_MASK0) | (st.step_outbits & STEP_MASK0); // Store out_bits to prevent overwriting.
  #else  // Normal operation
    STEP_PORT0 = (STEP_PORT0 & ~STEP_MASK0) | st.step_outbits & STEP_MASK0;
  #endif  

  // Set the direction pins a couple of nanoseconds before we step the steppers
  DIRECTION_PORT1 = (DIRECTION_PORT1 & ~DIRECTION_MASK1) | ((st.dir_outbits<<4) & DIRECTION_MASK1);

  // Then pulse the stepping pins
  #ifdef STEP_PULSE_DELAY
    st.step_bits[1] = (STEP_PORT1 & ~STEP_MASK1) | ((st.step_outbits<<4) & STEP_MASK1); // Store out_bits to prevent overwriting.
  #else  // Normal operation
    STEP_PORT1 = (STEP_PORT1 & ~STEP_MASK1) | (st.step_outbits<<4) & STEP_MASK1;
  #endif
#else
  // Set the direction pins a couple of nanoseconds before we step the steppers
  DIRECTION_PORT = (DIRECTION_PORT & ~DIRECTION_MASK) | (st.dir_outbits & DIRECTION_MASK);

  // Then pulse the stepping pins
  #ifdef STEP_PULSE_DELAY
    st.step_bits = (STEP_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.
  #else  // Normal operation
    STEP_PORT = (STEP_PORT & ~STEP_MASK) | st.step_outbits;
  #endif  
#endif

---

ISR(TIMER0_OVF_vect)
{
  // Reset stepping pins (leave the direction pins)
#ifdef CPU_MAP_ATMEGA328P
  STEP_PORT0 = (STEP_PORT0 & ~STEP_MASK0) | (step_port_invert_mask & STEP_MASK0); 
  STEP_PORT1 = (STEP_PORT1 & ~STEP_MASK1) | (step_port_invert_mask & STEP_MASK1); 
#else
  STEP_PORT = (STEP_PORT & ~STEP_MASK) | (step_port_invert_mask & STEP_MASK); 
#endif
  TCCR0B = 0; // Disable Timer0 to prevent re-entering this interrupt when it's not needed. 
}
#ifdef STEP_PULSE_DELAY
  // This interrupt is used only when STEP_PULSE_DELAY is enabled. Here, the step pulse is
  // initiated after the STEP_PULSE_DELAY time period has elapsed. The ISR TIMER2_OVF interrupt
  // will then trigger after the appropriate settings.pulse_microseconds, as in normal operation.
  // The new timing between direction, step pulse, and step complete events are setup in the
  // st_wake_up() routine.
  ISR(TIMER0_COMPA_vect) 
  { 
#ifdef CPU_MAP_ATMEGA328P
    STEP_PORT0 = st.step_bits[0]; // Begin step pulse.
    STEP_PORT1 = st.step_bits[1]; // Begin step pulse.
#else
    STEP_PORT = st.step_bits; // Begin step pulse.
#endif
  }
#endif

---

// Initialize and start the stepper motor subsystem
void stepper_init()
{
  // Configure step and direction interface pins
#ifdef CPU_MAP_ATMEGA328P
  STEP_DDR0 |= STEP_MASK0;
  STEP_DDR1 |= STEP_MASK1;
  STEPPERS_DISABLE_DDR |= 1<<STEPPERS_DISABLE_BIT;
  DIRECTION_DDR0 |= DIRECTION_MASK0;
  DIRECTION_DDR1 |= DIRECTION_MASK1;
#else
  STEP_DDR |= STEP_MASK;
  STEPPERS_DISABLE_DDR |= 1<<STEPPERS_DISABLE_BIT;
  DIRECTION_DDR |= DIRECTION_MASK;
#endif
```


```c:cpu_map/cpu_map_atmega2560.h
#define X_STEP_BIT    0 // MEGA2560 Digital Pin 22
#define Y_STEP_BIT    2 // MEGA2560 Digital Pin 24
#define Z_STEP_BIT    4 // MEGA2560 Digital Pin 26
#define A_STEP_BIT    6 // MEGA2560 Digital Pin 28
#define STEP_MASK ((1<<X_STEP_BIT)|(1<<Y_STEP_BIT)|(1<<Z_STEP_BIT)|(1<<A_STEP_BIT)) // All step bits

// Define step direction output pins. NOTE: All direction pins must be on the same port.
#define DIRECTION_DDR     DDRC
#define DIRECTION_PORT    PORTC
#define DIRECTION_PIN     PINC
#define X_DIRECTION_BIT   6 // MEGA2560 Digital Pin 31
#define Y_DIRECTION_BIT   4 // MEGA2560 Digital Pin 33
#define Z_DIRECTION_BIT   2 // MEGA2560 Digital Pin 35
#define A_DIRECTION_BIT   0 // MEGA2560 Digital Pin 37
#define DIRECTION_MASK ((1<<X_DIRECTION_BIT)|(1<<Y_DIRECTION_BIT)|(1<<Z_DIRECTION_BIT)|(1<<A_DIRECTION_BIT)) // All direction bits

// Define stepper driver enable/disable output pin.
#define STEPPERS_DISABLE_DDR   DDRH
#define STEPPERS_DISABLE_PORT  PORTH
#define STEPPERS_DISABLE_BIT   5 // MEGA2560 Digital Pin 8
#define STEPPERS_DISABLE_MASK (1<<STEPPERS_DISABLE_BIT)

// Define homing/hard limit switch input pins and limit interrupt vectors. 
// NOTE: All limit bit pins must be on the same port
#define LIMIT_DDR       DDRK
#define LIMIT_PORT      PORTK
#define LIMIT_PIN       PINK
#define X_LIMIT_BIT     0 // MEGA2560 Analog Pin A8
#define Y_LIMIT_BIT     1 // MEGA2560 Analog Pin A9
#define Z_LIMIT_BIT     2 // MEGA2560 Analog Pin A10
#define A_LIMIT_BIT     3 // MEGA2560 Analog Pin A11
#define LIMIT_INT       PCIE2  // Pin change interrupt enable pin
#define LIMIT_INT_vect  PCINT2_vect
#define LIMIT_PCMSK     PCMSK2 // Pin change interrupt register
#define LIMIT_MASK ((1<<X_LIMIT_BIT)|(1<<Y_LIMIT_BIT)|(1<<Z_LIMIT_BIT)|(1<<A_LIMIT_BIT)) // All limit bits

// Define spindle enable and spindle direction output pins.
#define SPINDLE_ENABLE_DDR      DDRB
#define SPINDLE_ENABLE_PORT     PORTB
#define SPINDLE_ENABLE_BIT      6 // MEGA2560 Digital Pin 12
#define SPINDLE_DIRECTION_DDR   DDRB
#define SPINDLE_DIRECTION_PORT  PORTB
#define SPINDLE_DIRECTION_BIT   7 // MEGA2560 Digital Pin 13

// Define flood and mist coolant enable output pins.
// NOTE: Uno analog pins 4 and 5 are reserved for an i2c interface, and may be installed at
// a later date if flash and memory space allows.
// #define COOLANT_FLOOD_DDR     DDRH
// #define COOLANT_FLOOD_PORT    PORTH
// #define COOLANT_FLOOD_BIT     5 // MEGA2560 Digital Pin 8
#ifdef ENABLE_M7 // Mist coolant disabled by default. See config.h to enable/disable.
#define COOLANT_MIST_DDR    DDRF
#define COOLANT_MIST_PORT   PORTF
#define COOLANT_MIST_BIT    3 // MEGA2560 Analog Pin 3
#endif  

// Define user-control CONTROLs (cycle start, reset, feed hold) input pins.
// NOTE: All CONTROLs pins must be on the same port and not on a port with other input pins (limits).
#define CONTROL_DDR       DDRB
#define CONTROL_PIN       PINB
#define CONTROL_PORT      PORTB
#define RESET_BIT         3  // MEGA2560 Digital Pin 50
#define FEED_HOLD_BIT     2  // MEGA2560 Digital Pin 51
#define CYCLE_START_BIT   1  // MEGA2560 Digital Pin 52
#define SAFETY_DOOR_BIT   0  // MEGA2560 Digital Pin 53
#define CONTROL_INT       PCIE0  // Pin change interrupt enable pin
#define CONTROL_INT_vect  PCINT0_vect
#define CONTROL_PCMSK     PCMSK0 // Pin change interrupt register
#define CONTROL_MASK ((1<<RESET_BIT)|(1<<FEED_HOLD_BIT)|(1<<CYCLE_START_BIT)|(1<<SAFETY_DOOR_BIT))
#define CONTROL_INVERT_MASK CONTROL_MASK // May be re-defined to only invert certain control pins.
```

# 結論その2
これでホーミングもできるようになりましたとさ