본문으로 바로가기

자이로센서의 출력에 온도를 고려하기

category Robot/Project 2009. 6. 15. 12:40


(주의 ) 이 글은 작성 후 다시 검토해본 결과 오류가 다수 검출되고 검증되지 않은 글입니다. 읽으시는 분들은 참고하시길 바랍니다. (2011년 2월 25일)

이번글은 두개의 의문사항을 가지고있습니다. 혼자힘으로 좀 해결하기 어렵길래 혹시 지나가던 고수가 계시다면 살짝 도움말을 요청하는 그런 글이라고 보시면 되겠습니다. 뭔가 잘 되지 않아 질문을 할때는 여러가지 상황을 자세히 설명해야겠기에 포스팅하는 거라는....^^;

먼저 지난번에 자이로센서(myGyro300SPI)의 단순 테스트결과를 보여드렸었습니다. -자이로센서를 이용한 각도검출과 그 한계 - 그 글의 댓글에 류크님이나 님이 살짝 말씀하신데로 온도값도 받고 아날로그 출력을 가지는 자이로를 myGyro300SPI에 물려 SPI로 데이터를 받는 것을 테스트해보고 싶어졌습니다. 그래서 안내문서를 확인하고


어디를 수정해야될지 보니까


저 표시된 부분을


이렇게 바꿔가며 원하는 데이터를 받을 수 있더군요. 뭐 몹시 간단하다 생각했습니다. 0x8310보내고 자이로값받고, 0x8710보내고 온도값받고... 등등등....

그래서 별도로 함수화하고 어쩌고 하기위해 헤더 화일을 다시 만들었습니다.

<myGyro300.h>

#ifndef MYGYRO300_H_
#define MYGYRO300_H_

void InitSSI(void);
unsigned long ReadGyro(unsigned short gyroOutType);

#define Gyro0_CS_PERIPH			SYSCTL_PERIPH_GPIOD
#define Gyro0_CS_PORT_BASE		GPIO_PORTD_BASE
#define Gyro0_CS_PIN			GPIO_PIN_0
#define Gyro0_CS_ON()			GPIOPinWrite(Gyro0_CS_PORT_BASE, Gyro0_CS_PIN, 0)
#define Gyro0_CS_OFF()			GPIOPinWrite(Gyro0_CS_PORT_BASE, Gyro0_CS_PIN, Gyro0_CS_PIN)

#endif


<myGyro300.c>
/*
 * myGyro300.c
 *
 *  Created on: 2009. 6. 12
 *      Author: PinkWink
 */

#include "../../../hw_types.h"
#include "../../../hw_memmap.h"
#include "hw_ssi.h"
#include "ssi.h"
#include "sysctl.h"
#include "gpio.h"
#include "myGyro300.h"

void InitSSI(void)
{
	SysCtlPeripheralEnable(Gyro0_CS_PERIPH);
	GPIOPinTypeGPIOOutput(Gyro0_CS_PORT_BASE, Gyro0_CS_PIN);
	Gyro0_CS_OFF();

    SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
    GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5);
    GPIOPadConfigSet(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5, GPIO_STRENGTH_4MA,
                     GPIO_PIN_TYPE_STD_WPU);
    SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_2,
                        SSI_MODE_MASTER, 1000000, 16);
    SSIEnable(SSI0_BASE);
}

unsigned long ReadGyro(unsigned short gyroOutType)
{
	unsigned long data;

	Gyro0_CS_ON();
	SSIDataPut(SSI0_BASE, gyroOutType);
	Gyro0_CS_OFF();
	Gyro0_CS_ON();
	SSIDataGet(SSI0_BASE, &data);
	Gyro0_CS_OFF();

	return data;
}


<QEI_encoder.c>
//*****************************************************************************
//
// Withrobot에서 배포하는
// 타이머, 엔코더, ADC, myAccel3LV02_I2C, myGyro300SPI 예제를
// 적절히 통합하여 일정시간(Sampling Time = 10ms)마다
// 엔코더의 위치, 가속도센서의 3축, 자이로값을 시리얼통신으로 전송하게
// 2009.06.13에 PinkWink가 수정함. (www.pinkwink.kr)
//
//*****************************************************************************

#include 
#include 
#include 
#include "../../../hw_types.h"
#include "../../../hw_memmap.h"
#include "hw_ssi.h"
#include "ssi.h"
#include "i2c.h"
#include "sysctl.h"
#include "gpio.h"
#include "uart.h"
#include "qei.h"
#include "ustdlib.h"
#include "timer.h"
// #include "adc.h"
#include "interrupt.h"
#include "myGyro300.h"

#include "myAccel3LV02.h"
#include "myAccel3LV02_hal_i2c.h"

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

#define BUFFER_LEN          	32

static void UARTPutString(char * str);
void UARTSend(const char *pucBuffer, unsigned long ulCount);
static void InitTIMERINT(void);
static void InitUART(void);
static void InitQEI(void);
// static void InitADC(void);
static void TimerIntHandler(void);

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

    InitTIMERINT();
    InitUART();
    InitQEI();
    InitSSI();
    MA3_Init();
    //InitADC();

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

static void TimerIntHandler(void)
{
    unsigned long pos;
    unsigned long data;
    char buffer[BUFFER_LEN];
    // short data_short_array[3];
    // unsigned long adc_result[16];
    // unsigned long cnt;

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

	// Read Encoder Data
    pos = QEIPositionGet(QEI0_BASE);
    usnprintf(buffer, BUFFER_LEN, "%d, ", pos);
    buffer[BUFFER_LEN - 1] = 0;
    UARTPutString(buffer);

    // Read Gyro angle myGyro300SPI Data, SPI
    data = ReadGyro(0x8310);
    usnprintf(buffer, BUFFER_LEN, "%d,  ", data & 0x0FFF);
    buffer[BUFFER_LEN - 1] = 0;
    UARTPutString(buffer);

    // Read Gyro temp Data, SPI
    data = ReadGyro(0x8710);
    usnprintf(buffer, BUFFER_LEN, "%d;\n  ", data & 0x0FFF);
    buffer[BUFFER_LEN - 1] = 0;
    UARTPutString(buffer);

    /*
    // Read Gyro NT-Gyro300 X Data, SPI
    data = ReadGyro(0x8B10);
    usnprintf(buffer, BUFFER_LEN, "%d;\n  ", data & 0x0FFF);
    buffer[BUFFER_LEN - 1] = 0;
    UARTPutString(buffer);
    */

    /*
    // Read Gyro NT-Gyro300 Y Data, SPI
    data = ReadGyro(0x8F10);
    usnprintf(buffer, BUFFER_LEN, "%d,  ", data & 0x0FFF);
    buffer[BUFFER_LEN - 1] = 0;
    UARTPutString(buffer);
    */

    /*
    // Read Gyro NT-Gyro300 X Data, ADC
    cnt = ADCSequenceDataGet(ADC_BASE, 0, adc_result);
	usnprintf(buffer, BUFFER_LEN, "%d;\n  ", adc_result[0]);
	buffer[BUFFER_LEN - 1] = 0;
	UARTSend(buffer, strlen(buffer));
	*/

    /*
	// Read Accel Data
    MA3_Read(MA3_REG_OUTX_L, (unsigned char *)data_short_array, sizeof(data_short_array));
	usnprintf(buffer, BUFFER_LEN, "%d,  %d,  %d;\n", data_short_array[0], data_short_array[1], data_short_array[2]);
	buffer[BUFFER_LEN - 1] = 0;
	UARTPutString(buffer);
	*/
}

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

void UARTSend(const char *pucBuffer, unsigned long ulCount)
{
    //
    // Loop while there are more characters to send.
    //
    while(ulCount--)
    {
        //
        // Write the next character to the UART.
        //
        UARTCharPut(UART0_BASE, *pucBuffer++);
    }
}

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);
}

/*
static void InitADC(void)
{
	SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC);
	SysCtlADCSpeedSet(SYSCTL_ADCSPEED_500KSPS);
	ADCSequenceDisable(ADC_BASE, 0);
	ADCSequenceConfigure(ADC_BASE, 0, ADC_TRIGGER_ALWAYS, 0);
	ADCSequenceStepConfigure(ADC_BASE, 0, 0, ADC_CTL_END);
	ADCSequenceEnable(ADC_BASE, 0);
}
*/


나머지는 엔코더예제에 덥어쓴거라 뭐 확인이 금방되실겁니다. 그런데... 출력해서 시리얼 통신으로 받아보니


이렇게 나오는 군요...이..이럴수가... 전 분명

[엔코더값], [자이로출력], [자이로온도];

이렇게 보내라고 했는데

[엔코더값], [자이로온도], [자이로출력];

이렇게 전송이 되는군요... 이유를... 모르겠네요... .ㅜ.ㅜ 도대체....
 물론 하나하나 받을때는 아주 잘 들어옵니다...ㅜ.ㅜ
(이게 첫 의문입니다. 잉~ 구세주 같은 분은 없나요?)

여하튼 순서가 좀 이상하지만, 뭐가 온도값이고 뭐가 자이로 값인지 아니까 보정해볼려고 했습니다. 그런데.... 잘 모르겠더라는... 음... 정확하게 어떻게 상수를 계산하고 그 계산된 상수는 또 어떻게 처리하는건지... 그럼 뭐 실험해야지요^^ 사실 데이터시트에 나와있던데. 오랜 밤샘등으로 머리도 아프고 모조리 5V에 대한 그래프던데 3.3V인 경우는 뭐 어떻게 달라지는지... 등등을 생각할려니 그냥 실험해서 일치시켜도 괜찮지 않을까? 하고 생각한것 뿐입니다. 역시 실험은 이전 자이로 실험처럼 흔들리는 진자에다가 고정시켜 엔코더값과 비교할겁니다.


전 위에서처럼 전송받은 자이로 출력에 온도에 대한 보정식을 생각했습니다. 저렇게 생각하고난다음 뒤에 곱해지는 0.08이라는 수치를 실험을 통해 찾은것입니다.


위 그림은 보정없는 자이로의 원 데이터를 적분한 것입니다. 대략 60초가 지났을때 5도의 드리프트가 확인됩니다.


아이고, 세로축이 잘못 표시되었네요. 전부 각도(degree)입니다. 위 두 그래프는 대략 70초까지는 가만히 나둬서 드리프트를 확인해봤고 그 후 진자를 흔들어서 그 기울어진 각도를 잘 따라가는지 엔코더의 출력(파랑색)과 확인한 것입니다. 이것이 온도보정을 나름대로(^^)한 것입니다. 응답속도는 이전과 비슷하고 그 자세한 그래프는 이전실험에서 보였으니 이번엔 그냥 저렇게 큼지막한 그래프로 드리프트만 확인하겠습니다. 괜찮다는 생각이 듭니다. 틈틈히 계속 실험해서 결과를 확인해봐야겠습니다.

(여기서 두번째 의문이 들지요. 자이로를 많이 다루시는 분들은 원래 온도에대한 보정이라는 것을 어떻게 하십니까? 저처럼 하면 안되는 건가요?^^)


댓글을 달아 주세요

  1. BlogIcon 허걱 2009.06.19 06:34

    온도보상까지 올리셨네요..^ ^ 저도 온도보상 감이 전혀 안오더군요; 어디서 읽은 기억이 나는데..
    실제로 온도보상을 위해서..온도에 따른 오차범위를;일일히 구해서 그것을 자료로 만든뒤에;;사용하셨다는 글을 본적이 있습니다..;;

    1번문제는; 잘모르겠네요; 출력문제이니; 실제로 통신이 안되셔서 안나오신값은 아니시니;

    좋은 포스팅 너무 감사합니다^ ^

  2. BlogIcon irmus 2009.06.20 12:34

    요즘 프로젝트 듀가 다가오고있어 홈페이지 관리에 좀 신경을 못쓰고 있었습니다. 답변을 홈페이지에 달아두었으니 한번 방문해 보시기 바랍니다.
    http://www.withrobot.com/41#comment2002486

  3. 자이로고 2009.07.23 14:50

    제가 자이로 초보라서...;; 너무 몰라서 공부할께 많네요~

    죄송하지만 질문 좀 해도 될까요?

    위에서 측정하신 자이로 출력이 1973~1975쯤 나오는데요~

    데이터 시트에 보시면 전형적으로 2048이 되게 돼 있는데...

    센서마다의 특징을 고려해서 차이가 있다고 치면

    scale factor 역시 바꿔야 하는 거 아닌가요? 4.1을 그대로 써도 되나요??

    • BlogIcon PinkWink 2009.07.23 18:29 신고

      저도 저 부분에 대해서는 뭐라 드릴말씀이 없습니다. 그러나 이번에 올린 자이로에 대한 글을 다시 확인하시면 이번엔 고려했습니다^^

  4. bytt 2009.08.05 11:34

    질문 있어요.
    PinkWink님이 하신대로 온도 보상을 해봤는데요..
    온도 센서 출력 값과 자이로 센서 출력 값에서 선형성을 찾을수가 없습니다.
    위에서 님이 세우신 온도 보상 식을 보면 어느 정도 선형성이 보장 되어야 가능한 식인데..
    제가 가지고 있는 자이로 센서가 고장일까요??
    궁금합니다. 어떻게 온도 보상 상수를 찾으셨는지 궁금 합니다.

    • BlogIcon PinkWink 2009.08.05 11:46 신고

      넓은 온도 범위에서는 선형성을 만족하지 못하는 듯합니다.
      보통의 온도 대략 20도 내외에서는 만족하는 듯 하구요.
      온도를 다양하게 가변시켜서 실험할 수 있는 장비와 환경이
      저에게 구축되어있지 않아서 명확한 실험을 할 수 없었습니다.
      즉, 위 식은 좀 아니다 싶습니다^^
      그러나 확인할 방법이 없어서... 쩝...
      http://pinkwink.kr/112
      에서 온도에 대한 이야기를 조금 더 했습니다.

  5. kdh 2010.08.14 23:46

    많은 도움 받고 있습니다!!

    이 시점에서(?) 소용없을 수도 있겠지만 :) 데이터가 밀려 나오는 이유를 한번 써보겠습니다.
    센서에서 사용하는 SPI통신은 MISO와 MOSI에서 데이터가 왔다갔다 합니다.
    (Master Input Slave Output; Master Output Slave Input)
    마스터에서 데이터를 밀어넣으면 슬레이브에서 데이터가 동시에(!) 밀려나오는 형태로 통신이 이루어집니다.

    이때 입력을 넣음과 동시에 밀려 나오는게 중요한건데...

    밀어 넣는 데이터가 '자이로 값을 출력해라'면
    명령을 줌과 동시에 결과값이 나오는건 센서에 예지능력이 없는한 불가능하죠!

    따라서 출력되는 값은 이전 명령에 대한 데이터가 나오게 되고
    두개의 값을 받아오기 때문에 데이터가 뒤바껴 나오는 것이지요...

    그렇기 때문에 센서를 켜자마자 처음으로 받아오는 값은 아마 의미없는 더미 데이터가 될 것입니다.

  6. mcu 2010.11.02 13:48

    온도보상에 관한 글 잘 보았습니다.
    위에서 0.08이라는 상수를 실험을 통해 구하셨다고 하셨는데;;
    구체적으로 어떤 실험을 수행하신건지궁금합니다.!!

    • BlogIcon PinkWink 2010.11.02 15:45 신고

      사실. 위 실험은 온도에 대한 자이로의 특성이 선형적일거라는 가정에서 출발한것인데, 요즘 드는 생각은 그 가정부터 틀린게 아닐까하는 생각을 합니다. 결국 이 포스팅은 그리 신뢰할만한게 아니라는 생각을 요즘 많이합니다. 그러나 이 포스팅을 지우지 않고 그대로 두는 이유는 누군가 고수가 나타나서 오류를 잡아주길 바라기 때문입니다.
      저는 당시 온도에 대한 보정은 오차의 발생을 막을진 몰라도 오차자체를 수정해주지는 못한다는 생각에, 온도 보정은 접고, complementary filter 로 넘어갔습니다. 고로.. 이글은 저 자신도 맞는 이야기인지 잘 모르겠습니다.ㅠㅠ

    • mcu 2010.11.02 16:04

      또 다른 센서와 퓨전하지 않고 자이로 센서 자체만으로 드리프트를 줄여보려고 하는데 역시 힘든 점이 많네요;;
      어느 논문을 보니 Fir필터를 사용한 것도 있던데 나름 오차 드리프트 보정 많이 되더군요 답변 감사합니다~~

    • BlogIcon PinkWink 2010.11.02 16:17 신고

      네.. 필터기술은 정말 어려우면서도 유용한것 같아요^^
      너무 어려운것들은 저의 이해 범위를 좀 넘어선것이 있어서..ㅠㅠ

  7. mijong 2011.03.02 11:33

    ㅎㅎ 온도 보상 하는 상수 4.1 을 역수 취하면 0.01435 ... 정도 의값이 나오는데

    0.08 보다 이걸 넣는게 더 정확 한 값이 나오네요.. lsb/k 이니까 역수를 취해줘야죠

    • BlogIcon PinkWink 2011.03.02 15:35 신고

      이 글은 현재 제가 중단한 글입니다. 일단 몇몇 이유로 상황에 맞지 않구요.ㅠㅠ. 즉. 잘못된 점이 있는듯해요.. 하여간.. 센서에 따라 좀 다르긴 하던데 어느정도 드리프트 오차를 막아주기는 합니다.

  8. mijong 2011.03.02 11:33

    그나저나 온도에 따른 각속도 변환 하는 상수 는 어떻게 변환 시켜줘야되는지???

  9. mmoo 2011.07.20 21:51

    안녕하세요. 위드로봇社의 myAccel3LV02 가속도센서와 myGyro300SPI 를 사용중인데, 가속도는 i2c로, 자이로는 SPI로 동시에 데이터를 10ms 간격으로 받으려고 합니다.
    임베디드쪽이 처음이라 위드로봇社에서 제공하는 예제를 합치기가 힘들어 검색을 해보다가 pinkwink님의 블로그까지 오게되었습니다..ㅠㅠ
    본문의 <QEI_encoder.c> 내용이 제가 원하는 [ i2c 가속도 + SPI 자이로 10ms로 동시에 데이터 받기] 같은데..저 소스만 있으면 되는건지요? 이쪽으로 전혀 문외한인지라 지식이 많이 부족합니다. 조언 부탁드립니다.

    • BlogIcon PinkWink 2011.07.21 07:50 신고

      QEI_encoder.c는 withrobot에서 제공하는 겁니다.
      그걸 제가 받고 싶은 형태로 살짝 수정만 가했던것 같습니다.
      withrobot은 자이로와 가속도센서에 대한 소스화일을 제공하니까.. 저도 그냥 뭐 자세히는 모르고 그냥 사용했습니다.^^ 당시에 저도 I2C가 뭔가 CAN이 뭔지도 모르고, withrobot이 제공하는 화일을 이용해서 데이터를 수신만 하고, 가공이나 관찰은 MATLAB에서 했었거든요.
      이 포스팅과 자이로+가속도센서의 결과를 받아오는 포스팅이 있는데 거기서 사용한게 전부랍니다.^^