본문 바로가기

Computer Science/Embedded Software

[Embedded Software] SPI Communication 실습

+ 한국항공대학교 최차봉 교수님의 임베디드 SW 과목 내용을 정리한 글입니다.

< 첫 번째 실습: Counter 값 주고 받기 >

SPI를 이용하여 Master / Slave를 제작하라.

 

2명이 한 조가 되어 Master 1대, Slave 1대로 실습한다.

 

< Master >

Serial Monitor에서 명령을 받아 Slave로 전송한다.- 't' 명령을 입력 받아 Slave에 전송한다.- 동시에 Slave에서 한 Time 이전의 데이터를 입력 받고 Serial Monitor에 출력한다.

 

< Slave >Master에서 't'명령을 받으면 현재 Counter 값을 Master에 전송한다.- 매 초마다 1씩 증가하는 Counter를 유지한다.- ISR (Interrupt Service Routine)을 사용하여 데이터를 수신한다.- Master에 전송한 Counter 값을 Serial Monitor에 출력한다.- Counter의 Update는 loop에서 수행한다.

 

첫 번째 실습 회로

 

< Master >

#include <SPI.h>
#define SS 10

void setup(){
  Serial.begin (9600);
 
  digitalWrite(SS, HIGH); // SS의 초기 값을 HIGH로 설정

  SPI.begin(); // SPI 통신 초기화
  
  // 속도가 빠른 경우 데이터가 정확히 전달되지 않으므로 분주비를 높여 전송속도 감소
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  
}

byte transferAndWait(const byte what){
  byte a = SPI.transfer(what); 
  // SPDR에 있던 값을 a에 저장(수신)하고, what을 SPDR에 저장(송신)한다. 
  delayMicroseconds(20);
  return a;
}

void loop(){
  if(Serial.available()){
    byte c = Serial.read();
    if(c == 't'){
      digitalWrite(SS, LOW); // SS와 통신을 위해 LOW로 설정
      byte a = transferAndWait(c);
      digitalWrite(SS, HIGH); // 통신이 끝났으므로 HIGH로 설정
      Serial.println(a);
    }
  }
}

 

< Slave >

#include <SPI.h>

byte count = 0;

void setup(){
  Serial.begin(9600);
  
  pinMode(SCK, INPUT); 
  // 13번 포트: Clock 신호를 Master에서 받으므로 INPUT 
  
  pinMode(MISO, OUTPUT); 
  // 12번 포트: 데이터를 Slave에서 Master로 송신하므로 OUTPUT 
  
  pinMode(MOSI, INPUT); 
  // 11번 포트: 데이터를 Master에서 Slave로 수신하므르 INPUT
  
  pinMode(SS, INPUT); 
  // 10번 포트: 몇 번 Slave를 선택했는지를 입력 받으므로 INPUT
  
  
  // SPCR: SPI 제어 레지스터
  SPCR |= _BV(SPE);
  // _BV: 비트 값 설정 함수
  // SPE: SPI 비트 활성화 
  // SPCR 레지스터의 SPE 비트를 1로 설정하여 SPI 통신 활성화
  
  SPCR &= ~_BV(MSTR);
  // Master -> Slave로 설정
  
  SPCR |= _BV(SPIE);
  // SPIE: SPI 인터럽트 활성화
  // SPCR 레지스터의 SPIE 비트를 1로 설정하여 SPI 인터럽트 활성화
  
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  // Clock 속도 낮추기
}

// 데이터 수신 인터럽트 발생 시 호출되는 함수
ISR(SPI_STC_vect){
  byte c = SPDR; // SPDR 8비트 레지스터에 있는 데이터 수신
  if(c == 't'){
  	byte d = count; // 도중에 count가 증가될 수 있으므로 저장 후 전달한다.
    SPDR = d; // SPDR 레지스터에 데이터 저장 (송신)
    Serial.println(d);
  }
}

void loop(){
  count++;
  delay(1000);
}

< 두 번째 실습: LED Blink 횟수 제어하기 >

SPI를 이용하여 Master / Slave를 제작하라.

 

3명이 한 조가 되어 Master 1대, Slave 2대로 실습한다.

 

< Message Format >

'#' + <id> + <bn> (3 Byte ASCII)

- <id>: <bn>을 전송할 Slave id이다. '0' 또는 '1'이다.

- <bn>: LED Blink 횟수이다. '0'~'9'이다.

 

< Master >

Serial Monitor에서 Message를 받아 Slave로 <bn>을 전송한다.

 

< Slave >

Master에서 전송 받은 <bn>만큼 LED를 점등한다.

- ACK를 만들어 Master에 전송한다.

- ACK = <bn> + <id>

 

두 번째 실습 회로

 

< Master >

#include <SPI.h>

#define CS0 10
#define CS1 9

void setup(){
  Serial.begin (9600);
  
  pinMode(CS0, OUTPUT); // CS0과 연결된 pin10을 출력으로 설정
  pinMode(CS1, OUTPUT); // CS1과 연결된 pin9을 출력으로 설정
 
  digitalWrite(CS0, HIGH); // CS0의 초기 값을 HIGH로 설정
  digitalWrite(CS1, HIGH); // CS1의 초기 값을 HIGH로 설정
  
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  
}

byte transferAndWait(const byte what){
  byte a = SPI.transfer(what);
  delayMicroseconds(20);
  return a;
}

void loop(){
  if(Serial.available()){
    if(Serial.read() == '#'){
      while(!Serial.available());
      byte id = Serial.read();
      while(!Serial.available());
      byte n = Serial.read();
      if(id == '0'){
        digitalWrite(CS0, LOW); // 슬레이브 선택
        int ACK = transferAndWait(n); // 1 Byte 데이터 전송 + 수신
        Serial.print("Send '");
    	Serial.print(n - '0');
    	Serial.println("' to Slave 0");
        digitalWrite(CS0, HIGH); // 슬레이브 선택 해제
        Serial.print("ACK: '");
        Serial.print(ACK);
        Serial.println("'");
      }
      else if(id == '1'){
        digitalWrite(CS1, LOW); // 슬레이브 선택
        int ACK = transferAndWait(n); // 1 Byte 데이터 전송 + 수신
        Serial.print("Send '");
    	Serial.print(n - '0');
    	Serial.println("' to Slave 1");
        digitalWrite(CS1, HIGH); // 슬레이브 선택 해제
        Serial.print("ACK: '");
        Serial.print(ACK);
        Serial.println("'");
      }
    }
  }
}

 

< Slave 0 >

#include <SPI.h>

bool iflag = 0;
byte rd = 0;
  
void setup(){
  Serial.begin(9600);
  pinMode(7, OUTPUT);
  pinMode(SCK, INPUT);
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT); 
  pinMode(SS, INPUT);
  
  SPCR |= _BV(SPE);
  SPCR &= ~_BV(MSTR);
  SPCR |= _BV(SPIE);
  
  SPI.setClockDivider(SPI_CLOCK_DIV8);
}

ISR(SPI_STC_vect){
  iflag = 1;
  byte n = SPDR;
  rd = n;
  SPDR = rd - '0';
  Serial.println("Interrupt");
  
}

void loop(){
  if(iflag) {
  	iflag = 0;
    int no = rd - '0';
    Serial.print("No: ");
    Serial.println(no);
    rd = 0;
    if(no > 0 && no < 10){
      for(int i = 0; i < no; i++){
    	digitalWrite(7, HIGH);
    	delay(500);
    	digitalWrite(7, LOW);
    	delay(500);
      }
    }
  }
}

 

< Slave 1 >

#include <SPI.h>

bool iflag = 0;
byte rd = 0;
  
void setup(){
  Serial.begin(9600);
  pinMode(7, OUTPUT);
  pinMode(SCK, INPUT);
  pinMode(MISO, OUTPUT);
  pinMode(MOSI, INPUT);
  pinMode(SS, INPUT);
  
  SPCR |= _BV(SPE);
  SPCR &= ~_BV(MSTR);
  SPCR |= _BV(SPIE);
  
  SPI.setClockDivider(SPI_CLOCK_DIV8);
}

ISR(SPI_STC_vect){
  iflag = 1;
  byte n = SPDR;
  rd = n;
  SPDR = (rd - '0') + 1;
  Serial.println("Interrupt");
  
}

void loop(){
  if(iflag) {
  	iflag = 0;
    int no = rd - '0';
    Serial.print("No: ");
    Serial.println(no);
    rd = 0;
    if(no > 0 && no < 10){
      for(int i = 0; i < no; i++){
    	digitalWrite(7, HIGH);
    	delay(500);
    	digitalWrite(7, LOW);
    	delay(500);
      }
    }
  }
}