2014/12/05

BeagleBone Black 커널 컴파일

Host PC 정보
  - OS : ubuntu 14.04 LTS
  프로세서 : Intel® Core™ i7-4610M CPU @ 3.00GHz × 4 
  그래픽 : Intel® Haswell Mobile 
  OS 종류 : 64비트
  메모리 : 3.8 GiB

기본 폴더는 /home/user_id/BBB 에서 작업을 시작하자.

1. ARM Cross Compiler 설치

samba@samba:~/BBB$ wget -c https://releases.linaro.org/14.09/components/toolchain/binaries/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux.tar.xz
samba@samba:~/BBB$ tar xf gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux.tar.xz
samba@samba:~/BBB$ export CC=`pwd`/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin/arm-linux-gnueabihf-


Linaro GCC 버전은 32비트니까 ia32-libs 설치해야한다.

  - sudo apt-get install ia32-libs

ia32-libs가 설치가 안되면 아래 링크로 직접 다운받아서 설치하라.
https://dl.dropboxusercontent.com/u/54450962/ia32-libs_1.0_all.deb

${CC}gcc --version 을 실행하면 잘 설치된건지 확인할 수 있다.
samba@samba:~/BBB$ ${CC}gcc --version
arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-4.9-2014.09 - Linaro GCC 4.9-2014.09) 4.9.2 20140904 (prerelease)
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

samba@samba:~/BBB$



2. Bootloader : U-Boot

일단 부트로더 다운로드 하고
samba@samba:~/BBB$ git clone git://git.denx.de/u-boot.git
samba@samba:~/BBB$ cd u-boot/
samba@samba:~/BBB/u-boot$ git checkout v2014.10 -b tmp




패치한 뒤
samba@samba:~/BBB/u-boot$ wget -c https://raw.githubusercontent.com/eewiki/u-boot-patches/master/v2014.10/0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch

samba@samba:~/BBB/u-boot$ patch -p1 < 0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch




빌드
samba@samba:~/BBB/u-boot$ make ARCH=arm CROSS_COMPILE=${CC} distclean
samba@samba:~/BBB/u-boot$ make ARCH=arm CROSS_COMPILE=${CC} am335x_evm_defconfig
samba@samba:~/BBB/u-boot$ make ARCH=arm CROSS_COMPILE=${CC}



3. Upgrade distro "device-tree-compiler" package

samba@samba:~/BBB$ wget -c https://raw.github.com/RobertCNelson/tools/master/pkgs/dtc.sh
samba@samba:~/BBB$ chmod +x dtc.sh
samba@samba:~/BBB$ ./dtc.sh


4. Linux Kernel

다운받고

samba@samba:~/BBB$ git clone https://github.com/RobertCNelson/bb-kernel.git
samba@samba:~/BBB$ cd bb-kernel/


v3.8.x로 쳌아웃

samba@samba:~/BBB$ git checkout origin/am33x-v3.8 -b tmp


TI BSP를 해야되는건지 안해도 되는건지....

v3.8.x로 쳌아웃한 경우에는 TI BSP를 쓰지않고,
v3.18x로 쳌아웃한 경우에 TI BSP를 쓰는거 같긴한데...

모르겠으니 TI BSP는 생략하고 커널 빌드를 해보자.

samba@samba:~/BBB/bb-kernel$ ./build_kernel.sh

혹시 머머머 설치하라고 뜨면 그대로 설치해 주자.
난 이거 깔라고 뜨드라.

  - apt-get install lzma u-boot-tools

5. Root File System

Debian 7, Debian 8, Ubuntu 14.04.01, small flash Debian 7 중에서 선택해서 하나 고르면 되는 모양이다. 난 무난하게 Debian 7...

다운받고...

samba@samba:~/BBB$ wget -c https://rcn-ee.net/deb/barefs/wheezy/debian-7.7-bare-armhf-2014-11-10.tar.xz



확인하고...

samba@samba:~/BBB$ md5sum debian-7.7-minimal-armhf-2014-11-10.tar.xz
edee8eef5fa47ed2427a01ddf2f726c8 debian-7.7-minimal-armhf-2014-11-10.tar.xz



풀기....

samba@samba:~/BBB$ tar xf debian-7.7-minimal-armhf-2014-11-10.tar.xz




이따 SD 카드로 옮기자



2014/12/04

STM32에 MCX314AL을 붙여보자 -- 5. TMC2660 굴려먹기

이젠 Bipolar 스텝모터 드라이버(TMC2660)를 돌려보자.

회로 설계할때 읽어봐서 스펙을 다 까먹었으니까 다시 확인해보자.


Features and Benefits

- Drive Capability up to 4A motor current
- Voltage up to 30V DC
- Highest Resolution up to 256 microsteps per full step
- Compact size 10x10mm QFP-44 package
- Low power dissipation, very low RDSON & synchronous rectification
- EMI-optimizes programmable slope
- Protection & Diagnostics overcurrent, short to GND, overtemperature & undervoltage
- stallGuard2 high precision sensorless motor load detection
- coolStep load dependent current control for energy savings up to 75%
- microPlyer microstep interpolation for increased smoothness with coarse step input
- spreadCycle high-precision chopper for best current sine wave form and zero crossing

요약하자면 4A, 30V 범위내의 바이폴라 모터를 돌릴 수 있고, 256 마이크로스텝을 지원하며, 여러가지 부가기능들이 있다...정도

만들어 놓은 회로도도 보자.































데이터시트의 샘플 회로를 매우 참조하였음.































내가 설계한 회로도의 R31, R32는 Rsense 저항값인데 값이 0.1 ohm 이면 전류값을 3A로 제한한다. 데이터시트의 샘플 회로도 상에서 Rsense 저항은 R3, R4 이다. 0.075 ohm 으로 설정되어 있는데 이건 전류값을 4A로 제한한다는 의미이다.
데이터시트에 이렇게 명시되어 있다.


모터 스펙에 맞춰쓰면 될거다.
그리고 SPI로도 전류를 세팅해 줄 수 있다. Rsense로 최대값을 제한해 두고 SPI로 세밀하게 전류를 설정해서 쓰는 모양이다.

TMC2660의 인터페이스는 아래와 같다.

- ENN
- SG_TST
- DIR
- STEP
- SDO
- SDI
- SCK
- CSN

SPI만으로 모터를 굴릴수 있는건 아닌거같고, 초기값 설정을 SPI로 해주고 Pulse/Dir으로 모터를 제어해 주는거 같다.
Pulse/Dir은 MCX314AL에서 제어해주면 될거고, MCU로 초기값을 SPI로 날려만 주면 되겠군.

TMC2660을 포함한 Trinamic의 모터 드라이버들은 20-bit가 SPI 통신의 기본 사이즈인거 같다.
STM32의 SPI는 8-bit or 16-bit가 기본인데 말이야.

TMC2660의 데이터시트 내용 중 SPI 관련 파트를 읽어보면 이런 내용이 있다.

If more than 20 clocks are driven, the additional bits shifted into SDI are shifted out on SDO after a 20-clock delay through an internal shift register. This can be used for daisy chaining multiple chips.

24 비트를 날려주면 최상위 4바이트는 SDO로 밀려나고 나머지 20 비트만 인식된다는 이야기 같은데...
0001 0002 0003 0004 0005 0006 이렇게 24 비트를 보내면 0001은 인식 안되고 0002 0003 0004 0005 0006 만 인식된다고 일단 생각하자.

그럼 초기화를 어떻게 시켜야 하느냐가 문제인디...
Device Initialization에 대한 내용이 데이터시트에 나온다.

 6.11 Device Initialization

The following sequence of SPI commands is an example of enabling the driver and initializing the chopper:

    SPI = $901B4;    // Hysteresis mode
    or
    SPI = $94557;    // Constant toff mode
    SPI = $D001F;    // Current setting : $d001d (max. current)
    SPI = $E0010;    // low driver strenth, stallGuard2 read, SDOFF = 0
    SPI = $00000;    // 256 microstep setting

First test of coolStep current control:
    SPI = $A8202;    // Enable coolStep with minimum current 1/4 CS

1010 1000 0010 0000 0010

The configuration parameters should be tuned to the motor and application for optimum performance.


TMC2660에는 5개의 write-only register가 존재한다.

- Driver Control Register(DRVCTRL)
- Chopper Configuration Register(CHOPCONF)
- coolStep Configuration Register(SMARTEN)
- stallGuard2 Configuration Register(SGCSCONF)
- Driver Configuration Register(DRVCONF)























DRVCTRL 레지스터는 DRVCONF 레지스터의 SDOFF 비트값에 따라 두가지 포맷을 가진다.
SDOFF 비트를 set 으로 설정하면 SPI로 모터를 컨트롤하고, clear로 설정하면 Step/Dir으로 모터를 컨트롤하게 된다.

으응??? SPI로 어떻게 모터를 돌린다는거지???

CA[7:0], CB[7:0] 레지스터의 값으로 코일 A와 B의 전류값을 세팅한다는데 아마도 이 값을 계속 변경시켜서 모터를 돌린다는 이야기 같긴 하다.


머...그건 그렇고 초기화 코드의 의미를 하나하나 확인해 보자.

그런데 아 안되겠어...타이핑을 다 할 수가 없어...

일단 저 초기화 코드에서 전류 세팅만 내가 사용할 모터에 맞게 바꾸고 나머진 그대로 쓸련다.


















1.4A에 최대한 가깝게 세팅해 주자.

SPI = $D001F; //이부분을
SPI = $D000F; //이렇게 바꾼다

하드웨어 상 3A로 전류 설정을 해놨으니까 15/32로 current scale을 설정하면 될거같다.
계산해 보면

    15/32*3 = 1.40625A

이렇다 이거지.



  //////////////////////////////////////////////////////////////////////////////
//TMC2660 Init
FT800_TxBuffer[0] = 0x09;
FT800_TxBuffer[1] = 0x45;
FT800_TxBuffer[2] = 0x57;
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, FT800_TxBuffer, FT800_RxBuffer, 3, 100);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_SET);

FT800_TxBuffer[0] = 0x0D;
FT800_TxBuffer[1] = 0x00;
FT800_TxBuffer[2] = 0x04;
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, FT800_TxBuffer, FT800_RxBuffer, 3, 100);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_SET);

FT800_TxBuffer[0] = 0x0E;
FT800_TxBuffer[1] = 0x00;
FT800_TxBuffer[2] = 0x10;
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, FT800_TxBuffer, FT800_RxBuffer, 3, 100);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_SET);

FT800_TxBuffer[0] = 0x00;
FT800_TxBuffer[1] = 0x00;
FT800_TxBuffer[2] = 0x00;
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, FT800_TxBuffer, FT800_RxBuffer, 3, 100);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_SET);
//TMC2660 Init
//////////////////////////////////////////////////////////////////////////////


TMC2660 초기화 코드를 이렇게 짜주고...아차 전류세팅은 15/32에서 4/32로 바꿨다.



          outpw(adr+wr0, 0x8000);

osDelay(200);

command(0x3,0xf);
outpw(adr+wr1, 0x0000);
outpw(adr+wr2, 0x0040);
outpw(adr+wr3, 0x0000);
expmode(0x3,0x5d08,0x497f);

accofst(0x3,0);
range(0x3,800000);
acac(0x3,1010);
dcac(0x3,1010);
acc(0x3,100);
dec(0x3,100);
startv(0x3,50);
speed(0x3,40);
pulse(0x3,10);
lp(0x3,0);
ep(0x3,0);

command(0xc,0xf);
outpw(adr+wr1, 0x0000);
outpw(adr+wr2, 0x0040);
outpw(adr+wr3, 0x0000);
expmode(0x4,0x5d08,0x01c4);
expmode(0x8,0x5d08,0x010c);

accofst(0xc,0);
range(0xc,800000);
acac(0xc,1010);
dcac(0xc,1010);
acc(0xc,100);
dec(0xc,100);
startv(0xc,50);
speed(0xc,40);
pulse(0xc,10);
lp(0xc,0);

outpw(adr+wr4, 0x0000);
outpw(adr+wr5, 0x0124);

acc(0x1,200);
speed(0x1,8000);
pulse(0x1,40000);
while(1){
command(0x1,0x20);
wait(0x1);
command(0x1,0x21);
wait(0x1);
}


MCX314AL 구동 루틴은 이렇게 만들어서 돌리보자.





ㅋㅋㅋㅋ 돈다 돌아 ㅋㅋㅋㅋ

2014/11/29

STM32에 MCX314AL을 붙여보자 -- 4. SLA7078MPR 굴려먹기

자 오늘은 드디어 모터를 돌려보자구.

MCX314AL은 4개의 axis를 구동 가능하다.

그 중 3개의 축에는 Bipolar 스텝모터 드라이버(TMC2660)를, 나머지 한개의 축에는 Unipolar 스텝모터 드라이버(SLA7078MPR)를 연결해놨다.

일단 유니폴라 스텝모터 먼저 돌려보고 바이폴라도 돌려보자.


 SLA7078MPR 스펙부터 확인해보자.

- Power supply voltages, Vbb : 46V(max), 10 to 44V normal operating range
- Logic supply voltages, Vdd : 3.0 to 5.5V
- Maximum output currents : 1A, 1.5A, 2A, 3A
- Built-in sequencer
- Simplified clock-in stepping control
- Microstepping at full-, half-, quater-, eighth-, and sixteenth-steps
- Built-in sense resistor, Rsint
- All variants are pin-compatible for enhanced design flexibility
- ZIP type 23-pin molded package(SLA package)
- Self-excitation PWM current control with fixed off-time; off-time adjusted automatically by step reference current ratio(3 levels)
- Built-in synchronous rectifying circuit reduces losses at PWM off
- Synchronous PWM chopping function prevents motor noise in Hold mode
- Sleep mode for reducing the IC input current in stand-by state
- Built-in protection circuitry against motor coil open/shorts option available

우왕 굳. 겁나 좋쿤.



























SLA7078MPR은 보호회로가 내장되어있고 3A까지 출력이 가능한 모델이다.

M1 : L, M2 : H, M3 : H 로 연결하면 16th Microstep 이 적용된다.

그리고 Clock과 F/R만 MCX314AL로 제어하면 된다 이거지.




          outpw(adr+wr0, 0x8000);

osDelay(200);

command(0x3,0xf);
outpw(adr+wr1, 0x0000);
outpw(adr+wr2, 0xe000);
outpw(adr+wr3, 0x0000);
expmode(0x3,0x5d08,0x497f);

accofst(0x3,0);
range(0x3,800000);
acac(0x3,1010);
dcac(0x3,1010);
acc(0x3,100);
dec(0x3,100);
startv(0x3,100);
speed(0x3,4000);
pulse(0x3,100000);
lp(0x3,0);
ep(0x3,0);

command(0xc,0xf);
outpw(adr+wr1, 0x0000);
outpw(adr+wr2, 0x0040); //D6 PLSMD, 1-pulse 1-direction setting.
outpw(adr+wr3, 0x0000);
expmode(0x4,0x5d08,0x01c4);
expmode(0x8,0x5d08,0x010c);

accofst(0xc,0);
range(0xc,800000);
acac(0xc,1010);
dcac(0xc,1010);
acc(0xc,100);
dec(0xc,100);
startv(0xc,50);
speed(0xc,40);
pulse(0xc,10);
lp(0xc,0);

outpw(adr+wr4, 0x0000);
outpw(adr+wr5, 0x0124);

acc(0x8,200);
speed(0x8,4000);
pulse(0x8,40000);
while(1){
command(0x8,0x20);
wait(0x8);
command(0x8,0x21);
wait(0x8);
}




기본 초기화 루틴은 2-pulse 타입으로 세팅이 되어 있는데, 1-pulse 1-direction 타입으로 변경해서 테스트 코드를 만들어 봤다.




ㅋㅋㅋ 미친듯이 도는구만 ㅋㅋㅋ












2014/11/27

STM32에 MCX314AL을 붙여보자 -- 3. 되는건지 마는건지

HardFault의 원인을 알아냈다.

FSMC 설정 중 WriteOperation이 Diable 이면 그냥 읽기만 되는 모양이다.

그래서 Enable 으로 변경하니까 문제없다.


STM32CubeMX에서 이부분을 변경하면 된다.









































타이밍은 변경없이 그대로

  /* Timing */
Timing.AddressSetupTime = 15;
Timing.AddressHoldTime = 15;
Timing.DataSetupTime = 255;
Timing.BusTurnAroundDuration = 15;
Timing.CLKDivision = 16;
Timing.DataLatency = 17;
Timing.AccessMode = FMC_ACCESS_MODE_A;
/* ExtTiming */




          outpw(adr+wr0, 0x8000);

osDelay(200);

command(0x3,0xf);
outpw(adr+wr1, 0x0000);
outpw(adr+wr2, 0xe000);
outpw(adr+wr3, 0x0000);
expmode(0x3,0x5d08,0x497f);

accofst(0x3,0);
range(0x3,800000);
acac(0x3,1010);
dcac(0x3,1010);
acc(0x3,100);
dec(0x3,100);
startv(0x3,100);
speed(0x3,4000);
pulse(0x3,100000);
lp(0x3,0);
ep(0x3,0);

command(0xc,0xf);
outpw(adr+wr1, 0x0000);
outpw(adr+wr2, 0x0000);
outpw(adr+wr3, 0x0000);
expmode(0x4,0x5d08,0x01c4);
expmode(0x8,0x5d08,0x010c);

accofst(0xc,0);
range(0xc,800000);
acac(0xc,1010);
dcac(0xc,1010);
acc(0xc,100);
dec(0xc,100);
startv(0xc,50);
speed(0xc,40);
pulse(0xc,10);
lp(0xc,0);

outpw(adr+wr4, 0x0000);
outpw(adr+wr5, 0x0124);

acc(0x3,200);
speed(0x3,4000);
pulse(0x1,80000);
pulse(0x2,40000);
command(0x3,0x22);


MCX314AML.C 파일의 main 함수내의 초기화 코드를 사용해봤다.


mcx314al의 레지스터는 구조가 매우 독특한데...문제가 좀 있어...

어떤 레지스터 번지에 데이터를 write한 후, read 해서 제대로 값이 들어갔는지 확인할 수가 없다.

그냥 Driving command를 날려서 펄스가 나오는지 확인하는거 외에는 디버깅 방법이 없다.

적어도 Chip ID 정도는 읽어서 확인할 수 있도록은 해줘야지





























일단 돌려보니까 펄스는 나온다.

우훗

2014/11/24

FT800, EVE(Embedded Video Engine) -- 4. Touch 테스트

오늘은 FT800의 터치 기능을 써먹어보자.

FT800 Programmers Guide의 5.47 CMD_CALIBRATE 항목을 유심히 읽어보자

5.47 CMD_CALIBRATE - execute the touch screen calibration routine

The calibration procedure collects three touches from the touch screen, then computes and loads an appropriate matrix into REG_TOUCH_TRANSFORM_A-F. To use it, create a display list and then use CMD_CALIBRATE. The co-processor engine overlays the touch targets on the current display list, gathers the calibration input and updates REG_TOUCH_TRANSFORM_A-F.



세 점을 터치해서 캘리브레이션 데이터를 REG_TOUCH_TRANSFORM에 저장한다. 

cmd_dlstart();
cmd(CLEAR(1,1,1));
cmt_text(80, 30, 27, OPT_CENTER, "Please tap on the dot");
cmd_calibrate();
이렇게 실행해라.

대충 이런 뜻인듯.

터치 보정 후 REG_TOUCH_TRANSFORM_A-F 레지스터의 값을 EEPROM에 저장해 두고 부팅시 저장값을 읽어와서 초기화 해주면 될거 같다.

터치 관련 레지스터나 함 찾아보자.

REG_TOUCH_ADC_MODE
-> The host can set this bit to control the ADC sampling mode of the FT800
     0 : Single Ended mode. It causes low power consumption but with less accuracy
     1 : Differential Mode. It causes higher power consumption but with more accuracy.
          The default mode after reset.

REG_TOUCH_SCREEN_XY
-> Bit[15:0] : Y coordinates of the touch screen. After doing calibration, it shall be within the height of the screen 
    size. If the touch screen is not being touched, it shall be 0x8000.
-> Bit[31:16] : X coordinates of the touch screen. After doing calibration, it shall be within the height of the screen 
    size. If the touch screen is not being touched, it shall be 0x8000.
Note : This register is the final computation output of the touch engine of the FT800. It has been mapped into screen size.

REG_TOUCH_RAW_XY
-> Bit[15:0] : raw Y coordinates of the touch screen before going through transformation matrix. 
    The valid range is from 0 to 1023. If there is no touch on screen, the value shall be 0xFFFF.
-> Bit[31:16] : raw X coordinates of the touch screen before going through transformation matrix. 
    The valid range is from 0 to 1023. If there is no touch on screen, the value shall be 0xFFFF.
Note : The coordinates in this register have not mapped into the screen coordinates. To get the screen coordinates, please refer to REG_TOUCH_SCREEN_XY.

REG_TOUCH_DIRECT_XY
-> Bit[9:0] : The 10 bit ADC value for Y coordinate.
-> Bit[25:16] : The 10 bit ADC value for X coordinate.
-> Bit[31] : 
    0 : Touch is being sensed and the two fields above contains the sensed data.
    1 : No touch is being sensed and the data in the two fields above shall be ignored.

REG_TOUCH_DIRECT_Z1Z2
-> Bit[9:0] : The 10 bit ADC value for touch screen resistance Z2.
-> Bit[25:16] : The 10 bit ADC value for touch screen resistance Z1.
Note : To know it is touched or not, please check the 31st bit of REG_TOUCH_DIRECT_XT. FT800 touch engine will do the post-processing for these Z1 and Z2 values and update the result in REG_TOUCH_RZ.

REG_ANALOG

REG_TOUCH_RZ
-> Bitp[15:0] : Resistance of touching on the touch screen. The valid value is 0 to 0x7fff.
    Highest value(0x7fff) means no touch, Lowest value(0) means maximum pressure.
    
REG_TOUCH_TRANSFORM_A-F

REG_TOUCH_TAG
-> Bit[7:0] : These bits are set as the tag value of the specific graphics object on the screen which is being touched. These bits are updated once when all the lines of the current frame is scanned out to the screen.
Note : The valid tag value range is from 1 to 255, therefore the default value of this register is zero, meaning there is no touch by default.

REG_TOUCH_TAG_XY
-> Bit[15:0] : Y coordinates of the touch screen, which was used by the touch engine to look up the tag result.
-> Bit[31:16] : X coordinates of the touch screen, which was used by the touch engine to look up the tag result.
Note : Host can read this register to check the coordinates used by the touch engine to update the tag register REG_TOUCH_TAG.

REG_TOUCH_RZTHRESH
-> Bit[15:0] : These bits control the touch screen resistance threshold. Host can adjust the touch screen touching sensitivity by setting this register. The default value after reset is 0xFFFF and it means the lightest touch will be accepted by the touch engine of the FT800. The host can set this register by doing experiments. The typical value is 1200.

REG_TOUCH_OVERSAMPLE
-> Bit[3:0] : These bits control the touch screen oversample factor. The higher value of this register causes more accuracy with more power consumption, but may not be necessary. The valid range is from 1 to 15.

REG_TOUCH_SETTLE
-> Bit[3:0] : These bits control the touch screen settle time, in the unit of 6 clocks. The default value is 3, meaning the settle time is 18(3*6) system clock cycles.

REG_TOUCH_CHARGE
-> Bit[15:0] : These bits control the touch-screen charge time, in the unit of 6 system clocks. The default value after reset is 6000, i.e. the charge time will be 6000*6 clock cycles.

REG_TOUCH_MODE
-> Bit[1:0] : The host can set these two bits to control the touch screen sampling mode of the FT800 touch engine.
    00 : off mode. No sampling happens.
    01 : Single mode. Cause one single sample to occur.
    10 : Frame mode. Cause a sample at the start of each frame.
    11 : Continuous mode. Up to 1000 time per seconds. Default mode after reset.

초기화 관련 레지스터들은 초기값으로 가도 될거같다. 

터치 처리 과정은 아마도 이렇지 않을까 싶은데...

1, 터치값을 읽어와서 REG_TOUCH_RAW_XY에 저장한다.
2. 보정값을 이용해서 스크린 좌표값으로 변경한 후 REG_TOUCH_SCREEN_XY에 저장한다.
3. 2번 값으로 tag를 찾는다.

설명이 너무 느슨해서 정확한 의미를 알기가 어려운데...실제로 써보면서 확인해보자.

일단은 캘리브레이션부터

  FT800_DLstart();
FT800_DL(CLEAR_COLOR_RGB(64,64,64));
FT800_DL(CLEAR(1,1,1));
FT800_DL(COLOR_RGB(0xff,0xff,0xff));
FT800_Text((480/2), (272/2), 27, OPT_CENTER, "Please Tap on the dot");
FT800_Calibrate(0);
FT800_Flush_Co_Buffer();
FT800_WaitCmdfifo_empty();



머 이런식이다.


 
FT800_Track(249, 86, 1, 1, 1);
FT800_Flush_Co_Buffer();
FT800_WaitCmdfifo_empty();

FT800_DLstart();
FT800_DL(CLEAR_COLOR_RGB(0,0,0));
FT800_DL(CLEAR(1,1,1));
FT800_DL(TAG(1));
FT800_Dial(249, 86, 55, 0, 0x8000);
FT800_DL(DISPLAY());
FT800_Swap();
FT800_Flush_Co_Buffer();
FT800_WaitCmdfifo_empty();

while(1){
TR_Val = ft800memRead32(REG_TRACKER);
if((TR_Val & 0xff) == 1)
color = TR_Val >> 16;

do{
cmdBufferRd = ft800memRead16(REG_CMD_READ);
cmdBufferWr = ft800memRead16(REG_CMD_WRITE);
printf("%x, %x\r\n", cmdBufferRd, cmdBufferWr);
}while(cmdBufferWr != cmdBufferRd);

FT800_DLstart();
FT800_DL(CLEAR_COLOR_RGB(0,0,0));
FT800_DL(CLEAR(1,1,1));
FT800_DL(TAG(1));
FT800_Dial(249, 86, 55, 0, color);
FT800_DL(DISPLAY());
FT800_Swap();
FT800_Flush_Co_Buffer();
FT800_WaitCmdfifo_empty();


되긴 되네.

2014/11/21

STM32에 MCX314AL을 붙여보자 -- 2. 드라이버를 만들자

노바 일렉트로닉스 홈페이지에 가면 MCX314AML.C 파일을 다운받을 수 있다.

MCX302/304/312/314/501을 사용하기 위한 샘플 프로그램 모음 파일인데 이중 MCX314AML.C 파일을 변경해서 모션 컨트롤 드라이버로 사용하자.

MCX314AL.C와 MCX314AL.H 파일을 만들어 봤다.

#define  adr 0x60000000 // Basic address

#define wr0 0x0 //Command register
#define wr1 0x2 //Mode register 1
#define wr2 0x4 //Mode register 2
#define wr3 0x6 //Mode register 3
#define wr4 0x8 //Output register
#define wr5 0xa //Interpolation mode register
#define wr6 0xc //Low word bits data writing register
#define wr7 0xe //High word bits data writing register

#define rr0 0x0 //Main status register
#define rr1 0x2 //Status register 1
#define rr2 0x4 //Status register 2
#define rr3 0x6 //Status register 3
#define rr4 0x8 //Input register 1
#define rr5 0xa //Input register 2
#define rr6 0xc //Low word bits data reading register
#define rr7 0xe //High word bits data reading register

#define bp1p 0x4 //BP + direction data register for the first axis control
#define bp1m 0x6 //BP ? direction data register for the first axis control
#define bp2p 0x8 //BP + direction data register for the second axis control
#define bp2m 0xa //BP ? direction data register for the second axis control
#define bp3p 0xc //BP + direction data register for the third axis control
#define bp3m 0xe //BP ? direction data register for the third axis control

void wreg1(int axis,int wdata);
void wreg2(int axis,int wdata);
void wreg3(int axis,int wdata);
void command(int axis,int cmd);
void range(int axis,long wdata);
void acac(int axis,int wdata);
void dcac(int axis,int wdata);
void acc(int axis,int wdata);
void dec(int axis,int wdata);
void startv(int axis,int wdata);
void speed(int axis,int wdata);
void pulse(int axis,long wdata);
void decp(int axis,long wdata);
void center(int axis,long wdata);
void lp(int axis,long wdata);
void ep(int axis,long wdata);
void compp(int axis,long wdata);
void compm(int axis,long wdata);
void accofst(int axis,long wdata);
void hsspeed(int axis,int wdata);
void expmode(int axis,int em6data,int em7data);
void syncmode(int axis,int sm6data,int sm7data);
long readlp(int axis);
long readep(int axis);
void wait(int axis);
void next_wait(void);
void bp_wait(void);
void homesrch(void);



#include "stm32f4xx_hal.h"
#include
#include "MCX314AL.h"

#define outpw(a, b) *(__IO uint16_t*) (a) = b
#define inpw(a) *(__IO uint16_t*) (a)

// wreg 1 (axis assignment, data) ----Write register 1 setting
void wreg1(int axis,int wdata)
{
outpw(adr+wr0, (axis << 8) + 0xf); //axis assignment
outpw(adr+wr1, wdata);
}

// wreg 2 (axis assignment, data) ----Write register 2 setting
void wreg2(int axis,int wdata)
{
outpw(adr+wr0, (axis << 8) + 0xf); //axis assignment
outpw(adr+wr2, wdata);
}

// wreg 3 (axis assignment, data) -----Write register 3 setting
void wreg3(int axis,int wdata)
{
outpw(adr+wr0, (axis << 8) + 0xf); //axis assignment
outpw(adr+wr3, wdata);
}

// command (axis assignment, data) -----For writing commands
void command(int axis,int cmd)
{
outpw(adr+wr0, (axis << 8) + cmd);
}

// range(axis assignment, data) -----For range (R) setting
void range(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x00);
}

// acac(axis assignment, data) -----For S-curve Deceleration increasing rate (L) setting
void acac(int axis,int wdata)
{
outpw(adr+wr6, wdata);
outpw(adr+wr0, (axis << 8) + 0x01);
}


// dcac(axis assignment, data) ----- For jerk (L) setting
void dcac(int axis,int wdata)
{
outpw(adr+wr6, wdata);
outpw(adr+wr0, (axis << 8) + 0x0e);
}

// acc(axis assignment, data) -----For acceleration/deceleration (A) setting

void acc(int axis,int wdata)
{
outpw(adr+wr6, wdata);
outpw(adr+wr0, (axis << 8) + 0x02);
}

// dec( axis assignment, data) -----For deceleration (D) setting
void dec(int axis,int wdata)
{
outpw(adr+wr6, wdata);
outpw(adr+wr0, (axis << 8) + 0x03);
}

// startv(axis assignment, data) -----For initial speed (SV) setting
void startv(int axis,int wdata)
{
outpw(adr+wr6, wdata);
outpw(adr+wr0, (axis << 8) + 0x04);
}

// speed(axis assignment, data) -----For drive speed (V) setting
void speed(int axis,int wdata)
{
outpw(adr+wr6, wdata);
outpw(adr+wr0, (axis << 8) + 0x05);
}

// pulse( axis assignment, data) -----For output pulse output/finish point (P) setting
void pulse(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x06);
}

// decp(axis assignment, data) -----For manual deceleration (DP) setting
void decp(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x07);
}

// center(axis assignment, data) -----For circular center point (C) setting
void center(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x08);
}

// lp(axis assignment, data) -----For logical position counter (LP ) setting
void lp(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x09);
}

// ep(axis assignment, data) -----For real position counter (EP) setting
void ep(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x0a);
}


// compp(axis assignment, data) -----For COMP+ (CP) setting
void compp(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x0b);
}

// compm(axis assignment, data) -----For COMP ? (CM) setting
void compm(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x0c);
}

// accofst(axis assignment, data) ----For acceleration counter shift (AO) setting
void accofst(int axis,long wdata)
{
outpw(adr+wr7, (wdata >> 16) & 0xffff);
outpw(adr+wr6, wdata & 0xffff);
outpw(adr+wr0, (axis << 8) + 0x0d);
}

// hsspeed(axis assignment, data) ------------------- Home Search Speed (HV) setting
void hsspeed(int axis,int wdata)
{
outpw(adr+wr6, wdata);
outpw(adr+wr0, (axis << 8) + 0x61);
}

// expmode(axis assignment, data) ------------------- Expansion Mode (EM) setting
void expmode(int axis,int em6data,int em7data)
{
outpw(adr+wr6, em6data);
outpw(adr+wr7, em7data);
outpw(adr+wr0, (axis << 8) + 0x60);
}

// syncmode(axis assignment, data) ------------------ Synchronous Mode (SM) setting
void syncmode(int axis,int sm6data,int sm7data)
{
outpw(adr+wr6, sm6data);
outpw(adr+wr7, sm7data);
outpw(adr+wr0, (axis << 8) + 0x64);
}

// readlp(axis assignment) -----For logical position counter (LP) reading
long readlp(int axis)
{
long a;long d6;long d7;
outpw(adr+wr0, (axis << 8) + 0x10);
d6 = inpw(adr+rr6);d7 = inpw(adr+rr7);
a = d6 + (d7 << 16);
return(a);
}

// readep(axis assignment) -----For real position counter (EP) reading
long readep(int axis)
{
long a;long d6;long d7;
outpw(adr+wr0, (axis << 8) + 0x11);
d6 = inpw(adr+rr6);d7 = inpw(adr+rr7);
a = d6 + (d7 << 16);
return(a);
}

// wait(axis assignment) -----For waiting for drive stop
void wait(int axis)
{
while(inpw(adr+rr0) & axis);
}

// next_wait() -----Next data setting of waiting for continuous interpolation
void next_wait(void)
{
while((inpw(adr+rr0) & 0x0200) == 0x0);
}


// bp_wait() ----- Next data setting of waiting for BP interpolation
void bp_wait(void)
{
while((inpw(adr+rr0) & 0x6000) == 0x6000);
}

// home search() ------------------------------- All axes home search
//
// ------ X axis home search ---------------------------------
// Step1 Near home (IN0) signal high-speed search in the ? direction at 20,000pps
// Step2 Home (IN1) signal low-speed search in the ? direction at 500pps
// Step3 Z-phase (IN2) signal low-speed search in the ? direction at 500pps
// Deviation counter clear output at Z-phase search
// Step4 3500 pulse offset high-speed drive in the + direction at 20,000pps
//
// ------ Y axis home search ---------------------------------
// Step1 Near home (IN0) signal high-speed search in the ? direction at 20,000pps
// Step2 Home (IN1) signal low-speed search in the ? direction at 500pps
// Step3 Z-phase (IN2) signal low-speed search in the ? direction at 500pps
// Deviation counter clear output at Z-phase search
// Step4 700 pulse offset high-speed drive in the + direction at 20,000pps
//
// ------ Z axis home search ---------------------------------
// Step1 High-speed search: None
// Step2 Home (IN1) signal low-speed search in the + direction at 400pps
// Step3 Z-phase search: None
// Step4 20 pulse offset drive in the ? direction at 400pps
//
// ------ U axis home search ---------------------------------
// Step1 High-speed search: None
// Step2 Home (IN1) signal low-speed search in the ? direction at 300pps
// Step3 Z-phase search: None
// Step4 Offset drive: None
//

void homesrch(void)
{
// X and Y axes home search parameter setting
// (See the initial setting of main for mode setting)
speed(0x3,2000); // Step1 and 4 High speed: 20000pps
hsspeed(0x3,50); // Step2 and 3 Low speed: 500pps
pulse(0x1,3500); // X axis offset: 3500 pulse
pulse(0x2,700); // Y axis offset: 700 pulse

// Z axis home search parameter setting
speed(0x4,40); // Step4 drive speed: 400pps
hsspeed(0x4,40); // Step2 search speed: 400pps
pulse(0x4,20); // Offset:20 pulses

// U axis home search parameter setting
hsspeed(0x8,30); // Step2 search speed: 300pps

command(0xf,0x62); // Execution of automatic home search for all the axes
wait(0xf); // Waits for termination of all the axes

if(inpw(adr+rr0) & 0x0010) // Error display
{
printf("X-axis Home Search Error \n");
}
if(inpw(adr+rr0) & 0x0020)
{
printf("Y-axis Home Search Error \n");
}
if(inpw(adr+rr0) & 0x0040)
{
printf("Z-axis Home Search Error \n");
}
if(inpw(adr+rr0) & 0x0080)
{
printf("U-axis Home Search Error \n");
}
}


adr을 메모리 맵에 맞게 수정해주고, outpw와 inpw만 재정의 해주면 끝인거 같다.

근데 HardFault 뜬다. 끄으

2014/11/20

FT800, EVE(Embedded Video Engine) -- 3. 돌려봅시다

MBED용 라이브러리를 IAR용으로 바꿔봤다.

FT800.h

FT800.c

잘 돌아 갈래나 모르겠네...


FT800 Programmers Guide의 2.2.5 Initialization Sequence 에는 초기화 시퀀스를 아래와 같이 소개한다.

Initialization Sequence during the boot up:
1. Use MCU SPI clock not more than 11MHz
2. Send Host Command "CLKEXT" to FT800
3. Send Host Command "ACTIVE" to enable clock to FT800
4. Configure video timing registers, except REG_PCLK
5. Write first display list
6. Write REG_DLSWAP, FT800 swaps display list immediately
7. Enable back light control for display
8. Wriet REG_PCLK, video output begins with the first display list
9. Use MCU SPI clock not more than 30MHz






























Initialization Sequence from Power Down using PD_N pin:
1. Drive the PD_N pin high
2. Wait for at least 20ms
3. Execute "Initialization Sequence during the Boot Up" from step 1 to 9

Initialization Sequence from Sleep Mode:
1. Send Host command "ACTIVE" to enable clock to FT800
2. Wait for at least 20ms
3. Execute "Initialization Sequence during Boot Up" from steps 5 to 8

Initialization sequence from standby mode:
Execute all the steps mentioned in "Initialization Sequence from Sleep Mode" except waiting for at least 20ms in step 2.


AN240 FT800 From thr Ground Up 문서의 4.2 Configuration 항목도 함 보자.

4.2 Configuration

  4.2.1 MCU setup

    4.2.1.1 SPI
      - <= 10MHz initial SPI clock
        -> Use slower clock while on internal oscillator
      - Mode zero
        -> CPOL = 0 - clock idles at zero
        -> CPHA = 0 - data is sampled on rising edge, propagated on falling edge
      - Little Endian data byte ordering

  4.2.2 Wake Up
  After configuring the MCU interface the first step in communicating with the FT800 is to wake it up.

    1) Reset the FT800
      - Drive PD_N low for 20ms, then back high
      - Wait for 20ms after PD_N is high
    2) Issue the Wake-up command
      - Write 0x00, 0x00, 0x00
    3) If using an external crystal or clock source on the FT800, issue the external clock command
      - Write 0x44, 0x00, 0x00
    4) Set the FT800 internal clock speed to 48MHz
      - Wriet 0x62, 0x00, 0x00
    5) At this point, the Host MCU SPI Master can change the SPI clock up to 30MHz
    6) Read the Device ID register
      - Read one byte from location 0x102400
      - Check for the value 0x7C
    7) Set bit 7 of REG_GPIO to 0 to turn off the LCD_DISP signal
      - Write 0x80 to location 0x102490

  4.2.3 Configuring the Display Timing
  Once the FT800 is awake and the internal clock set and Device ID checked, the next task is to configure the LCD display parameters for the chosen display with the values determined in Section 2.3.3 above.

    1) Set REG_PCLK to zero - This disables the pizel clock output while the LCD and other system parameters are configured
    2) Set the following registers with values for the chosen display. Typical WQVGA and QVGA values are shown:


























    3) Enable or disable REG_CSPREAD with a value of 01h or 00h, respectively. Enabling REG_CSPREAD will offset the R, G, and B output bits so all they do not all change at the same time.

  4.2.4 Configure the Touch Sensitivity

생략

  4.2.5 Configure the audio

생략

  4.2.6 Initialize and enable the display
  At this point, all the necessary configuration registers are initialized and the system is ready to start displaying video, as well as sensing touch events and playing audio. All of this is done through a Display List.

The Display List

 The Display List is formed by writing a series of commands to the RAM_DL memory portion of the FT800 memory map. Graphics elements are handled through commands stored in the list. Register writes for touch and audio elements are handled in line with the Display List.

The FT800 is then instructed to "swap" the Display List that was just created to make it active. While the active list is being shown on the LCD panel, or touch and audio activities processed, a new Display List is formed. Once ready, the lists swap again so the new commands are executed. This process continues for each update shown on the display, new audio sound, etc.


Display list commands are always 32 bits long. The first command on a display list should be to address 0. Subsequent commands should be sent on an increment of 4 bytes to avoid overlap.

Since the system is just starting, there is not active Display List, and the pixel clock is not yet started. The first Display List should start with a blank screen of a chosen color as an initial condition to avoid displaying any artifacts once the pixel clock is started. Here is an example start-up Display List:

    wr32(RAM_DL + 0, CLEAR_COLOR_REG(0,0,0));
    wr32(RAM_DL + 4, CLEAR(1,1,1));
    wr32(RAM_DL + 8, DISPLAY());
    wr32(REG_DLSWAP, SWAP_FRAME);

wr32(address, value) indicates the MCU would write the value (CLEAR, POINT_SIZE, etc.) to the address within the display list (RAM_DL + n). This notation is used throughout other FT800 documents.

Up until this point, no output has been generated on the LCD interface. With the configuration and initial display list in place, the LCD DISP signal, backlight and pixel clock can now be turned on:

머 그렇탄다...

초기화 함수 함 만들어봤다

void FT800_Init(void){

  uint16_t cmdBufferWr, cmdBufferRd;


  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET);

  HAL_Delay(20);

  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_RESET);

  HAL_Delay(20);

  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET);

  HAL_Delay(20);

       
  ft800cmdWrite(FT800_CLKEXT);

  HAL_Delay(5);

  ft800cmdWrite(FT800_ACTIVE);

  HAL_Delay(5);

  ft800memWrite16(REG_HCYCLE, 548);

  ft800memWrite16(REG_HOFFSET, 43);

  ft800memWrite16(REG_HSYNC0, 0);

  ft800memWrite16(REG_HSYNC1, 41);

  ft800memWrite16(REG_VCYCLE, 292);

  ft800memWrite16(REG_VOFFSET, 12);

  ft800memWrite16(REG_VSYNC0, 0);

  ft800memWrite16(REG_VSYNC1, 10);

  ft800memWrite8(REG_SWIZZLE, 0);

  ft800memWrite8(REG_PCLK_POL, 1);

  ft800memWrite8(REG_CSPREAD, 1);

  ft800memWrite16(REG_HSIZE, 480);

  ft800memWrite16(REG_VSIZE, 272);


  ft800memWrite32(RAM_DL, CLEAR_COLOR_RGB(0,0,0));

  ft800memWrite32(RAM_DL+4, CLEAR(1,1,1));

  ft800memWrite32(RAM_DL+8, DISPLAY());

  ft800memWrite32(REG_DLSWAP, DLSWAP_FRAME);

  ft800memWrite8(REG_PCLK, 5);


  HAL_Delay(50);

       
  ft800memWrite32(RAM_DL, CLEAR(1,1,1));

  ft800memWrite32(RAM_DL+4, BEGIN(1));

  ft800memWrite32(RAM_DL+8, VERTEX2II(220,110,31,'F'));

  ft800memWrite32(RAM_DL+12, VERTEX2II(244,110,31,'T'));

  ft800memWrite32(RAM_DL+16, VERTEX2II(270,110,31,'D'));

  ft800memWrite32(RAM_DL+20, VERTEX2II(299,110,31,'I'));

  ft800memWrite32(RAM_DL+24, END());

  ft800memWrite32(RAM_DL+28, COLOR_RGB(160,22,22));

  ft800memWrite32(RAM_DL+32, POINT_SIZE(320));

  ft800memWrite32(RAM_DL+36, BEGIN(2));

  ft800memWrite32(RAM_DL+40, VERTEX2II(192,133,0,0));

  ft800memWrite32(RAM_DL+44, END());

  ft800memWrite32(RAM_DL+48, DISPLAY());

       
  ft800memWrite32(REG_DLSWAP, DLSWAP_FRAME);


  MX_SPI1_Init_HIGH();

}









































와 나온다

2014/11/18

STM32에 MCX314AL을 붙여보자 -- 1. FSMC로 연결하자

NOVA electronics의 모션 컨트롤러인 MCX314AL을 사용해보자.

Ali에서 검색해보니까 겁나 비싼 IC네...123달러다.

내가 이걸 왜... 걍 FPGA에 로직 올려서 팔아도 이거보다 싸겠는데...




어쨋건간에 ADDR[3:0], DATA[15:0], CSN, WRN, RDN 핀을 STM32의 FSMC에 연결했다.

H16L8 핀은 High로 연결하면 16비트, Low로 연결하면 8비트란 뜻이다. 난 16비트로 간다.

그 외 INTN, EXPLSN, RESETN은 적당히 연결해뒀다.



STM32CubeMX에서 설정한건데 STM32에서 FSMC를 써보는건 처음이라 이게 맞는지 모르겠네...


셋업/홀드 타임 설정하는 부분같은데 일단 기본값으로 가보자. 나중에 오실로 보면서 수정하든가 하고...

STM32CubeMX에서 Generate Code 하면 프로젝트를 생성해준다.

편하고 조으네..



생성된 프로젝트의 main 함수 내부에 FSMC 초기화 함수가 포함되어 있다.



아랫부분의 타이밍 설정 부분만 요리조리 손보면 금방 돌려볼 수 있을거 같은데...


이게 FSMC에 연결되는 외부 디바이스의 메모리 맵이다. MCX314AL은 NOR/PSRAM/SRAM 타입으로 연결되니까 Bank1 부분이지.


BANK1은 또 4개의 부분으로 나뉜다. STM32CubeMX의 FSMC설정 부분의 Chip Select 항목을 보면 NE1~NE4까지 설정할 수 있게 되어있다.

NE1 : 0x6000 0000 ~ 0x63ff ffff
NE2 : 0x6400 0000 ~ 0x67ff ffff
NE3 : 0x6800 0000 ~ 0x6bff ffff
NE4 : 0x6c00 0000 ~ 0x6fff fffff

이렇게 메모리 맵이 구성된다 이거지.

난 MCX314AL의 CE를 NE1에 연결했으니까 0x6000 0000를 베이스 어드레스로 사용하면 된다.

물론 첨 해보는거니까 내 생각일 뿐이다.

프로젝트도 생성했고 메모리 구조도 확인했으니 프로그램 짜서 함 돌리보자.

FT800, EVE(Embedded Video Engine) -- 2. 라이브러리 어떻게 할까.

스펙은 겁나게 맘에 드는데...

라이브러리는???

FTDI 애네 IAR ARM 라이브러리는 지원을 안해주네....어쩌라는거지


FTDI 홈페이지에서 확인 가능한 라이브러리는 Arduino, ARM, PIC 가 있다.


이 중 ARM과 PIC은 영 쓸게 아닌거 같고, Arduino는 잘 만들었지만 난 STM32를 쓰거든...



MBED에도 라이브러리가 있는 모양이다.


http://developer.mbed.org/users/dreschpe/code/FT800/

오케이 이걸 IAR용 C로 바꾸면 쓸만할거 같다.

이걸 베이스로 하자.

2014/11/17

FT800, EVE(Embedded Video Engine) -- 1. 일단 스펙 읽어보고 하드웨어를 준비하자.

USB 관련 디바이스만 만들던 FTDI가 밑도 끝도 없이 툭 내 놓은 EVE 시리즈...

MCU 기반 시스템에서 매우 큰 리소스를 점유하는 LCD를 SPI로 간단히 제어할 수 있다. 우와아!!!

보통 AVR, PIC, Cortex-M 정도의 MCU에서 Character LCD가 아닌 TFT-LCD를 구동하기는 쉽지가 않다. 단순히 정지화면만 뿌려주는거야 쉽게 구현가능하지만, 사용자의 입력을 받아서 처리한 후 화면에 뿌려주는건 어렵지...처리 속도도 그렇고 내부 용량도 그렇고...

난 MCU를 사용하는 시스템에 외부 메모리는 되도록 인터페이스하지 않는다. 별로 많지도 않은 IO를 메모리 전용으로 쓰기엔 너무 아까워서 눈물이 나니까...차라리 메모리가 좀 더 많은 디바이스를 선택하는게 낫다.

그러니까 주로 Character LCD, 좀 무리하면 흑백 그래픽 LCD 정도나 쓰지...
TFT-LCD는 리눅스 올라가는 임베디드 시스템 아니면 쳐다도 못봤지. 지금까진



MBED와 Arduino에서 돌리는 영상이다. STM32나 AVR에서 저정도로 TFT-LCD를 굴려먹을 수 있다. 우왕 신난다.



EVE(Embedded Video Engine) 시리즈는 현재 FT800과 FT801 두 가지를 사용할 수 있다.
일단 특징부터 보자.

- Targets QVGA/WQVGA displays with 1/16th resolution, maximum 512x512 resolution
- 18-bit interface with 2-bit color dither offering 262K color palette with 24-bit quality
- Store up to 2000 objects/commands in 8K byte display list
- FT800 with integrated touch controller; FT801 with I2C capacitive touch interface
- PWM output for programmable display brightness
- Anti-aliasing algorithms improve display perception
- Low-power - 24mA typical in active mode
- Integrated audio synthesizer outputs beeps, tones or recorded audio
- Support audio playback for PCM, 4bit ADPCM and u-Law coding formats
- Extended temperature range : -40 ~ 85

최대 512x512 해상도를 지원하고 320x240, 480x272 해상도를 타겟으로 한다. 2비트 디더링으로 24비트 RGB를 처리해줌. 터치, 사운드 지원하고 저전력형이고... 등등등




MCU와 FT800간의 인터페이스는 SPI로, I2C도 지원되지만 최대 3.4MHz 니까 30MHz까지 지원되는 SPI를 써야지.

Backlight 디밍 컨트롤용 PWM 핀이 있으니까 LED Driver 회로와 Audio AMP 회로만 넣으면 회로 설계는 끝이다.

정말 이걸로 LCD를 돌릴 수 있나??? 정말 이게 단가??? 터치하고 오디오는 정말 된다는건가???
싶을정도다.


FT800 데이터시트에 레퍼런스 회로도를 제공한다.


개발용 보드로 판매하는 VM800C 를 구매해서 사용하는것도 좋다. 개당 65달라 정도인거 같다.

근데 난 회로를 만들었지...
LCD를 미리 선정해놔서 어쩔수가 없다. LCD 핀맵이 다르다...ㅠㅠ


내가 사용할 LCD는 LTE430WQ-F0C-0BS 로 4.3인치 터치 LCD다.
이거 LCD 핀맵으로 설계했다.



백라이트는 다이렉트로 30V 넣어주고, 사운드는 일단 빼고 만들었다.



보드는 이런 느낌...

사운드도 넣고 LED 드라이버도 추가해 줄 생각이다.



자 다음엔 FT800 드라이버를 만들어보자.


ps. IC 가격을 깜빡했네. 엘레먼트14는 현재 1~99 단위로 $8.76
      디지키에 좀더 싼 가격대가 있기는 하다. 

2014/03/20

Nios II 시작해보자-5(PIO Interrupt)

이번엔 PIO 인터럽트를 사용해보자.

유저 인터페이스로 12분할 엔코더와 스위치를 달아놨는데 함 써먹어 봐야겠어.


엔코더는 A, B signal 외에 스위치 시그널도 있다. 푸쉬타입을 샀거등...

그 외 버튼이 두개가 있고 이걸 interface_Logic에 연결해서 입력신호 필터링을 한 후

인터럽트 신호와 무슨 신호인지 알려주는 데이터 신호가 출력된다.


library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity Interface_Logic_v001 is
port
(
clk_in : in std_logic;
EN0_S : in std_logic;
EN0_A, EN0_B : in std_logic;
Key_01, Key_02 : in std_logic;

data : out std_logic_vector(2 downto 0);
intr : out std_logic
);
end entity;

architecture rtl of Interface_Logic_v001 is
signal data_signal : std_logic_vector(2 downto 0);
signal ff_EN0_A, ff_EN0_S, ff_Key_01, ff_Key_02 : std_logic_vector(1 downto 0);
signal rotary_in : std_logic_vector(1 downto 0);
signal rotary_q1, rotary_q2 : std_logic;
begin
process(clk_in)
variable clk_cnt_ENR, clk_cnt_ENL, clk_cnt : integer := 0;
variable phase : integer := 0;
begin
if(clk_in 'event and clk_in = '1') then
if(phase = 0) then
ff_EN0_A(0) <= rotary_q1;
ff_EN0_A(1) <= ff_EN0_A(0);

ff_EN0_S(0) <= EN0_S;
ff_EN0_S(1) <= ff_EN0_S(0);

ff_Key_01(0) <= Key_01;
ff_Key_01(1) <= ff_Key_01(0);

ff_Key_02(0) <= Key_02;
ff_Key_02(1) <= ff_Key_02(0);

if(ff_EN0_A(1) = '0' and ff_EN0_A(0) = '1') then
if(rotary_q2 = '0') then
data <= "000";
else
data <= "001";
end if;
phase := 1;
end if;
elsif(phase = 1) then
intr <= '1';
clk_cnt := clk_cnt + 1;
if(clk_cnt = 100) then
clk_cnt := 0;
intr <= '0';
phase := 0;
end if;
end if;
end if;
end process;

process(clk_in)
begin
if(clk_in 'event and clk_in = '1') then
rotary_in <= (not EN0_B) & (not EN0_A);

case rotary_in is
when "00" => rotary_q1 <= '0';
rotary_q2 <= rotary_q2;
when "01" => rotary_q1 <= rotary_q1;
rotary_q2 <= '0';
when "10" => rotary_q1 <= rotary_q1;
rotary_q2 <= '1';
when "11" => rotary_q1 <= '1';
rotary_q2 <= rotary_q2;
when others => rotary_q1 <= rotary_q1;
rotary_q2 <= rotary_q2;
end case;
end if;
end process;
end rtl;

인터페이스 로직은 일단 이렇다. 엔코더만 구현해 놓은 상태...
Syntax Highlighting을 어떻게 하는지 몰라서 보기 좀 불편하네.

엔코더를 오른쪽으로 돌리면 data 핀으로 "000"가 출력되고, 왼쪽으로 돌리면 "001"이 출력된다. 그리고 인터럽트 핀에서 한 펄스 날라간다. Nios II 에서는 이 인터럽트 시그널을 받아서 인터럽트 처리를 해주면 된다는 시나리온데...잘 되려나...

Qsys 띄워서 시작해보자.






















현재 내 시스템은 이렇다. 인터럽트 입력용으로 사용할 PIO 하나 추가해보자.































이런식으로 설정했다. Rising Edge에 인터럽트가 발생하는 설정이라고 한건데...해봐야 알지 머






















추가했다. 저장하고 제너레이션 해보자.

하드웨어 설정하고 만드는건 별 문제가 없는데...
정작 C 프로그래밍은 어떻게 해야되는지 간단명료하게 설명된 문서를 찾기가 너무 힘드네...

결국 Nios II Software Developer's Handbook에서 PIO Interrupt 관련 예제를 찾았다.
인터넷 뒤진다고 눈깔 빠지는줄 알았다.

우선 ISR 함수 만들고, PIO 인터럽트로 등록해주는 소스다.

#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "alt_types.h"
#ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT
static void handle_button_interrupts(void* context)
#else
static void handle_button_interrupts(void* context, alt_u32 id)
#endif
{
/* Cast context to edge_capture's type. It is important that this
be declared volatile to avoid unwanted compiler optimization. */
volatile int* edge_capture_ptr = (volatile int*) context;
/*
* Read the edge capture register on the button PIO.
* Store value.
*/
*edge_capture_ptr =
IORD_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE);
/* Write to the edge capture register to reset it. */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE, 0);
/* Read the PIO to delay ISR exit. This is done to prevent a
spurious interrupt in systems with high processor -> pio
latency and fast interrupts. */
IORD_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE);
}


#include "sys/alt_irq.h"
#include "system.h"
...
/* Declare a global variable to hold the edge capture value. */
volatile int edge_capture;
...
/* Initialize the button_pio. */
static void init_button_pio()
{
/* Recast the edge_capture pointer to match the
alt_irq_register() function prototype. */
void* edge_capture_ptr = (void*) &edge_capture;
/* Enable all 4 button interrupts. */
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTON_PIO_BASE, 0xf);
/* Reset the edge capture register. */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE, 0x0);
/* Register the ISR. */
#ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT
alt_ic_isr_register(BUTTON_PIO_IRQ_INTERRUPT_CONTROLLER_ID,
BUTTON_PIO_IRQ,
handle_button_interrupts,
edge_capture_ptr, 0x0);
#else
alt_irq_register( BUTTON_PIO_IRQ,
edge_capture_ptr,
handle_button_interrupts );
#endif
}

소스 찾기 드럽게 힘드네...


이걸 베이스로 해서 일단 인터럽트가 발생하면 '.'을 찍어서 제대로 돌아가는지 확인해보자.

static void handle_UI_interrupts(void* context){
volatile int* edge_capture_ptr = (volatile int*) context;

*edge_capture_ptr = IORD_ALTERA_AVALON_PIO_EDGE_CAP(PIO_3_BASE);

alt_putchar('.');

IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_3_BASE, 0);
IORD_ALTERA_AVALON_PIO_EDGE_CAP(PIO_3_BASE);
}

이렇게 해서 돌려봤다.
























엔코더를 좌우로 돌리면 화면상에 ......이 출력된다.

흠,,, 되는거 같다.

여기에 좀 더 살을 붙여서 유저 인터페이스를 완성해야겠다.


PIO 인터럽트 하는김에 UART도 인터럽트 형태로 바꿀려고 했는데...
도저히 샘플 소스를 찾을 수가 없다...ㅠㅠ
나중에 한가해지면 핸드북 꼼꼼하게 읽어보면서 바꿀 생각이다.

오늘은 여기까지.

2014/02/18

Nios II 시작해보자-4(SPI-Software)

NIOS II EDS 실행해서 프로젝트 만들고

아래와 같이 만들어봤다.
#include "sys/alt_stdio.h"
#include "system.h"
#include <stdlib .h>
#include "altera_avalon_spi_regs.h"

#define _NOP() asm ("nop")

unsigned char RxBuffer1[32];
unsigned char RxCounter1 = 0;

int UART1_get_Number(void){
unsigned char key_value, i;
int KeyIn;

do{
key_value = alt_getchar();
alt_putchar(key_value);
if(key_value == '\r'){
RxBuffer1[RxCounter1]='\0';
alt_putstr("\r\n");
KeyIn = atoi(RxBuffer1);

RxCounter1 = 0;
for(i = 0; i < 32; i++)
RxBuffer1[i] = 0;

return KeyIn;
}
else
RxBuffer1[RxCounter1++] = key_value;
}while((key_value != '\r')&&(RxCounter1 != 32));

}

int main()
{
int tmp, sum, avg, i, j, k, l;
alt_u16 write_data[1], read_data[1], adcs[15];
alt_u16 adc_ch, adc_cmd;

alt_putstr("20131118_1\r\n");
alt_putstr("1. HELP : Show menu explanation\r\n");
alt_putstr("2. Read ADS7961S\r\n");
alt_putstr("anonymous>");

/* Event loop never exits. */
while (1){
tmp = UART1_get_Number();

switch(tmp){
case 1:
alt_putstr("1. HELP : Show menu explanation\r\n");
alt_putstr("2. Read ADS7961S\r\n");
break;
case 2:
alt_putstr("2. Read ADS7961S\r\n");

//0010 01xx x000 0000
write_data[0] = 0x2400;
alt_avalon_spi_command(SPI_0_BASE, 0,
1, write_data,
0, read_data,
0);

for(i=0;i<16;i++){
alt_avalon_spi_command(SPI_0_BASE, 0,
0, write_data,
1, read_data,
0);
adcs[read_data[0]>>12] = read_data[0];
}

for(i=0;i<16;i++)
alt_printf("adc_ch %x : %x\r\n",i, (adcs[i]>>4) & 0xff);

break;
default :
alt_putstr("Invalid input value\r\n");
break;
}
alt_putstr("anonymous>");
}
return 0;
}



ADS7961S는 Manual, Auto-1, Auto-2 라는 세가지 채널 시퀀싱 모드를 제공한다.
난 Auto-1 모드를 선택해서 써볼란다.

write_data[0] =  0x2400;
alt_avalon_spi_command(SPI_0_BASE, 0,
1, write_data,
0, read_data,
0);
0x2400을 write해서 Auto-1 모드를 선택한다.

for(i=0;i<16;i++){
alt_avalon_spi_command(SPI_0_BASE, 0,
0, write_data,
1, read_data,
0);
adcs[read_data[0]>>12] = read_data[0];
}
16번 read해서 16채널의 adc 값을 읽어온다.

이게 다지 머



제대로 돌아가는지 함 돌려보자.

시발 좆같은 오실로를 쓰고있어서 파형 저장도 안돼고 나 썅.




























CH1 : MISO
CH2 : SCLK
CH3 : MOSI

인데 클럭말곤 안나오네. 0x2400 시그널이 왜 안나오지??

SPI read 부분은 빼고 0x1234를 write해서 시그널을 다시 확인해보자.
    //0010 01xx x000 0000
//write_data[0] = 0x2400;
write_data[0] = 0x1234;
alt_avalon_spi_command(SPI_0_BASE, 0,
1, write_data,
0, read_data,
0);
#if 0
for(i=0;i<16;i++){
alt_avalon_spi_command(SPI_0_BASE, 0,
0, write_data,
1, read_data,
0);
adcs[read_data[0]>>12] = read_data[0];
}

for(i=0;i<16;i++)
alt_printf("adc_ch %x : %x\r\n",i, (adcs[i]>>4) & 0xff);
#endif





























응??
0x0034??
앞에 12는 어디간겨??

int alt_avalon_spi_command(alt_u32 base, alt_u32 slave,
alt_u32 write_length, const alt_u8 * write_data,
alt_u32 read_length, alt_u8 * read_data,
alt_u32 flags)
Nios의 SPI 관련 함수의 선언부다...만 먼가 좀 이상하군 난 분명히 16bit width로 설정했는데 왜 write_data, read_data가 8bit지?? 하위 8bit만 write되고, 하위 8bit만 read되는 상황인디...

내가 머리가 나빠서 왜 저렇게 만든건진 모르것고 변수 선언부를 alt_u16으로 수정했다.

int alt_avalon_spi_command(alt_u32 base, alt_u32 slave,
alt_u32 write_length, const alt_u16 * write_data,//changed 16bit
alt_u32 read_length, alt_u16 * read_data, //changed 16bit
alt_u32 flags)
{
const alt_u16 * write_end = write_data + write_length; //changed 16bit
alt_u16 * read_end = read_data + read_length; //changed 16bit

alt_u32 write_zeros = read_length;
alt_u32 read_ignore = write_length;
alt_u32 status;

/* We must not send more than two bytes to the target before it has
* returned any as otherwise it will overflow. */
/* Unfortunately the hardware does not seem to work with credits > 1,
* leave it at 1 for now. */
alt_32 credits = 1;

/* Warning: this function is not currently safe if called in a multi-threaded
* environment, something above must perform locking to make it safe if more
* than one thread intends to use it.
*/

IOWR_ALTERA_AVALON_SPI_SLAVE_SEL(base, 1 << slave);

/* Set the SSO bit (force chipselect) only if the toggle flag is not set */
if ((flags & ALT_AVALON_SPI_COMMAND_TOGGLE_SS_N) == 0) {
IOWR_ALTERA_AVALON_SPI_CONTROL(base, ALTERA_AVALON_SPI_CONTROL_SSO_MSK);
}

/*
* Discard any stale data present in the RXDATA register, in case
* previous communication was interrupted and stale data was left
* behind.
*/
IORD_ALTERA_AVALON_SPI_RXDATA(base);

/* Keep clocking until all the data has been processed. */
for ( ; ; )
{

do
{
status = IORD_ALTERA_AVALON_SPI_STATUS(base);
}
while (((status & ALTERA_AVALON_SPI_STATUS_TRDY_MSK) == 0 || credits == 0) &&
(status & ALTERA_AVALON_SPI_STATUS_RRDY_MSK) == 0);

if ((status & ALTERA_AVALON_SPI_STATUS_TRDY_MSK) != 0 && credits > 0)
{
credits--;

if (write_data < write_end)
IOWR_ALTERA_AVALON_SPI_TXDATA(base, *write_data++);
else if (write_zeros > 0)
{
write_zeros--;
IOWR_ALTERA_AVALON_SPI_TXDATA(base, 0);
}
else
credits = -1024;
};

if ((status & ALTERA_AVALON_SPI_STATUS_RRDY_MSK) != 0)
{
alt_u32 rxdata = IORD_ALTERA_AVALON_SPI_RXDATA(base);

if (read_ignore > 0)
read_ignore--;
else
*read_data++ = (alt_u16)rxdata; //changed 16bit
credits++;

if (read_ignore == 0 && read_data == read_end)
break;
}

}

/* Wait until the interface has finished transmitting */
do
{
status = IORD_ALTERA_AVALON_SPI_STATUS(base);
}
while ((status & ALTERA_AVALON_SPI_STATUS_TMT_MSK) == 0);

/* Clear SSO (release chipselect) unless the caller is going to
* keep using this chip
*/
if ((flags & ALT_AVALON_SPI_COMMAND_MERGE) == 0)
IOWR_ALTERA_AVALON_SPI_CONTROL(base, 0);

return read_length;
}



자 다시 돌려보자.





























좀 흔들렸네...어쨌든 0x1234 나왔다.

SPI write는 확인했고, 다음은 read를 확인해보자.
0x1234를 0x2400으로 바꾸고 주석처리한 부분도 해제해서 돌려보자.





























오케이 ADC값이 나온다.
10번 채널에 33이 출력된다. 16비트 데이터 중 상위 4비트는 채널값이고 그 다음 8비트가 ADC 값이다. 하위 4비트는 Don't care.















시리얼로 출력은 이렇게 나온다. 0x21 = 33























ADS7961S의 10번 채널에 센싱 저항을 연결해서 LED 듀티를 변경하면서 adc 값을 읽어봤다.

잘 되네.