วันพฤหัสบดีที่ 30 พฤศจิกายน พ.ศ. 2560

การเขียนโปรแกรมเบื้องต้นกับ Arduino (การส่งข้อมูลแบบ SPI)

ไม่มีความคิดเห็น

การสื่อสารระหว่าง SPI Master และ Slave โดยใช้บอร์ด Arduino

บทความนี้กล่าวถึงการเขียนโปรแกรมเพื่อทำให้บอร์ด Arduino จำนวน 2 บอร์ด สามารถสื่อสารกันด้วยบัส SPI โดยที่บอร์ดหนึ่งทำหน้าที่เป็นอุปกรณ์ SPI Master และอีกบอร์ดหนึ่งทำหน้าที่เป็น SPI Slave
คำสำคัญ / Keywords: Arduino, SPI Interfacing, SPI Master / Slave, RC Servo Output
SPI Bus (Serial Peripheral Interface Bus) เป็นรูปแบบหนึ่งของการสื่อสารข้อมูลระหว่างอุปกรณ์แบบดิจิทัลที่พบเห็นได้บ่อย และใช้กับอุปกรณ์ได้มากกว่าสองขึ้นไปและนำมาต่อกันเป็นบัส (Bus)   บัส SPI ส่งและรับข้อมูลทีละบิต (Bit Serial) และใช้สัญญาณ Clock เป็นตัวกำหนดจังหวะการทำงาน (ดังนั้นจึงเรียกว่า Synchronous, Bit-Serial Data Communication) มีการกำหนดบทบาทในการทำงานของอุปกรณ์ในระบบบัส แบ่งเป็น SPI Masterและ SPI Slave โดยที่ SPI Master เป็นฝ่ายเริ่มการสื่อสารข้อมูล และสร้างสัญญาณ Clock (มักใช้ชื่อสัญญาณว่า SCK) มากำหนดจังหวะการส่งและรับข้อมูล และด้าน SPI Slave จะเป็นฝ่ายคอยตอบสนอง และในระบบบัส SPI อาจมีอุปกรณ์ที่เป็น SPI Slaveได้มากกว่าหนึ่ง (Single-Master, Multi-Slave)

SPI ใช้สัญญาณ 4 เส้น (ใช้งานในแบบที่เรียกว่า 4-Wire SPI) ได้แก่
  • SCK (Serial Clock) — เป็นสัญญาณ CLK ที่ถูกสร้างโดยอุปกรณ์ที่เป็น SPI Master
  • MOSI (Master-Out Slave-In) — เป็นสัญญาณสำหรับส่งข้อมูลบิตออกจาก SPI Master ไปยัง SPI Slave
  • MISO (Master-In Slave-Out) — เป็นสัญญาณสำหรับส่งข้อมูลบิตออกจาก SPI Slave ไปยัง SPI Master
  • /SS (Slave Select, Active-Low) — เป็นสัญญาณที่สร้างโดย SPI Master เพื่อใช้ระบุว่า ต้องการสื่อสารกับ SPI Slave หรือไม่ — ในกรณีที่มีอุปกรณ์ SPI Slave มากกว่าหนึ่งชุด จะต้องมีสัญญาณ Slave Select มากกว่าหนึ่งเส้น และแยกสำหรับแต่ละอุปกรณ์
เมื่อจะส่ง-รับข้อมูลผ่านบัส SPI (เรียกว่า SPI Data Transfer) สัญญาณ Slave Select (/SS) จะต้องเปลี่ยนจาก HIGH เป็น LOW จากนั้นข้อมูลหนึ่งไบต์จะถูกเลื่อนบิตและส่งออกไปทีละบิตจาก SPI Master ตามจังหวะของ SCK และเลือกได้ว่าจะให้บิต MSB (Most-Significant Bit) หรือ LSB (Least-Significant Bit) ถูกส่งออกไปก่อน และในขณะเดียวกันก็จะรับข้อมูลทีละบิตจาก SPI Slave จนได้ครบหนึ่งไบต์ (หรือกล่าวได้ว่า Data Frame = 8 บิต) ดังนั้นเมื่อ SPI Master ส่งข้อมูลจำนวนหนึ่งไบต์ไปยัง SPI Slave ก็จะได้ข้อมูลหนึ่งไบต์จาก SPI Slave เช่นกัน ในช่วงเวลาที่สัญญาณ /SS เป็น LOW อาจมีการส่ง-รับข้อมูลได้มากกว่าหนึ่งไบต์ (Multi-byte SPI transfer)
การทำงานของ SPI มี 4 โหมด จำแนกตามพารามิเตอร์สองตัวที่เรียกว่า CPOL (Clock Polarity) และ CPHA (Clock Phase) ซึ่งจะเป็นตัวกำหนดลักษณะการทำงานอย่างเช่น จะส่ง-รับบิตที่ขอบขาขึ้นหรือลงของสัญญาณ CLK และสัญญาณ CLK จะอยู่ที่ลอจิก HIGH หรือ LOW เมื่อไม่อยู่ในช่วงของการส่งข้อมูลใดๆในบัส SPI (ช่วงที่เรียกว่า Idle) แต่โดยทั่วไปจะใช้ SPI Mode 0

การเปรียบเทียบระหว่าง SPI และ I2C ในการใช้งาน

I2C เป็นอีกรูปแบบการสื่อสารข้อมูลแบบดิจิทัลในประเภทที่เรียกว่า Synchronous, Bit-Serial Data Communication นิยมใช้งานอย่างแพร่หลายเช่นเดียวกับ SPI ลองมาดูตัวอย่างการเปรียบเทียบประเด็นในการใช้งานของบัสทั้งสองแบบ

ประเด็นSPII2C
การรับส่งข้อมูลสองทิศทางfull-duplexhalf-duplex
ความเร็วโดยทั่วไป100kHz หรือ 400kHzได้สูงกว่า 10MHz
จำนวนสัญญาณเพื่อเชื่อมต่อ4 หรือมากกว่า2 (SCL และ SDA)
การเลือกสื่อสารกับ Slaveใช้สัญญาณ Slave Selectระบุ Device Address
การตอบกลับเมื่อได้รับข้อมูลไม่มี Acknowledgeใช้ ACK/NACK bit
ขนาด Data Frame8 บิต8 บิต

ถ้าลองสำรวจอุปกรณ์หรือไอซีบางประเภท ก็จะพบว่า สามารถสื่อสารข้อมูลได้ทั้งแบบ SPI และ I2C อย่างเช่น ไอซีเซนเซอร์วัดความเร่งหรือความเร็วเชิงมุม

แหล่งข้อมูลอ้างอิงและศึกษาเพิ่มเติม:

Arduino Sketch สำหรับตัวอย่างแรก

ตัวอย่างแรกสาธิตการเขียนโปรแกรมให้บอร์ด Arduino (เช่น Arduino Uno) ให้ทำหน้าที่เป็น SPI Master และอีกบอร์ดหนึ่งเป็น SPI Slave และสาธิตการส่ง-รับข้อมูลหลายไบต์ในช่วงที่สัญญาณ /SS เป็น LOW   เมื่อ SPI Master ได้ส่งข้อมูลหนึ่งไบต์ไปยัง SPI Slave ได้แล้ว ข้อมูลไบต์นี้จะถูกส่งกลับไปยัง SPI Master ในการส่งและรับข้อมูลไบต์ครั้งถัดไป ในตัวอย่างนี้ได้เลือกใช้ความเร็ว SCK เท่ากับ 2MHz (โดยการเลือกตัวหารความถี่เท่ากับ 8 และบอร์ด Arduino ใช้ความถี่เท่ากับ 16MHz)
[Wiring Diagram for Arduino Uno boards]

  Arduino Master  Arduino Slave
  D13 = SCK  ---- SCK  = D13
  D12 = MISO ---- MISO = D12
  D11 = MOSI ---- MOSI = D11
  D10 = SS   ---- SS   = D10
        GND  ---- GND
การเชื่อมต่อระหว่างบอร์ด Arduino ทั้งสองบอร์ดให้สื่อสารกแบบ SPI ได้นั้น ให้ต่อสายสัญญาณตาม Wiring Diagram ที่ให้ไว้ (สำหรับ Arduino Uno, 328P, 5V / 16MHz) ได้แก่สัญญาณ SCK, MISO, MOSI, SS และอย่าลืมต่อขา GND ร่วมกัน
ถ้าดูจากโค้ดตัวอย่าง Arduino ที่เป็น SPI Master จะส่งข้อมูลเป็นชุดๆละ 6 ไบต์ โดยไบต์แรกจะเป็นค่าของตัวนับขนาด 8 บิต (เพิ่มค่าทีละหนึ่ง) แล้วตามด้วยไบต์ 0x55, 0x3C, 0xC3, 0xFF, 0x00 ตามลำดับ และเมื่อส่งจนครบ 6 ไบต์ ก็จะได้ข้อมูลจำนวน 6 ไบต์จาก SPI Slave ตามลำดับต่อไปนี้ (?? หมายถึง ไบต์ที่เป็นค่าของตัวนับขนาด 8 บิต)
  Master to Slave: [??][55][3C][C3][F0][00]
  Slave to Master: [00][??][55][3C][C3][FF]

Sourcecode: spi-test-master.ino
////////////////////////////////////////////////////////////////////////
// Author: RSP @ Embedded Systems Lab (ESL), KMUTNB
// Date: 22-Apr-2014
// Target Board: Arduino Uno (ATmega328P, 5V, 16MHz)
// Arduino IDE: version 1.0.5
// Description: This Arduino sketch shows how to use an Arduino Uno board
//   as an SPI master device which sends and receives a block of data bytes 
//   periodically through the SPI. The SCK frequency is set to 2MHz.
////////////////////////////////////////////////////////////////////////

// SPI Master

#include <SPI.h> // use the Arduino SPI library
/*
  SPI Pins for Arduino Uno:
    D13 = SCK, D12 = MISO, D11 = MOSI, D10 = SS
  SPI Pins for Arduino Mega:
    D50 = MISO, D51 = MOSI, D52 = SCK, D53 = SCK
 */

#define SS          (10)
#define BUTTON_PIN  (2)
#define LED_PIN     (3)

void setup() {
  Serial.begin( 115200 );          // use Serial, baudrate = 115200
  pinMode( BUTTON_PIN, INPUT );
  digitalWrite( BUTTON_PIN, HIGH ); // enable internal pull-up for button input
  pinMode( LED_PIN, OUTPUT );
  digitalWrite( LED_PIN, LOW );
  
  SPI.begin();
  SPI.setDataMode( SPI_MODE0 );
  SPI.setBitOrder( MSBFIRST );
  // set clock divider for SCK 
  // use SPI_CLOCK_DIVx, where x=4,8,16,32,64,128
  SPI.setClockDivider( SPI_CLOCK_DIV8 ); // -> 16MHz/8 = 2MHz
  digitalWrite( SS, HIGH );
  Serial.println( "SPI Master..." );
  delay( 1000 );
}

#define SPI_TX_DELAY  (10)      // in microseconds

void spi_transferx( uint8_t *wdata, uint8_t *rdata, uint8_t num ) {
  digitalWrite( SS, LOW );
  for ( uint8_t i=0; i < num; i++ ) {
     rdata[i] = SPI.transfer( wdata[i] ); 
     delayMicroseconds( SPI_TX_DELAY ); // give the SPI slave some time to process
  }
  digitalWrite( SS, HIGH );
}

boolean state = false;

const int BUF_SIZE = 6; // set the buffer size 
const uint8_t TEST_DATA_BYTES[] = { 0x55, 0x3C, 0xC3, 0xF0 }; 
uint8_t data_buf[ BUF_SIZE ];
uint8_t count = 0;
char sbuf[32]; // used for sprintf()

void loop() {
  if ( digitalRead( BUTTON_PIN ) == LOW ) { // check whether the button is pressed
    while ( digitalRead( BUTTON_PIN ) == LOW ) ; // wait until the button is released
    // write data bytes to the data buffer
    memset( data_buf, 0x00, BUF_SIZE );
    data_buf[0] = count++;
    memcpy( data_buf+1, TEST_DATA_BYTES, sizeof( TEST_DATA_BYTES ) );
    // write/read the SPI bus
    spi_transferx( data_buf, data_buf, BUF_SIZE );
    Serial.print( "Read: " );
    for ( uint8_t i=0; i < BUF_SIZE; i++) { // show the received data bytes
      sprintf( (sbuf+4*i), "%02Xh ", data_buf[i] ); 
    }
    Serial.println( sbuf ); 
    state = !state; // toggle state for LED
  }
  digitalWrite( LED_PIN, state ); // update LED output
  delay(10);
}

///////////////////////////////////////////////////////////////////////

Sourcecode: spi-test-slave.ino
////////////////////////////////////////////////////////////////////////
// Author: RSP @ Embedded Systems Lab (ESL), KMUTNB
// Date: 22-Apr-2014
// Target Board: Arduino Uno (ATmega328P, 5V, 16MHz)
// Arduino IDE: version 1.0.5
// Description:
//   This Arduino Sketch shows how to use Arduino Uno as an SPI 
//   slave device which receives/sends a block of data bytes 
//   through the SPI bus. Each received data byte will be sent back
//   to the SPI master in the next-byte transfer.
////////////////////////////////////////////////////////////////////////

// SPI Slave

#include <SPI.h> // use the Arduino SPI library
#include <Servo.h>

#define SS  (10)  // specify the SPI Slave Slect pin

volatile uint8_t data = 0;

ISR(SPI_STC_vect) { // ISR for 'SPI reception complete'
  data = SPDR;
  SPDR = data;
} 

void setup() {
  Serial.begin( 115200 );     // use Serial port and baudrate=115200
  pinMode( MISO, OUTPUT );    // configure the MISO pin as output
  digitalWrite( SS, HIGH );   // enable pull-up on the SS pin
  SPCR |= _BV(SPE);           // set SPE bit in SPCR to enable SPI
  //SPI.attachInterrupt();    // enable SPI interrupt
  SPCR |= _BV(SPIE);
  // In Slave mode the MSTR bit in SPCR is clear.
  Serial.println( "SPI Slave..." );
}

void loop() {
  if ( digitalRead( SS ) ) {
    SPDR = 0x00; 
  }
  delay(10);
}

////////////////////////////////////////////////////////////////////////


รูปแสดงตัวอย่างการต่อวงจรโดยใช้บอร์ด Arduino จำนวน 2 บอร์ด
เชื่อมต่อเข้าด้วยกันแบบ SPI


รูปแสดงคลื่นสัญญาณ SCK (1) และ MOSI (2)
ทำงานใน Mode 0, MSB First

รูปแสดงคลื่นสัญญาณ SCK ซึ่งได้ความถี่ 2MHz
อ่านค่าของไบต์ที่ถูกส่งผ่าน MOSI=0x55

รูปแสดงคลื่นสัญญาณ SCK (1) และ MOSI (2)
อ่านค่าข้อมูลที่ถูกส่งผ่าน MOSI=0x3C

รูปแสดงคลื่นสัญญาณ SCK (1) และ /SS (2)
แสดงช่วงที่ /SS เปลี่ยนจาก High เป็น LOW

รูปแสดงคลื่นสัญญาณ MOSI (1) และ MISO (2)
อ่านค่าไบต์: MOSI=0x55 และ MISO=0x01

รูปแสดงคลื่นสัญญาณ MOSI (1) และ MISO (2)
อ่านค่าไบต์: MOSI=0x55 และ MISO=0x05

Arduino Sketch สำหรับตัวอย่างที่สอง

ตัวอย่างที่สองสาธิตการทำให้บอร์ด Arduino ทำหน้าที่เป็น SPI Slave และสร้างสัญญาณ R/C Servo จำนวน 4 ช่อง (50Hz คงที่) สัญญาณ R/C Servo ทุกช่องมีค่า Duty Cycle เริ่มต้นเป็น 1500μsec แต่สามารถปรับเปลี่ยนค่าได้ โดยรับคำสั่งจากบอร์ด Arduino อีกบอร์ดหนึ่งที่ทำหน้าที่เป็น SPI Master เพื่อกำหนดความกว้างช่วงที่เป็น HIGH ได้ในหน่วยเป็นไมโครวินาที (μsec) นอกจากนั้นยังสามารถอ่านค่า Duty Cycle ของแต่ละช่องได้เช่นกัน
รูปแบบของคำสั่งและข้อมูลมีดังนี้ กรณีแรกเป็นการกำหนดค่า Duty Cycle (12 บิต) ให้ช่องสัญญาณที่เลือก จะต้องส่งข้อมูลไบต์จำนวน 2 ไบต์ และไม่สนใจข้อมูลไบต์ที่ได้รับกลับมา
Write Operation (Bytes sent by SPI Master):
   First Byte:
     Bit   7 = R/W bit = '1'
     Bit 6-5 = Channel Number
     Bit   4 = don't care
     Bit 3-0 = DutyCycle (Bit 11..8)
   Second Byte: 
     Bit 7-0 = DutyCycle (Bit 7..0)

กรณีที่สองเป็นการอ่านค่า Duty Cycle ของช่องสัญญาณที่เลือก จะต้องส่งข้อมูลไบต์ทั้งหมด 3 ไบต์ ไม่สนใจไบต์แรกที่ได้รับมา และค่า Duty Cycle ที่ได้จะอยู่ในข้อมูลไบต์ 2 ไบต์สุดท้าย
Read Operation (Bytes sent by SPI Master):
   First Byte:
     Bit   7 = R/W bit = '0'
     Bit 6-5 = Channel Number
     Bit 4-0 = don't care
   Second Byte:
     Bit 7-0 = don't care
   Third Byte:
     Bit 7-0 = don't care
โค้ดตัวอย่างนี้สาธิตการเปลี่ยนค่า Duty Cycle ของสัญญาณ R/C Servo 4 ช่อง เมื่อมีการกดปุ่มแต่ละครั้ง และสลับไปมาระหว่างสองกรณี โดยที่กรณีแรก ให้ทุกช่องมีค่า 1500 μsec และในอีกกรณีให้สัญญาณ 4 ช่อง มีค่า 1000,1250,1750,2000 μsec ตามลำดับ

Sourcecode: spi-servo-master.ino
////////////////////////////////////////////////////////////////////////
// Author: RSP @ Embedded Systems Lab (ESL), KMUTNB
// Date: 22-Apr-2014
// Target Board: Arduino Uno (ATmega328P, 5V, 16MHz)
// Arduino IDE: version 1.0.5
// Description: This Arduino sketch shows how to use the Arduino Uno 
//   as SPI master to communicate with another Arduino Uno which
//   operates as SPI slave. The SPI slave device also generates four 
//   R/C servo signals with adjustable duty cycles.
//   The SPI master can set/get the duty cycle of the selected R/C signal
//   via the SPI bus.
////////////////////////////////////////////////////////////////////////

// SPI Master

#include <SPI.h> // use the Arduino SPI library
/*
  SPI Pins for Arduino Uno:
    D13 = SCK, D12 = MISO, D11 = MOSI, D10 = SS
  SPI Pins for Arduino Mega:
    D50 = MISO, D51 = MOSI, D52 = SCK, D53 = SCK
 */

#define SS             (10)
#define BUTTON_PIN     (2)
#define LED_PIN        (3)
#define NUM_CHANNELS   (4)

const uint16_t DEFAULT_PULSE_WIDTHS[ NUM_CHANNELS ] = { 1500,1500,1500,1500, };
const uint16_t TEST_PULSE_WIDTHS[ NUM_CHANNELS ]    = { 1000,1250,1750,2000, };
const uint16_t *pulse_widths;

void set_pulse_widths();

void setup() {
  Serial.begin( 115200 ); // use Serial, baudrate = 115200
  pinMode( BUTTON_PIN, INPUT );
  digitalWrite( BUTTON_PIN, HIGH ); // enable internal pull-up
  pinMode( LED_PIN, OUTPUT );
  digitalWrite( LED_PIN, LOW );
  
  SPI.begin();
  SPI.setDataMode( SPI_MODE0 );
  SPI.setBitOrder( MSBFIRST );
  // set clock divider for SCK -> use 16MHz/8 = 2MHz
  SPI.setClockDivider( SPI_CLOCK_DIV8 ); // SPI_CLOCK_DIVx, where x=4,8,16,32,64,128
  digitalWrite( SS, HIGH );
  pulse_widths = DEFAULT_PULSE_WIDTHS;
  set_pulse_widths();
  
  Serial.println( "SPI Master..." );
  delay( 100 );
}

#define SPI_WAIT_DELAY  (16)  // in microseconds

void set_pulse_widths() {
  for ( uint8_t i=0; i < NUM_CHANNELS; i++ ) {
    uint16_t pulse_width = pulse_widths[i];
    digitalWrite( SS, LOW );
    // write operation the i-th PWM channel
    SPI.transfer( 0x80 | (i << 5) | ((pulse_width >> 8) & 0x0f) );
    delayMicroseconds( SPI_WAIT_DELAY );
    SPI.transfer( pulse_width & 0xff ); // write high byte
    delayMicroseconds( SPI_WAIT_DELAY );
    digitalWrite( SS, HIGH );           // write low byte
    delayMicroseconds( 30 ); // give the SPI slave some time to process
  }
}

void get_pulse_widths( uint16_t *values ) {
  uint8_t lo_byte, hi_byte;
  for ( uint8_t i=0; i < NUM_CHANNELS; i++ ) {
    digitalWrite( SS, LOW );
    SPI.transfer( (i << 5) ); // read operation for i-th PWM channel
    delayMicroseconds( SPI_WAIT_DELAY );
    hi_byte = SPI.transfer( 0x00 );
    delayMicroseconds( SPI_WAIT_DELAY );
    lo_byte = SPI.transfer( 0x00 );
    delayMicroseconds( SPI_WAIT_DELAY );
    digitalWrite( SS, HIGH );
    values[i] = (hi_byte & 0x0f);
    values[i] = (values[i] << 8) | lo_byte;
    delayMicroseconds( 30 ); // give the SPI slave some time to process
  }
}

boolean state = false;
char sbuf[32];  // used for sprintf()

void loop() {
  if ( digitalRead( BUTTON_PIN ) == LOW ) { // check whether the button is pressed
    while ( digitalRead( BUTTON_PIN ) == LOW ) ; // wait until the button is released
    uint16_t values[ NUM_CHANNELS ];
    pulse_widths = (state) ? TEST_PULSE_WIDTHS : DEFAULT_PULSE_WIDTHS;
    set_pulse_widths(); // update the pulse widths of all servos
    get_pulse_widths( values );
    Serial.print( "Pulse widths (usec): " );
    for ( uint8_t i=0; i < NUM_CHANNELS; i++ ) {
      sprintf( sbuf+i*5, "%5u", values[i] );
    }
    Serial.println( sbuf );
    state = !state; // toggle LED state
  }
  digitalWrite( LED_PIN, state );
  delay(10);
}
///////////////////////////////////////////////////////////////////////

Sourcecode: spi-servo-slave.ino
////////////////////////////////////////////////////////////////////////
// Author: RSP @ Embedded Systems Lab (ESL), KMUTNB
// Date: 22-Apr-2014
// Target Board: Arduino Uno (ATmega328P, 5V, 16MHz)
// Arduino IDE: version 1.0.5
// Description: This Arduino sketch is an example of how to
//   make an Arduino Uno board an SPI slave which generates
//   four R/C servo signals and allows the SPI master to change
//   the duty cycle of each R/C signal.
////////////////////////////////////////////////////////////////////////

// SPI Slave

#include <SPI.h> // use the Arduino SPI library
#include <Servo.h>

#define SS      (10)
#define LED_PIN (3)

const uint8_t PWM_PINS[4] = {5,6,7,8};
Servo servos[4];
uint16_t servo_pulse_widths[4];

volatile uint8_t  byte_count  = 0;
volatile uint8_t  channel     = 0;
volatile uint16_t pulse_width = 0;
volatile boolean  update_flag = false;

ISR(SPI_STC_vect) { // SPI reception complete
  static uint8_t rw_bit = 0;
  static uint8_t data;
  digitalWrite( LED_PIN, HIGH );
  data = SPDR;
  if ( byte_count == 0 ) {
     rw_bit  = (data >> 7) & 1;
     channel = (data >> 5) & 3;
     if ( rw_bit == 1 ) { // write operation
       pulse_width = (uint16_t)(data & 0x0f);
       SPDR = 0x00; // don't care
     } else { // read operation
       pulse_width = servo_pulse_widths[ channel ];
       SPDR = (pulse_width >> 8) & 0x0f; 
     }
     byte_count++;
  } 
  else if ( byte_count == 1 ) {
     if ( rw_bit == 1 ) { // write operation
       pulse_width = (pulse_width << 8) | (((uint16_t)data) & 0x00ff);
       SPDR = 0x00; // don't care
       update_flag = true;
       byte_count = 0;
     } else { // read operation
       SPDR = pulse_width & 0xff;
       byte_count++;
     }
  }
  else {
     byte_count = 0;
     SPDR = 0x00;
  }
  digitalWrite( LED_PIN, LOW );
}

void setup() {
  pinMode( LED_PIN, OUTPUT );
  digitalWrite( LED_PIN, LOW );
  Serial.begin( 115200 );     // use Serial port and baudrate=115200
  pinMode( MISO, OUTPUT );    // configure the MISO pin as output
  digitalWrite( SS, HIGH );   // enable pull-up on the SS pin
  for ( uint8_t i=0; i < 4; i++) {
     servo_pulse_widths[i] = 1500;
     servos[i].attach( PWM_PINS[i] );
     servos[i].writeMicroseconds( servo_pulse_widths[i] );
  }
  SPCR |= _BV(SPE);           // set SPE bit in SPCR to enable SPI
  //SPI.attachInterrupt();    // enable SPI interrupt
  SPCR |= _BV(SPIE);
  // In Slave mode the MSTR bit in SPCR is clear.
  Serial.println( "SPI Slave..." );
}


void loop() {
  static uint8_t _channel;
  static uint16_t _pulse_width;
  if ( update_flag ) { // update 
    _pulse_width = pulse_width;
    _channel = channel;
    byte_count = 0;
    update_flag = false;
    servo_pulse_widths[ _channel ] = _pulse_width;
    servos[ _channel ].writeMicroseconds( servo_pulse_widths[ _channel ] ); 
  }
  if ( digitalRead( SS ) ) { 
    byte_count = 0;
    update_flag = false;
  }
}

////////////////////////////////////////////////////////////////////////


รูปแสดงคลื่นสัญญาณช่อง 0 และ 1 (default)
1500μsec และ 1500μsec ตามลำดับ

รูปแสดงคลื่นสัญญาณช่อง 0 และ 1
1000μsec และ 1250μsec ตามลำดับ

รูปแสดงคลื่นสัญญาณช่อง 2 และ 3 (default)
1500μsec และ 1500μsec ตามลำดับ

รูปแสดงคลื่นสัญญาณช่อง 2 และ 3
1750μsec และ 2000μsec ตามลำดับ


รูปแสดงคลื่นสัญญาณ SCK (1) และ LED (2)
ในกรณีของการกำหนดค่า (ข้อมูล 2 ไบต์)

รูปแสดงคลื่นสัญญาณ SCK (1) และ LED (2)
ในกรณีของการอ่านค่า (ข้อมูล 3 ไบต์)
** ในตัวอย่างนี้ใช้สัญญาณ LED เพื่อดูพฤติกรรมการทำงานของ SPI Slave เมื่อสัญญาณ LED เป็น HIGH แสดงถึงช่วงการทำงาน (active) ของ ISR(SPI_STC_vect) และจะถูกเรียกให้ทำงานทุกครั้งที่ได้รับข้อมูลหนึ่งไบต์จาก SPI Master


รูปแสดงข้อความที่ปรากฏใน Serial Monitor ของ Arduino IDE
หลังจากที่ได้กดปุ่มหลายครั้งเพื่อลองเปลี่ยนและอ่านค่า Duty Cycle ของสัญญาณ

ไม่มีความคิดเห็น :

แสดงความคิดเห็น