산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

위치 피드백 기능이 있는 정밀 리니어 서보 액추에이터 만들기 – 단계별 가이드

이 튜토리얼에서는 리니어 서보 액추에이터를 만드는 방법을 배워보겠습니다. 전압을 가하면 특정 방향으로 움직이는 일반 리니어 액추에이터와 달리, 이 커스텀 리니어 서보 액추에이터는 피드백 루프 시스템을 통해 출력 움직임을 정밀하고 반복 가능하게 제어할 수 있습니다.

피드백 루프 시스템을 통해 액추에이터 출력 움직임을 정확하게 제어할 수 있어 '서보 액추에이터'라고 부릅니다.

다음 영상을 시청하거나 아래의 텍스트 튜토리얼을 읽어보세요.

개요

이 리니어 서보 액추에이터의 제어 입력은 아날로그 또는 디지털일 수 있습니다. 아날로그 입력의 경우 여기에 표시된 것처럼 선형 포텐쇼미터, 일반 회전 포텐쇼미터, 조이스틱(역시 회전 포텐쇼미터) 등 어떤 유형의 포텐쇼미터든 사용할 수 있습니다.

디지털 입력의 경우 RC 송신기로 액추에이터를 제어할 수 있습니다. 이 설정에는 RC 수신기도 필요하며, 이것이 액추에이터의 입력이 됩니다.

아날로그와 디지털 입력 모드 모두 연결에 3선만 필요합니다. 그중 2선은 입력 장치 전원용, 1선은 입력 신호용입니다.

이 커스텀 리니어 서보 액추에이터의 멋진 기능 중 하나는 출력 로드의 사용자 지정 시작 및 끝 위치를 설정할 수 있고, 감도(액추에이터가 입력에 얼마나 빨리 반응하는지)도 조절할 수 있다는 점입니다.

하지만 제가 가장 좋아하는 기능은 PC나 노트북에서 시리얼 포트 통신으로 이 액추에이터를 제어할 수 있다는 것입니다. 아두이노 IDE 시리얼 모니터에 밀리미터 단위 값을 입력하면 액추에이터가 그 위치로 이동합니다.

더 멋진 건 반복 움직임을 만들거나 위치를 저장할 수 있다는 겁니다. 시리얼 모니터에 "save"를 입력해 원하는 위치마다 저장하고, "run"을 입력하면 저장된 위치들을 루프로 반복 실행합니다.

이제 이 커스텀 리니어 서보 액추에이터에 대해 알아야 할 모든 것, 작동 원리와 설계 방법을 설명해드리니 여러분도 직접 만들어보실 수 있습니다.

작동 원리

폐루프 제어 시스템은 AS5600 자기 회전 위치 센서와 DC 모터 구동을 위한 PID 제어 구현을 기반으로 합니다.

사실 이전 영상에서 만든 동일한 커스텀 서보 모터 컨트롤러 보드를 사용하는데, 여기엔 자체 마이크로컨트롤러와 DC 모터를 독립형 서보 모터로 바꾸는 데 필요한 모든 것이 들어 있습니다.

서보 모터와 폐루프 제어 시스템 작동 원리에 대한 자세한 설명은 해당 튜토리얼을 참고하세요.

간단히 말해, 서보 모터는 입력 신호(목표 위치)와 위치 피드백 센서에서 얻는 모터 실제 위치를 비교하는 폐루프 제어 시스템입니다.

발생하는 차이(오차)를 컨트롤러가 처리해 모터에 명령하여 목표 위치에 도달할 때까지 움직이게 합니다.

이 리니어 서보 액추에이터도 서보 모터와 같은 원리지만, 모터의 회전 운동을 리드 스크류 메커니즘으로 직선 운동으로 변환하는 단계가 하나 더 있습니다.

리니어 서보 액추에이터 설계

이 리니어 서보 액추에이터의 3D 모델로 모든 작동 원리를 확인할 수 있습니다.

AS5600 자기 회전 위치 센서는 액추에이터 후면에 위치해 리드 스크류의 회전을 추적합니다. 사용하는 리드 스크류는 8mm 피치로, 한 바퀴 돌 때마다 리드 스크류 너트가 8mm 직선 운동을 합니다.

AS5600은 12비트 인코더로 회전당 4096 위치를 출력합니다. 8을 4096으로 나누면 0.001953mm 해상도가 나옵니다. AS5600 인코더가 감지할 수 있는 가장 작은 위치 변화량입니다. 꽤 인상적입니다.

사용한 DC 모터는 내장 감속 기어박스가 있는 12V 모터로 480 RPM 출력입니다. 480을 60으로 나누면 초당 8회전, 여기에 리드 스크류 피치 8mm를 곱하면 액추에이터 선속도 64mm/s가 나옵니다.

이 액추에이터 로드의 최대 스트로크가 150mm이니 최대 속도로 시동부터 종단까지 약 2.5초, 가감속 포함 약 3초 걸립니다. 그래서 리드 스크류 구동용 기어비를 1:1로 잡았습니다.

전체 리니어 액추에이터 설계는 커스텀 서보 모터 컨트롤러 PCB 크기(40x40mm)와 리드 스크류, 리드 스크류 너트 크기를 기준으로 했습니다. PCB가 실린더 블록 최소 크기를 정했습니다.

8mm 리드 스크류 너트 외경이 22mm라 그에 맞춰 로드를 설계했습니다. 너트와 로드는 M3 볼트 4개와 나사 인서트로 연결합니다. 로드 상단에는 실린더 블록을 미끄러지는 가이드 베어링이 있어 로드를 안내하고 회전을 방지합니다.

출력 실린더 캡에는 작은 베어링 4개가 있어 로드가 실린더 블록 밖으로 부드럽게 나가게 안내합니다.

전체적으로 사용된 부품들을 고려하면 꽤 콤팩트하게 나왔다고 생각합니다.

실린더 블록 내부에 마이크로 리미트 스위치도 장착했는데, 이는 호밍과 액추에이터 시작 위치 설정에 사용됩니다.

3D 모델 다운로드

이 커스텀 리니어 서보 액추에이터의 3D 모델을 Onshape에서 웹 브라우저로 확인하고 탐색할 수 있습니다. (Onshape 계정 필요, 가정용 무료 계정 생성 가능)

3D 프린팅용 STL 파일과 이 3D 모델의 STEP 파일은 Cults3D에서 다운로드할 수 있습니다.

한 가지 더: 실린더 블록과 로드 길이를 늘리기만 하면 이 리니어 액추에이터의 최대 스트로크 길이를 쉽게 늘릴 수 있습니다. 저는 모든 부품이 220x220mm 작은 프린트 베드에도 들어가게 하려고 이 치수를 선택했습니다. 가장 큰 부품인 로드가 215mm입니다.

3D 프린팅

새로운 Creality Ender-3 V3 SE가 Y축 방향 수평 배치로 훌륭하게 출력해줬습니다. 약간의 샌딩이 필요하지만, 이 방향으로 로드를 출력하면 더 부드러운 동작과 더 강한 로드가 됩니다.

3D 프린팅 시 필라멘트 팽창을 보정하고 치수 정확도를 높이기 위해 슬라이싱 소프트웨어의 수평 확장(Horizontal Expansion) 기능을 사용하는 것이 중요합니다.

전 -0.1mm 값을 사용했지만, 본인의 3D 프린터에 맞는 값을 테스트 프린트로 찾아보세요.

실린더 블록은 Z축 방향으로 출력해 서포트 재료를 많이 쓰지 않게 했습니다. Creality Ender-3 V3 SE가 이 출력도 훌륭하게 해냈습니다.

이 가격대의 3D 프린터가 제공하는 출력 품질에 즐겁게 놀랐습니다. 설정하기 매우 쉽고, 오토 베드 레벨링, 다이렉트 익스트루더, 훌륭한 출력 품질, 최대 250mm/s 향상된 출력 속도. 이 모든 게 200달러 미만이라 예산이 한정된 분께 최고의 3D 프린터 중 하나입니다.

Creality 스토어 또는 Amazon에서 확인하세요. 제 웹사이트의 상세 리뷰도 참고하세요.

조립

자, 모든 3D 프린팅 부품이 준비됐으니 리니어 액추에이터 조립을 시작하겠습니다.

부품 목록

이 리니어 서보 액추에이터 프로젝트에 필요한 부품은 아래 링크에서 구매할 수 있습니다:

기계 부품:

전자 부품:

공개: 위 링크는 제휴 링크입니다. 아마존 어소시에이트로서 자격 있는 구매로부터 수익을 얻습니다.

리드 스크류 메커니즘 조립

먼저 실린더 베이스 블록에 리드 스크류를 장착합니다. 이를 위해 리드 스크류와 같은 나사산의 3D 프린팅 너트를 끼웁니다.

너트를 리드 스크류에 조이는 게 좀 빡빡한데, 그게 정상입니다. 이 너트가 로드가 밀 때 전체 힘을 받으므로 끼워맞춤이 타이트할수록 더 큰 힘을 견딥니다. 또한 너트엔 그럽 스크류로 샤프트에 고정하기 위한 나사 인서트 구멍도 있습니다.

리드 스크류는 22mm 외경 볼 베어링 두 개로 실린더 베이스 블록에 고정됩니다.

후면에는 리드 스크류를 구동할 기어가 들어갑니다. 이 기어도 같은 나사산에 나사 인서트 구멍 두 개가 있어 그럽 스크류로 리드 스크류에 고정합니다. 이 연결도 모터 토크를 리드 스크류로 전달하는 중요한 부분이라 미끄러지면 안 됩니다.

이 서브 어셈블리를 위해 기어와 너트, 실린더 블록에 나사 인서트를 먼저 박습니다. 기어와 너트를 반대 방향으로 조이되 베어링에 축방향 힘이 가해지지 않게 너무 조이진 않습니다. 그 후 그럽 스크류로 너트와 기어를 리드 스크류에 고정합니다.

고정 후에도 리드 스크류가 아직 고정되지 않은 상태입니다. 베어링이 실린더 블록에 제자리에 머물게 해줄 플레이트를 추가합니다.

이제 리드 스크류가 제자리에 단단히 서면서 자유롭게 회전할 수 있습니다.

다음으로 로드를 준비합니다. 로드는 전체 길이가 비어 있어 리드 스크류가 들어갑니다. 리드 스크류 너트와 로드 연결을 위해 나사 인서트를 박습니다.

로드 상단에는 가이드 베어링(13mm 외경, 6mm 내경)을 장착합니다. 3D 프린팅 6mm 중공 샤프트에 베어링을 올리고 M3 카운터싱크 볼트 10mm로 로드에 고정합니다. 이제 로드가 실린더의 가이드 레일 사이로 어떻게 미끄러질지 확인할 수 있습니다.

다음으로 실린더를 실린더 베이스 블록에 부착합니다. 그 전에 마이크로 리미트 스위치를 장착해야 합니다.

먼저 NC 단자에 전선을 납땜합니다. 전선 길이는 약 15cm. 전선은 실린더 상단 구멍을 통과시키고, M3 볼트 8mm 두 개로 마이크로 리미트 스위치를 실린더에 고정합니다.

가이드 베어링이 적절한 타이밍에 리미트 스위치를 작동시키되 다른 곳에 부딪히지 않게 하려면 이 특정 리미트 스위치 모델이 필요합니다. 구할 수 없으면 구멍과 메커니즘을 수정하세요.

실린더를 베이스 블록에 부착하기 위해 나사 인서트를 박고, M4 볼트 25mm 두 개로 고정합니다. 이때 위쪽 두 볼트만 넣고 아래 두 볼트는 나중에 기어박스와 PCB 커버 장착 시 같은 구멍을 쓰니 그때 넣습니다.

이제 로드를 리드 스크류에 조립합니다. 가이드 베어링이 실린더의 가이드 레일 사이에 들어가야 합니다. 후면 기어를 돌리면 로드가 뒤로 움직여 마이크로 리미트 스위치에 닿을 때까지 갑니다.

그 다음 실린더 캡을 장착합니다. 실린더 캡엔 9mm 외경 작은 베어링 4개가 들어갑니다. 이 베어링용 샤프트는 3D 프린팅합니다. 이 부분이 꽤 작고 쉽게 부러질 수 있으니 조심스럽게 끼우세요. 저도 몇 번 부러뜨렸습니다. 이 베어링들이 로드를 지지하고 안내해 부드러운 동작을 만듭니다. 실린더 캡은 M4 볼트 4개로 고정합니다.

DC 모터 장착

이제 DC 모터를 장착합니다. M3 볼트 6개로 고정합니다. 모터 샤프트에 기어를 끼우고, 나사 인서트 두 개와 그럽 스크류로 고정합니다.

기어가 제대로 맞물리면 후면의 기어 및 PCB 커버를 부착합니다. 실린더 베이스 블록에 나사 인서트를 몇 개 더 박습니다.

DC 모터 전선을 연결합니다. 제 경우 모터에 직접 납땜했습니다. 전선 길이는 약 20cm. 실린더 베이스 블록의 구멍으로 DC 모터와 리미트 스위치 전선을 모두 통과시킵니다. 커버의 홀더 두 개도 통과시켜 기어에서 전선이 떨어지지 않게 합니다.

이제 커버를 베이스 블록에 고정합니다. 아래쪽 M4 볼트 두 개를 먼저 넣되 끝까지 조이지 않고 2~3mm 남겨둡니다. 커버 홀더를 사이에 끼우고 볼트를 조여 커버와 함께 고정합니다. 이 작업이 좀 지저분한데, 커버를 최대한 작고 한 번에 출력하고 싶어서 PCB 홀더들이 볼트 길을 막아 어쩔 수 없었습니다.

커스텀 서보 컨트롤러 PCB 장착

커버 작업이 끝나면 커스텀 서보 모터 컨트롤러 보드를 장착합니다. 이전 영상에서 DC 모터를 서보 모터로 바꾸는 컨트롤러를 보여드렸던 그 보드입니다.

핵심 부품은 AS5600 자기 회전 위치 인코더로, 출력 샤프트에 부착된 자석의 각도 위치를 추적합니다. 여기선 출력 기어(리드 스크류 쪽)에 자석을 붙입니다. PCB가 제자리에 가면 자석이 AS5600 센서와 완벽하게 정렬됩니다.

PCB 고정을 위해 홀더 슬롯에 M2 너트를 넣고 M2 볼트 4개로 조입니다.

이제 전선 연결만 남았습니다. DC 모터 전선은 모터 터미널 블록에 연결하고, 극성은 컨트롤러 프로그램과 맞는지 추가 확인하세요.

사실 PCB에 모터를 연결하기 전에 모터에 전압을 가해 리드 스크류 메커니즘이 제대로 작동하는지 확인할 수 있습니다.

리미트 스위치 전선은 전용 핀이 없어 그라운드 선을 전해 콘덴서 그라운드 패드에, NC 연결선을 SCK 핀(ATMEGA328 마이크로컨롤러의 디지털 핀 13)에 납땜했습니다.

전원용 터미널 블록이 커버 옆에 있어 5.5mm 전원 커넥터를 구멍을 통해 연결했습니다. DC 모터 드라이버에 방열판도 달았습니다. 마지막으로 후면 스냅핏 뚜껑을 덮으면 프로젝트 완료입니다.

이제 어떤 유형의 포텐쇼미터나 RC 수신기를 적절한 입력 핀에 연결해 리니어 액추에이터 위치를 제어할 수 있습니다.

앞서 언급했듯 이전 영상에서 이 컨트롤러 작동 원리, 회로도, PCB 제작 과정을 자세히 설명했습니다. 컨트롤러 보드를 직접 만들고 싶다면 그 튜토리얼을 참고하세요.

회로도

핵심 부품은 AS5600 자기 센서로, 액추에이터 출력 위치를 추적합니다. 센서 데이터는 이 서보 컨트롤러 보드의 두뇌인 Atmega328 마이크로컨트롤러로 들어가 연산 후 DRV8871 DC 모터 드라이버에 DC 모터 구동 명령을 내립니다.

DRV8871은 최대 3.6A 피크 전류를 감당합니다. 보드 전원은 12V를 입력받아 AMS1117 전압 레귤레이터로 5V로 낮춰 Atmega328과 다른 5V 부품에 공급합니다. 2채널 DIP 스위치로 액추에이터 입력 모드(아날로그, 디지털, 시리얼 통신)를 선택합니다. 트리머 포텐쇼미터 하나는 액추에이터 감도 조절용, SDM 푸시 버튼은 시작/끝 위치 설정용입니다.

PCB는 PCBWay에서 주문했습니다. 거버 파일 업로드, PCB 속성 선택, 합리적 가격 주문. 4층 설계(중간 층은 GND)로 가격이 좀 오르지만 기본 속성 유지, 색상만 흰색으로, 표면 처리는 추가 비용 없이 침금(Immersion gold) 허용에 체크했습니다.

거버 파일은 PCBWay 프로젝트 공유 커뮤니티에서 다운로드 및 직접 주문 가능합니다.

이 커스텀 서보 컨트롤러 없이도 이 리니어 액추에이터 프로젝트를 만들 수 있습니다. AS5600 센서 브레이크아웃 보드와 아두이노 보드 조합으로 DC 모터를 제어하면 됩니다.

코드

이제 이 리니어 서보 액추에이터 코드를 살펴보겠습니다:

/* Linear Servo Actuator - Arduino Code by Dejan, www.HowToMechatronics.com
Libraries:
AS5600 encoder: https://github.com/RobTillaart/AS5600
PID conroller: https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.h */
#include "AS5600.h"
#include "Wire.h"
#include <PID_v1.h>
AS5600 as5600; // use default Wire
double Pk1 = 0.65; //speed it gets there
double Ik1 = 0;
double Dk1 = 0.1;
//Define Variables we'll be connecting to
double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint, Pk1, Ik1, Dk1, DIRECT);
#define motor_IN1 5
#define motor_IN2 6
#define ch1 2
#define setButton 7
#define inputSwitch 3
#define modeSwitch 4
#define limitSwitch 13
int ch1Value;
long encoderValue, desiredValue, pwmValue;
String serialInput = ""; // string to hold input
int serialIntInput;
double totalDistance = 0;
long startPosition = 358;
long endPosition = 6750;
long rangeAdjustment = 0;
float sensitivityAdjustment = 0;
float angle = 0;
float angleValue = 0;
float rodPosition;
float positionsArray[100];
int positionsCounter = 0;
long quadrantNumber = 2;
long previousQuadrantNumber = 3;
long numberOfTurns = 0;
float totalAngle = 0;
char incomingByte = 0;
int intInput = 0;
void setup() {
  Serial.begin(115200);
  Serial.println(__FILE__);
  Serial.print("AS5600_LIB_VERSION: ");
  Serial.println(AS5600_LIB_VERSION);
  Wire.begin();
  pinMode(motor_IN1, OUTPUT);
  pinMode(motor_IN2, OUTPUT);
  // Activate the Arduino internal pull-up resistors
  pinMode(setButton, INPUT_PULLUP);
  pinMode(inputSwitch, INPUT_PULLUP);
  pinMode(modeSwitch, INPUT_PULLUP);
  pinMode(limitSwitch, INPUT_PULLUP);
  // PID Setup
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(-255, 255);
  myPID.SetSampleTime(20);
  // --- HOMING ----
  // Move backward until you ...
  while (digitalRead(limitSwitch) != 1) {
    digitalWrite(motor_IN1, LOW);
    analogWrite(motor_IN2, 70);
    encoderValue = as5600.readAngle();
  }
  while (digitalRead(limitSwitch) != 0) {
    analogWrite(motor_IN1, 50);
    digitalWrite(motor_IN2, LOW);
  }
  digitalWrite(motor_IN1, LOW);
  digitalWrite(motor_IN2, LOW);
  startPosition = as5600.readAngle() * 0.087890625;
  endPosition = 6000;
  Setpoint = startPosition;
  // --- HOMING End ---
}
void loop() {
  // Read encoder value - current position
  rodPosition = as5600.readAngle() / 0.001953125; // in millimters - The lead screw is 8mm per rotation, and the encoder RAW value is 4096 per roration, so 8/4096 to get the value in milimiters
  // Serial communication mode - Read data from the serial monitor
  if (digitalRead(modeSwitch) == 0) {
    while (Serial.available() > 0) {
      serialInput = Serial.readString();
      serialInput.trim();
      // If "save" string is sent through the serial monitor, save the current rodPosition into the array
      if (serialInput == "save") {
        positionsArray[positionsCounter] = totalDistance;
        delay(1000);
        positionsCounter++;
      }
      // Clear the saved positions
      if (serialInput == "clear") {
        // Clear the array data to 0
        memset(positionsArray, 0, sizeof(positionsArray));
        positionsCounter = 0;
      }
      // Convert the String to Integer and use it as a Setpoint for the PID control
      serialIntInput = serialInput.toInt();
      if (serialIntInput != 0) {
        if (serialIntInput < 0) { serialIntInput = 0; }
        if (serialIntInput > 150) { serialIntInput = 150; }
        Setpoint = serialIntInput * 45; // convert mm into degrees (1mm linear movement equals 45 degrees rotational movement)
      }
    }
    // Run stored positions
    if (serialInput == "run") {
      while (serialInput != "stop") {
        for (int i = 0; i <= positionsCounter - 1; i++) {
          if (serialInput == "stop") { break; }
          while (positionsArray[i] > totalDistance + 25 || positionsArray[i] < totalDistance - 25 || pwmValue != 0) {
            // Desired position / setpoint for the PID contorller
            Setpoint = positionsArray[i];
            // Read encoder - use that value as an Input for the PID control
            readEncoder();
            // Run motor - PID controller inside runMotor();
          }
          delay(2000); // Delay between steps
          // Check the serial monitor for a stop command
          while (Serial.available() > 0) {
            serialInput = Serial.readString();
            serialInput.trim();
          }
        }
      }
    }
  }
  // Potentiometer and RC Receiver control mode
  else if (digitalRead(modeSwitch) == 1) {
    if (digitalRead(inputSwitch) == 0) {
      // Analog input - Potentiometer
      // Get value from potentiometer
      desiredValue = analogRead(A0);
      if (desiredValue < 15) { desiredValue = 15; }
      if (desiredValue > 1008) { desiredValue = 1008; }
      Setpoint = map(desiredValue, 15, 1008, startPosition, endPosition);
    } else if (digitalRead(inputSwitch) == 1) {
      // Digital input - RC transmitter
      desiredValue = pulseIn(ch1, HIGH, 30000); // Read RC receiver as input
      if (desiredValue < 1000 || desiredValue > 2000) { desiredValue = 1000; }
      Setpoint = map(desiredValue, 1000, 2000, startPosition, endPosition);
    }
  }
  // Confine the minimum and maximum values of the setpoint
  if (Setpoint > endPosition) { Setpoint = endPosition; }
  if (Setpoint < startPosition) { Setpoint = startPosition; }
  // Adjusting sensitivity
  //Pk1 = analogRead(A2) * 0.002; // Adjust the PID gain term
  //myPID.SetTunings(Pk1, Ik1, Dk1);
  // Read encoder - use that value as an input for the PID control
  readEncoder();
  // Run motor
  runMotor();
  // Set start and end positions by pressing the "set" button
  if (digitalRead(setButton) == LOW) {
    delay(3000);
    if (digitalRead(setButton) == LOW) {
      endPosition = totalDistance;
      while (digitalRead(setButton) != HIGH) ;
    } else {
      startPosition = totalDistance;
    }
  }
}
void readEncoder() {
  // Convert encoder RAW values into angle values for keeping track of the angular position of the shaft
  encoderValue = as5600.readAngle() * 0.087890625;
  // Quadrant 1
  if (encoderValue >= 0 && encoderValue <= 90) { quadrantNumber = 1; }
  // Quadrant 2
  if (encoderValue >= 90 && encoderValue <= 180) { quadrantNumber = 2; }
  // Quadrant 3
  if (encoderValue >= 180 && encoderValue <= 270) { quadrantNumber = 3; }
  // Quadrant 4
  if (encoderValue >= 270 && encoderValue <= 360) { quadrantNumber = 4; }
  if (quadrantNumber != previousQuadrantNumber) {
    // Transition from 4th to 1st quadrant
    if (quadrantNumber == 1 && previousQuadrantNumber == 4) { numberOfTurns++; }
    // Transition from 1st to 4th quadrant
    if (quadrantNumber == 4 && previousQuadrantNumber == 1) { numberOfTurns--; }
    previousQuadrantNumber = quadrantNumber;
  }
  if (totalDistance >= 0) {
    totalDistance = (numberOfTurns * 360) + encoderValue;
  } else {
    totalDistance = (numberOfTurns * 360) + encoderValue;
  }
  // Establish Input value for PID
  Input = totalDistance; // current value of the position
}
void runMotor() {
  // Run PID process to get Output value
  myPID.Compute();
  // Move right
  if (Output > 1) {
    pwmValue = Output;
    if (pwmValue < 50 && pwmValue > 15) { pwmValue = pwmValue + 40; }
    if (pwmValue <= 15) { pwmValue = 0; }
    analogWrite(motor_IN1, pwmValue);
    digitalWrite(motor_IN2, LOW);
  }
  // Move left
  else if (Output < 1) {
    pwmValue = abs(Output);
    if (pwmValue < 50 && pwmValue > 15) { pwmValue = pwmValue + 40; }
    if (pwmValue <= 15) { pwmValue = 0; }
    digitalWrite(motor_IN1, LOW);
    analogWrite(motor_IN2, pwmValue);
  }
  // Do not move
  else if (Output > -1 && Output < 1) {
    pwmValue = 0;
    digitalWrite(motor_IN1, LOW);
    digitalWrite(motor_IN2, LOW);
  }
}

코드 설명

루프 시작 시 인코더 값(액추에이터 현재 위치)을 읽어 밀리미터로 변환합니다.

// Read encoder value - current position
rodPosition = as5600.readAngle() / 0.001953125; // in millimters - The lead screw is 8mm per rotation, and the encoder RAW value is 4096 per roration, so 8/4096 to get the value in milimiters

"시리얼 통신 모드"라면 시리얼 모니터로 들어오는 데이터를 읽습니다. 입력이 "save"면 현재 액추에이터 위치 저장, "clear"면 저장된 위치 모두 삭제.

// Serial communication mode - Read data from the serial monitor
if (digitalRead(modeSwitch) == 0) {
  while (Serial.available() > 0) {
    serialInput = Serial.readString();
    serialInput.trim();
    // If "save" string is sent through the serial monitor, save the current rodPosition into the array
    if (serialInput == "save") {
      positionsArray[positionsCounter] = totalDistance;
      delay(1000);
      positionsCounter++;
    }
    // Clear the saved positions
    if (serialInput == "clear") {
      // Clear the array data to 0
      memset(positionsArray, 0, sizeof(positionsArray));
      positionsCounter = 0;
    }

입력이 0~150 사이의 정수(숫자)면 이를 세트포인트로 사용합니다.

// Convert the String to Integer and use it as a Setpoint for the PID control
serialIntInput = serialInput.toInt();
if (serialIntInput != 0) {
  if (serialIntInput < 0) { serialIntInput = 0; }
  if (serialIntInput > 150) { serialIntInput = 150; }
  Setpoint = serialIntInput * 45; // convert mm into degrees (1mm linear movement equals 45 degrees rotational movement)
}

값을 밀리미터로 입력하지만 회전축 추적에는 도(degree)를 쓰므로 45를 곱해 변환합니다. 1mm 직선 운동 = 리드 스크류 45도 회전이기 때문입니다. 리드 스크류 피치가 다르면 이 숫자도 달라집니다.

"run"을 입력하면 while/for 루프로 저장된 위치들을 반복 실행합니다.

// Run stored positions
if (serialInput == "run") {
  while (serialInput != "stop") {
    for (int i = 0; i <= positionsCounter - 1; i++) {
      if (serialInput == "stop") { break; }
      while (positionsArray[i] > totalDistance + 25 || positionsArray[i] < totalDistance - 25 || pwmValue != 0) {
        // Desired position / setpoint for the PID contorller
        Setpoint = positionsArray[i];
        // Read encoder - use that value as an Input for the PID control
        readEncoder();
        // Run motor - PID controller inside runMotor();
      }
      delay(2000); // Delay between steps
      // Check the serial monitor for a stop command
      while (Serial.available() > 0) {
        serialInput = Serial.readString();
        serialInput.trim();
      }
    }
  }
}

반면 "포텐쇼미터 및 RC 수신기 제어 모드"라면 아날로그/디지털 입력을 확인합니다.

// Potentiometer and RC Receiver control mode
else if (digitalRead(modeSwitch) == 1) {
  if (digitalRead(inputSwitch) == 0) {
    // Analog input - Potentiometer
    // Get value from potentiometer
    desiredValue = analogRead(A0);
    if (desiredValue < 15) { desiredValue = 15; }
    if (desiredValue > 1008) { desiredValue = 1008; }
    Setpoint = map(desiredValue, 15, 1008, startPosition, endPosition);
  } else if (digitalRead(inputSwitch) == 1) {
    // Digital input - RC transmitter
    desiredValue = pulseIn(ch1, HIGH, 30000); // Read RC receiver as input
    if (desiredValue < 1000 || desiredValue > 2000) { desiredValue = 1000; }
    Setpoint = map(desiredValue, 1000, 2000, startPosition, endPosition);
  }
}

아날로그면 포텐쇼미터 아날로그 입력을 읽어 세트포인트(목표 위치)로 사용, 디지털이면 RC 수신기 입력 데이터를 읽어 세트포인트로 사용.

이후 readEncoder()와 runMotor() 커스텀 함수를 호출해 현재 위치 읽고 PID 제어 실행. readEncoder()는 센서 현재 값을 각도 값으로 읽고, if문들로 현재 축 위치가 어느 사분면에 있는지 추적합니다.

void readEncoder() {
  // Convert encoder RAW values into angle values for keeping track of the angular position of the shaft
  encoderValue = as5600.readAngle() * 0.087890625;
  // Quadrant 1
  if (encoderValue >= 0 && encoderValue <= 90) { quadrantNumber = 1; }
  // Quadrant 2
  if (encoderValue >= 90 && encoderValue <= 180) { quadrantNumber = 2; }
  // Quadrant 3
  if (encoderValue >= 180 && encoderValue <= 270) { quadrantNumber = 3; }
  // Quadrant 4
  if (encoderValue >= 270 && encoderValue <= 360) { quadrantNumber = 4; }
  if (quadrantNumber != previousQuadrantNumber) {
    // Transition from 4th to 1st quadrant
    if (quadrantNumber == 1 && previousQuadrantNumber == 4) { numberOfTurns++; }
    // Transition from 1st to 4th quadrant
    if (quadrantNumber == 4 && previousQuadrantNumber == 1) { numberOfTurns--; }
    previousQuadrantNumber = quadrantNumber;
  }
  if (totalDistance >= 0) {
    totalDistance = (numberOfTurns * 360) + encoderValue;
  } else {
    totalDistance = (numberOfTurns * 360) + encoderValue;
  }
  // Establish Input value for PID
  Input = totalDistance; // current value of the position
}

이 정보로 축 회전과 온전한 한 바퀴 돌 때를 추적. 총 각도가 PID 컨트롤러의 입력 값이 됩니다.

트리머 포텐쇼미터 아날로그 입력으로 PID 컨트롤러의 비례 이득 조절, 마지막으로 PID 프로세스 실행해 출력 값 얻음.

// Adjusting sensitivity
//Pk1 = analogRead(A2) * 0.002; // Adjust the PID gain term
//myPID.SetTunings(Pk1, Ik1, Dk1);
void runMotor() {
  // Run PID process to get Output value
  myPID.Compute();
  // Move right
  if (Output > 1) {
    pwmValue = Output;
    if (pwmValue < 50 && pwmValue > 15) { pwmValue = pwmValue + 40; }
    if (pwmValue <= 15) { pwmValue = 0; }
    analogWrite(motor_IN1, pwmValue);
    digitalWrite(motor_IN2, LOW);
  }
  // Move left
  else if (Output < 1) {
    pwmValue = abs(Output);
    if (pwmValue < 50 && pwmValue > 15) { pwmValue = pwmValue + 40; }
    if (pwmValue <= 15) { pwmValue = 0; }
    digitalWrite(motor_IN1, LOW);
    analogWrite(motor_IN2, pwmValue);
  }
  // Do not move
  else if (Output > -1 && Output < 1) {
    pwmValue = 0;
    digitalWrite(motor_IN1, LOW);
    digitalWrite(motor_IN2, LOW);
  }
}

PID 출력 값으로 DC 모터를 PWM 신호로 좌/우/정지 구동. PID 컨트롤러의 출력 값, 즉 인코더가 읽은 실제 위치와 목표 위치 사이의 오차에 따라 결정됩니다.

PID 컨트롤러의 세 항(비례, 적분, 미분)은 상단에 정의돼 있으며, 이 값들을 조정해 다양한 출력 반응을 얻을 수 있습니다.

double Pk1 = 0.65; //speed it gets there
double Ik1 = 0;
double Dk1 = 0.1;

액추에이터가 입력에 얼마나 잘 반응하고 작동하는지는 이 값들의 품질에 직접 달려 있습니다.

테스트

액추에이터 정확도 테스트입니다. 제자리로 꽤 잘 돌아옵니다. 로드를 1mm씩 움직여봤습니다. 첫 움직임은 1mm 대신 0.8mm였지만, 그 다음 4번은 1mm에 꽤 가까웠습니다. 4mm 움직임은 약 0.15mm 오차.

로드에 약 0.25mm 백래시가 있습니다. 리드 스크류와 리드 스크류 너트 사이의 백래시입니다. 거기에 3D 프린팅 기어와 DC 모터 자체 기어의 백래시도 있을 겁니다.

로드에 힘을 가하며 정확도 테스트하면 오차가 더 커지겠지만, PID 컨트롤러 튜닝으로 개선 가능합니다.

이상으로 튜토리얼을 마칩니다. 즐겁게 보셨고 새로운 것을 배우셨기를 바랍니다.

제조공정

  1. 보드카: 고대 증류에서 현대 생산까지
  2. 스마트 펜: 라즈베리파이와 IMU로 실시간 필기·모션 추적
  3. 자동 납땜 로봇팔: 정밀도, 속도, DIY 제작 가이드
  4. 크로스컨트리 압연기: 강철 생산 및 주요 설비 개요
  5. 아두이노 다기능 RC 송신기 직접 만들기: 단계별 DIY 가이드
  6. 재고 손실(수축) 이해와 감소: 창고 성공을 위한 핵심 요소
  7. 클래식 뷰마스터 입체 뷰어의 역사와 생산
  8. 마커의 역사, 생산, 미래: 고대 잉크에서 현대 혁신까지
  9. 게이밍용 커스텀 아두이노 조이스틱 스티어링 휠 만들기
  10. 아날로그 센서로 정밀 압력 및 공기유속 측정 장치 만들기