이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.


(2014.12.24.에 다음 블로그(http://blog.daum.net/microcontroller/13651140)에 적었던 글을 옮겨왔습니다.)

통합 개발 환경은 MPLAB IDE 8.92와 MPLAB X IDE 2.26을 번갈아 사용했습니다.

AVR은 처음부터 AVR Studio를 써와서 Atmel Studio보다 편한지라 가급적 AVR Studio 4.19 버전을 사용하는데, PIC는 처음이라 MPLAB IDE나 MPLAB X IDE 모두 기능도 제대로 파악하지 못하고 있습니다.

프로그래밍에 주로 사용한 SENS NT-R55/200에 램을 3GB로 늘려 놨지만, 아무래도 오래된 노트북이라 사양이 뒤떨어져 빠릿빠릿하지는 못합니다.

컴파일하는 속도는 MPLAB IDE나 MPLAB X IDE가 별로 차이나는 것 같지는 않지만, 로딩되는 시간은 MPLAB X IDE가 훨씬 많이 걸립니다.


컴파일러는 microchip의 홈페이지에서 http://www.microchip.com/pagehandler/en-us/devtools/mplabxc/home.html 에서 XC8 v1.33을 다운 받아 사용했습니다.


프로그래머로는 PICKIT3 clone을 사용했습니다.

잘 동작하지만 가끔 먹통이 되는 현상이 발생하기도 합니다.

이럴 때에는 전원을 분리했다가 다시 연결하면 잘됩니다.

PICKIT3와 CNK_HUD 연결을 확실히 마무리하지 않고 대충 선을 꼽아서 가끔 빠지기도 하는데, 그런 영향도 있을 듯합니다.

PICKIT2 복제품도 있는데 이상하게도 PICKIT2 복제품에서는 18F67J10을 인식하지 못합니다.

Device ID를 읽지 못하는 증상을 보입니다


CNK HUD에 EN25F16-100HIP가 18F67J10의 MSSP1에 연결되어 있습니다.

테스터로 배선을 찍어 다음과 같이 배선되어 있음을 확인했습니다.


25F16 핀번호 

 25F16 기능

 18F67J10 핀번호

 18F67J10 기능

 1

 /CS

 11

 RF7/SS1

 2

 DO

 35

 RC4/SDI1/SDA1

 3

 /WP

 13

 RF5/AN10/CVref

 4

 VSS(GND)

 

 

 5

 DI

 36

 RC5/SDO1

 6

 CLK

 34

 RC3/SCK1/SCL1

 7

 /HOLD

 12

 RF6/AN11

 8

 VCC(3.3V)

 

 



전체적으로 보아 18F67J10의 SPI 1과 정상적으로 연결되어 있습니다.

25F16의 DataOut(DO), WriteProtect(/WP), DataIn(DI), Clock(CLK), /HOLD와 연결된 18F67J10 핀의 데이터 입출력 방향을 지정하고, XC8 컴파일러 라이브러리의 SPI 함수를 사용하면 됩니다.

다만 한 가지 이상한 점은 /CS를 RF7/SS1과 연결했다는 점입니다.

25F16과 18F67J10의 연결은 당연히 18F67J10이 master가 되고 25F16이 slave가 되어야 합니다.

25F16자체가 master 기능이 없으므로 절대 master로 동작할 수 없기때문입니다.

나중에 다루겠지만 RF7/SS1 포트는 18F67J10이 slave로 동작할 때 slave select로 사용되는 핀입니다.

즉, CNK HUD에서 18F67J10은 slave로 SPI 통신할 일이 없으므로 멀리 있는 11번 RF7/SS1과 연결할 필요 없이, 바로 옆에 아무 것과도 연결하지 않은 33번 RC2로 /CS를 제어하는 것이 합리적이었을 것입니다.


왜 이랬을까? 추측컨데 이 시스템의 설계자가 /SS1의 기능을 잘못 알고 있지 않았을까 생각해봅니다. master로 동작할 때에 이 핀이 자동으로 동작하는 것으로 잘못 알고 있었던 것 같습니다.

취미로 마이크로컨트롤러를 가지고 노는 아마추어이면서 특히 PIC는 초보 수준이라 좀 조심스럽기는 하지만, 74HC42 배선한 것, /SS1 배선한 것, I2C 연결한 것 등을 종합해서 보면 이 시스템을 설계한 사람이 PIC에 대해서 잘 이해하고 있는 것 같아 보이지는 않습니다. 나중에 I2C 부분을 다루면서 또 더 심각한 오류에 관해서 이야기 하겠지만, 숙련된 설계자는 아닌 것 같습니다. 행여나 설계하신 분이 이글을 보시면 화가 나시겠지만, 초보가 보기에도 많이 부적절해 보입니다. 실력만큼 대접 받는 사회가 되기를 바라면서...


본론으로 돌아가 SPI에 관해 더 알아보기로 합니다.

책에서 여러번 보기는 했지만 실제로 SPI 프로그래밍하는 것은 이번이 처음입니다.

PIC를 다루는 것도 처음이라 공부했던 내용을 정리한다는 관점에서 글을 씁니다.

SPI 관련 내용을 한 꼭지로 쓰려니 내용이 많이 깁니다.

18F67J10에서는 SPI를 두 개까지 사용할 수 있습니다.

아래 설명에서 SSPx 또는 SSx 등과 같은 표현을 사용하는데 x는 SPI 번호로 1 또는 2를 쓸 수 있습니다.

x 없이 SSP나 SS라고 쓴 경우는 특별한 경우가 아니면 SSP1 또는 SS1입니다.


SPI 통신의 전체적인 흐름은 다음과 같습니다.

1) 각 포트들을 적절한 상태로 설정

  이 기능은 위의 SPI_Init() 함수에서 수행합니다.

2) 필요한 모드로 SPI를 설정

  이 기능은 XC8 라이브러리의 OpenSPI() 함수를 호출하여 설정합니다.

3) 읽기 쓰기 등의 작업 프로그래밍

  이 기능은 XC8 라이브러리의 readSPI(), writeSPI() 함수를 이용하여 구현합니다.


1) 단계부터 3)단계까지 순서대로 자세히 다루어 보겠습니다.


1) 단계 포트 설정에 관하여 알아봅니다.


헤더 파일에 다음과 같이 매크로를 지정했습니다.

#define SPI_WP_OUTPUT   TRISFbits.RF5  = 0     // Write Protect Pin on Serial EEPROM
#define SPI_WP_SET      LATFbits.LATF5 = 1
#define SPI_WP_CLEAR    LATFbits.LATF5 = 0
#define SPI_HOLD_OUTPUT TRISFbits.RF6  = 0     // Hold Pin on Serial EEPROM
#define SPI_HOLD_SET    LATFbits.LATF6 = 1
#define SPI_HOLD_CLEAR  LATFbits.LATF6 = 0
#define SPI_CS_OUTPUT   TRISFbits.RF7  = 0     // Chip Select Pin on Serial EEPROM
#define SPI_CS_SET      LATFbits.LATF7 = 1
#define SPI_CS_CLEAR    LATFbits.LATF7 = 0

끝에 OUTPUT이 붙은 매크로는 포트의 출력 방향을 output으로 설정하는 것이고, 끝에 SET이 붙은 매크로는 포트의 값을 1로 설정하는 것이며, 끝에 CLEAR가 붙은 매크로는 포트의 값을 0으로 설정하는 것입니다.

다음은 SPI에 사용하는 포트들을 설정하는 초기화 함수 SPI_Init()입니다.

void SPI_Init(void)
{
    SPI_WP_OUTPUT;         // Set output RF5 = WP
    SPI_HOLD_OUTPUT;       // Set output RF6 = HOLD
    SPI_CS_OUTPUT;         // Set output RF7 = CS
    SPI_WP_SET;            // Set write protect
    SPI_HOLD_SET;          // Set HOLD High
    SPI_CS_SET;            // Disable CS
}

위 함수에서는 WP, HOLD, CS 핀을 output으로 정하고, 각각의 핀을 high로 설정합니다.

매크로 정의할 때와 SPI_Init() 함수에서는 CLK(clock)과 DO(DataOut), DI(DataIn)은 다루지 않았습니다.


이들을 처리하지 않는 이유는 SPI 통신을 준비하는 다음 단계에서 OpenSPI() 함수 안에서 적절히 설정하기 때문입니다.



2) 단계 OpenSPI()함수에 관하여 살펴 봅니다.


OpenSPI() 함수의 원형은 아래와 같이 세 개의 인수를 받습니다.

void OpenSPI( unsigned char sync_mode, unsigned char bus_mode, unsigned char smp_phase)

첫째 매개 변수 sync_mode의 용도를 살펴봅니다.

XC8과 같이 설치된 소스 파일 중 spi_open.c 안에 OpenSPI() 함수의 소스가 있습니다.

(spi_open.c의 경로는 XC8 v1.33을 디폴트로 설치한 경우 C:\Program Files\Microchip\xc8\V1.33\sources\pic18\plib\SPI 폴더 안에 있습니다.)

OpenSPI() 함수의 내부를 보면

SSPCON = 0x00;              // power on state
SSPCON |= sync_mode;        // select serial mode
SSPCON |= SSPENB;           // enable synchronous serial port

등의 코드가 있습니다.

즉, 매개 변수 sync_mode는 SSPCON의 값을 가져야 합니다.

18F67J10(18F87J10)의 데이터시트 p195에 SSPxCON1에 대한 설명이 있습니다.

sync_mode는 이 중 SSPM<3:0>의 내용을 담아야 합니다.


 0b0000 (0)

 Master Mode, clock = Fosc/4

 0b0001 (1)

 Master Mode, clock = Fosc/16

 0b0010 (2)

 Master Mode, clock = Fosc/64

 0b0011 (3)

 Master Mode, clock = TMR2 output/2

 0b0100 (4)

 Slave Mode, clock = SCKx, /SSx 사용

 0b0101 (5)

 Slave Mode, clock = SCKx, /SSx 사용 안함.


CNK HUD의 18F67J10은 SPI를 master mode에서 사용해야 하므로 sync_mode는 0-3중의 한 값을 가져야 합니다.

SPI와 관련된 내용을 가지고 있는 헤더 파일 spi.h (C:\Progrma Files\Microchip\xc8\v1.33\include\plib)에 매크로가 다음과 같이 정의되어 있습니다. 이 매크로 중 적절한 것을 선택하여 OpenSPI() 함수의 sync_mode 값으로 전달하면 됩니다.

#define   SPI_FOSC_4     0b00000000 // SPI Master mode, clock = Fosc/4
#define   SPI_FOSC_16    0b00000001 // SPI Master mode, clock = Fosc/16
#define   SPI_FOSC_64    0b00000010 // SPI Master mode, clock = Fosc/64
#define   SPI_FOSC_TMR2  0b00000011 // SPI Master mode, clock = TMR2 output/2
#define   SLV_SSON       0b00000100 // SPI Slave mode, /SS pin control enabled
#define   SLV_SSOFF      0b00000101 // SPI Slave mode, /SS pin control disabled

계속하여 OepnSPI() 함수의 소스 코드를 살펴 보기로 합니다.

이 함수의 소스 코드 아랫 부분에 있는 switch 문에서 sync_mode를 다시 한 번 사용하고 있습니다. 여기서는 sync_mode의 값에 따라 각 포트들을 적절한 값으로설정합니다.


SPI를 slave mode로 사용하는 경우의 sync_mode 값은 4 또는 5입니다. switch 문 안에 case 4:와 case 5: 로 slave mode를 위한 값을 설정하고 있습니다. 위의 sync_mode 설명에서 보듯이 sync_mode 4에서는 CLK와 /SSx 신호를 모두 사용합니다. 따라서 case 4:에서는 /SSx와 CLK를 모두 입력으로 설정하고 있습니다.

반면에 sync_mode 5에서는 CLK만 사용하고 /SSx 신호는 사용하지 않습니다. 따라서 case 5:에서는 CLK만 입력으로 설정하는 것을 볼 수 있습니다. case4:와 case 5: 이외에는 master mode이므로 CLK를 출력으로 설정합니다.


switch 문 밖에서는 SDI는 입력으로 SDO는 출력으로 설정하고 있습니다. SDI는 slave 장치로부터 데이터를 받는 것이니까 입력으로 설정해야 합니다. 18F67J10의 경우 MSSP1의 SDI는 35번 핀(RC4/SDI1/SDA1)입니다. 이 핀이 slave인 25F16의 2번 핀(DO : DataOutput)과 연결되어 있습니다.

OpenSPI() 함수의 마지막 부분에서 SSPxCON1 레지스터의 SSPEN 비트를 설정하여 SPI 통신을 시작합니다.(SSPCON |= SSPENB;)


둘째 매개 변수 bus_mode는 SPI의 모드를 설정합니다.

SPI모드는 0,1,2,3으로 4 가지가 있습니다.

인터넷 상에서 가져온 그림으로 간단히 언급하고자 합니다.




SPI mode 0과 1에서는 clock이 평상시에는 0이다가 신호 전송시에 1로 올라가고,

mode 2와 3에서는 반대로 clock이 평상시에는 1이다가 신호 전송시에 0으로 내려갑니다.

SPI mode 0과 2에서는 clock이 시작될 때에 sampling 하고,

mode 1과 3에서는 clock이 끝날 때에 sampling 합니다.

18F67J10의 SPI는 mode 0, 2, 3을 지원합니다.

SPI 모드는 SSPxCON 레지스터의 CKP 비트와 SSPxSTAT 레지스터의 CKE 비트로 설정합니다.

CKP 비트는 위 그림의 Clock Polarity(CPOL)이고, CKE 비트는 Clock Phase(CPHA)입니다.


CKP 

 CKE

 mode

 0

 0

 0

 0

 1

 1(18F67J10에서는 지원하지 않음)

 1

 0

 2

 1

 1

 3


spi.h를 살펴보면 다음과 같이 매크로가 정의되어 있습니다.

#define   MODE_00       0b00000000    // Setting for SPI bus Mode 0,0
#define   MODE_01       0b00000001    // Setting for SPI bus Mode 0,1
#define   MODE_10       0b00000010    // Setting for SPI bus Mode 1,0
#define   MODE_11       0b00000011    // Setting for SPI bus Mode 1,1

25F16은 데이터시트 7 페이지에 mode 0와 mode 3만을 지원한다고 되어 있습니다. 18F67J10으로 25F16을 제어할 때에는 mode 0 또는 mode 3, 둘 중에서 하나를 써야합니다. 


세째 매개 변수 smp_phase는 샘플링 시기를 지정하는 역할을 합니다.

OpenSPI() 함수에서 SSPxSTAT 레지스터에 값을 넣는 부분만 가져와 봅니다.

SSPSTAT &= 0x3F; // power on state SSPSTAT |= smp_phase; // select data input sample phase


18F67J10의 데이터시트 p190에 SSPxSTAT 레지스터에 관한 설명을 가져와 봅니다.


bit 

 name

 function

 7

 SMP

SPI Master mode:
1 = Input data sampled at end of data output time
0 = Input data sampled at middle of data output time
SPI Slave mode:
SMP must be cleared when SPI is used in Slave mode.

 6

 CKE

SPI Clock Select bit (SPI mode 설명할 때 다루었습니다.)

 5

 D/A

Data/Address bit

Used in I2C mode only.(SPI에서는 안 씁니다.)

 4

 P

Stop bit

Used in I2C mode only.(SPI에서는 안 씁니다.)

This bit is cleared when the MSSP module is disabled, SSPEN is cleared.

 3

 S

Start bit

Used in I2C mode only.(SPI에서는 안 씁니다.)

 2

 R/W

Read/Write Information bit

Used in I2C mode only.(SPI에서는 안 씁니다.)

 1

 UA

UUpdate Address bit

sed in I2C mode only.(SPI에서는 안 씁니다.)

 0

 BF

Buffer Full Status bit


SSPSTAT &= 0x3F;에서 상위 비트인 bit 7과 bit 6을 clear하고 나머지 비트들은 그대로 두고 있습니다. bit 6 CKE는 둘째 매개 변수 bus_mode에서 설정한 것을 건드리지 말아야 하므로

SSPSTAT |= smp_phase; 에서 설정하는 것은 bit 7 밖에 없습니다.

spi.h를 찾아보면 

#define   SMPEND        0b10000000          // Input data sample at end of data out
#define   SMPMID        0b00000000          // Input data sample at middle of data out

이렇게 두 개의 매크로가 있는데 결국은 bit 7(SMP)를 1로 설정할 것인지 0으로 할 것인지를 결정하는 것입니다. dataout time의 끝 부분에서 data를 가져오도록 하려면 SMP를 1로 하고, dataout time의 중간 부분에서 data를 가져 오도록 하려면 SMP를 0으로 합니다. 이 둘의 차이점은 18F67J10의 데이터 시트 p194의 그림에서 아래 부분의 SDIx 부분을 보면 이해가 쉽게 될 것입니다. 25F16과 SPI 통신하는 데는 어느 값을 주어도 잘 됩니다.


장황한 설명을 했지만 18F67J10과 EN25F16이 SPI 통신하기 위해서는 OpenSPI() 함수를 다음과 같이 호출하면 됩니다.


OpenSPI(SPI_FOSC_4,MODE_00,SMPMID);


3) 단계 읽기 쓰기 등의 작업 프로그래밍을 할 차례입니다.


EN25F16의 데이터 시트에 따라 몇 가지 기능을 구현 해보겠습니다.

다음은 25F16의 매뉴얼을 보고 status register bit를 정의한 매크로입니다.

#define SPI_SR_WIP                0
#define SPI_SR_WEL                1
#define SPI_SR_BP0                2
#define SPI_SR_BP1                3
#define SPI_SR_BP2                4
#define SPI_SR_RES1               5
#define SPI_SR_RES2               6
#define SPI_SR_SRP                7
#define SPI_SR_S0                 8
#define SPI_SR_S1                 9
#define SPI_SR_S2                 10
#define SPI_SR_S3                 11
#define SPI_SR_S4                 12
#define SPI_SR_S5                 13
#define SPI_SR_S6                 14
#define SPI_SR_S7                 15

다음은 역시 25F16의 데이터시트에 나오는 명령어들을 정의한 매크로들입니다.

#define COMMAND_EN25F16_WREN      0x06     // Write enable
#define COMMAND_EN25F16_WRDI      0x04     // Write disable
#define COMMAND_EN25F16_OTPOUT    0x04     // OTP mode
#define COMMAND_EN25F16_RDSR      0x05     // Read status register (16bit)
#define COMMAND_EN25F16_WRSR      0x01     // Write status register
#define COMMAND_EN25F16_READ      0x03     // Read data
#define COMMAND_EN25F16_FAST      0x0B     // Read data fast
#define COMMAND_EN25F16_PP        0x02     // Page programming
#define COMMAND_EN25F16_WRITE     0x02     // Write data
#define COMMAND_EN25F16_SE        0x20     // Sector Erase
#define COMMAND_EN25F16_BE        0xD8     // Block Erase
#define COMMAND_EN25F16_CE        0xC7     // Chip Erase
#define COMMAND_EN25F16_DPDN      0xB9     // Deep Power Down
#define COMMAND_EN25F16_DEVID     0xAB     // Read Device ID
#define COMMAND_EN25F16_MANUID    0x90     // Read Manufacturer ID
#define COMMAND_EN25F16_RDID      0x9F     // Read Manufacturer and device ID
#define COMMAND_EN25F16_OTP       0x3A     // Read Manufacturer and device ID
#define PAGE_SIZE_EN25F16         256

SPI에 직접 입력과 출력을 하는 것은 XC8의 함수 WriteSPI()와 ReadSPI() 함수를 사용합니다.

다음은 25F16의 데이터 시트 p12에 있는 Status Register를 읽어 오는 방법을 설명하는 그림입니다.

위의 내용에 따라 25F16의 status register 값을 읽어 오는 함수 SPI_ReadStatusReg()를 작성했습니다.
구체적인 기능은 주석으로 대신합니다.

unsigned int SPI_ReadStatusReg(void)
{
    unsigned int data1,data2;
    SPI_CS_CLEAR;                      // set cs low
    WriteSPI(COMMAND_EN25F16_RDSR);    // send command read status register (0x05)
    data1 = ReadSPI();                 // read status reegister high byte
    data2 = ReadSPI();                 // read status register low byte
    SPI_CS_SET;                        // set cs high
    return((data1 << 8) | data2);
}

다음은 25F16의 데이터시트 p20에 있는 Device ID를 읽어 오는 방법을 설명하는 그림입니다.



위의 내용에 따라 Device ID를 읽어 오는 함수 Read_EN25F16_DeviceID()를 만들었습니다.

unsigned char Read_EN25F16_DeviceID(void)
{
    unsigned char ID;

    SPI_CS_CLEAR;                      // set cs low
    WriteSPI(COMMAND_EN25F16_DEVID);   // send command for device id (0xAB)
    WriteSPI(0);                       // Write three dummy bytes
    WriteSPI(0);                       // Write three dummy bytes
    WriteSPI(0);                       // Write three dummy bytes
    ID = ReadSPI();                    // Read Device ID
    SPI_CS_SET;                        // set cs high
    return(ID);
}

다음은 25F16의 데이터시트 p21에 있는 Manufacturer / Device ID를 읽어 오는 방법을 설명하는 그림입니다.


위의 내용에 따라 Device ID를 읽어 오는 함수 Read_EN25F16_ManufacturerID()를 만들었습니다.

unsigned int Read_EN25F16_ManufacturerID(void)
{
    unsigned int ID;

    SPI_CS_CLEAR;                      // set cs low
    WriteSPI(COMMAND_EN25F16_MANUID);
    WriteSPI(0);                       // Write Adress 0 in three bytes.
    WriteSPI(0);                       // Write Adress 0 in three bytes.
    WriteSPI(0);                       // Write Adress 0 in three bytes.
    ID = (ReadSPI() << 8);             // Manufacturer ID
    ID += ReadSPI();                   // Device ID
    SPI_CS_SET;                        // set cs high
    return(ID);
}

Page Program (PP), Sector Erase (SE), Block Erase (BE), Chip Erase (CE) and Write Status Register (WRSR) 명령을 수행하기 전에는 반드시 Status Register의 WEL(Write Enable Latch)를 1로 설정해야 합니다. Status Register의 WEL 비트를 1로 설정하는 명령이 WREN(Write Enable) 명령입니다.

Page Program의 구현을 예로 듭니다.



void SPI_PageProgram(unsigned long address,int bytes,unsigned char *arr)
{
    int I;

    // Send WREN(Write Enable).
    SPI_CS_CLEAR;
    WriteSPI(COMMAND_EN25F16_WREN);
    SPI_CS_SET;
    while(!(SPI_ReadStatusReg() & (1 << SPI_SR_WEL)));    // status register의 WEL 비트 기다림
    SPI_CS_CLEAR;
    WriteSPI(COMMAND_EN25F16_WRITE);
    WriteSPI((address & 0xFF0000) >> 16);                 // send address
    WriteSPI((address & 0x00FF00) >> 8);
    WriteSPI(address & 0x0000FF);
    for(i = 0;i < bytes;i++) WriteSPI(*(arr + i));
    SPI_CS_SET;
    while(SPI_ReadStatusReg() & (1 << SPI_SR_WIP));       // write 완료까지 기다림.
}

이 함수의 매개 변수 address는 25F16에 기록할 주소이며, bytes는  25F16에 기록할 데이터의 바이트 수이고, arr은 기록할 데이터가 있는 18F67J10의 메모리 주소입니다. 18F67J10의 arr 번지로부터 bytes만큼의 데이터를 25F16의 address 번지에 기록합니다. 25F16은 용량이 2mega byte이므로 주소룰 unsigned long 으로 받아 그 중 24비트(3바이트)를 사용합니다.


다른 기능들은 위에서 만든 함수들의 내용을 참고하여 만듭니다.

블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,