usb 장치를 연결하는 어플리케이션을 개발하다 보니 나름 곤란한 문제가 발생했다. 초반에야 그냥 기본적인 문제라 usb를 연결을 하고 디버깅이나 다운로드를 하여도 상관 없었는데, 장치를 연결하려고 보니, 아... 연결을 할 수 없더라.

그래도 아는게 이거라고 계속 usb로 다운로드하고 주요 지점을 log로 빼내고 하면서 진행을 하다가 옆자리에 윈도우즈 프로그램 개발하는 동료에서 불평을 하였다. 그랬더니 그 분의 말... "무선으로 안되?" 그렇다. 이 간단한거 하나 생각 못해서 찾아 보지도 않았는데, 검색해보니 와우~ 가능하단다. 상세한 내용은 링크를 참고 하면 될 듯하다.(링크)



일단 무선을 하기 위해선 전재 조건이 하나 있다. 안드로이드 버전 3.1 이상이어야 한다는 것. 왜 아래에선 안된다고 하면 나도 모른다. 설명에 안된단다;;;(안드로이드 버전 2.3.4 이후에 도입된 backport를 이용하는 듯...) 아무튼 영어가 짧으니 간단하게 넘어 가고...


하는 방법은 간단하다. 안드로이드 라이브러리 설치된 디렉토리에 위치한 platform-tools 로 넘어간다.(정확히는 adb 파일을 찾아 가는 것) 그리고 "adb tcpip 5555"(연결할 포트를 지정하는 것 인데 반드시 5555일 필요는 없다.)를 해주고, "adb connect 휴대폰ip"를 해주면 된다.(본인 휴대폰의 ip는 휴대폰 정보나, WiFi의 설정화면의 상세, 고급 같은 메뉴에 가면 볼 수 있다.)


c:\android\platform-tools> adb devices                      : 연결된 디바이스 확인

c:\android\platform-tools> adb tcpip 5555                    : 연결된 디바이스의 5555 포트 열기

c:\android\platform-tools> adb connect 192.168.0.200       : 연결된 디바이스의 연결


아무튼 이 과정에서 대충 휴대폰을 공유기를 통해 고정 ip로 만들어 버리면 좋다. 물론 대부분의 공유기들이 dhcp에 의한 ip할당을 어지간하면 기기마다 계속 동일하게 부여하지만, 혹시 모르니 매번 확인해야 하는 번거로움도 있고 해서 고정하면 편하다는 이야기이다.


그리고 이건 다른 이야기지만 아무래도 공유기하고 휴대폰이 연결히 해지가 되면 adb로 열었던 port가 다시 닫히는 것 같다. 반복적으로 해보니깐 그렇더라. 그러면 위에것을 다시 해주면 되는데 귀찮으면 bat 파일로 만들어서 하는 방법도 있긴 하다.


# 그냥 참고 사항.

디렉토리 이동 :  cd <원하는 위치> , c:\> cd test , c:\> cd c:\test

상위 디렉토리 이동 : cd..

드라이브 이동 : d: , c:\> d:

마우스 오른쪽 : 팝업메뉴, 붙여넣기 있음


# bat 파일

wifi_debug.bat


메모장에 bat 파일을 드래그엔드랍을 하고 ip 주소만 변경하면 된다.

bat 파일은 platform-tools에 위치해야 한다.



# 추가

WiFi로 디버깅을 했나가 나갔다 와서 디버깅을 하려고 할 때 잘 안되면 위에 내용대로 안되는 경우가 있다. 그러면 usb를 다시 연결하고 다음의 순서로 입력한다.


adb device

adb connect 192.168.0.200    : 이것만 해주어도 상관 없다.


그러면 reconnect가 되면서 되는 경우가 있다.

그런데 이것을 해도 안되는 경우는 다시 usb로 바꾸었다가, 하면 된다.


adb device

adb kill-server                 : 이것만 해주어도 상관 없다.         

adb usb                        : usb mode로 바꾸어 주지만 kill-server를 하지 않으면 안 먹는 경우가 있다.


wifi_debug_re.bat

usb_debug.bat


Posted by klisty
,

리스트뷰(ListView)를 사용하다 보면 아무것도 추가되지 않은 화면이 표시되어야만 하는 경우가 종종 발생한다. 그래서 그런가? 많은 분들이 xml에 명시된 textview에 "@android:id/empty"를 작성하여 하게 되는데 본인이 미숙해서 인지 이렇게 사용하려고 하면 "@android:id/list"와 "@android:id/empty"을 아예 고정해서 사용해야 하는 불편함을 느꼈다. 다른걸로 바꿀수 없었다는 말이다.


그래서 어떻게 할까 고민하다 간단한걸 너무 돌아가서 고민했던지라 그냥 난 이렇게 처리했다 정도로 정리하고 넘어 갈까 까한다.



이렇게 해놓곤, ListView가 있는 곳에 다음과 같이 코드를 삽입하였다. 물론 OnCreateView 뿐만 아니라 List가 추가되거나 삭제되는 부분에도 추가로 넣어야 한다.






Posted by klisty
,

다른 사람들은 모르겠지만, 나의 경우는 xml에 미리 만들어 놓고 사용하는 것보다, 그때 화면에 띄우는 것을 선호하는 편이다. 그래서 Class 생성 시 onCreateView에서 이것저것 처리하는데, 그 기본이 되는 TextView의 동적 생성을 알아 볼까 한다.



FrameLayout                      frame   = (FrameLayout) view.findViewById(R.id.frameView);

RelativeLayout.LayoutParams   layouts = (RelativeLayout.LayoutParams) frame.getLayoutParams();

TextView                          textview = new TextView(view.getContext());

textview.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,

                                                                   LayoutParams.WRAP_CONTENT));

layouts.addRule(RelativeLayout.BELOW, R.id.frameMaster);

textview.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);

textview.setText(R.string.list_empty);

frame.addView(textview, layouts);



위 코드가 동적생성을 한 모습이다.



보면 frame은 이미 xml에서 정의한 녀석을 사용하였고, 그 때 정의된 layout parameter를 받아오기 위하여 getLayoutParams() 메소드를 통하여 정보를 받아 왔다.사실 여기서 나의 삽질이 발생했는데, 바로 나는 layouts가 받아야 하는 줄 알고 RelativeLayout relative = (RelativeLayout)view.findViewById(R.id.frameRelative); 하고 나서  RelativeLayout.LayoutParams 에서 relative.getLayoutParams();를 출창 하였다. 이렇게 하니깐 당연히 app은 뻗어 버리고 강제 종료 당했다.


그래서 무엇이 잘못 되었나 찬찬히 살펴보니 RelativeLayout.LayoutParams 자체가 parent의 정보를 받아와 적용하는 부분이라는 설명을 읽고선 relative가 필요 없음을 알게 되었다. 이걸 지우고 layouts에 framedml LayoutParams의 정보를 넣어 주니 바로 해결이 되었다.


아무튼 그리고 이후에 나오는 rule은 relative와 관련해서 어떠한 조건을 붙일 것이냐인데, 나는 frameMaster 아래에 놓을 것이므로 해당 룰을 BELOW로 변경 하였다. 


그리고 마지막은 frame에 내가 생성한 객체들을 연결해 주는 것으로 종료가 된다.






덧. xml은 다음과 같이 정리하였다.

<FrameLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/fragment"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    tools:context="com.autonics.daqmaster.UnitListFragment">


    <!-- TODO: Update blank fragment layout -->

    <RelativeLayout

        android:layout_width="fill_parent"

        android:layout_height="fill_parent" >


        <FrameLayout

            android:id="@+id/frameMaster"

            android:layout_width="match_parent"

            android:layout_height="@dimen/fragment_master_layout_height"

            android:layout_gravity="top">

... 생략 ...


        </FrameLayout>

        <FrameLayout

            android:id="@+id/frameView"

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:layout_below="@+id/frameMaster">


            <ListView

                 android:id="@+id/listViewUnitList"

           android:layout_width="match_parent"

           android:layout_height="match_parent" >

      </ListView> 

        </FrameLayout>

    </RelativeLayout>


</FrameLayout>





Posted by klisty
,

처음엔 그냥 폰의 뒤로가기 버튼을 누르면 종료 되도록 하였으나, 같이 일하는 선임 분께서 요즘 앱들과 비슷하게 하라고 하셔서 변경하였다. 그런데 여기서 문제는 시간같은 것을 정해 주지 않으면 뒤로가기 키를 한번 누르고, 한참 후에 뒤로가기를 하면 그것을 두번째 키로 인식한다는 것이다. 그래서 시간을 추가하게 되었다.




우선 KeyUp 이벤트를 추가한다.



void __fastcall TFormMain::FormKeyUp(TObject *Sender, WORD &Key, System::WideChar &KeyChar, TShiftState Shift)

{

if( Key == vkHardwareBack ){

// key값 초기화

Key = 0;


}

}


그리고 if 부분 아래에 다음과 같이 추가를 하면 된다.




class 선언 부에는 다음과 같이하고,


#define AppExitTime (5)

bool   bExitChecker;

time_t   iExitTime;



메소드 정의부의 KeyUp 이벤트에는 다음과 같이한다.


if( bExitChecker ){

if( (time(NULL) - iExitTime) < AppExitTime ) {  // 이미 얻은 시간에서 현재시간을 뺀다.

FormMain->Close();  // app 종료

} else {

iExitTime = 0;

bExitChecker = false;

return;

}

} else {

time(&iExitTime);  // 현재 시간을 얻는다.

bExitChecker = true;

return;

}




여기서 쓰인 time의 사용은 두 가지이다.


iExitTime = time(NULL);

또는

time(&iExitTime);



그냥 간단한 것이지만 처음에 time을 어떻게 쓰는지 혼동되서 정리해 보았다.

(두가지 다 가능할 줄은;;;)




같이보면 좋은 정보 (delphi) : http://cafe.naver.com/delphifmx/634
참고 : http://www.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_qna&no=38935



Posted by klisty
,

Firemonkey에서의 C++ Builder로 Layout을 만들다 보면 BringToFront, SendToBack 등을 자주 사용하게 된다. 문제는 이것이 계층을 구분하기 때문에 우선순위등이 존재한다는 것이다. 솔직히 이것에 대해서 설명해 주는 사람이나 책자들이 없어서 몰랐는데 이번에 영역 외 터치 동작을 구현하기 위해서 뻘짓을 하다 알게 되었다.


험프리님의 예제를 보고 이런식으로 적용해야지 했는데 되질 않아서 고생을 좀 했다.


보던 예제는 "[모바일앱예제] 사이드바 형태 메뉴(Sidebar drawer menu) 만들기" 이고, 역시 델파이로 되어 있다. 문제는 이대로 따라 하면 안된다는 것이다. 여기서 lytSidebarHelper라는 Layout이 있는데, 이거 Layout이 아니라 TRectangle 같다. 여기에 Properties의 Opacity를 0으로 만들어서 투명화 시킨 것 같은데, 이에 대한 언급이 없다. 이거 보면서 Layout인줄 알고 여기에 OnClick 이벤트를 만들어서 했는데, 죽어라 해도 안되어서 엄청 해맸다. 이것저것 해본 결과 Layout의 OnClick 이벤트는 안 먹히는 이벤트 같다. 발동이 안된다;;; 


아무튼 4일간의 뻘짓 결과 Layout은 OnClick 등과 같은 마우스 이벤트는 먹지 않는 것 같다는 결론을 내렸다.




이제 본격적으로 이야기 하고자 하는 것을 해보자.


위를 보면 Layout과 Rect가 Form 아래에 같은 계층이 있고, Layout 아래에 3개의 Layput과 ListBox가 있는 것이 보일 것이다.


여기서 말하고자 하는 것은 다음과 같다.


LayoutMain.BringToFront(); 하고,

RectLayoutBG.BringToFornt();를 하면...


바닥에 깔리는것인 LayoutMain 이고, 그 위에 RectLayoutBG가 깔려 최상위에 RectLayoutBG가 올라 오게 된다.

문제는 여기서 발생한다.


만약 이렇게 했다면 어떻게 될까?


LayoutMain.BringToFront(); 하고,

RectLayoutBG.BringToFornt(); 를 한 다음에

LayoutPopupClose.BringToFront(); 를 하였다면,

가장 위에 깔린 컴포넌트가 무엇인지 알 수 있을까?



여기서 답은 RectLayoutBG이다.


즉, LayoutPopupClose의 BringToFront는 어디까지나 LayoutMain 내의서의 BringToFront 인 것이다.

LayoutMain의 내부에서 가장 최상위에 보여지는 것이 LayoutPopupClose가 맞지만,

Form전체에서는LayoutPopupClose가 아닌 RectLayoutBG이 된다.


그래서 LayoutPopupClose가 FormMain 아래에 있었다면, 위에 설명한것과는 다르게 LayoutPopupClose가 되었을 것이다. 하고자 하는 말은 바로 이것이다.

같은 계층내에 있는 것 끼리만 BringToFront, SendToBack 의 순위가 결정되고, 자신의 계층별 부모의 순위에 의존해서 자신의 전체 순위가 결정된다.



이걸 몰라서 엄청 해맸었다. 이걸 알고 나니 험프리님이 lytSidebarHelper 언급하신 "사이드바 헬퍼를 구성할때 Z-Order(같은 자식간의 정렬순서, 즉 어떤 컨트롤이 위에 표시될지 여부)를 이용해 사이드바가 사이드바 헬퍼의 위에 위치하도록 해야합니다."를 이해하게 되었다.



덧. 여기서 언급한 계층이라는 용어는 정확한 용어가 Z-Order라는걸 오늘에서야 알았다. 

Posted by klisty
,

FireMonkey XE7에서 안드로이드 어플을 개발하다 보면 다이얼로그를 추가할 일이 반드시 생긴다.

일단 기본적인 틀은 윈도우즈 프로그램 개발하듯이 작성을 하면

"blocking dialogs not implemented on this platform"

이라고 휴대폰에서 뱉어 내면서 되질 않는다.



그래서 어떻게 해야 할지 몰라 인터넷 검색하니...

http://community.embarcadero.com/index.php/forum/programming/273-blocking-dialogs-not-implemented-on-this-platform

여기의 글을 보면 

http://docwiki.embarcadero.com/RADStudio/XE6/en/Creating_an_Android_App#Using_Modal_Dialog_Boxes_in_Mobile_Apps

이 문서를 보라고 한다. 그러나 내가 볼 것은 XE7이므로 저기서 XE6을 XE7로 변경하여 보았으나 나오지 않는다. 

(저 문서는 MessageDlg가 Blocking 기능을 안드로이드 OS에서 사용 못한다는 이야기였다.)


그래서 google 검색에서 두번째에 있는 것을 읽어 보았다.

http://docwiki.embarcadero.com/RADStudio/XE7/en/What's_New_in_Delphi_and_C%2B%2BBuilder_XE7

"Dialog Box Methods Support Anonymous Methods to Handle Their Closings"

항목에서 지원을 해준다고 한다. (InputBox, InputQuery, MessageDlg)

그런데 무언가 특별한 방법을 써야 하는 듯 하다.

<ACloseDialogProc> 이것을 써야 한다는데, 뭔소린지 봐도 모르겠다.

(사실 바로 이해했으면  이렇게 정리도 안하겠지만...)



그래서 다시 검색했더니 험프리님께서 정리를 해 놓으신 블로그가 나왔으나...

역시 델파이, 그래도 하단에 있는 참고자료가 도움이 될까 싶어 살펴 보았다.

http://sourceforge.net/p/radstudiodemos/code/HEAD/tree/branches/RadStudio_XE7/Object%20Pascal/Mobile%20Snippets/MessageAlerts/

솔직히 혹시나 하는 마음에서였다. 그리고 검색... 

디렉토리 구조에서 RadStudio XE7까지 올라가니 cpp가 보여서 들어가 보았다. 

그리고 동일한 구조로 들어가기 위해서 Mobile Snippets, MessageAlerts, uMain.cpp 를 선택했다.

http://sourceforge.net/p/radstudiodemos/code/HEAD/tree/branches/RadStudio_XE7/CPP/Mobile%20Snippets/MessageAlerts/


그 주소가 바로 다음과 같다.

http://sourceforge.net/p/radstudiodemos/code/HEAD/tree/branches/RadStudio_XE7/CPP/Mobile%20Snippets/MessageAlerts/uMain.cpp



빙고!! 찾았다.



struct TCloseDialogHandler : public TCppInterfacedObject<TInputCloseDialogProc> {

    void __fastcall Invoke(const System::Uitypes::TModalResult AResult) {

      switch (AResult) {

      case mrYes :

        ShowMessage("You chose Yes");

        break;

      case mrNo:

        ShowMessage("You chose No");

        break;

      case mrCancel:

        ShowMessage("You chose Cancel");

        break;

      }

    }

  };

  _di_TInputCloseDialogProc handler = new TCloseDialogHandler();


  /* Show a multiple-button alert that triggers different code blocks according to

    your input */

  MessageDlg("Choose a button:", TMsgDlgType::mtInformation,

    TMsgDlgButtons() << TMsgDlgBtn::mbYes << TMsgDlgBtn::mbNo << TMsgDlgBtn::mbCancel , 0, handler);



이 형태에 맞추어서 추가하니 문제없이 잘 돌아간다.




그런데 sourceforge 디렉토리 구조가 어디서 많이 본 구조였다.

그래서 내 컴퓨터의 공유문서를 살펴보니 역시나... RADStudio의 기본 예제...


그러나;;;;;;;


그 내용을 살펴 보고 처음부터 인터넷 검색하길 잘했다라는 생각이 들었다.


switch (MessageDlg("Choose a button:", TMsgDlgType::mtInformation,

   TMsgDlgButtons() << TMsgDlgBtn::mbYes << TMsgDlgBtn::mbNo << TMsgDlgBtn::mbCancel , 0))

{

case mrYes :

ShowMessage("You choose Yes");

break;

case mrNo:

ShowMessage("You choose No");

break;

case mrCancel:

ShowMessage("You choose Cancel");

break;

}


만약 이걸 먼저 보았으면 기본 예제인데 왜 안되는 건지 몰라서 정말 맨붕 왔을 듯 하다.


Posted by klisty
,

RAD Studio에서 Designer를 이용하여 MessageBox , MessageDlg를 만드는건 생각보다 간단하다. 

위 그림처럼 CnPack에 들어가 MessageBox를 선택해 MessageBox Designer을 통해 만들면 된다.


그러면 다음과 같은 소스가 나오는데...


switch (MessageDlg("TEST",  mtCustom, TMsgDlgButtons() << mbYes << mbNo, 0)) {

        case mrYes: {


break;

}

case mrNo: {


break;

}

}


문제가 발생한다.



이를 그대로 하면 컴파일 에러가 발생하는데 자기가 뭘 써야 하는지 모르겠다고 아우성 거린다.

분명 FMX쪽 라이브러리만 참조했는데도 말이다.


그래서 MessageDlg와 관련하여 다음과 같이 수정해 주어야 한다.


switch ( Fmx::Dialogs::MessageDlg("TEST",

   TMsgDlgType::mtCustom,

   TMsgDlgButtons() << TMsgDlgBtn::mbYes << TMsgDlgBtn::mbNo, 0)) {

case mrYes: {


break;

}

case mrNo: {


break;

}

}


아무튼 자신들이 만든 기능을 써서 만든건데... 에러를 뿜어 내서 급 당황했었다;;;

Posted by klisty
,

MSP430AFE253은 시그마-델타(SD) 방식의 아날로그 디지털 컨버터(ADC) 3채널이 내장되어 있어 학생들에[겐 조금은 특이하게 느껴  질 수 있는 마이크로컨트로러(MCU)입니다. 하지만 개발을 하는 회사 입장에선 고가의 ADC 전용 칩을 저가의 MCU($1 이하)와 소프트웨어 기술로 대체가 가능하다는 점에서 관심을 가질 수 밖에 없는 제품입니다.

그러다 보니 해당 제품을 사용하는 회사들이 생각보다 많고 저 역시 이 제품으로 개발을 하게 되었습니다. 그런데 주먹구구식으로 한참 개발하다 보니 교정기에서 나오는 전압을 선형적으로 받아 들이지 못한다는 느낌을 받게 되었습니다. 여기서 선형적이다라는 말은 0mV 부터 750mV 까지 1mV식 증가 시킬 때 ADC의 결과가 일정하게 증가하느냐 말합니다. 그런데 제가 구성한 회로에선 일정하게 증가하지 않고 특정 구간에서 너무 많은 차이를 보이면서 증가를 하더군요. 그래서 다음과 같은 실험을 하게 되었습니다.




실험환경은 다음과 같습니다.



1. 교정기는 YOGOKAYA사의 CA100을 가지고 하였습니다.

2. MSP430AFE253의 CH0을 이용하여 측정을 하였습니다.

3. Vref는 TI 사의 TLVH431을 이용하여 1.5V를 구성하였습니다.

4. 저항은 가급적 정밀급인 D급을 사용하였습니다.(일부 F급 존재)

5. 직접 설계한 SMD PCB 상에서 측정하였습니다.

6. 위 회로의 R을 바꿔가며 측정을 하였습니다.




실험 결과는 다음의 구글 스프레드시트로 정리가 되어 있습니다.

https://docs.google.com/spreadsheet/ccc?key=0Ahy59sLNIHx3dFl6aU84WmdaSzQ0RTkySFJKb0dxanc


실험 결과을 한 결과 다음과 같이 정리를 할 수 있었습니다.


1. MSP430AFE253 추세 선형성




2. MSP430AFE253 차동 선형성




그리고 마지막으로 위의 시스템에서 R을 제거 하고 바로 전압을 인가 하였을 때의 결과 입니다.







제가 전기전자 회로 이론을 잘 아는 것이 아니라서 이론적으로는 말씀 들릴 수는 없습니다. 하지만, 하루정도를 허비해서 실험한 결과를 정리하면 다음과 같습니다.

1. R을 100Kohm으로 바꾸면 위의 차동 선형성 그래프처럼 골짜기 형상이 나타나는 것이라 아니라 Z형 그래프를 그리게 됩니다.

2. R을 10Kohm부터 560ohm 까지 해보면서 저항값이 작아 질수록 자동 선형성 그래프가 좋아 짐을 확인 할 수 있습니다.

이는옴의 법칙인 전압=전류*저항(V=I*R)을 바탕으로 단순하게 생각을 해본다면, V와 R을 실험에서의 고정시켜서 관찰한 결 결과이므로 MCU의 ADC의 입력 전류가 일정 이하로 떨어지게 되면 이것이 ADC 결과에 영향을 미치는 것이라고 결론을 내릴 수 밖에 없다고 생각합니다.

이부분에 대해선 제가 마이크로프로세서를 가지고 먹고 살고 있지만 지식이 미천하여 더 이상의 상세한 설명은 불가능 하므로 추후 해당 문제에 대해서 알게 되면 다시 정리 하도록 하겠습니다.

 


회로 수정하면서 OPAMP로 버퍼 회로를 구성하여 테스트 한 결과 기존의 있던 비 선형성은 줄어 들게 되었습니다. 다만 OPAMP가 계측용이 아니라서 오차가 발생하는 데다, Zero Shift가 발생되면서, 또 다른 문제를 야기하게 되었습니다. 아무튼 이 테스트로 알수 있게 된 것은 MSP430AFE253의 ADC에 버퍼가 없음으로 인해서 발생한 문제로 여겨 집니다. 만약 해당 MCU를 사용하신다면 외부에 버퍼 회로를 구성하여 사용하시는 것을 고려 하시거나 경쟁사인 ADI의 ADuM36x 시리즈(ADuM360, ADuM361)를 이용하는 것을 고려해 볼만 하다고 여겨집니다.

Posted by klisty
,

제품을 만들다 보면 어떻한 데이터를 저장해야 하는 경우가 발생합니다. 가령 제품이 전원이 꺼지기 전의 상태를 기억해야 하거나, 제품이 정확한 값을 출력 시키기 위해서 교정한 데이터를 기억하여 활용하는 상황이 발생하게 됩니다. 이런 경우 데이터의 저장 빈도나 제품의 목적 및 활용성에 따라 다양한 방법으로 데이터를 저장하게 됩니다. 일반적인 소규모의 임베디드 제품에선 저장해야 하는 데이터의 양도 적을 뿐더러 저장 빈도도 적기 때문에 주로 MCU 내부에 존재하는 저장 장소를 이용합니다. 하지만 모바일 제품으로 갈수록 저장 빈도도 많아지고 데이터의 크기도 많기 때문에 MCU 외부에 존재하는 저장 장솔르 이용합니다. 가령 저장량은 작지만 저장빈도가 높을 경우는 외부의 EEPROM 같은 걸 사용하고 용량이 커질수록 Flash 로 넘어 가게 됩니다.

하지만 우리가 지금 언급하려는 TI의 MSP430AFE253는 소규모 임베디드 시스템에 적용하는 MCU이면서 내부에 EEPROM 같은 별도의 저장 공간이 들어 있지 않습니다. 그러다 보니 TI에선 다른 방법을 활용하여 EEPROM 같은 기능을 제공하게 됩니다.

 

그렇다면 어떻게 하느냐? 여러 방법들이 존재 하겠지만 TI의 경우는 MCU의 프로그램이 구동되는 Flash 영역 일부를 특정 영역으로 지정하여 이 부분에는 사용자가 임으로 데이터를 기록 할 수 있도록 하였습니다. 그런데 Datasheet에서 이 부분을 언급하는 part에선 TI에서 지정한 영역 이외에도 프로그램이 하다고 설명을 합니다. 자세히 읽어 보면 이게 바로 일종의 bootloader를 만들 때 참고해야 할 부분으로 보여 집니다. 

아무튼, 지금 언급하려는 것은 그런 부분이 아니니 넘기겠습니다.

 

그렇다면 지금까지 설명한 부분이 무엇이냐?! 하시겠죠. 그부분이 바로 제목에서 보셨듯이 Information Layer 입니다. Information Layer의 설명은 MSP430 User's Guide 의 Flash Memory Controller에 존재합니다.(여기서 설명할 MCU는 MSP430AFE253을 기준으로 설명해 드리고 있으며 해당 문서의 번호는 SLAU144H 입니다.) Flash Memory Controller는 bit, byte, word 단위로 Address 접근과 프로그램이 가능합니다. 물론 MSP430의 Flash Memory Controller은 많은 장점을 가지고 있으나 지금 궁금한것은 이것이 아니니 넘기도록 하겠습니다.


MSP의 Flash Memory는 Information Layer 라는 부분을 따로 때어 놓았을 때부터 알아 보셨듯이 하나의 Flash Memory를 자잘하게 나누어 놓았습니다. 다음과 같이 크게 Flash Main Memory와 Flash Information Memory로 구분됨을 보실수 있으실 것입니다.



일단은 User's Guide에 있는 32KByte로 예로 들었습니다만, Flash Memory는 Segment로 구분이 되고 세그 먼트는 다시 Block으로 구분이 됨을 알 수 있습니다. 여기서 Information Layer은 Block 단위로는 지울수 없고, 오직 Segment 단위로만 삭제가 가능합니다. 그리고 Main Memory의 Block의 크기는 64byte이고 Segment의 크기는 256byte입니다.

Infomation Layer의 Segment 크기는 64byte인데 이것이 최소 삭제 가능 메모리의 크기입니다. 이것이 나타내는 다른 의미는 Segment A의 내용을 수정하기 위해선 최소한 64byte의 ram이 필요합니다. 이런 이유는 MSP430의 Flash Memory 동작 특성 때문에 쓰기는 Byte 나 Word 단위로 가능하지만 삭제는 최소 Block이나 Segment 단위로 가능하기 때문입니다. 이런 동작 특성은 프로그램을 작성할 때 Information Layer 수정시 반드시 읽기 먼저 수행하고 이후에 쓰기를 실시하게 하는 이유가 됩니다.


그렇다면 Information Layer에 프로그램을 작성하기 위해선 어떻게 해야 할까요? 여기에 대해선 TI가 참 친절하게도 Example Source을 제공하고 있습니다. 특별하게 Basic Clock Module를 변경하지 않으셨다면 다음의 예제를 참고 하시면 됩니다.



[MSP430AFE_FLASHWRITE_01‎‎‎‎]


#include <msp430afe253.h>


char value;  // 8-bit value to write to seg C


// Function prototypes

void write_SegC(char value);

void copy_C2D(void);


void main(void)

{

    WDTCTL = WDTPW + WDTHOLD;                 // Stop watchdog timer


    FCTL2 = FWKEY + FSSEL0 + FN1;             // MCLK/3 for Flash Timing Generator

    value = 0;                                // initialize value


    while(1)                                  // Repeat forever

    {

        write_SegC(value++);                    // Write segment C, increment value

        copy_C2D();                             // Copy segment C to D

        __no_operation();                       // SET BREAKPOINT HERE

    }

}


void write_SegC(char value)

{

    char *Flash_ptr;                          // Flash pointer

    unsigned int i;


    Flash_ptr = (char *)0x1040;               // Initialize Flash pointer

    FCTL3 = FWKEY;                            // Clear Lock bit

    FCTL1 = FWKEY + ERASE;                    // Set Erase bit

    *Flash_ptr = 0;                           // Dummy write to erase Flash seg


    FCTL1 = FWKEY + WRT;                      // Set WRT bit for write operation


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

    {

        *Flash_ptr++ = value;                   // Write value to flash

    }


    FCTL1 = FWKEY;                            // Clear WRT bit

    FCTL3 = FWKEY + LOCK;                     // Set LOCK bit

}


void copy_C2D(void)

{

    char *Flash_ptrC;                         // Segment C pointer

    char *Flash_ptrD;                         // Segment D pointer

    unsigned int i;


    Flash_ptrC = (char *)0x1040;              // Initialize Flash segment C ptr

    Flash_ptrD = (char *)0x1000;              // Initialize Flash segment D ptr

    FCTL3 = FWKEY;                            // Clear Lock bit

    FCTL1 = FWKEY + ERASE;                    // Set Erase bit

    *Flash_ptrD = 0;                          // Dummy write to erase Flash seg D

    FCTL1 = FWKEY + WRT;                      // Set WRT bit for write operation


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

    {

        *Flash_ptrD++ = *Flash_ptrC++;          // copy value segment C to seg D

    }


    FCTL1 = FWKEY;                            // Clear WRT bit

    FCTL3 = FWKEY + LOCK;                     // Set LOCK bit

}



하지만 만약 외부 클럭을 받거나 내부 클럭을 변경하였다면 그에 따라 설정도 바뀌게 되고, 이러한 클럭 변화는 당연하게도 MCU 전반에 영향을 미치게 됩니다. 이는 Flash Memory(Information Layer)도 예외는 아닙니다. 따라서 위와 같은 기본 예제의 소스를 활용해선 답이 나오지 않습니다. 그렇다면 어떻게 해야 하느냐? 이는 다시 User's Guide를 확인해 봐야 합니다. 일단 여기선 Basic Clock Module에 대한 설명은 제외 하도록 하겠습니다. User's Guide에 보면 'Initiating an Erase from Within Flash Memory', 'Initiating an Erase from RAM', 'Initiating a Byte/Word Write from Within Flash Memory', 'Initiating a Byte/Word Write from RAM', 'Block Write Flow and Example' 라는 항목이 존재합니다. 여기에 보면 바로 Flow-Chart가 나오는데, 이에 맞추어서 프로그램을 작성하시면 됩니다.
 
우선 Information Layer를 초기화 하기 위해선 Basic Clock Module의 영향을 어떻게 받았는지 알아야 합니다. 이는 FCTL2 레지스터의 설정이 변경됨을 의미합니다. 역시서 우리가 변경해야 할 것은 FNx의 부분인 5~0번 bit들 입니다. 그렇다면 어떻게 설정을 하느냐? 생각보다 간단합니다. User's Guide의 Flash Memory Timing Generator부분을 보면 256K <= f-FTG <=477가 되도록 FNx를 수정하라고 합니다. 만약 사용하는 Basic Clock이 MCLK(또는 SMCLK) = 14.745MHz 이고 FN을 36으로 설정한다고 한다면 그 값은 MCLK / (FNx + 1) 이므로 398.5153가 됩니다. 즉 f-FTG 범위내에 들어 가는 것이죠. 아무튼 다음의 공식에 따라 FNx를 구하시면 됩니다. [ FNx = (S)MCLK / f-FTG - 1 ]
 
그 다음으론 'Initiating an Erase from Within Flash Memory', 'Initiating an Erase from RAM', 'Initiating a Byte/Word Write from Within Flash Memory', 'Initiating a Byte/Word Write from RAM', 'Block Write Flow and Example' 등 다양한 것들이 있지만 사실 이것들은 어떻게 보면 'Block Write Flow and Example'의 일부분이라고 볼 수 있기에 이 부분만 설명 하도록 하겠습니다.
'Block Write Flow and Example'의 Flow-Chart는 다음과 같습니다.




여기서 중요한 것은 바로 Flash Memory Controller가 충분하게 Flash에 data를 기록할 수 있는 시간을 주는 것입니다. 만약 가장 기본적인 클럭(MSP430AFE253은 Basic Clock Module이 1MHz이다.)보다 낮으면 상관 없지만 만약 높다면 Debuger 상에선 Information Layer에 기록 된 것처럼 보이지만 리셋지 해당 영역이 초기화 되거나 엉뚱한 값이 저장되어 있는 것을 보실 수가 있습니다. 따라서 위의 Flow-Chart에서 BUSY와 WAIT를 명심하셔서 삽입하셔야 합니다. 이는 MSP430의 Information Layer 뿐만 아니라 SRAM, EEPROM 등도 마찬가지입니다. 해당 모듈들이 기존의 작업을 완료하지 못한 상태인데 외부에서 접근을 시도하게 되면 해당 접근 번지나 이전 접근 번지엔 원하지 않는 필요없는 데이터가 기록되게 됩니다.

이러한 BUSY와 WAIT를 고려하여 프로그램을 수정하게 되면 다음과 같습니다.



[MSP430AFE_FLASHWRITE_02‎]


//****************************************************************//

//                                                                //

//           MSP430 : Flash Memory (Informaion Area)              //

//                                                                //

//****************************************************************//



#define FLASH_WRITE_ENABLE     (1)

#define FLASH_SIZE             (0x30)


#define FLASH_FRKEY            (0x9600)  // FRKEY

#define FLASH_FWKEY            (0xA500)  // FWKEY


// FCTL3, This bit indicates the status of the flash timing generator

#define FlashBusy() (FCTL3 & BUSY == BUSY) ? true : false

// FCTL3, Indicates the flash memory is being written to.

#define FlashWait() (FCTL3 & WAIT == WAIT) ? true : false



unsigned char flash[FLASH_SIZE];



// Interrupt enable

void IntGlobal(bool bConfig){

    // Interrupt enable (power mode : normal)

    if( bConfig ) _EINT();

    // Interrupt disable (power mode : normal)

    else          _DINT();

}    




void FlashConfigSet(void){

    while( FlashBusy() );

    FCTL2 = FWKEY + FSSEL0 + FN5 + FN2;   MCLK/3 for Flash Timing Generator

    FCTL1 = FWKEY;

    FCTL3 = FWKEY;

}



void FlashPutEnable(void){

    unsigned char* address = (unsigned char *)0x1000;  

    

    while( FlashBusy() );  // Busy check

    FCTL3 = FLASH_FWKEY;  // Clear Lock bit

    FCTL1 = FLASH_FWKEY | ERASE;  // Set Erase bit

}

void FlashPutDisable(void){

    FCTL1 = FLASH_FWKEY; // Clear WRT bit

    FCTL3 = FLASH_FWKEY | LOCK;  // Set LOCK bit

}



void FlashCharGet(unsigned char *pucData, unsigned short usAddress){

    unsigned char* address = (unsigned char *)0x1000;  // Flash segment D ptr (This time is Initialize Flash segment D ptr)


    *pucData = *(address + usAddress);  // Ram to flash memory

}


void FlashRead(unsigned char *pucData, unsigned short usAddress, unsigned short usCount){

    unsigned char* address = (unsigned char *)0x1000;  // Flash segment D


    for( int i = 0; i < usCount ; i++ ){

        *(pucData + i) = *(address + usAddress + i);  // Ram to flash memory

    }

}



void FlashStringPut(unsigned char *pucData, unsigned short usAddress, unsigned short usCount){

    // Flash segment D ptr, Flash pointer, Initialize Flash pointer

    unsigned char* address = (unsigned char *)0x1000;      

    

    while( FlashBusy() );  // Busy check

    FCTL1 = FLASH_FWKEY | WRT;  // Set WRT bit for write operation    

        

    for( int i = 0; i < usCount ; i++ )

    {

        *(address + usAddress + i) = *(pucData+i);  // Data save

        while( FlashWait() );  // Wait check

    }    

    while( FlashBusy() );  // Busy check

}


void FlashCharWrite(unsigned char ucData, unsigned short usAddress){

    if( FLASH_WRITE_ENABLE )

    {

        IntGlobal(false);

    

        FlashRead( flash, 0, FLASH_SIZE );

        

        flash[usAddress] = ucData;

        

        FlashPutEnable();

        FlashStringPut( flash, 0, FLASH_SIZE);

        FlashPutDisable();

    

        IntGlobal(true);

    }

}


void FlashWrite(unsigned char *pucData, unsigned short usAddress, unsigned short usCount){

    if( FLASH_WRITE_ENABLE ){

        IntGlobal(false);

        

        FlashRead( flash, 0, FLASH_SIZE );

        

        for( int i = 0; i < usCount ; i++ ){

            flash[usAddress + i ] = *(pucData+i);          // Data save

        }

        

        FlashPutEnable();

        FlashStringPut( flash, 0, FLASH_SIZE);

        FlashPutDisable();

        

        IntGlobal(true);

    }

}





Posted by klisty
,

IAR Embedded Workbench IDE를 처음 사용자를 위한 사용법을 구글 프리젠테이션으로 정리하였습니다. 아래 프리젠테이션 만 따라 하신다면 MSP를 처음 사용하는 분이더라도 MSP430 MCU에 예제 정도는 다운로드 하실 수 있으 실 것입니다.


또한 사진이 안 보이거나 그럴수 있어서 다음의 주소로 캡쳐화면의 원본과 프린젠테이션을 올려 놓았으니 참고 하시면 될 것 같습니다.

https://docs.google.com/folder/d/0B6FeAfTHOXQYaExrYS1kUS1YZjg/edit




Posted by klisty
,