20140729 (Assembly imul, idiv, div, cbw, cwd, cdq, cwde, movsx, movzx)

112일차









--------------

Assembly

--------------




------- imul Instruction


부호 있는 곱셈

flags는 CF, OF만 사용하고 함께 set된다.

3가지가 있다.


--- 1. imul source



eax, ax, al의 값과 operand 값과 연산을 하고

연산된 값은 edx:eax, dx:ax, ax에 저장된다.






--- 2. imul Reg, source



위와 좀 다른데

operand1 과 operand2와 연산하여

operand1 에 저장한다.

넘는 값은 짤리고 CF, OF가 set 됨







--- 3. imul Reg, source, immediate



이것도 좀 다른데

source 와 immediate 를 연산하여

Destination에 저장한다.

넘는 값은 짤리고 CF, OF가 set 됨






몇 가지 예제)













------- div, idiv Instruction


나눗셈

나눗셈은 flags가 바뀌지 않는다.

연산을 할 때에 잿수는 두 배의 크기여야 한다.






위의 표와 같이

byte를 연산할 경우

AX의 값을 다 바꿔야하고


word를 연산할 경우

DX의 값도 바꿔야하고


double word를 연산할 경우

EDX의 값도 함께 바꿔줘야 한다.



예를 들면

div ebx 를 한다고 치면


mov   edx ,  0

mov   eax ,  2

mov   ebx ,  2

div    ebx


mov   edx ,  -1 (FF FF FF FF)

mov   eax ,  -2

mov   ebx ,  2

div    ebx



이런식으로 확장된 곳의 값도 함께 바꿔줘야 한다.



이런 불편함을 덜어주는 명령어가


cbw        AL  ->  AX

cwd        AX  ->  DX:AX

cdq         EAX  ->  EDX:EAX

cwde      AX  ->  EAX


이다.




cwd        AX  ->  DX:AX







cdq         EAX  ->  EDX:EAX









cbw        AL  ->  AX










cwde      AX  ->  EAX










------- movzx , movsx Instruction

8 bit를 16 bit로,

16 bit를 32 bit로

확장하여 값을 넣는다.





movzx , movsx 의 다른 점은

movzx 는 확장된 곳에 무조건 0 을 채워 넣고

movsx 는 확장된 곳에 부호에 맞게 채워 넣는다.












설정

트랙백

댓글

20140728 (add, sub, inc, dec, neg, mul)

111일차










------------------

Assembly

------------------




------- add, sub Instruction


(덧셈, 뺄셈)




위의 표에서 메모리와 상수 간에 명령어의 속도를 보면 다른 것보다 상당히 느리다.

예제 코드로 설명을 덧붙이자면...



int A = 0;


A = 0;

while(A < 10)

{

A++;

}



A를 한번 더 초기화하면서 위의 표를 참고 하면 엄청나게 많은 시간이 소모되었다.

최적활를 위해서 소스를 짤 때에 이런 것을 유의하여 작성해야 한다.










------- inc, dec Instruction


(increase, decreace 1 증가, 감소)





386 속도    용량

add   eax ,  1                2            5

inc   eax                      2            1



똑같은 +1 을 하는 코드를 사용한다면

inc를 사용하는게 용량면에서 좋다.






그런데 MS 컴파일러는 최적화를 하지 않는다.

MS Visual Studio를 통해 확인해 본다면



아래와 같은 소스를 짜고 저 위치에 Break Point를 걸고





F5로 디버그 모드를 실행 후, 디스어셈블리를 확인 할 수 있다.





아래 Assembly 소스를 보면 add, sub을 사용하였다.




속도면에서는 add, sub과 inc, dec의 차이는 없지만,

용량면에서 차이가 나기 때문에 inc, dec를 써주면 더 최적화된 소스이나,

컴파일러마다(?) 이런 요소를 상관하지 않을 수 있으므로

사람의 손이 필요하다.



위에서

mov    eax, dword ptr [a]

라는게 있는데 C로 치자면 포인터이다.

a   는 값 a

[a]   는 a에 저장된 주소를 따라간다는 뜻이다.








몇 가지 예제













------- neg Instruction


(negative : 부호를 반대로. 2의 보수법을 취함)








몇 가지 예제

1.







2. -(x + y - 2z + 1)







3. 2(-x + y -1) + z













------- mul, imul Instruction


곱셈

mul : 부호가 없을 때

imul : 부호가 있을 때




1. 1byte 연산


mul 0x77


=> AX = AL * 0x77




2. 2byte 연산


mul 0x1234


=> DX : AX = AX * 0x1234



3. 4byte 연산


mul 0x12345678


=> EDX : EAX = EAX * 0x12345678





예제)




설정

트랙백

댓글

20140725 (Assembly Flags Register)

110일차









-------------------------

Assembly mnemonics

-------------------------




------- 덧셈, 뺄셈, Flag Reg










위에 것 중에 한가지만 예로 든다면.









EFL 이 Flags Register이다.

하위 12bit 중에 OF, SF, ZF, CF Flag만 보자.











------- batch file


윈도우에는 실행파일이 3종류가 있다.

- bat (cmd)

- com

- exe


이 중 같은 이름이고 확장자만 다를 때 실행할 경우

bat 파일이 실행된다.




---- batch 파일을 만드는 방법


@echo ---------------- compile ----------------

ml /c /coff /Zi %1.asm

@echo ----------------    link    ----------------

link /debug /subsystem:console /entry:start /out:%1.exe %1.obj kernel32.lib

@dir %1.*

@pause

@start windbg %1.exe


이런식으로 사용할 명령어를 적어주고

파일 이름의 확장자를 .bat로 해주기만 하면 끝.


%1은 첫 번째 인수를 말한다.


















--------------

ARM PMC

--------------



------- 그냥 테스트




LCD에 1 만 찍힌다. datasheet를 참고하면 Processor Clock 만 켜져 있다.

PMC_SCDR(PMC System Clock Disable Register) 에 1을 넣으면

Processor Clock이 Disable 되는데 그대로 동작이 멈춘다.

그 밑에 어떤 코드도 실행하지 않는다.


그래서 인터럽트로 다시 동작을 시켜주면 동작할까 싶어



이 코드를 추가하고 나니

약 5초 정도 후에 다시 동작하기 시작했다.


PMC로 MCU를 끄더라도 다른 장치가 동작하고 있고

다시 MCU도 킬 수 있었다.





설정

트랙백

댓글

20140724 (ASM mov, xchg)

109일차








--------------

Assembly

--------------







------- []의미












------- mov 명령어



mov 명령어의 operand에 오는 속성과 정보들이다.


--- 경우 1. operand에 Reg, 상수






prefix byte를 실제로 확인.










--- 경우 2. operand에 Mem, 상수









--- 경우 3. 추가적으로...












------- 문법


mov   buf ,  number


C로 표현하면

=> buf = number;



그런데 위에 mov 옵션을 보면

메모리에서 메모리로 옴겨지는 경우는 없다.


그래서 mov   buf ,  number

이 문법은 틀렸다.


맞는 어셈블리로 짜려면


mov   eax ,  number

mov   buf ,  eax


이렇게 짜야한다.





또,







또,






그래서 INVOKE를 넣어줘야 한다.

(다른 방법이 있다고 하는데 아직 초보 수준에서는 이 정도만...)









------- xchg


C로


buf = number1;

number1 = number2;

number2 = buf;


이런 swap 하는 명령인데

어셈블리로 바꾸면


mov   eax ,  number1

mov   ebx ,  number2


mov   ecx ,  eax

mov   eax ,  ebx

mov   ebx ,  ecx


mov   number1 ,  eax

mov   number2 ,  ebx



이렇게 한다.

중간에 swap을 더 빨리 해 주는 명령어가 xchg 이다.



mov   eax ,  number1

mov   ebx ,  number2


xchg   eax ,  ebx


mov   number1 ,  eax

mov   number2 ,  ebx



이러면 끝.

용량과 속도도 더 빠르다.




--- xchg operand 정보








설정

트랙백

댓글

20140723 (Assembly 기초, windbg 사용법)

108일차










--------------------

Assembly Windbg

--------------------





------- windbg 로 프로그램 관찰





- string 변수에 1234567890을 입력합니다.







- string 에 1234567890 이 아스키코드로 입력됐음을 확인할 수 있습니다.







- 1234567890 이라는 10진수를 16진수로 바꾸면 49 96 02 D2 입니다.






- 다음 코드로 진행하면 number1에 eax에 저장된 49 96 02 D2 를 저장합니다.







- Register는 Big Endian이고, Memory는 Little Endian이라서

  Memory에 거꾸로 저장된 것을 볼 수 있습니다.







- 위와 같은 과정을 거쳐 number2 에 255 를 입력하면

  메모리에 아래와 같이 16진수로 FF 가 입력된 것을 볼 수 있습니다.







- 다음으로 진행해서     add  eax, number2     명령을 EIP Reg를 통해 주소를 찾아가

  기계 코드로 쓰여진 것을 확인할 수 있습니다.

  03 05 04 40 40 00 이라고 입력되어 있는데요.







- Appendix D 목록을 보면  add 명령의 정보를 볼 수 있습니다.

  Opcode 가 03 으로, add 명령의 기계코드가 03 이란 것을 알 수 있습니다.

  그런데 기계코드가 03 05 로 뒤에 05 가 더 붙었습니다.

  16 bit와 32 bit 를 분별하기 위해서 더 붙는다고 합니다.





디버깅으로 이렇게 관찰할 수 있습니다.











계속해서 PDF 파일을 봅니다.



































설정

트랙백

댓글

20140722 (ARM ADC, Assembly 기초, Debug)

107일차












------------------

ARM ADC

------------------





------- ADC 타이밍도





1. ADC_CR Reg에 START bit가 set 되면

변환을 시작한다.


2. 변환이 끝나면 ADC_SR Reg에 해당 채널 EOC bit가 set 되고

ADC_SR Reg에 DRDY bit도 set 된다. (DRDY는 가장 먼저 변환이 끝난 후 계속 H)


3. 해당 ADC_CDRx Reg로 값을 읽으면 ADC_SR Reg에 해당 채널 EOC bit가 clear 된다.


4. 다시 ADC_CR Reg에 START bit가 set 되면 변환을 시작하고

변환이 끝나면 또 해당 채널의 bit가 set 된다.


5. ADC_LCDR Reg에는 최근에 변환이 완료된 값을 가지고 있고

이 값을 읽을 시 모든 채널의 EOC bit가 clear 된다.







--- 소스



- adc.c





- main.c










---------------

ASSEMBLY

---------------




------- example source code








------- compile, link, run






------- debug 모드






------- windbg 로 디버깅


1. 요놈 실행.



2. 실행 파일 열기





3. 실행 파일 열기





4. 이 화면이 뜸.





5. 알림창이 한 번 뜨고 두 번정도 더 누르면





6. 아래 창이 뜸.





7. View -> Registers 실행





8. View -> Memory 실행





9. 감시할 함수 이름을 써주는데 _start 함수를 감시할 예정.





10. 한 번 더 실행 시켜서 감시할 변수의 주소값을 적어주는데

     number1이 가장 먼저 시작하는 변수라 이것을 적으면 뒤에 있는 메모리의 다른 변수도

     함께 볼 수 있다.





11. 그럼 여러 창들이 뜨고 아래와 같이 배치하였다.





12. F11 키를 눌러 몇 번 실행하였다. 그림 안에 설명 참고.





13. 이렇게 Memory와 Register의 값이 어떻게 변하는지 관찰할 수 있다.





14. 여기서 중요한것은 12번 그림에서 0x00401072 주소에 명령어를 보면

    a3 00 40 40 00 이라는 숫자가 있는데

    mov number1, eax 를 의미한다.


위의 그림을 참고하면 명령어가 각 장비마다 실행 속도를 확인할 수 있고,

만약 같은 동작을 하는 명령어인데 속도와, 크기가 더 효과적인 명령어가 있을 수도 있다.

C에서는 바꿀 수 없지만 Assembly를 통해 더 효과적인 명령으로 수정하여

더욱 최적화가 가능하다.



또한, 이 처럼 실행 파일을 Asm 파일로 바꾸는 과정을 Disassembly 라고 하는데,

Disassembly 를 하고 코드를 바꿔 실행의 흐름을 바꾸는 기술을 Reverse Engineering 이라고 부른다.








설정

트랙백

댓글

20140721 (ARM adc 소스 작성중, asm)

106일차













--------------

ARM ADC

--------------



------- 소스


--- adc.h







--- adc.c















-----------

asm

-----------


------- Reg


registers 종류 : data reg , general reg

EAX - EBX - ECX - EDX = Data Reg Or General Reg 라 한다.

여기서 특히 사용 많이 하는 EAX 레지스터를 accumulator 라 한다.

그래서 특히 4개중에 속도가 최고로 빠르다.



각각의 레지스터들은 32 비트이다 .

여기서 EAX는 반을 나누어서 사용한다 . 오른쪽은 AX 라하며 왼쪽은 호칭은 없다

여기서 AX를 또 반을 이용해서 사용하는데 반을 나눈 두가지를 AH AL 이라한다.



Ex) 레지스터 사용되는 형식

int A = 100;

 short B = 100;

 char C = 100;

 여기서 연산을 A= A+1 을 할 경우

 1. mov EAX,A의 주소

 2. add EAX,1      ; EAX=EAX + 1

 3. mov A의 주소,EAX

  B = B+1 을하면 EAX 대신에 AX를 대입하고 A의 주소에 B의 주소를 넣으면 된다.

 C = c+1 을하면 EAX 대신에 AL를 대입하고 A의 주소에 C의 주소를 넣으면 된다.

이렇게 앞의 type에 따라 용량에 맞춰 레지스터가 사용된다.







---- Index Reg


-- Index Reg -  ESI  ,  EDI

1. 주로 주소를 저장하는 레지스터이다.

2. 연산을 할 때도 사용할 수 있다.

3. 데이터를 복사할때 사용하면 편리하다.


-출발점 주소를 ESI 도착점 주소를 EDI로 지정하면

ESI가 가리키는 주소를 EDI가 가르키는 복사된다..


-- Index Reg -  ESP , EBP

ESP = Stack Pointer  EBP = Base Pointer 라 한다.

이 두개의 레지스터는 함수호출할때 사용되며 중요한 레지스터이므로 함부로 설정하지 않는다.




----- Segment Reg


CS

코드 영역 

DS

데이터 영역 

ES

확장 영역

FS

안 중요함 

GS

안 중요함 

SS

스택 영역 





----- EIP (Reg Or instruction pointer)

 - 명령어를 저장하는 주소

 - 다른 CPU에서는 PROGRAM COUNT 라 하고 PC이다. PC 와 EIP는 같은 것이다.



EFLAGS Reg : 상태를 저장하는 레지스터





----- PC Hardware : Input / Output

Memory mapped i/o : ARM/AVR 방식 , C로 접근가능 I/O레지스터가 메모리에 있음

I/O mapped i/o : 인텔방식 C로 접근불가 I/O레지스터가 메모리에 없음  어셈블리로 접근가능

Integrated development environments (IDE)

- test compiler debugger 3개를 합쳐서 통합개발환경이라 한다.










--- 예제 소스





- 명령어


add eax, 158




- directive (라벨, 지시자)


. 으로 시작.




- macro




- mnemonic 니모닉이라고 읽음.


name           mnemonic operand(s)       ; comments


Zerocount:          mov          ecx, 0              ; ecx = 0;

(name은

생략 가능)

(라벨, 지시자)


name만 쓸 수도 있다.




- .386

   .MODEL FLAT

이 두 명령어는 sam 작성시 가장 앞에 써줘야 한다.



- asm으로 작성할 때 .386을 안쓰면 그 하위 버전용으로만 동작한다.

 .586등 높은 수를 사용하면 최신기능을 사용할 수 있으나

 하위 cpu들은 그 기능을 사용할 수 없다.



- 함수 선언

ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD

(cpu, reg를 만지고 원래 상태로 되돌리는 기능)



- 헤더파일 포함

INCLUDE io.h



- cr EQU 0dh (h:hexa, 0d = 13)

이 코드는 C로

#define cr 0dh


EQU(이퀄, 같다)


정리하면 cr이란 단어가 나오면 16진수 0D로 변환하라는 뜻.







설정

트랙백

댓글

20140717 (ARM Ultrasonic 초음파 모듈)

104일차











-------------------------------

ARM Ultrasonic (초음파 모듈)

-------------------------------




------- 어제 소스에 이어 계속 거리를 측정하는 소스를 작성한다.


--- 원리는



트리거 신호를 발사하면

초음파 모듈에서 버스트 신호를 발사하고

Echo로 H를 내보내다가

버스트 신호가 들어오면

Echo가 L로 들온다.


이 때 걸린 시간을 계산하면 물체와의 거리가 된다.












------- 소스



--- usonic.c




#include "usonic.h"


static volatile unsigned int uiCm;
static volatile unsigned int uiDist;

void Ultra_Init(void)
{
  Trg_Init();
  Echo_Init();
  Timer1_Init();
}


void Timer1_Init(void)
{
// PIOA에 전원공급
  *AT91C_PMC_PCER = (1<<AT91C_ID_TC1);

// Clock disable
  *AT91C_TC1_CCR = AT91C_TC_CLKDIS;

// TC1 인터럽트 비활성화
  *AT91C_AIC_IDCR = (1<<AT91C_ID_TC1);

// TC1 인터럽트 비활성화
  *AT91C_TC1_IDR =  AT91C_TC_COVFS | AT91C_TC_LOVRS | AT91C_TC_CPAS
          | AT91C_TC_CPBS | AT91C_TC_CPCS | AT91C_TC_LDRAS
          | AT91C_TC_LDRBS | AT91C_TC_ETRGS;

// Status Reg를 한번 읽어야 한다.
// ATmel 사에서 해야한다고 함.
  *AT91C_TC1_SR;

// TC1 Clock Select 128 , RC Compare Trigger Enable
  *AT91C_TC1_CMR = AT91C_TC_CLKS_TIMER_DIV2_CLOCK
            | AT91C_TC_CPCTRG;

// 분주비 128일 때 375마다 1ms.
  *AT91C_TC1_RC = 353;

// 인터럽트시 실행할 함수
  AT91C_AIC_SVR[AT91C_ID_TC1] = (volatile unsigned int)Ultra_Handler;

// 인터럽트 걸리는 조건 및 우선순위
  AT91C_AIC_SMR[AT91C_ID_TC1] = AT91C_AIC_SRCTYPE_HIGH_LEVEL
                  | AT91C_AIC_PRIOR_LOWEST;

// 인터럽트 Edge detecter 끄기
  *AT91C_AIC_ICCR = (1<<AT91C_ID_TC1);

// TC1 인터럽트 RC compare 활성화
  *AT91C_TC1_IER = AT91C_TC_CPCS;

// TC1 인터럽트 활성화
  *AT91C_AIC_IECR = (1<<AT91C_ID_TC1);

// Clock enable
  *AT91C_TC1_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
  
// Clock start
//  *AT91C_TC1_CCR = AT91C_TC_SWTRG;

}


void Ultra_Handler(void)
{
  ++uiCm;
  
// Status Reg를 한번 읽어야 다음 인터럽트가 걸린다.
// ATmel 사에서 해야한다고 함.
  *AT91C_TC1_SR;
}


void Echo_Init(void)
{
// PIOA에 전원공급
  *AT91C_PMC_PCER = (1<<AT91C_ID_PIOA);

// PIN_ECHO 출력 비활성화
  *AT91C_PIOA_ODR = PIN_ECHO;

// PIN_ECHO 핀 활성화
  *AT91C_PIOA_PER = PIN_ECHO;

// PIN_ECHO 인터럽트 비활성화
  *AT91C_PIOA_IDR = PIN_ECHO;

// pull up 저항 OFF
  *AT91C_PIOA_PPUDR = PIN_ECHO;

// 인터럽트시 실행할 함수
  AT91C_AIC_SVR[AT91C_ID_PIOA] = (volatile unsigned int)Echo_Handler;

// 인터럽트 걸리는 조건 및 우선순위
  AT91C_AIC_SMR[AT91C_ID_PIOA] = AT91C_AIC_SRCTYPE_POSITIVE_EDGE
                  | AT91C_AIC_PRIOR_LOWEST;

// 인터럽트 Edge detecter 끄기
  *AT91C_AIC_ICCR = (1<<AT91C_ID_PIOA);

// PIOA 인터럽트 Edge detecter 켜기
  *AT91C_AIC_ISCR = (1<<AT91C_ID_PIOA);

}


void Trg_Init(void)
{
  *AT91C_PMC_PCER = (1<<AT91C_ID_PIOA);
  *AT91C_PIOA_PER = PIN_TRG;
  *AT91C_PIOA_OER = PIN_TRG;
}


void Echo_Handler(void)
{
  static unsigned int uiState;
  static volatile unsigned int uiMode = MODE_POSITIVE;

// ISR은 한 번 읽으면 바로 다 지워짐
// 따라서 따로 저장해 두고 사용.
  uiState = *AT91C_PIOA_ISR;

// 해당 인터럽트가 오면
  if(PIN_ECHO == (uiState & PIN_ECHO))
  {
// 상승 edge
    if(MODE_POSITIVE == uiMode)
    {
      uiCm = 0;

// 인터럽트 걸리는 조건 및 우선순위
      AT91C_AIC_SMR[AT91C_ID_PIOA] =
      AT91C_AIC_SRCTYPE_EXT_NEGATIVE_EDGE
      | AT91C_AIC_PRIOR_LOWEST;

      uiMode = MODE_NEGATIVE;
    }
 // 하강 edge
    else
    {
// PIN_ECHO 인터럽트 비활성화
      *AT91C_PIOA_IDR = PIN_ECHO;

      uiDist = uiCm;

      uiMode = MODE_POSITIVE;
    }
  }

// 처리중 인터럽트 발생시 초기화.  
  uiState = *AT91C_PIOA_ISR;

// Atmel 사에서 무슨 의미인지 모르지만
// 인터럽트가 끝날 때 마지막에 꼭 넣으라 함.
  *AT91C_AIC_EOICR = 0;
}


void Ultra_Trigger(void)
{
  volatile unsigned int uiCnt;

// PIN_ECHO 인터럽트 활성화
  *AT91C_PIOA_IER = PIN_ECHO;

// PIOA 인터럽트 활성화
  *AT91C_AIC_IECR = (1<<AT91C_ID_PIOA);

// 시작 펄스 신호
  *AT91C_PIOA_CODR = PIN_TRG;
  DELAY(1000);
  *AT91C_PIOA_SODR = PIN_TRG;
  DELAY(5000);
  *AT91C_PIOA_CODR = PIN_TRG;
}


unsigned int Ultra_Act(void)
{
  uiDist = 0;
  
  Ultra_Init();
  Ultra_Trigger();

  while(0 == uiDist);
  
  return uiDist;
}






--- usonic.h



#ifndef _USONIC_H_
#define _USONIC_H_


#include "project.h"


#define PIN_TRG    AT91C_PIO_PA15
#define PIN_ECHO  AT91C_PIO_PA14

#define MODE_POSITIVE  1
#define MODE_NEGATIVE  0



void Ultra_Init(void);
void Timer1_Init(void);
void Ultra_Handler(void);
void Echo_Init(void);
void Trg_Init(void);
void Echo_Handler(void);
void Ultra_Trigger(void);
unsigned int Ultra_Act(void);



#endif  //_USONIC_H_







--- main.c


#include "project.h"
#include "lcd.h"
#include "dbgu.h"
#include "irq.h"
#include "tc.h"
#include "usonic.h"


int main(void)
{
  volatile unsigned int uiCnt;
  unsigned int uiDist;

  Lcd_Init();
//  DBGU_Init();
//  IRQ_Init();
//  IRQ_LED_Init();
//  TC0_Init();
  
  Lcd_Str((const unsigned char *)"Ultrasonic");


  while (1)
  {
    uiDist = Ultra_Act();
    Lcd_Inst(INST_SET_DDRAM | 0x40);
    Lcd_Num(uiDist);
    DELAY(1000000);
  }

  while(1);
  return 0;
}











설정

트랙백

댓글

20140716 (ARM 초음파 모듈, Ultrasonic)

103일차








--------------------

ARM 초음파 모듈

--------------------






------- atmega 에서 사용하던 소스를 가져오는데

ARM에 맞게 변형하여 사용한다.



--- usonic.c







오늘은 여기까지....


설정

트랙백

댓글

20140715 (ARM Timer & Counter)

102일차










------------------------

ARM Timer & Counter

------------------------






------- Block Diagram
















------- Product Dependencies


1. I/O Lines

우리는 I/O Lines를 사용하지 않을 것이라서 넘어감.


2. Power Management

TC 장치를 켜야 사용 가능하므로 PMC를 사용한다.


3. Interrupt

TC 는 인터럽트가 필수다.









------- 소스


--- tc.c






--- main.c





문제점.

지금은 고쳤지만 처음엔 아래와 같이 소스를 짰었다.

--- tc.c


이 소스로 하니 타이머가 똑바로 동작하지 않았다.

설정

트랙백

댓글