본문 바로가기

Hardware/MCU

[Cortex M3] 엔코더해석 결과를 일정시간간격으로 시리얼통신으로 전송하기

Cortex-M3 LM8962의 경우 적절한 가이드북이 아직 없더군요. 그런 경우 뭐 예제를 분석해보는 수 밖에요. 일단 복적은 제목에도 나와있지만, 일정시간간격(Timer Interrupt)을 가지고 엔코더를 해석해서 그 결과를 시리얼통신으로 전송하는 걸로 하겠습니다. 이미 myCortexM3 LM8962보드를 설치해서 사용하신 분들이라면 딱 예제3개를 한 덩어리로 묶었구만뭐~~ 하시겠지만, 사실 예제를 그대로 파는건 별로 재미없으니까 그냥 한번 합쳐본것도 있구요. 또 제가 수행할려고하는 목표가 딱 위 예제 3개에를 합쳐서 출발해야하기도 하거든요... 그런데, 물론 제가 다뤄봤다던지 공부했다는 프로세서가 얼마안되지만, (80c196, AVR, DSP2812) 이 CortexM3의 예제는 뭐라할까 좀 어색하더군요.... 음.. 역시 어색하다는 말이 맞는듯합니다. 일단 제가 (만든건 아니고, 수정했다고 하기도 좀 민망한) 편집한 예제에서 main문을 보시면

int main(void)
{
    SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_8MHZ);

    InitTIMERINT();
    InitUART();
    InitQEI();

    // Loop Start.
    while(1)
    {
    }
}

3번행에 나오는 저 SysCtlClockSet 이게 뭔지 하는 의문부터 들더군요. 당연히 검색했지만, 시스템의 클럭을 설정하는 거라고 하더라는...ㅜ.ㅜ.


그래서 예제폴더의 인클루드된 헤더화일들을 찾아보니 있긴 하더군요...


저렇게... 근데... 내용이 없어요...ㅜ.ㅜ 그냥 선언만 되어있더군요. .. 어디 있을까요..? 제가 못찾은 건지...ㅎㅎ ---> 헤헤 있더군요...sysctl.h랑 같은 폴더에 당연히 거기 있는건데.. 제가 멍청한 멘트를 날렸다는... (아래 irmus님께 감사 -> 역시 고수의 질책은 힘이 됩니다..ㅋㅋ) 그러나, 찾긴했는데 영~ 못알아보겠네요. ㅎㅎ 역시 결론은 차차 공부해야겠습니다..ㅋㅋ;;


저렇게 인자들은 찾았습니다. 뭐 주석만 봐도 대충이해되긴 하더군요. 그러나 정작 SysCtlClockSet 저게 하는 일이 코딩된 부분은 없더군요. 그래도 또 찾아봤는데요. API라는 저 글자...

마이크로프로세서가 진화 하면서 같이 변화된 것이 있다. 8비트 MPU에서 데이터시트를 보고 비트 제어를 하는 원시적인 코딩을 주로 했다면 32비트 특히 Cortex-M3로 오면서 전달 인수와 리턴값과 변수, define 정의 등을 요약해 놓은 API(Application Program Interface)를 이용하여 프로그램 하는 것으로 바뀌었다. 제공된 API는 데이터 시트를 일일이 분석하여 코딩 하지 않아도 하드웨어 제어에 손쉽게 입문 할 수 있으며 코딩 적응을 빠르게 하는 장점이 있다. 또한 PC에서 운영되었던 C/C++ 언어로 프로그램 수정을 극히 적게 하여 Cortex-M3 알고리듬으로 옮겨 올 수 있다.
ARM Processor의 특성 및 최신동향 08.11.14. 디지털 파워. 김형태 발췌


음.. API는 저런거인 모양입니다. 뭐 편하다고 하니(사실 잘 모르겠지만, 그냥 사용하지요. 틈틈히 그 의미를 공부해야겠습니다.) 일단 아까 그 메인문에서 시스템클럭을 8Mhz로 설정하고 -ㅎ~ 여기서 또 의문이 하나 생기긴 했습니다. Withrobot이 제공하는 설명서에는 50MHz로 동작한다고 되어있거든요. 그런데 왜 클럭은 8MHz일까요?... 2분주, 4분주 등등을 해도 50MHz로는 안가지 않습니까?^^ ㅎ... 모르는게 너무 많습니다... 쩝... 그리고 나서 타이머인터럽트와 시리얼통신, 엔코더해석부분을 초기화시키는 부분이 필요할 것입니다.

static void InitTIMERINT(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    TimerConfigure( TIMER0_BASE, TIMER_CFG_32_BIT_PER );
    TimerLoadSet( TIMER0_BASE, TIMER_A, SysCtlClockGet() / 100 );	// 10ms
    TimerIntRegister( TIMER0_BASE, TIMER_A, TimerIntHandler );
    IntMasterEnable();
    TimerIntEnable( TIMER0_BASE, TIMER_TIMA_TIMEOUT );
    TimerEnable( TIMER0_BASE, TIMER_A );
}

근데 희한하게 하드웨어 설정은 뭐 잘 모르겠지만, 해석은 또 대충되긴합니다. 일단 5번줄의 끝부분에 있는 숫자 '100'은 1/100의 시간 즉 10ms의 시간마다 인터럽트가 걸리도록 설정한 겁니다. 그리고, 그 인터럽트가 걸렸을때 수행할 함수가 6번줄 끝의 TimerIntHander로 설정되어있습니다.

다음으로 시리얼통신부분을 초기화합니다.

static void InitUART(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200, (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
}


6번줄의 115200이 Baud Rate를 맞춰준 부분입니다. 뭐 8비트고 어쩌고하는 설정도 있네요. 5번줄은 Withrobot의 설명서에 있는 회로도를 보면 시리얼통신의 Tx/Rx핀이 PA0/PA1에 연결되어있습니다. 아마 그걸 설정하는 듯합니다.

그 다음... 엔코더를 해석한다는 QEI설정이 필요하겠네요.

static void InitQEI(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_QEI);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);

    GPIOPinTypeQEI(GPIO_PORTC_BASE, GPIO_PIN_4 | GPIO_PIN_6);
    QEIConfigure(QEI0_BASE, (QEI_CONFIG_CAPTURE_A_B | QEI_CONFIG_NO_RESET | QEI_CONFIG_QUADRATURE | QEI_CONFIG_NO_SWAP), 0xffffffff);
    QEIVelocityConfigure(QEI0_BASE, QEI_VELDIV_1, SysCtlClockGet() / 100);

    QEIEnable(QEI0_BASE);
    QEIVelocityEnable(QEI0_BASE);
}


위 내용에서 엔코더의 A,B상은 PC4, PC6번 핀에 연결되도록하고, 4분주해서 사용한다는 말도 확인이 됩니다. 또한 엔코더에서 들어온 카운터값 뿐만 아니라 방향과 속도성분까지 주어진다는 설정도 확인할수있습니다.

위의 3개의 초기화코드를 보더라도 사실 포기(^^)하고 그냥 사용하겠다고 생각하면 뭐 편해보입니다. 게으름에 대한 변명입니다...ㅜ.ㅜ 이제 타이머인터럽트가 걸리면 수행할 TimerIntHandler 구문을 보겠습니다.

static void TimerIntHandler(void)
{
    unsigned long vel, pos;
    long dir;
    char buffer[BUFFER_LEN];

	// Clear interrupt flag
	TimerIntClear( TIMER0_BASE, TIMER_TIMA_TIMEOUT );

    dir = QEIDirectionGet(QEI0_BASE);
    vel = QEIVelocityGet(QEI0_BASE);
    pos = QEIPositionGet(QEI0_BASE);
    usnprintf(buffer, BUFFER_LEN, "%d,  %d,  %d; \n", dir, vel, pos);
    buffer[BUFFER_LEN - 1] = 0;
    UARTPutString(buffer);
}

음 위 코드에서는 방향(dir), 속도(vel), 위치(pos)를 시리얼로 보내는 것입니다. 13번행에 있는 데로 구성한 이유는 제가 데이터를 받아서 처리할 PC쪽 프로그램이 MATLAB이라서 그렇습니다. 받을 때부터 (Data1, Data2, Data3;) 의 형태로 받으면 아주 편하거든요.

일단, 보드의 외부전원을 연결하고 엔코더도 연결해야하고, 또 차후에 여러센서들도 연결해야하니까. 뭐 보드하나 준비해야겠네요..


저렇게 꾸몄습니다. JTAG부분의 선이 짧더군요..ㅜ.ㅜ... 그래서 중간에 선을 좀 더 길게 할려고했는데 때마침 제 주위에 저 커넥터가 없더군요.. 그래서 저런 좀 지저분한 작업을 했습니다. 그리고, 전원공급장치로는



를 연결했습니다. 어차피 myCortex-M3 LM8962는 3.3V 레귤레이터를 가지고 있다니까. 전 엔코더의 5V나 넣어줄겸해서 저걸 그냥 다이렉트로 연결했습니다... 하필 가지고 있는 엔코더가


5V짜리라서 Cortex에 5V 출력을 바로 물려도 될까? 하고 고민좀 했는데 어디선가 정 안되겠거든 5V 그냥 인가해도 된다길래 그냥 연결했습니다.


적당한 흔들림을 위해 저렇게 진자를 엔코더에 다시 연결했습니다.



- 단순 시리얼통신 테스트 ComPortMaster

에서 이야기했던 ComPortMaster입니다. 시리얼 통신을 수행할때 표시되어있는 StartCapture를 누르고 화일 저장창에서 꼭 txt화일로 저장을 합니다.


그리고 MATLAB에서 읽겠습니다. 물론 MATLAB도 시리얼 통신이 됩니다. 실제로도 테스트도 했는데요. 문제는 제가 시리얼 통신을 강제종료할려고 하니까 에러가 나면서 이후코드가 실행되지 않더군요. 음... 그래서 그냥 txt로 저장하기로했습니다. MATLAB쪽 코드가 좀 아름답게 다듬어지면 ComPortMaster도 필요없겠지요...^^


내가 ComPortMaster에서 바탕화면에 저장을 해서 MATLAB도 메인 폴더를 바탕화면으로 지정합니다.

그리고 test = load('123.txt'); 라고 명령어를 주면


저렇게 데이터가 들어와있는것을 확인할수있습니다. 음 MATLAB쪽 m-file을 보면
 
test = load('123.txt');
 
ts = 0.01;
scaleEnc = 360/2000;
test = [test(:,1), [test(:,2), test(:,3)]*scaleEnc];
[sizeTest, temp] = size(test);
 
t = [0:ts:ts*(sizeTest-1)]';
 
figure
plot(t,test(:,3))
grid on
hold on
xlabel('s (Second)');
ylabel('degree / degree per sencond');
plot(t,test(:,2).*test(:,1)/ts,'r')
plot(t,[0; diff(test(:,3))/ts],'c')
 
1번행에서 위에서 이야기한데로 읽구요
3번행에서 10ms 샘플링잡고
4번행에서 500펄스짜리 엔코더가 4분주되었으니 한 펄스당 360/2000 도라고 잡고
5번행에서 받은 test행렬의 값을 다시 맞춰준겁니다.
그리고 시간축을 8번줄에서 만들어주고
나머진 그렸네요^^


음 그 결과 그래프인데요. 파랑색이 각도입니다. Cyan색과 Red가 겹쳐서 잘 안보이는데요. 제가 확인할려고 했던것은 Cortex에서 넘어온 엔코더의 속도성분과 제가 MATLAB에서 계산(차분으로)한 속도성분이 일치하는지를 확인할려고 한것입니다. 거의 일치하네요. 그러니까 저 결과로 보자면, Cortex의 엔코더 해석기는 한 펄스의 길이를 잡아서 시간을 가지고 계산한 속도가 아니라 어떤 주기에서 엔코더의 출력 한 상을 읽어서 그 카운더로 만들어진 속도라는 생각이 드는군요. 뭐 하여간 여기까지, 예제3개(시리얼, 타이머인터럽트, 엔코더해석)을 합쳐서 연습했습니다. 아직 해소되지 않은 의문점들이 있지만, 차차 해결해야겠네요. 그나저나 Eclipse는 프로젝트 생성하는게 생각보다 힘들군요... 이번 테스트 코드도 엔코더해석 예제어 덥어서 만든겁니다...ㅜ.ㅜ

아래는 프로세서쪽 전체 코드입니다.

//*****************************************************************************
//
// Withrobot에서 배포하는 엔코더 테스트화일에 시리얼 통신 예제와 타이머 인터럽트 사용예를
// 통합해서 PinkWink가 수정함. 2009.06.04
//
// 일정 시간간격(Sampling Time)으로 엔코더에서 해석한 방향, 속도, 위치값 을
// 시리얼 통신으로 전송함.
//
//*****************************************************************************

#include "../../../hw_types.h"
#include "../../../hw_memmap.h"
#include "sysctl.h"
#include "gpio.h"
#include "uart.h"
#include "qei.h"
#include "ustdlib.h"
#include "timer.h"
#include "interrupt.h"

#ifdef DEBUG
void
__error__(char *pcFilename, unsigned long ulLine)
{
}
#endif

#define BUFFER_LEN              32

static void UARTPutString(char * str);
static void InitTIMERINT(void);
static void InitUART(void);
static void InitQEI(void);
static void TimerIntHandler(void);

int main(void)
{
    SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_8MHZ);

    InitTIMERINT();
    InitUART();
    InitQEI();

    // Loop Start.
    while(1)
    {
    }
}

static void TimerIntHandler(void)
{
    unsigned long vel, pos;
    long dir;
    char buffer[BUFFER_LEN];

	// Clear interrupt flag
	TimerIntClear( TIMER0_BASE, TIMER_TIMA_TIMEOUT );

    dir = QEIDirectionGet(QEI0_BASE);
    vel = QEIVelocityGet(QEI0_BASE);
    pos = QEIPositionGet(QEI0_BASE);
    usnprintf(buffer, BUFFER_LEN, "%d,  %d,  %d; \n", dir, vel, pos);
    buffer[BUFFER_LEN - 1] = 0;
    UARTPutString(buffer);
}

static void UARTPutString(char * str)
{
    while(*str)
        UARTCharPut(UART0_BASE, *str++);
}

static void InitTIMERINT(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    TimerConfigure( TIMER0_BASE, TIMER_CFG_32_BIT_PER );
    TimerLoadSet( TIMER0_BASE, TIMER_A, SysCtlClockGet() / 100 );	// 10ms
    TimerIntRegister( TIMER0_BASE, TIMER_A, TimerIntHandler );
    IntMasterEnable();
    TimerIntEnable( TIMER0_BASE, TIMER_TIMA_TIMEOUT );
    TimerEnable( TIMER0_BASE, TIMER_A );
}

static void InitUART(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200, (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
}

static void InitQEI(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_QEI);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);

    GPIOPinTypeQEI(GPIO_PORTC_BASE, GPIO_PIN_4 | GPIO_PIN_6);
    QEIConfigure(QEI0_BASE, (QEI_CONFIG_CAPTURE_A_B | QEI_CONFIG_NO_RESET | QEI_CONFIG_QUADRATURE | QEI_CONFIG_NO_SWAP), 0xffffffff);
    QEIVelocityConfigure(QEI0_BASE, QEI_VELDIV_1, SysCtlClockGet() / 100);

    QEIEnable(QEI0_BASE);
    QEIVelocityEnable(QEI0_BASE);
}



반응형