일반 DC 모터를 커스텀 360° 서보로 변환하기 – 단계별 가이드
이 튜토리얼에서는 일반 DC 모터를 다양한 기능을 갖춘 독립형 커스텀 서보 모터로 바꾸는 방법을 배워보겠습니다. 일반 서보가 180도 또는 270도 제한된 동작 범위를 가진 것과 달리, 이 서보는 360도 무제한 범위를 가지며 회전 범위를 필요한 값으로 조절할 수도 있습니다.
꽤 유용하죠. 게다가 서보의 중심점(Center point)도 조절할 수 있습니다. 즉, 중심점과 회전 범위를 동시에 조정 가능합니다.
다음 영상을 시청하거나 아래의 텍스트 튜토리얼을 읽어보세요.
또 다른 기능은 감도, 즉 서보가 입력에 얼마나 빨리 반응할지 조절할 수 있다는 점. 입력 방식으로는 세 가지 모드를 지원합니다.
포텐쇼미터나 아날로그 전압 입력으로 제어, RC 송신기로 제어, PC/노트북 시리얼 포트로 시리얼 모니터에 각도 값을 입력해 제어. 이들을 동시에 사용할 수도 있습니다. 시리얼 모니터로 값을 입력하면서 RC 송신기로 수동 조작도 가능합니다. 서보 모터는 항상 현재 위치를 알 수 있고 시리얼 모니터에서도 확인 가능합니다.
이 서보 모터 기능 목록의 하이라이트는 연속 회전 모드입니다. 맞습니다, 연속 회전 모드에서도 서보 모터 위치를 제어하고 추적할 수 있습니다. 무한 회전 횟수로 서보 모터 샤프트를 원하는 위치로 보낼 수 있습니다.
이 모든 게 12비트 인코더인 AS5600 자기 회전 위치 센서와 DC 모터 구동을 위한 PID 제어 구현 덕분입니다.
자체 마이크로컨트롤러와 DC 모터를 독립형 서보 모터로 만들기 위한 모든 것을 포함한 커스텀 서보 모터 컨트롤러 보드를 만들었습니다.
출력 샤프트 중심(특정 자석 포함)에 보드를 위치시키고, 최대 3.5A 정격 전류의 어떤 크기 DC 모터든 연결, 12V로 전체 시스템 전원 공급하면 끝. 위에 나열된 모든 기능을 가진 서보 모터가 일반 DC 모터에서 탄생합니다.
이제 이 커스텀 서보 모터 제작 전 과정을 안내해드리니 여러분도 직접 만들어보실 수 있습니다. 서보 모터 작동 원리, 폐루프 컨트롤러, PID 컨트롤러, 커스텀 PCB와 기어박스 설계, 코드 설명까지 다룹니다.
서보 모터 작동 원리
서보 모터 작동 원리를 설명하기 위해 일반적인 RC 서보 모터를 분해해 내부를 살펴보겠습니다.
작은 DC 모터, 컨트롤러 보드, 포텐쇼미터, 3선 연결(전원 2선, 입력 신호 1선)이 보입니다. DC 모터 속도 감속과 토크 증가를 위한 기어들도 있습니다.
이게 대부분의 RC/하비 서보 모터의 전형적인 구성입니다. 포텐쇼미터는 DC 모터 출력 샤프트에 연결돼 위치 센서 역할을 하며, 컨트롤러에 서보 모터 샤프트의 현재 위치를 알려줍니다. 컨트롤러 보드는 입력 신호(목표 위치)와 포텐쇼미터 피드백인 실제 위치를 기반으로 DC 모터를 제어합니다. 이게 폐루프 제어 시스템입니다.
입력 신호(목표 위치)와 위치 피드백 센서에서 얻는 모터 실제 위치를 비교합니다. 발생하는 차이(오차)를 컨트롤러가 처리해 모터에 명령하여 목표 위치에 도달할 때까지 움직이게 합니다.
커스텀 서보 모터 만들기
일반 RC 서보보다 큰 DC 모터로 우리만의 서보 모터를 만들고 싶다면 같은 폐루프 제어 시스템을 구현하면 됩니다.
출력 샤프트에 어떤 방식으로든 위치 센서를 부착하고, DC 모터를 구동할 마이크로컨트롤러만 있으면 됩니다.
위치 센서로는 RC 서보에서 본 단순 포텐쇼미터를 쓰는 게 가장 간단합니다. 하지만 이런 포텐쇼미터는 회전 범위가 270도로 제한돼 서보 모터 회전 범위도 직접 제한됩니다. 다회전 포텐쇼미터 등 더 나은 범위와 해상도를 주는 다른 유형도 있지만 여전히 회전 제한이 있습니다.
서보 모터에 무제한 회전 범위가 필요하다면 인코더를 써야 합니다. 인코더는 샤프트 각도 위치를 무제한 회전으로 추적하는 전기기계 장치입니다. 증분/절대 타입, 감지 기술(광학, 자기, 정전용량) 등 다양한 인코더가 있으며 각자 장단점이 있습니다.
AS5600 인코더 – 자기 회전 위치 센서
자기 인코더, 즉 AS5600 자기 회전 위치 센서를 선택했습니다. 매우 콤팩트하고 구현하기 쉬우면서 높은 정밀도(해상도)를 제공하기 때문입니다. 이 마이크로칩이 얼마나 작은지 보세요.
내장 홀 이펙트 센서로 자기장 방향 변화를 감지합니다. 출력 샤프트에 자석을 붙이고 마이크로칩에서 0.5~3mm 거리 두고 배치하면 됩니다.
모터 샤프트와 자석이 회전하면 홀 이펙트 센서가 자기장 방향 변화를 포착합니다. 내장 12비트 A/D 컨버터로 AS5600은 회전당 4096 위치, 즉 360도 회전을 출력합니다.
각도 위치 변화를 최소 0.0878도까지 감지할 수 있습니다. 꽤 인상적이고, 저렴하고 구하기 쉬워 커스텀 서보 모터에 적합한 선택입니다.
그 외에 마이크로컨트롤러와 DC 모터 드라이버가 필요합니다. DRV8871 DC 모터 드라이버(최대 3.5A 감당)와 Atmega328 마이크로컨트롤러를 선택했습니다.
Atmega328은 표면 실장(SMD) 버전을 썼습니다. DIP 버전보다 훨씬 콤팩트해 서보가 독립형 장치로 작동할 수 있게 모든 것을 담을 최소 크기의 커스텀 PCB를 만드는 게 목표였으니까요.
커스텀 서보 모터 회로도
이 커스텀 서보 모터의 완전한 회로도입니다.
필요한 부품은 아래 링크에서 구매할 수 있습니다:
- AS5600 자기 인코더 ………………….. Amazon / AliExpress
- DRV8871 DC 모터 드라이버 ……………….…. Amazon / AliExpress
- Atmega328p-AU …………………………………. Amazon / AliExpress
- 16MHz 크리스탈 오실레이터 …………………….. Amazon / AliExpress
- AMS1117 5V 전압 레귤레이터 ………..…. Amazon / AliExpress
- 3386P 정방형 포텐쇼미터 ………………. Amazon / AliExpress
- 커패시터 0805 키트 …………………………….…. Amazon / AliExpress
- 12V DC 모터 – ~50 RPM …………….……. Amazon / AliExpress
공개: 위 링크는 제휴 링크입니다. 아마존 어소시에이트로서 자격 있는 구매로부터 수익을 얻습니다.
Atmega328 마이크로컨트롤러와 권장 최소 회로(16MHz 오실레이터, 몇 개 커패시터, 저항)가 있습니다.
마이크로컨트롤러와 5V 필요 부품 전원용으로 AMS1117 전압 레귤레이터를 써서 12V 입력을 5V로 낮춥니다.
AS5600 위치 센서와 권장 회로(커패시터 2개, I2C 통신용 풀업 저항 2개)가 있습니다.
DRV8871 DC 모터 드라이버는 전류 제한용 저항 1개, 디커플링 커패시터 2개만 필요. 아날로그 입력에 연결된 포텐쇼미터 두 개로 회전 범위와 서보 감도 조절. 푸시 버튼으로 서보 중심점 설정, 2위 DIP 스위치로 서보 작동 모드 선택. 서보 입력용 핀 헤더(아날로그 전압 입력 또는 RC 수신기 디지털 PWM 입력, 5V, 그라운드). SPI 프로토콜과 시리얼 포트로 마이크로컨트롤러 프로그래밍용 핀 헤더도 있습니다.
이 회로와 워크플로 요약: 입력(목표 각도 위치)은 두 핀으로 받으며 아날로그 전압(포텐쇼미터) 또는 디지털 PWM 신호(RC 수신기)일 수 있습니다. 입력이 마이크로컨트롤러로 들어가 인코더(AS5600 위치 센서)가 감지한 실제 각도 위치와 비교됩니다. 이 센서는 I2C 프로토콜로 마이크로컨트롤러와 통신합니다.
마이크로컨트롤러가 연산해 오차를 계산하고, 그에 따라 DRV8871 드라이버에 PWM 신호를 보내 DC 모터를 목표 위치까지 구동합니다.
전체 회로는 12V로 구동되며, AMS1117 전압 레귤레이터가 마이크로컨트롤러와 다른 부품용 5V를 적절히 공급합니다.
PCB 설계
회로도에 따라 PCB를 최대한 작게 설계했고, 40x40mm가 나왔습니다.
인코더를 하단에, PCB 중심점 정확히 중앙에 배치해 출력 샤프트에 쉽게 장착하고 정렬할 수 있게 했습니다.
다른 모든 부품은 반대쪽에 배치해 인코더와 출력 샤프트에 간섭하지 않게 했습니다.
PCBWay에서 주문했습니다. 거버 파일 업로드, PCB 속성 선택, 합리적 가격 주문.
4층 설계(중간 층은 GND)로 가격이 좀 오르지만 기본 속성 유지, 색상만 흰색으로, 표면 처리는 추가 비용 없이 침금(Immersion gold) 허용에 체크했습니다.
거버 파일은 PCBWay 프로젝트 공유 커뮤니티에서 다운로드 및 직접 주문 가능합니다.
며칠 후 PCB가 도착했습니다. 품질 훌륭하고 설계와 정확히 일치하며 침금 표면 처리로 나왔습니다.
이제 부품 납땜을 진행합니다. 표시 LED, 커패시터, 저항 같은 작은 부품부터 시작했습니다.
사실 이렇게 작은 SMD 부품 납땜은 처음이라 정말 서툴렀습니다.
가장 어려웠던 건 Atmega328 마이크로컨트롤러 납땜이었습니다. 핀이 정말 작고 서로 가까이 있어 힘들었지만 어찌어찌 해냈습니다.
AS5600 인코더 마이크로칩은 PCB 뒷면에 납땜하기 쉬웠고, DIP 스위치, 포텐쇼미터, 터미널 블록, 핀 헤더 같은 큰 스루홀 부품들도 수월했습니다.
어쨌든 최종 컨트롤러 보드 모습입니다. 나름 괜찮게 나왔습니다.
이제 DC 모터와 이 컨트롤러 보드에 맞는 기어박스를 만들 차례입니다.
커스텀 서보 3D 모델
이 커스텀 서보 모터용 기어박스를 Onshape로 설계했습니다. 기어박스 설계는 당연히 DC 모터에 의존합니다. 컨트롤러 보드와 조합해 어떤 크기 DC 모터든 사용할 수 있다고 했죠.
여기선 지름 37mm, 내장 기어박스로 50 RPM 출력하는 DC 모터를 썼습니다. 50 RPM은 서보 모터에 좋은 속도지만 토크를 더 내려고 3배 감속 기어박스를 만들었습니다. 3D 프린터로 만들기 쉽고 효율적인 헤링본 기어를 사용했습니다.
물론 DC 모터와 원하는 출력 속도에 따라 이 기어박스 설계를 마음껏 바꿀 수 있습니다. 무한한 가능성이 있습니다.
컨트롤러 보드를 기어박스 후면에 놓고 출력 샤프트 중심에 완벽하게 정렬시켰습니다.
DC 모터 샤프트를 직접 출력으로 쓰고 싶으면 1:1 기어 세트를 써서 샤프트 위치를 정확히 추적하게 할 수 있습니다. 벨트 시스템도 가능합니다. 무한한 기어박스 설계 가능성이 있습니다.
3D 모델 및 STL 파일 다운로드
이 커스텀 서보 모터의 3D 모델을 Onshape에서 웹 브라우저로 확인하고 탐색할 수 있습니다. (Onshape 계정 필요, 가정용 무료 계정 생성 가능)
물론 3D 모델 다운로드와 3D 프린팅용 STL 파일도 여기서 받을 수 있습니다:
STEP 파일:
STL 파일:
커스텀 서보 조립
이 빌드용 3D 프린팅 부품들이 준비됐으니 서보 모터 조립을 시작하겠습니다.
이들과 M3 볼트, 나사 인서트, 베어링이 필요합니다.
먼저 DC 모터를 베이스 플레이트에 M3 볼트 8mm로 고정합니다.
두 기어를 장착합니다. 작은 기어는 DC 모터 샤프트에 직접, 큰 기어는 서보 출력이 됩니다. 출력 샤프트는 두 부분으로 구성됩니다.
출력 샤프트 양쪽에 나사 인서트를 박았습니다. 한 쪽은 기어 연결용, 다른 쪽은 서보 출력에 물건 부착용. 작은 기어에도 나사 인서트를 박아 DC 모터 샤프트 고정용으로 씁니다. 이제 짝지어진 기어를 위치에 슬라이드해 넣습니다. 헤링본 기어라 한 번에 둘 다 슬라이드해 넣어야 합니다. 하나씩 넣으면 맞물리지 않습니다.
그럽 스크류로 작은 기어를 DC 모터 샤프트에 고정. 12V를 DC 모터에 걸어 기어 세트가 제대로 작동하는지 확인.
사이드 패널, 출력 샤프트용 볼 베어링, 탑 커버를 넣어 기어박스 조립 마무리.
백 플레이트에 M3 나사 인서트를 박고 20mm M3 볼트로 전체 어셈블리 고정. 기어박스 다시 테스트, 잘 작동합니다. 출력 샤프트가 후면에서 회전하는 게 보이고, 여기에 AS5600 인코더가 추적할 자석을 끼워야 합니다.
컨트롤러 보드를 기어박스에 M2 볼트와 너트로 고정. AS5600 위치 센서가 자석과 완벽하게 정렬돼 출력 샤프트 회전 시 자기장 변화를 제대로 측정합니다.
여기서 영구 자석의 자화 방향이 매우 중요합니다. 축방향 또는 지름방향 자화 여부에 따라 자석을 AS5600 IC에 수직 또는 평행하게 배치해야 합니다.
자석 방향을 바꿔야 했습니다. 원래 자화 방향이 AS5600 인코더가 측정하기에 적절하지 않았습니다.
DC 모터에 전선 두 개 납땜해 터미널 블록으로 컨트롤러에 연결. 전원은 파워 터미널 블록에 전선 두 개 연결하고 반대쪽에 DC 파워 커넥터 달아 12V 전원 공급기와 연결. 이제 커스텀 서보 모터 완성입니다.
컨트롤러 프로그래밍
이제 이 서보에 생명을 불어넣을 차례, 컨트롤러를 프로그래밍합니다. 먼저 ATmega328p 마이크로컨트롤러에 부트로더를 굽습니다. 부트로더 없으면 우리가 보낼 코드/언어를 이해할 수 없습니다.
부트로더 굽기
ATmega328p에 부트로더를 굽기 위해 아두이노 보드가 필요합니다. 전 아두이노 나노를 쓰겠습니다.
SPI 통신을 쓰니 아두이노 보드와 우리 컨트롤러 보드의 적절한 SPI 핀을 연결합니다.
아두이노 IDE에서 ArduinoISP 예제 스케치를 열고 아두이노 나노에 업로드. 이 코드로 아두이노 나노가 ATmega328 마이크로컨트롤러에 부트로더를 굽는 프로그래머가 됩니다.
Tools 메뉴에서 Programmer를 "Arduino as ISP"로 선택하고 "Burn Bootloader" 클릭.
부트로더 굽는 동안 아두이노 나노 LED가 많이 깜빡이며, 성공적으로 완료됩니다.
코드 업로드
부트로더 굽기 완료 후 USB to UART 인터페이스 모듈로 컨트롤러 보드에 코드를 업로드합니다.
컨트롤러 보드에 회로도처럼 쉽게 연결할 수 있는 전용 핀이 있습니다.
이 커스텀 서보용 코드를 열고 업로드합니다. 그 전에 AS5600 센서와 PID 제어 라이브러리를 아두이노 IDE 라이브러리 매니저에서 설치해야 합니다. 업로드 버튼 누르면 USB to UART 모듈을 통해 ATmega328 컨트롤러에 코드가 기록됩니다.
이제 커스텀 서보 모터 완성. 포텐쇼미터 연결해 테스트해보세요. 참고로 아날로그 입력은 컨트롤러 보드의 "S" 핀으로 가야 합니다. "A" 핀이 아닙니다.
PCB 설계 시 이 두 핀을 ATmega328에 잘못 연결했습니다. DIP 스위치로 아날로그 입력 모드 선택하고 서보 전원 켭니다.
자, 포텐쇼미터 아날로그 입력으로 서보 모터 위치를 제어할 수 있습니다. 일반 DC 모터를 서보 모터로 변환하는 데 성공했습니다.
소스 코드
이제 이 커스텀 서보 모터 코드를 살펴보겠습니다.
/*
* Custom-built Servo Motor - 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 = 2; //speed it gets there
double Ik1 = 0;
double Dk1 = 0.025;
//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 centerSet 7
#define inputSwitch 3
#define modeSwitch 4
int ch1Value;
int encoderValue, inputValue, pwmValue;
String inString = ""; // string to hold input
int centerAngle = 2047; // 180 degrees
int angleDifference = 0;
int angleValue = 0;
int leftLimit = 30;
int rightLimit = 4067;
int rangeAdjustment = 0;
float sensitivityAdjustment = 0;
float angle = 0;
int quadrantNumber = 2;
int previousQuadrantNumber = 3;
int numberOfTurns = 0;
float totalAngle = 0;
int error = 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(centerSet, INPUT_PULLUP);
pinMode(inputSwitch, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
myPID.SetMode(AUTOMATIC);
// PID Setup
myPID.SetOutputLimits(-255, 255);
myPID.SetSampleTime(20);
}
void loop() {
// Read encoder value - current position
encoderValue = as5600.readAngle();
// Continuous rotation mode
if (digitalRead(modeSwitch) == 0) {
// Enter desired angle for the servo to go to through the serial monitor
while (Serial.available() > 0) {
int inChar = Serial.read();
if (isDigit(inChar)) {
// convert the incoming byte to a char and add it to the string:
inString += (char)inChar;
}
// if you get a newline, print the string, then the string's value:
if (inChar == '\n') {
Setpoint = inString.toInt(); // Setpoint - desired angle
// clear the string for new input:
inString = "";
}
}
if (digitalRead(inputSwitch) == 0) {
// Potentiometer as input
inputValue = analogRead(A0);
if (inputValue < 400) { Setpoint = Setpoint - 0.3; }
if (inputValue < 300) { Setpoint = Setpoint - 0.3; }
if (inputValue < 200) { Setpoint = Setpoint - 0.3; }
if (inputValue > 600) { Setpoint = Setpoint + 0.3; }
if (inputValue > 700) { Setpoint = Setpoint + 0.3; }
if (inputValue > 800) { Setpoint = Setpoint + 0.3; }
} else if (digitalRead(inputSwitch) == 1) {
inputValue = pulseIn(ch1, HIGH, 30000); // RC receiver as input
if (inputValue < 1450) { Setpoint--; }
if (inputValue < 1350) { Setpoint--; }
if (inputValue < 1250) { Setpoint--; }
if (inputValue < 1150) { Setpoint--; }
if (inputValue > 1550) { Setpoint++; }
if (inputValue > 1650) { Setpoint++; }
if (inputValue > 1750) { Setpoint++; }
if (inputValue > 1850) { Setpoint++; }
}
// Convert encoder RAW values into angle value
angle = encoderValue * 0.087890625;
// Quadrant 1
if (angle >= 0 && angle <= 90) { quadrantNumber = 1; }
// Quadrant 2
if (angle >= 90 && angle <= 180) { quadrantNumber = 2; }
// Quadrant 3
if (angle >= 180 && angle <= 270) { quadrantNumber = 3; }
// Quadrant 4
if (angle >= 270 && angle <= 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 (totalAngle >= 0) {
totalAngle = (numberOfTurns * 360) + angle;
} else {
totalAngle = (numberOfTurns * 360) + angle;
}
// Establish Input value for PID
Input = totalAngle;
}
// Limited Rotation Mode
else if (digitalRead(modeSwitch) == 1) {
rangeAdjustment = analogRead(A1);
leftLimit = 0 + 30 + rangeAdjustment;
rightLimit = 4097 - 30 - rangeAdjustment;
if (digitalRead(inputSwitch) == 0) {
// Analog input - Potentiometer
// Get value from potentiometer
inputValue = analogRead(A0);
if (inputValue < 15) { inputValue = 15; }
if (inputValue > 1008) { inputValue = 1008; }
Setpoint = map(inputValue, 15, 1008, -255, 255);
} else if (digitalRead(inputSwitch) == 1) {
// Digital input - RC transmitter
inputValue = pulseIn(ch1, HIGH, 30000); // Read RC receiver as input
Setpoint = map(inputValue, 1000, 2000, -255, 255);
}
// Set center angle
if (digitalRead(centerSet) == LOW) {
centerAngle = encoderValue;
angleDifference = 2047 - encoderValue;
delay(1000);
}
// Adjust angle value according to the center point (angleDifference)
if (centerAngle < 2047) {
angleValue = encoderValue + angleDifference;
if (encoderValue < 4097 && encoderValue > (4096 - angleDifference)) {
angleValue = encoderValue - 4097 + angleDifference;
}
}
if (centerAngle > 2047) {
angleValue = encoderValue + angleDifference;
if (encoderValue >= 0 && encoderValue < abs(angleDifference)) {
angleValue = encoderValue + 4097 + angleDifference;
}
} else if (centerAngle == 2047) {
angleValue = encoderValue;
}
// Establish Input value for PID
Input = map(angleValue, leftLimit, rightLimit, -255, 255);
}
// Adjusting sensitivity
Pk1 = analogRead(A2) * 0.002;
myPID.SetTunings(Pk1, Ik1, Dk1);
// Run PID process to get Output value
myPID.Compute();
// Move right
if (Output > 1 ) {
pwmValue = Output;
if (pwmValue < 30 && pwmValue > 5) { pwmValue = pwmValue + 30; }
if (pwmValue <= 5) { pwmValue = 0; }
digitalWrite(motor_IN1, LOW);
analogWrite(motor_IN2, pwmValue);
}
// Move left
else if (Output < 1 ) {
pwmValue = abs(Output);
if (pwmValue < 30 && pwmValue > 5) { pwmValue = pwmValue + 30; }
if (pwmValue <= 5) { pwmValue = 0; }
analogWrite(motor_IN1, pwmValue);
digitalWrite(motor_IN2, LOW);
}
// Do not move
else if (Output > -1 && Output < 1) {
pwmValue = 0;
digitalWrite(motor_IN1, LOW);
digitalWrite(motor_IN2, LOW);
}
//Serial.print(Setpoint);
//Serial.print("\t");
//Serial.println(totalAngle);
}
코드 개요
루프 시작 시 인코더 값(축 현재 위치)을 읽습니다.
// Read encoder value - current position
encoderValue = as5600.readAngle();
연속 회전 모드라면 시리얼 모니터에서 값을 받아 세트포인트(PID 컨트롤러 목표 각도)로 사용합니다.
// Enter desired angle for the servo to go to through the serial monitor
while (Serial.available() > 0) {
int inChar = Serial.read();
if (isDigit(inChar)) {
// convert the incoming byte to a char and add it to the string:
inString += (char)inChar;
}
// if you get a newline, print the string, then the string's value:
if (inChar == '\n') {
Setpoint = inString.toInt(); // Setpoint - desired angle
// clear the string for new input:
inString = "";
}
}
입력 모드가 포텐쇼미터면 아날로그 입력을 읽고, 얼마나 돌렸는지에 따라 세트포인트 값을 보정합니다.
if (digitalRead(inputSwitch) == 0) {
// Potentiometer as input
inputValue = analogRead(A0);
if (inputValue < 400) { Setpoint = Setpoint - 0.3; }
if (inputValue < 300) { Setpoint = Setpoint - 0.3; }
if (inputValue < 200) { Setpoint = Setpoint - 0.3; }
if (inputValue > 600) { Setpoint = Setpoint + 0.3; }
if (inputValue > 700) { Setpoint = Setpoint + 0.3; }
if (inputValue > 800) { Setpoint = Setpoint + 0.3; }
}
RC 수신기 입력이어도 같은 방식으로 세트포인트 보정.
RAW 인코더 값을 각도 값으로 변환하고, if문들로 현재 축 위치가 어느 사분면에 있는지 추적합니다.
// Convert encoder RAW values into angle value
angle = encoderValue * 0.087890625;
// Quadrant 1
if (angle >= 0 && angle <= 90) { quadrantNumber = 1; }
// Quadrant 2
if (angle >= 90 && angle <= 180) { quadrantNumber = 2; }
// Quadrant 3
if (angle >= 180 && angle <= 270) { quadrantNumber = 3; }
// Quadrant 4
if (angle >= 270 && angle <= 360) { quadrantNumber = 4; }
이 정보로 축 회전과 온전한 한 바퀴 돌 때를 추적. 총 각도가 PID 컨트롤러의 입력 값이 됩니다.
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 (totalAngle >= 0) {
totalAngle = (numberOfTurns * 360) + angle;
} else {
totalAngle = (numberOfTurns * 360) + angle;
}
// Establish Input value for PID
Input = totalAngle;
제한 회전 모드라면, 먼저 회전 범위 조절용 포텐쇼미터 값을 읽어 좌우 회전 한계를 조정합니다.
rangeAdjustment = analogRead(A1);
leftLimit = 0 + 30 + rangeAdjustment;
rightLimit = 4097 - 30 - rangeAdjustment;
입력 모드가 포텐쇼미터면 그 값을 PID 컨트롤러 세트포인트로 사용.
if (digitalRead(inputSwitch) == 0) {
// Analog input - Potentiometer
// Get value from potentiometer
inputValue = analogRead(A0);
if (inputValue < 15) { inputValue = 15; }
if (inputValue > 1008) { inputValue = 1008; }
Setpoint = map(inputValue, 15, 1008, -255, 255);
}
입력 모드가 RC 수신기면 수신기에서 들어오는 PWM 값을 읽어 세트포인트로 사용.
else if (digitalRead(inputSwitch) == 1) {
// Digital input - RC transmitter
inputValue = pulseIn(ch1, HIGH, 30000); // Read RC receiver as input
Setpoint = map(inputValue, 1000, 2000, -255, 255);
}
중심점 변경을 위해 푸시 버튼 눌림 감지, 그 위치를 새 중심점으로 저장.
// Set center angle
if (digitalRead(centerSet) == LOW) {
centerAngle = encoderValue;
angleDifference = 2047 - encoderValue;
delay(1000);
}
이에 따라 인코더 실제 읽기 값을 새 중심점과 옛 중심점 사이 각도 차이만큼 오프셋 조정. 이 값을 PID 컨트롤러 입력 값으로 사용.
if (centerAngle > 2047) {
angleValue = encoderValue + angleDifference;
if (encoderValue >= 0 && encoderValue < abs(angleDifference)) {
angleValue = encoderValue + 4097 + angleDifference;
}
} else if (centerAngle == 2047) {
angleValue = encoderValue;
}
// Establish Input value for PID
Input = map(angleValue, leftLimit, rightLimit, -255, 255);
다른 포텐쇼미터 아날로그 입력으로 PID 컨트롤러 비례 이득 조정, 마지막으로 PID 프로세스 실행해 출력 값 얻음.
// Adjusting sensitivity
Pk1 = analogRead(A2) * 0.002;
myPID.SetTunings(Pk1, Ik1, Dk1);
// Run PID process to get Output value
myPID.Compute();
이 출력 값으로 DC 모터를 PWM 신호로 좌/우/정지 구동. PID 컨트롤러 출력 값, 즉 인코더가 읽은 실제 위치와 목표 위치 사이 오차에 따라 결정.
// Move right
if (Output > 1 ) {
pwmValue = Output;
if (pwmValue < 30 && pwmValue > 5) { pwmValue = pwmValue + 30; }
if (pwmValue <= 5) { pwmValue = 0; }
digitalWrite(motor_IN1, LOW);
analogWrite(motor_IN2, pwmValue);
}
이상으로 영상 마칩니다. 코드가 잘 최적화되지 않았고 개선 여지가 있음을 밝혀둡니다.
이 프로젝트 재현 시 트러블슈팅에 대비하세요. 특히 작은 SMD 부품 납땜에서 많은 게 잘못될 수 있습니다.
처음부터 작동하지 않았습니다. 초기엔 PCB에 잘못된 연결이 있었고, 업데이트된 PCB를 다시 주문했지만 몇 번 더 시도해야 작동했습니다.
이 섹션은 아직 작성 중입니다. 나중에 다시 확인해주세요….