리튬이온 배터리다. 대표적인 전원 공급 부품이다.  전위차가 생겼다면 다음과 같은 그래프를 표현 할 수 있다.

 

 

 

 

이상적인 배터리는 전력이 무한이다. (P =VI)  물론 현실세계에서 말도 안되는 경우다. 다음과 같이 배터리는 어느 전압이든 전류가 최대로 나오게 된다. 그래서 배터리는 그냥 사용하면 된다. 물론 온도, 습도에 영향을 받는다. 

 

 

 

 

 

솔라셀이다. 태양에너지는 공짜에다가 환경오염도 없고, 효율이 점점 좋아지고있으며, 가격도 저렴해지는 중이다. 솔라셀의 IV그래프를 보자

 

 

솔라셀의 경우 아까 봤던 배터리 IV그래프와는 다르다. 전압이 최대일 때, 전류는 0이고 전류가 최대일 때, 전압은0 이다.  위 그래프에서 POWER(전력)가 최대일 때는 대략 0.52V의 전압이 출력될 때 이다. 그럼 모든 솔라셀의 전압을 0.52로 맞추면 모두 최대 전력을 뽑아 낼 수 있는건가? 아니다. 솔라셀은 온도에 따라서 출력 전압이 다르다. 또 받는 광량에 따라 전류도 변한다. 그래서 최대전력점이 항상 바뀐다.

 

왼쪽은 광량에 따른 IV커브  오른쪽은 온도에 따른 IV커브

 

 

위 그래프에서는 광량에 따른 IV커브를 보여주는데 MPP(최대전력점)이 광량에 따라 계속 변한다. 광량에 따라 MPP가 변하는걸 위 그래프에서 볼 수 있다. 우리는 최대전력점을 찾아서 전압을 계속 맞춰줘야한다. 그래야 효율이 좋아진다. 최대전력점을 찾는 모듈을 MPPT라고 부른다. '맥시멈 파워 포인트 트래커' 말 그대로 최대 전력 점 찾는넘 이다. 이 모듈이 있으면 전압을 MPP로 맞춰 최대전력을 뽑아 낼 수 있다.

 

 

 

'공부 > 기타' 카테고리의 다른 글

유성 기어 모터의 장점 및 단점  (0) 2018.03.06
BLDC 모터 드라이버  (1) 2018.01.31
opamp 회로 사이트  (0) 2018.01.31
BLDC 모터 컨트롤러 회로도  (0) 2018.01.18
C언어 hex string -> real hex  (0) 2017.12.22

이번 프로젝트를 위해 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





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


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

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

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










+ Recent posts