이번 프로젝트를 위해 c#으로 시리얼 통신 프로그램을 만들고 있다.





시리얼 통신의 경우 하드웨어에서 보내주는 직렬신호를 받아오는데 이는 하드웨어의속도 아니면 PC가 받는 속도에 따라 한번에 받아오는 데이터 개수가 달라진다. 데이터를 안전하게 처리하기 위해서는 받아온 데이터를 저장해서 쌓아두고, 타이머 함수를 만들어서 일정시간에 한번씩 데이터 패킷을 가져와 분석을 하는 방법을 사용해야한다.


데이터 패킷은 데이터를 주는 하드웨어에 따라 다르고, 하드웨어를 직접 제작 했다면 그거에 맡게 C#프로그래밍을 하면된다. 이번 프로젝트의 하드웨어도 우리가 만들기 때문에 C#데이터 패킷 분석 알고리즘도 우리가 코딩 해야 한다.


우리의 데이터는 이렇게 구성되어진다.


[시작바이트] [데이터개수] [데이터(모듈아이디)] [데이터(가스농도)] [데이터(각종정보)] [체크섬바이트] [끝바이트]


총 7바이트로 구성되어있다.

1987년부터 2년에 한 번씩 개최되는 대회로 2017년에 출전을 했다. 호주 다윈에서부터 애들레이드까지 3022km를 태양광 자동차로 경주를 한다. 




태양광 자동차는 전기자동차와 원리가 같다. 배터리의 전기에너지로 모터를 돌려 구동된다. 태양광 자동차의 경우에는 배터리 충전을 태양전지를 이용한다. 태양광 자동차의 경우 태양전지 솔라셀, MPPT, 배터리, BMS, LDC(저전압 컨버터), 모터 인버터, 모터가 사용된다. 그 외 수많은 작은 부품들이 들어가지만, 전체적으로 저렇다. 





<솔라카 전기전자시스템>

 

 

 

완주는 못했다. 차가 너무 무거웠다. 이상

 

 

 

 

대형 트랙 테스트 영상

 

인피니온 32비트 MCU 를 사용해서 센서와 모터를 제어했다.

인피니온 MCU SDK 정리가 잘 되어있어 잘 사용했다.

 

외부 인터럽트, 타이머, GPIO, I2C, SPI, ADC 를 사용했다.

 

라인스캔카메라에서 데이터를 받아 간단한 필터 2가지를 거쳐 차선을 검출했다.

라인스캔카메라에 노이즈가 많아서 HW 필터(cap 병렬) 를 사용했고, 그래도 튀는 노이즈가 있어서 SW로 처리했다.

차선 중앙으로 차가 오게 하게끔한다. (점선도 인식해야한다. 점선쪽으로 차선 변경을 해야한다.)

 

동력부는 dc 모터 드라이버를 사용했고, 엔코더를 사용하여 모터 속도를 피드백 받아 모터 속도를 PID 제어 했다.

(횡단보도 이후는 스쿨존 이라고해서 속도를 30cm/s 로 줄이라고했던것같다.)

 

테스트중에 차량이 계속 AEB가 걸려 정차를 했는데 

라이다센서가 벽에있는 유리로 인해 오작동을 일으켜 AEB가 걸려 차량이 계속 멈췄다.

라이다센서에서 들어오는 데이터를 필터링 해서 유리쪽으로 반사되어오는 신호는 거르려했는데, 잘 안됬다.

그래서 그냥 유리를 막았다 

 

대회에는 유리가 없다 사방이 불투명 무반사 박스로 막혀있다.







카메라에서는 왼쪽 차선과 오른쪽 차선을 보게 된다.

하지만 회전구간에서 차량의 속도가 빠를경우 한쪽 차선밖에 인식을 하지 못한다.

이를 위해 과거의 인식된 차선을 SRAM에 계속 저장하여 알고리즘을 통해 

현재 차선이 어느 차선인지 인식하게 한다.


과거 차선 데이터들을 미분하여 기울기를 보고 현재 차선이 왼쪽으로 회전되는지 오른쪽으로 회전 하고 있는지도 볼 수 있다.


스쿨존에서의 장애물인식은 라이다 센서로 하게 되었다. 물론 적외선 센서를 사용해도 되지만 측정 시간이 20ms로 길다.

 라이다의 경우 6ms로 짧기 때문에 더 빠른 대응을 할 수 있다.

주행 중 장애물을 만나거나 긴급상황에는 급격한 속도 변화가 필요하다. 이를 위해 모터의 속도를 제어해야 하는데, 모터의 속도를 제어하기 위해서는 엔코더를 이용하여 PID제어를 사용했다. 10ms 인터럽트를 이용하여 모터 회전 속도를 귀환 받고, 계산된 제어값은 PWM을 이용하여 모터드라이버에 신호를 인가했다. 아래 사진은 PID를 적용하여 모터 속도를 시리얼통신으로 받아온 데이터이며, 목표치는 600이었다. 시간 간격은 10ms단위로 나타내었다. 여러 번 테스트를 하였고, 결과 오버슈트가 적고, 목표치에 가장 빠르게 접근 할 수 있도록 P I D계수를 찾았다.



x축 간격은 10ms로 270ms안에 원하는 목표치로 근접했다.





가우시안 필터를 사용하기전 원본 데이터






7개의 가우시안 배열을 사용했다. 확실히 노이즈가 많이 줄어들었다.








가우시안과 엣지 디텍션을 적용한 데이터

확실히 라인이 잘 검출된다.


모형차의 모터가 돌아갈때 라인카메라에 노이즈가 많이 낀다. 모터에 세라믹 캐패시터를 달아주면 좋다.






'프로젝트 > 자율주행모형차' 카테고리의 다른 글

지능형 모형차 주행 테스트 영상  (0) 2018.07.13
모터속도제어(PID) 및 조향각 제어  (0) 2018.07.13
라인스캔카메라  (11) 2018.06.07
라이다(RIDAR) tc237  (0) 2018.06.05
tc237 ADC(SAR)  (0) 2018.04.19

라인감지를 하기위해 카메라 테스트








센싱 최소값은 빨간색 최대값은 초록색 현재값 하얀색     노멀라이징 된 데이터는 노란색으로 LCD에 표시



컴퓨터로 시리얼통신을 이용해서 받아온 노멀라이징 하기전 원본 센싱데이터








 다음은 노멀라이징 된 데이터를 받아봤다.




우선 16비트로 받아봤다. 




이건 8비트로 



라인카메라 특성상 외각 데이터들이 심하게 잡음이 끼고, 감도가 좋지 않았다.

라인감지 알고리즘을 작성 할 때 외각은 배제하고 하는게 좋을것같다.

갖고있는 라이다(Ridar lite v3)는 기본적인 인터페이스가 I2C와 PWM방식을 지원한다.

하지만 TC237의 경우 I2C를 하드웨어적으로 지원하지 않는다고 나와있다

그래서 GPIO로 직접 I2C를 구현해야한다.


라이다 데이터시트를 보고 I2C구현




오실로 스코프로 클럭 비트 하나하나 확인했다...












#include "IfxPort_reg.h"

#include "IfxPort.h"

#include <SysSe/Bsp/Bsp.h>


#define RIDAR_ADD 0x62<<1


#define RIDAR_SDA 2

#define RIDAR_SCL 0



#define RIDAR_CMD_WRITE 0xA0

#define RIDAR_CMD_READ 0xA1



#define SET 1

#define RESET 0


#define I2C_PageSize     16



void I2CStart( void )

{

IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_high);

waitTime(1*TimeConst_100us);

  IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_high);

  waitTime(1*TimeConst_100us);

  IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_low);

  waitTime(1*TimeConst_100us);

  IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_low);

}




void I2CStop( void )

{

IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_low);

waitTime(1*TimeConst_100us);

  IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_high);

  waitTime(1*TimeConst_100us);

  IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_high);

  waitTime(1*TimeConst_100us);

  IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_low);

}




unsigned char I2CSlaveAck( void )

{

waitTime(1*TimeConst_100us);

  unsigned int TimeOut;

  unsigned char RetValue;


  Dio_Configuration(&MODULE_P22, RIDAR_SDA, IfxPort_Mode_inputPullUp ,IfxPort_PadDriver_cmosAutomotiveSpeed1,IfxPort_State_high);


  IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_high);

  TimeOut = 10000;

  while( TimeOut-- > 0 )

  {

    if( SET == IfxPort_getPinState(&MODULE_P22, RIDAR_SDA ) )

    {

      RetValue = RESET;

      break;

    }

    else

    {

      RetValue = SET;

    }

  }

  IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_low);


  Dio_Configuration(&MODULE_P22, RIDAR_SDA, IfxPort_Mode_outputPushPullGeneral ,IfxPort_PadDriver_cmosAutomotiveSpeed1,IfxPort_State_high);

  return RetValue;

  waitTime(1*TimeConst_100us);

}






void I2CWriteByte( unsigned char byte )

{

  unsigned char i;


  for( i=0; i<8; i++ )

  {

    if( 0X80 & byte )

      IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_high);

    else

      IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_low);

    byte <<= 1;

    waitTime(1*TimeConst_100us);


    IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_high);

    waitTime(1*TimeConst_100us);

    IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_low);

    waitTime(1*TimeConst_100us);

  }

}



unsigned char I2CReadByte( void )

{

waitTime(1*TimeConst_100us);

  unsigned char i;

  unsigned char ReadValue = 0;


  unsigned char bit;


  Dio_Configuration(&MODULE_P22, RIDAR_SDA, IfxPort_Mode_inputPullUp ,IfxPort_PadDriver_cmosAutomotiveSpeed1,IfxPort_State_high);

  for( i=0; i<8; i++ )

  {

    IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_high);

    waitTime(1*TimeConst_100us);

    if( SET == IfxPort_getPinState(&MODULE_P22, RIDAR_SDA ) )

      bit = 0X01;

    else

      bit = 0x00;


    ReadValue = (ReadValue<<1)|bit;

    IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_low);

    waitTime(1*TimeConst_100us);

  }


  Dio_Configuration(&MODULE_P22, RIDAR_SDA, IfxPort_Mode_outputPushPullGeneral ,IfxPort_PadDriver_cmosAutomotiveSpeed1,IfxPort_State_low);

  return ReadValue;

  waitTime(1*TimeConst_100us);

}


unsigned char RidarWriteByte( uint8 Sen_addr, uint8 Reg_addr,  uint8 data )

{

waitTime(1*TimeConst_100us);

I2CStart();

waitTime(1*TimeConst_100us);

I2CWriteByte((uint8)Sen_addr);

if( RESET == I2CSlaveAck() )

{

return RESET;

}

I2CWriteByte((uint8)Reg_addr);

if( RESET == I2CSlaveAck() )

{

return RESET;

}

I2CWriteByte(data);

if( RESET == I2CSlaveAck() )

{

return RESET;

}


I2CStop();

waitTime(1*TimeConst_100us);

return SET;


}




unsigned char RidarReadByte( uint8 Sen_addr, uint8 Reg_addr, uint8* ReadValue )

{

waitTime(1*TimeConst_100us);

//unsigned char *ReadValue;

I2CStart();

waitTime(1*TimeConst_100us);

I2CWriteByte((uint8)Sen_addr);

if( RESET == I2CSlaveAck() )

{

return RESET;

}

I2CWriteByte((uint8)Reg_addr);

if( RESET == I2CSlaveAck() )

{

return RESET;

}


I2CStop();

waitTime(10*TimeConst_100us);


I2CStart();


I2CWriteByte((uint8)Sen_addr | 0x01);

if( RESET == I2CSlaveAck() )

{

return RESET;

}

ReadValue[0] = I2CReadByte();


IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_low);

IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_high);

waitTime(1*TimeConst_100us);

IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_low);

waitTime(2*TimeConst_100us);



ReadValue[1] = I2CReadByte();


IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_high);

IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_high);

waitTime(1*TimeConst_100us);

IfxPort_setPinState(&MODULE_P22, RIDAR_SCL, IfxPort_State_low);

waitTime(1*TimeConst_100us);

IfxPort_setPinState(&MODULE_P22, RIDAR_SDA, IfxPort_State_low);

I2CStop();

waitTime(1*TimeConst_100us);


}





void Ridarconfigure(int configuration, uint8 Sen_addr)

{

RidarWriteByte(Sen_addr,0x00,0x04);


  switch (configuration)

  {

    case 0: // Default mode, balanced performance

      RidarWriteByte(Sen_addr,0x02,0x80);

      RidarWriteByte(Sen_addr,0x04,0x08);

      RidarWriteByte(Sen_addr,0x1c,0x00);

    break;


    case 1: // Short range, high speed

      RidarWriteByte(Sen_addr,0x02,0x1d);

            RidarWriteByte(Sen_addr,0x04,0x08);

            RidarWriteByte(Sen_addr,0x1c,0x00);

    break;


    case 2: // Default range, higher speed short range


      RidarWriteByte(Sen_addr,0x02,0x80);

            RidarWriteByte(Sen_addr,0x04,0x00);

            RidarWriteByte(Sen_addr,0x1c,0x00);

    break;


    case 3: // Maximum range

    RidarWriteByte(Sen_addr,0x02,0xff);

          RidarWriteByte(Sen_addr,0x04,0x08);

          RidarWriteByte(Sen_addr,0x1c,0x00);

    break;


    case 4: // High sensitivity detection, high erroneous measurements

    RidarWriteByte(Sen_addr,0x02,0x80);

          RidarWriteByte(Sen_addr,0x04,0x08);

          RidarWriteByte(Sen_addr,0x1c,0x80);

    break;


    case 5: // Low sensitivity detection, low erroneous measurements

    RidarWriteByte(Sen_addr,0x02,0x80);

          RidarWriteByte(Sen_addr,0x04,0x08);

          RidarWriteByte(Sen_addr,0x1c,0xb0);

    break;

  }

} /* LIDARLite::configure */






참고: http://blog.naver.com/PostView.nhn?blogId=io044&logNo=20172757719

참고2: https://www.ngolongtech.net/2016/12/in-this-topic-i-show-how-to-get-data.html




'프로젝트 > 자율주행모형차' 카테고리의 다른 글

라인스캔카메라 가우시안 필터, 엣지 디텍션  (1) 2018.06.13
라인스캔카메라  (11) 2018.06.07
tc237 ADC(SAR)  (0) 2018.04.19
tc237 uart tx 테스트  (3) 2018.04.17
Infineon TC237 LCD 프로그램 업로드  (0) 2018.04.03

솔라카가 얼마나 더 달릴 수 있을까 (배터리 잔량 측정)


 솔라카 프로젝트에서 가장 재미있으면서도 힘겨웠던것은 배터리 잔량을 계산해서 메모리에 저장하고 이를 무선통신으로 날려서 앞의 앞의 리드카에 전송해 주는 과정이었다. 

리튬이온 배터리의 경우 배터리의 남아있는 에너지에 따라 배터리 전압이 변한다. 물론 이를 이용해서 배터리의 잔량을 계산할 수있다. 하지만 정확하지 않다. 특히나 배터리가 충,방전중일때는 배터리의 분극전압이형성되어 배터리 전압이 예상 값보다 더 떨어지거나 올라가있다. 


<

Equivalent circuit model of the lithium-ion battery.

리튬이온 배터리의 등가회로>




출처 : https://endless-sphere.com/forums/viewtopic.php?t=62932





위 그래프들은 리튬이온 전지의 방전곡선이다. 보면 방전하는 전류에따라 잔량별 전압도 다르다.


분극전압을 최소하하여 측정하려고 하면 배터리가 충방전을 하지 않고 많은 시간을 기다려야한다. 그게 휴지시간인데, 솔라카 주행을 하면서 휴지시간을 기다리며 대회를 치룰 수 없다. 

그래서 사용한 방식이 전류 적산 방식이다. 전류센서를 이용해서 방전 전류와 충전 전류를 적산하여 잔량을 계산한다. 하지만 전류적산 방식에서도 전류센서의 측정오차가 있기 때문에 오차가 누적되어 잔량에 엄청난 오차를 생기게 할 수 있다.

그래서 솔라카의 주행이 잠시 쉬는 컨트롤스탑지점이나, 밤에 주행을 중지할때의 배터리의 휴지시간을 이용하여 분극전압의 영향이 거의 없는 전압을 체크하고 전류적산 오차가 있는 배터리의 잔량을 리셋해주는 방법을 사용했다.










복잡한 제어를 하고 싶다면 아날로그 회로로는 한계가 있다. 이를 위해 ADC(아날로그 디지털 컨버터)는 반드시 필요한 기술이다.  아날로그 신호(센서의 전압신호, 전류신호)를 프로세서가 받아드려 프로세서에서 원하는 제어를 하는 프로젝트일 경우 반드시 사용할 것이다. 다만 아날로그 신호를 디지털 신호로 변환하게 되면 반드시 생기는 왜곡은 감수해야한다. 그래서 좋은 ADC변환 칩을 사용하는것이 좋다. ADC의 변환 방식은 여러가지가 있다. 병렬비교형, 파이프라인형, 축자비교형, 델타시그마형, 이중적분형 등이 있는데, TC237에는 델타시그마형과 축자비교형을 사용하고있다. 각 변환방식마다 장점과 단점 성능 차이가 있다.

이번 TC237 ADC에서는 축자비교형(SAR)방식을 사용한다.




축자비교형의 경우 이진탐색 방식으로 양자화하는 방식이다.

내부에 DAC와 비교기를 사용해서 각 클럭마다 상위 MSB 부터 LSB쪽으로 결정한다.


하지만 우리는 이러한 것을 몰라도 사용이 가능하다. TC237의 내부 하드웨어가 처리를 해주며 우리는 제어 할 수 있는 레지스터를 건드려 주면 된다.

 


아래는 유저 메뉴얼에 명시되어있는 ADC 변환 예제이다.

1. Load the global VADC module registers

2. Enable the analog/digital converter 0 (G0)

3. Select channel 0 of G0 as analog input

4. Initialize the conversion result service request

5. Start first conversion


Basic Initialization of the VADC:

//=========================> Load global module registers

(1) SCU_vResetENDINIT (0); // Access to ENDINIT-prot. reg.

(2) VADC_CLC = 0x0000; // Enable module clock and ctrl.

(3) dummy = VADC_CLC; // Read back ensures write oper.

(4) SCU_vSetENDINIT (0); // Lock ENDINIT-protected reg.

//=========================> Enable converter for group 0

(5) VADC_G0ARBCFG = 0x3; // ANONC = 11, analog converter ON

(6) VADC_GLOBCFG = (1<<31) /* SUCAL = 1, start-up calib. */ \

|(1<<15) /* DIVWC = 1, enable write */ \

| 0x9; // DIVA = 9 (clock prescaler)

(7) VADC_G0ARBPR = (1<<26); // AREN2 = 1, enable arb. slot 2

// (= background source)

(8) VADC_G0CHCTR0 = (1<<20); // RESTBS = 1, global result reg.

(9) VADC_BRSSEL0 = 0x1; // Select CH0 of group 0 for scan

(10) VADC_BRSMR = 0x1; // ENGT = 10B, enable conv. req.

//=========================> Init and install service request

(11) SRC_VADCCG0SR0 = (1<<10) /* Enable SR node 0, group 0 */ \

|VADC0INT; // Set prio to <VADC0INT>(1..255)

(12) interruptHandlerInstall (VADC0INT, & VADC_SCAN_irq);

(13) VADC_GLOBRCR = (1<<31); // SRGEN = 1, result service requ.

//=========================> Wait for completion of startup cal.

(14) while((VADC_G0ARBCFG.U & 0x30000000) != 0x20000000);

// CALS = 1, CAL = 0: calibr. done

//=========================> Start a conversion

(15) VADC_BRSMR |= (1<<9); // LDEV = 1, generate a load event










tftlcd에 간이 오실로스코프

lcd 하단에 전압 구형파를 출력했다.

 






SAR 방식의 adc



빠른 주행과 CPU에 부담을 주지 않기 위해 DMA채널을 이용해서 ADC를 할 계획이다.

delta-sigma adc도 해볼예정


Cpu0_Main.c

Cpu0_Main.h

Cpu1_Main.c

Cpu2_Main.c

VadcAutoScanDemo.c

VadcAutoScanDemo.h



+ Recent posts