DPM(직접 포트 조작)의 경우
프로그래밍이 대중화됨에 따라 직접 포트 조작(DPM)과 같은 기본적이지만 중요한 코드를 실패하거나 잊어버리는 경우가 있습니다. 일부 프로그래머는 이러한 명령어가 읽기 어렵고 초보자에게 혼란스러울 수 있으므로 보다 친숙한 구조와 함께 사용하면 안 된다고 주장합니다. 코드의 간결함과 효율성을 유지해 주는 도구를 사용하지 않을 이유가 있을까요?
예를 들어, 8비트 마이크로 컨트롤러의 각 포트에는 포트 활동을 제어하기 위해 3개의 8비트 레지스터가 필요합니다. 이러한 레지스터는 입력/출력 방향을 위한 DDRx, 핀 논리 레벨 제어를 위한 PORTx, 포트의 현재 핀 상태가 저장되는 PINx입니다. 포트 C를 예로 들어 자세히 살펴보겠습니다. 프로그래머는 설정 도중 포트 C 핀을 출력으로 설정하고 초기 상태를 LOW로 설정하고 싶을 수 있습니다. 이 경우 “for loop”를 작성해야 하는데 3줄~ 4줄의 반복 코드가 0부터 7까지의 핀을 반복해야 하므로 총 32줄의 코드가 실행되어야 합니다. pinMode() 및 digitalWrite()와 같은 라이브러리 함수를 루프 내에서 사용하면 "이면"의 활동이 더 많이 초래되어 다수의 코드가 추가적으로 실행됩니다.
궁극적으로 포트 C에 대한 8개의 방향 레지스터 비트(DDRC)가 HIGH로 설정되고 포트 C에 대한 8개의 논리 레벨 레지스터 비트(PORTC)가 LOW로 설정됩니다. 다음의 직접 포트 레지스터 명령을 사용하면 비트 HIGH 또는 LOW를 설정하여 정확하게 동일한 작업을 수행할 수 있습니다.
DDRC = 0xFF; //포트 C 핀을 OUTPUTS로 설정 (이진 DDRD = 0b11111111;)
PORTC = 0x00; //포트 C 핀을 LOW로 설정(이진 PORTC = 0b00000000;)
포트 A를 입력으로 설정:
DDRA = 0x00; //포트 A 핀을 INPUTS로 설정(이진 DDRA = 0b00000000;)
마지막으로, 포트 D를 혼합된 입력 및 출력으로 설정:
DDRD = 0x0F; //포트 D 상위 4개 비트를 INPUTS로 설정하고 하위 4개 비트를 OUTPUTS로 설정(이진 DDRD = 0b00001111);
0xFF와 같은 16진수 값을 사용하면 레지스터의 비트 값 할당에 대한 흥미로운 점을 이해할 수 있습니다. “0x”가 16진수 인디케이터이면, 첫 번째 숫자 ‘F’는 8비트 레지스터의 상위 4비트를 나타내며 두 번째 숫자 ‘F’는 하위 4비트를 나타냅니다.
비트 설정 HIGH 또는 LOW의 가능한 모든 조합을 16진수 또는 바이너리 리터럴(숫자)로 표현할 수 있습니다. 바이너리 리터럴의 경우 코드 작성 시 8개 비트를 모두 볼 수 있기 때문에 더 시각적입니다. 컴파일러에서 지원되는 경우 두 가지 모두 가능하지만, 16진수가 더 짧고 보기에 간결합니다.
참고: 바이너리 리터럴 사용은 C/C++에서 범용 표준이 아닙니다.
한 단계 더 나아가기
설정에서 포트 A 및 C를 구성하면, 메인 루프에 DPM을 사용한 몇 줄의 추가적인 코드만으로 디지털 장치를 읽고 다른 디지털 장치를 조작할 수 있습니다. 실제로, 디지털 조이스틱으로 제어되고 이동 거리 리미트 스위치가 있는 두 개의 스테퍼 모터를 최소한의 레지스터 명령과 조회 테이블로 손쉽게 프로그래밍할 수 있습니다. 이 시나리오의 하드웨어 설정이 그림 1에 표시되어 있습니다.
그림 1: 마이크로 컨트롤러 포트 A와 C가 강조 표시된 조이스틱/스테퍼 모터 하드웨어 설정 (이미지 출처: DigiKey)
하드웨어:
네 개의 상시 개방(NO) 접점은 VCC에 연결된 단일 디지털 조이스틱이 포트 A의 핀 0-3에 연결됩니다. 출력은 마이크로 컨트롤러에서 LOW로 풀링됩니다. 접점이 닫히면 포트 핀이 HIGH가 됩니다. 접점은 UP, DOWN, LEFT 및 RIGHT 제어를 나타내며 두 개의 인접 접점이 동시에 활성화되는 것을 허용하도록 구성된 경우, 결과적으로 8개의 스위치 출력 조합이 가능합니다. 아홉 번째 출력은 조이스틱이 가운데에 있고 모든 접점이 열린 상태인 ALL STOP을 나타냅니다.
NO 접점이 접지에 연결된 네 개의 리미트 스위치 역시 조이스틱 UP, DOWN, LEFT, RIGHT 구성에 해당하는 포트 A의 나머지 핀에 연결됩니다. 스위치 출력은 마이크로 컨트롤러에서 HIGH로 풀링됩니다. 접점이 닫히면 핀이 LOW가 됩니다.
스테퍼 모터 구동 기판은 세 개의 제어 핀을 HIGH 및 LOW로 변환하여 스텝, 방향 및 유지 기능을 구현함으로써 기계적 동작을 만듭니다. 이 예에서는 포트 C의 8개 비트 모두가 두 개의 구동기를 작동하기 위해 전용으로 사용되지만 두 개 핀은 사용되지 않습니다.
프로그래밍:
구동기에 올바른 출력을 생성하기 위해 4개의 조이스틱 비트를 8개의 구동기 비트로 변환하는 데 조회 테이블이 사용됩니다. 메인 루프에 있는 한 줄의 코드가 함수 ‘get_output()’을 호출하고 PINA 레지스터의 내용을 함수로 전달합니다. 함수에서 반환되는 값은 PORTC 레지스터에 직접 기록됩니다.
PORTC = get_output(PINA);
함수 내에서 조회 테이블 ‘lookup_output[ ]’에 액세스하고 인덱스된 값이 반환됩니다. 하지만 이 함수 내에서는 리미트 스위치와 관련된 다른 작업이 수행됩니다. ‘lookup_output[ ]’의 꺾쇠괄호 안에 포함된 조회 테이블 인덱스 값은 비트 변환 및 마스킹 표현식으로 표현됩니다. 결과 인덱스 변수는 입력 레지스터의 상위 4비트(리미트 스위치 값) 및 하위 4비트(조이스틱 값)의 bitwise AND입니다. 리미트 스위치 접점 중 닫힌 것이 있고 상위 4비트 중 제로 값이 있는 경우, 해당 하위 비트가 지워집니다.
참고: 4비트는 16개의 고유한 비트 조합을 나타낼 수 있으므로, 사용되지 않는 7개의 조회 테이블 인덱스 위치는 0x00으로 변환되어 오류를 방지합니다.
복사uint8_t get_output(uint8_t porta_val) { return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; }
예:
모든 리미트 스위치가 열려 있고 UP 및 RIGHT 위치에 있는 조이스틱의 경우, 0b11111001(또는 16진수 0xF9)의 바이너리 PINA 레지스터 값이 함수에 전달됩니다. 함수는 0b00001111(0x0F)의 bitwise AND를 사용하여 상위 4비트를 제로화하며 조회 테이블 인덱스 값으로 0b00001001(0x09)라는 결과가 제공됩니다. 다른 bitwise AND를 사용하여 이 결과를 시프트된 리미트 스위치 상태 값 0b00001111(0x0F)과 비교하면 이전 값이 변경되지 않고 0b00001001(0x09)이 최종 인덱스 값으로 반환됩니다. 이는 조회 테이블의 0b00100001(0x21)을 가리킵니다. 이것이 UP 및 RIGHT에 대한 스테퍼 구동기 변환입니다. 그림 2를 참조하십시오.
그림 2: 조이스틱이 UP 및 RIGHT 위치에 있을 때 포트 A 및 포트 C 입력의 마이크로 컨트롤러 해석 (이미지 출처: DigiKey)
UP 리미트 스위치가 도달하여 제로 비트가 되면, 시프트된 값은 0b00001111(0x0F)이 아니라 0b00000111(0x07)이 되며, 이는 해당 UP 조이스틱 값을 음수로 만들어서 최종 인덱스 값은 0b00001001(0x09)이 아니라 0b00000001(0x01)이 됩니다. 0b00000001(0x01)의 변환된 값은 조회 테이블에서 0x26입니다. 이것이 RIGHT에 대한 스테퍼 구동기 변환입니다. 그림 3을 참조하십시오.
그림 3: UP 리미트 스위치가 트립되어 있는 동안 조이스틱이 UP 및 RIGHT 위치에 있을 때 포트 A 및 포트 C 입력의 마이크로 컨트롤러 해석 (이미지 출처: DigiKey)
결론
프로그래머가 DPM을 포트 데이터의 구성, 읽기 또는 쓰기 중 어떤 용도로 사용하는지에 상관없이 코드가 최소화되기 때문에 충분한 가치가 있습니다. 표준 라이브러리 함수를 사용하여 같은 작업을 하려면 훨씬 많은 코드가 필요하며 가용 프로그램 메모리도 훨씬 더 많이 차지할 수 있습니다. 아래 제공된 코드는 벤치 테스트에 사용되는 ATMEGA328P 마이크로 컨트롤러의 메모리를 1% 미만으로 사용합니다. 코드에 대한 적절한 주석은 코드의 DPM 함수를 이해하는 데 매우 중요하며, 모든 수준의 프로그래머가 손쉽게 디버깅하는 데 도움이 됩니다.
DigiKey 하드웨어 예:
스테퍼 모터 - https://www.digikey.com/short/pdnfp4
스테퍼 컨트롤러 - https://www.digikey.com/short/pdnf4r
조이스틱 - https://www.digikey.com/short/pdnf57
리미트 스위치 - https://www.digikey.com/short/pdnfwm
샘플 코드:
복사const uint8_t lookup_output[16] = { 0x09, //Index 0 All Stop. Apply hold current 0x26, // Index 1 Right 0x34, // Index 2 Left 0x00, // Index 3 Unused 0x36, // Index 4 Down 0x0C, // Index 5 Down/Right 0x31, // Index 6 Down/Left 0x00, // Index 7 Unused 0x24, // Index 8 Up 0x21, // Index 9 Up/Right 0x0E, // Index 10 Up/Left 0x00, // Index 11 Unused 0x00, // Index 12 Unused 0x00, // Index 13 Unused 0x00, // Index 14 Unused 0x00 // Index 15 Unused }; void setup() { // Set all bits in port A direction register as INPUTs; // Limits (up, down, left, right) Joystick (up, down, left, right) DDRA = 0x00; // Set all bits of port C direction register as OUTPUTs; // Motor control (Not Used, Mot_1, Dir_1, En_1, Not Used, Mot_2, Dir_2, En_2 DDRC = 0xFF; } void loop() { //Send the port A values to the function. Write the return value to port C. PORTC = get_output(PINA); } /***** Input Value Translation Function *******/ uint8_t get_output(uint8_t porta_val) { // Compare the limit switch and joystick values. Retrieve and return the translated value.
return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; }
Have questions or comments? Continue the conversation on TechForum, Digi-Key's online community and technical resource.
Visit TechForum


