คุณใช้ SPI บน Arduino ได้อย่างไร


44

อ้างอิงถึง Arduino Uno, Mega2560, Leonardo และบอร์ดที่คล้ายกัน:

  • SPI ทำงานอย่างไร
  • SPI เร็วแค่ไหน
  • ฉันจะเชื่อมต่อระหว่างนายกับทาสได้อย่างไร?
  • ฉันจะสร้างทาส SPI ได้อย่างไร

โปรดทราบ: นี่เป็นคำถามสำหรับการอ้างอิง


คุณสามารถตอบคำถามที่เกี่ยวข้องนี้arduino.stackexchange.com/questions/60703/…
qwr

คำตอบ:


80

SPI เบื้องต้น

อนุกรมอุปกรณ์ต่อพ่วงอินเตอร์เฟสบัส (SPI) อินเตอร์เฟซที่ใช้สำหรับการสื่อสารระหว่างอุปกรณ์หลายในระยะทางสั้น ๆ และที่ความเร็วสูง

โดยทั่วไปจะมีอุปกรณ์ "หลัก" เดียวซึ่งจะเริ่มการสื่อสารและส่งสัญญาณนาฬิกาที่ควบคุมอัตราการถ่ายโอนข้อมูล อาจมีทาสตั้งแต่หนึ่งตัวขึ้นไป สำหรับทาสมากกว่าหนึ่งคนแต่ละคนมีสัญญาณ "ทาสเลือก" ของตัวเองอธิบายไว้ในภายหลัง


สัญญาณ SPI

ในระบบ SPI แบบเต็มคุณจะมีสี่สายสัญญาณ:

  • Master Out, Slave In ( MOSI ) - ซึ่งเป็นข้อมูลที่ได้จากเจ้านายไปยังทาส
  • Master In, Slave Out ( MISO ) - ซึ่งเป็นข้อมูลที่ส่งจากทาสไปยังต้นแบบ
  • นาฬิกาอนุกรม ( SCK ) - เมื่อสิ่งนี้สลับทั้งต้นแบบและทาสตัวอย่างบิตถัดไป
  • Slave Select ( SS ) - ตัวเลือกนี้จะบอกให้ทาสใช้งาน "แอคทีฟ"

เมื่อทาสหลายตัวเชื่อมต่อกับสัญญาณ MISO พวกเขาคาดว่าจะเป็นสามสถานะ (เก็บที่อิมพีแดนซ์สูง) ที่เส้น MISO จนกว่าพวกเขาจะถูกเลือกโดย Slave Select ที่ถูกเลือก โดยปกติ Slave Select (SS) จะต่ำเพื่อยืนยัน นั่นคือมันใช้งานต่ำ เมื่อเลือกทาสเฉพาะควรกำหนดค่าสาย MISO เป็นเอาท์พุทเพื่อให้สามารถส่งข้อมูลไปยังต้นแบบ

รูปภาพนี้แสดงวิธีการแลกเปลี่ยนข้อมูลเมื่อส่งหนึ่งไบต์:

โปรโตคอล SPI แสดง 4 สัญญาณ

โปรดทราบว่าสัญญาณทั้งสามเป็นสัญญาณจากอาจารย์ (MOSI, SCK, SS) และสัญญาณหนึ่งเป็นสัญญาณเข้า (MISO)


การจับเวลา

ลำดับเหตุการณ์คือ:

  • SS ต่ำลงเพื่อยืนยันและเปิดใช้งานทาส
  • SCKสายสลับเพื่อระบุว่าเมื่อสายข้อมูลที่ควรจะเก็บตัวอย่าง
  • ข้อมูลตัวอย่างทั้งต้นแบบและทาสบนขอบนำของSCK(ใช้เฟสนาฬิกาเริ่มต้น)
  • ทั้งมาสเตอร์และทาสเตรียมพร้อมสำหรับบิตถัดไปที่ขอบต่อท้ายของSCK(ใช้เฟสนาฬิกาเริ่มต้น) โดยการเปลี่ยนMISO/ MOSIหากจำเป็น
  • เมื่อการส่งข้อมูลเสร็จสิ้น (อาจเป็นไปได้ว่าหลังจากหลายไบต์ถูกส่งไป) จากนั้นSSจะสูงเพื่อยกเลิกการยืนยัน

โปรดทราบว่า:

  • บิตที่สำคัญที่สุดจะถูกส่งก่อน (โดยค่าเริ่มต้น)
  • ข้อมูลถูกส่งและรับในเวลาเดียวกัน (full duplex)

เนื่องจากข้อมูลถูกส่งและรับในพัลส์นาฬิกาเดียวกันมันเป็นไปไม่ได้ที่ทาสจะตอบกลับนายทันที โปรโตคอล SPI มักจะคาดหวังว่าเจ้านายจะขอข้อมูลจากการส่งครั้งเดียวและได้รับการตอบกลับในครั้งต่อไป

การใช้ไลบรารี SPI บน Arduino การถ่ายโอนครั้งเดียวจะมีลักษณะดังนี้ในรหัส:

 byte outgoing = 0xAB;
 byte incoming = SPI.transfer (outgoing);

โค้ดตัวอย่าง

ตัวอย่างการส่งเท่านั้น (ไม่สนใจข้อมูลที่เข้ามา):

#include <SPI.h>

void setup (void)
  {
  digitalWrite(SS, HIGH);  // ensure SS stays high
  SPI.begin ();
  } // end of setup

void loop (void)
  {
  byte c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Fab" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (100);
  } // end of loop

เดินสายสำหรับ SPI เฉพาะเอาต์พุต

รหัสข้างต้น (ซึ่งส่งเท่านั้น) อาจถูกใช้เพื่อขับเคลื่อนการลงทะเบียนซีเรียลเอาต์พุตอนุกรม อุปกรณ์เหล่านี้เป็นอุปกรณ์เอาท์พุทเท่านั้นดังนั้นเราจึงไม่ต้องกังวลเกี่ยวกับข้อมูลขาเข้าใด ๆ ในกรณีของพวกเขาพิน SS อาจเรียกว่า "store" หรือ "latch" pin

โปรโตคอล SPI แสดงสัญญาณ 3 ตัว

ตัวอย่างของสิ่งนี้คือการลงทะเบียนซีเรียล 74HC595 และแถบ LED ต่าง ๆ เพียงพูดถึงสองสามอย่าง ตัวอย่างเช่นจอแสดงผล LED 64 พิกเซลนี้ขับเคลื่อนด้วยชิป MAX7219:

จอแสดงผล LED 64 พิกเซล

ในกรณีนี้คุณจะเห็นว่าผู้ผลิตบอร์ดใช้ชื่อสัญญาณที่แตกต่างกันเล็กน้อย:

  • DIN (Data In) คือ MOSI (Master Out, Slave In)
  • CS (เลือกชิป) คือ SS (เลือกทาส)
  • CLK (นาฬิกา) คือ SCK (นาฬิกาแบบอนุกรม)

บอร์ดส่วนใหญ่จะเป็นไปตามรูปแบบที่คล้ายกัน บางครั้ง DIN เป็นเพียง DI (Data In)

นี่เป็นอีกตัวอย่างหนึ่งคราวนี้บอร์ดแสดงผล LED 7 ส่วน (ตามชิป MAX7219):

จอแสดงผล LED 7 ส่วน

ใช้ชื่อสัญญาณตรงกับบอร์ดอื่น ๆ ในทั้งสองกรณีนี้คุณจะเห็นได้ว่าบอร์ดต้องการสายไฟ 5 เส้นเท่านั้นสายไฟสามสายสำหรับ SPI รวมทั้งพลังงานและกราวด์


เฟสนาฬิกาและกระแสไฟฟ้า

มีสี่วิธีที่คุณสามารถสุ่มตัวอย่างนาฬิกา SPI ได้

โปรโตคอล SPI อนุญาตให้มีการเปลี่ยนแปลงในขั้วของพัลส์นาฬิกา CPOL เป็นขั้วนาฬิกาและ CPHA เป็นเฟสสัญญาณนาฬิกา

  • โหมด 0 (ค่าเริ่มต้น) - นาฬิกาปกติต่ำ (CPOL = 0) และข้อมูลถูกสุ่มตัวอย่างจากช่วงการเปลี่ยนภาพจากต่ำไปสูง (นำหน้า) (CPHA = 0)
  • โหมด 1 - นาฬิกาปกติต่ำ (CPOL = 0) และข้อมูลถูกสุ่มตัวอย่างจากช่วงการเปลี่ยนภาพจากสูงไปต่ำ (ต่อท้าย) (CPHA = 1)
  • โหมด 2 - นาฬิกาปกติสูง (CPOL = 1) และข้อมูลถูกสุ่มตัวอย่างจากช่วงการเปลี่ยนภาพจากสูงไปต่ำ (นำหน้า) (CPHA = 0)
  • โหมด 3 - นาฬิกาปกติสูง (CPOL = 1) และข้อมูลถูกสุ่มตัวอย่างจากช่วงการเปลี่ยนภาพจากต่ำไปสูง (ขอบต่อท้าย) (CPHA = 1)

สิ่งเหล่านี้แสดงในกราฟิกนี้:

SPI clock phase และขั้วไฟฟ้า

คุณควรอ้างอิงแผ่นข้อมูลสำหรับอุปกรณ์ของคุณเพื่อให้เฟสและขั้วถูกต้อง โดยปกติจะมีไดอะแกรมซึ่งแสดงวิธีการสุ่มตัวอย่างนาฬิกา ตัวอย่างเช่นจากแผ่นข้อมูลสำหรับชิป 74HC595:

นาฬิกา 74HC595

ตามที่คุณเห็นว่านาฬิกาอยู่ในระดับต่ำ (CPOL = 0) และถูกสุ่มตัวอย่างที่ขอบนำ (CPHA = 0) ดังนั้นนี่จึงเป็นโหมด SPI 0

คุณสามารถเปลี่ยนขั้วนาฬิกาและเฟสเป็นรหัสได้เช่นนี้ (เลือกอย่างใดอย่างหนึ่งเท่านั้น):

SPI.setDataMode (SPI_MODE0);
SPI.setDataMode (SPI_MODE1);
SPI.setDataMode (SPI_MODE2);
SPI.setDataMode (SPI_MODE3);

วิธีนี้เลิกใช้ในรุ่น 1.6.0 เป็นต้นไปของ Arduino IDE สำหรับรุ่นล่าสุดคุณเปลี่ยนโหมดนาฬิกาในการSPI.beginTransactionโทรเช่นนี้

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));  // 2 MHz clock, MSB first, mode 0

สั่งซื้อข้อมูล

ค่าเริ่มต้นคือบิตที่สำคัญที่สุดก่อนอย่างไรก็ตามคุณสามารถบอกให้ฮาร์ดแวร์ประมวลผลบิตที่มีนัยสำคัญน้อยที่สุดก่อนเช่นนี้:

SPI.setBitOrder (LSBFIRST);   // least significant bit first
SPI.setBitOrder (MSBFIRST);   // most significant bit first

อีกครั้งสิ่งนี้เลิกใช้แล้วในรุ่น 1.6.0 เป็นต้นไปของ Arduino IDE สำหรับรุ่นล่าสุดคุณเปลี่ยนลำดับบิตในการSPI.beginTransactionโทรเช่นนี้

SPI.beginTransaction (SPISettings (1000000, LSBFIRST, SPI_MODE2));  // 1 MHz clock, LSB first, mode 2

ความเร็ว

การตั้งค่าเริ่มต้นสำหรับ SPI คือการใช้ความเร็วสัญญาณนาฬิกาของระบบหารด้วยสี่นั่นคือหนึ่งนาฬิกา SPI พัลส์ทุก 250 ns สมมติว่านาฬิกาซีพียู 16 MHz คุณสามารถเปลี่ยนตัวแบ่งนาฬิกาโดยใช้setClockDividerดังนี้:

SPI.setClockDivider (divider);

โดยที่ "ตัวแบ่ง" เป็นหนึ่งใน:

  • SPI_CLOCK_DIV2
  • SPI_CLOCK_DIV4
  • SPI_CLOCK_DIV8
  • SPI_CLOCK_DIV16
  • SPI_CLOCK_DIV32
  • SPI_CLOCK_DIV64
  • SPI_CLOCK_DIV128

อัตราที่เร็วที่สุดคือ "หารด้วย 2" หรือหนึ่งพัลส์นาฬิกา SPI ทุก ๆ 125 ns โดยถือว่านาฬิกาซีพียู 16 MHz ดังนั้นจะใช้เวลา 8 * 125 ns หรือ 1 tos ในการส่งหนึ่งไบต์

วิธีนี้เลิกใช้ในรุ่น 1.6.0 เป็นต้นไปของ Arduino IDE สำหรับรุ่นล่าสุดคุณเปลี่ยนความเร็วในการโอนSPI.beginTransactionสายเช่นนี้:

SPI.beginTransaction (SPISettings (4000000, MSBFIRST, SPI_MODE0));  // 4 MHz clock, MSB first, mode 0

อย่างไรก็ตามการทดสอบเชิงประจักษ์แสดงให้เห็นว่าจำเป็นต้องมีพัลส์นาฬิกาสองอันระหว่างไบต์ดังนั้นอัตราสูงสุดที่สามารถโอเวอร์คล็อกไบต์ได้คือ 1.125 eachs ต่อวินาที (โดยมีตัวแบ่งสัญญาณนาฬิกาเป็น 2)

เพื่อสรุปแต่ละไบต์สามารถส่งในอัตราสูงสุดหนึ่งต่อ 1.125 (s (ด้วยนาฬิกา 16 MHz) ให้อัตราการถ่ายโอนสูงสุดทางทฤษฎีของ 1 / 1.125 µs หรือ 888,888 ไบต์ต่อวินาที (ยกเว้นค่าใช้จ่ายเช่นการตั้งค่า SS ต่ำและดังนั้น บน).


กำลังเชื่อมต่อกับ Arduino

Arduino Uno

การเชื่อมต่อผ่านหมุดดิจิตอล 10 ถึง 13:

Arduino Uno SPI พิน

การเชื่อมต่อผ่านส่วนหัว ICSP:

ICSP pinouts - Uno

ส่วนหัว ICSP

Arduino Atmega2560

การเชื่อมต่อผ่านพินดิจิตอล 50 ถึง 52:

Arduino Mega2560 SPI พิน

คุณยังสามารถใช้ส่วนหัว ICSP คล้ายกับ Uno ด้านบน

Arduino เลโอนาร์โด

Leonardo และ Micro ไม่เปิดเผย SPI ของพินดิจิตอลซึ่งแตกต่างจาก Uno และ Mega ตัวเลือกเดียวของคุณคือใช้พินส่วนหัว ICSP ดังที่แสดงไว้ด้านบนสำหรับ Uno


ทาสหลายคน

ต้นแบบสามารถสื่อสารกับทาสหลายคน (ครั้งละเพียงคนเดียว) มันทำเช่นนี้โดยการเข้าไปยุ่งกับเอสเอสอสำหรับทาสคนหนึ่ง ทาสซึ่งมี SS ยืนยัน (โดยปกติหมายถึงต่ำ) กำหนดค่า MISO พินของมันเป็นเอาต์พุตเพื่อให้ทาสและทาสนั้นเพียงอย่างเดียวสามารถตอบสนองต่อต้นแบบได้ ทาสอื่น ๆ จะไม่สนใจสัญญาณนาฬิกาที่เข้ามาหากไม่มีการยืนยัน SS ดังนั้นคุณต้องการสัญญาณเพิ่มเติมหนึ่งสัญญาณสำหรับแต่ละทาสเช่นนี้:

ทาส SPI หลายตัว

ในภาพนี้คุณจะเห็นได้ว่า MISO, MOSI, SCK จะถูกแชร์ระหว่างทาสทั้งสองอย่างไรก็ตามทาสแต่ละคนมีสัญญาณ SS (เลือกทาส) ของตัวเอง


โปรโตคอล

ข้อมูลจำเพาะ SPI ไม่ได้ระบุโปรโตคอลเช่นนี้ดังนั้นจึงขึ้นอยู่กับการจับคู่ของ Master / Slave แต่ละคนที่จะเห็นด้วยกับความหมายของข้อมูล ในขณะที่คุณสามารถส่งและรับไบต์พร้อมกันได้รับไม่สามารถตอบสนองโดยตรงไปยังไบต์ที่ส่ง (เนื่องจากพวกเขากำลังรวมกันพร้อมกัน)

ดังนั้นมันจะมีเหตุผลมากขึ้นสำหรับปลายด้านหนึ่งที่จะส่งคำขอ (เช่น 4 อาจหมายถึง "รายการไดเรกทอรีของดิสก์") และจากนั้นทำการถ่ายโอน การตอบกลับอาจยุติลงด้วยการขึ้นบรรทัดใหม่หรืออักขระ 0x00

อ่านแผ่นข้อมูลสำหรับอุปกรณ์สลาฟของคุณเพื่อดูว่าโพรโทคอลลำดับใดที่คาดหวัง


วิธีการสร้างทาส SPI

ตัวอย่างก่อนหน้านี้แสดงให้เห็นว่า Arduino เป็นต้นแบบการส่งข้อมูลไปยังอุปกรณ์ทาส ตัวอย่างนี้แสดงให้เห็นว่า Arduino สามารถเป็นทาสได้อย่างไร

การตั้งค่าฮาร์ดแวร์

เชื่อมต่อ Arduino Unos สองตัวพร้อมกับหมุดต่อไปนี้ซึ่งเชื่อมต่อเข้าด้วยกัน:

  • 10 (SS)
  • 11 (MOSI)
  • 12 (MISO)
  • 13 (SCK)

  • + 5v (ถ้าจำเป็น)

  • GND (สำหรับการส่งสัญญาณกลับ)

บน Arduino Mega หมุดมี 50 (MISO), 51 (MOSI), 52 (SCK), และ 53 (SS)

ไม่ว่าในกรณีใด MOSI ที่ปลายด้านหนึ่งจะเชื่อมต่อกับ MOSI ที่ปลายอีกด้านหนึ่งคุณจะไม่สลับไปมา (นั่นคือคุณไม่มี MOSI <-> MISO) ซอฟต์แวร์กำหนดค่าปลายด้านหนึ่งของ MOSI (ต้นแบบปลาย) เป็นเอาต์พุตและปลายอีกด้าน (ปลายทาส) เป็นอินพุต

ตัวอย่างต้นแบบ

#include <SPI.h>

void setup (void)
{

  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

}  // end of setup


void loop (void)
{

  char c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Hello, world!\n" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (1000);  // 1 seconds delay
}  // end of loop

ตัวอย่างทาส

#include <SPI.h>

char buf [100];
volatile byte pos;
volatile bool process_it;

void setup (void)
{
  Serial.begin (115200);   // debugging

  // turn on SPI in slave mode
  SPCR |= bit (SPE);

  // have to send on master in, *slave out*
  pinMode (MISO, OUTPUT);

  // get ready for an interrupt
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;

    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;

    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set

}  // end of loop

ทาสนั้นถูกขัดจังหวะโดยสิ้นเชิงดังนั้นมันจึงสามารถทำสิ่งอื่นได้ ข้อมูล SPI ที่เข้ามาจะถูกรวบรวมในบัฟเฟอร์และตั้งค่าสถานะเมื่อ "ไบต์สำคัญ" (ในกรณีนี้มีการขึ้นบรรทัดใหม่) สิ่งนี้จะบอกให้ทาสเริ่มทำงานและเริ่มประมวลผลข้อมูล

ตัวอย่างของการเชื่อมต่อ master กับ slave โดยใช้ SPI

Arduino SPI master และ slave


วิธีรับการตอบกลับจากทาส

ตามจากโค้ดด้านบนซึ่งส่งข้อมูลจากต้นแบบ SPI ไปยังสลาฟตัวอย่างด้านล่างแสดงการส่งข้อมูลไปยังสลาฟให้ทำข้อมูลและส่งคืนการตอบกลับ

ต้นแบบมีลักษณะคล้ายกับตัวอย่างด้านบน อย่างไรก็ตามจุดสำคัญคือเราต้องเพิ่มความล่าช้าเล็กน้อย (บางอย่างเช่น 20 ไมโครวินาที) มิฉะนั้นทาสไม่มีโอกาสโต้ตอบกับข้อมูลที่เข้ามาและทำอะไรกับมัน

ตัวอย่างแสดงการส่งคำสั่ง "" ในกรณีนี้ "a" (เพิ่มบางอย่าง) หรือ "s" (ลบบางอย่าง) นี่คือการแสดงให้เห็นว่าทาสกำลังทำอะไรกับข้อมูล

หลังจากยืนยันการเลือก slave (SS) เพื่อเริ่มต้นการทำธุรกรรมมาสเตอร์จะส่งคำสั่งตามด้วยจำนวนไบต์ใด ๆ แล้วจึงยก SS เพื่อยกเลิกการทำธุรกรรม

จุดสำคัญมากคือทาสไม่สามารถตอบสนองต่อไบต์ขาเข้าในเวลาเดียวกัน การตอบสนองจะต้องอยู่ในไบต์ต่อไป นี่เป็นเพราะบิตที่ถูกส่งและบิตที่ได้รับถูกส่งพร้อมกัน ดังนั้นเมื่อต้องการเพิ่มบางสิ่งลงในหมายเลขสี่เราต้องมีการถ่ายโอนห้าแบบดังนี้:

transferAndWait ('a');  // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);

ก่อนอื่นเราขอให้ดำเนินการตามข้อ 10 แต่เราไม่ได้รับคำตอบจนกว่าจะถึงการถ่ายโอนครั้งต่อไป (อันที่ 17) อย่างไรก็ตาม "a" จะถูกตั้งค่าเป็นคำตอบที่ 10 ในที่สุดเราก็สิ้นสุดการส่ง "dummy" หมายเลข 0 เพื่อรับการตอบกลับสำหรับ 42

ต้นแบบ (ตัวอย่าง)

  #include <SPI.h>

  void setup (void)
    {
    Serial.begin (115200);
    Serial.println ();

    digitalWrite(SS, HIGH);  // ensure SS stays high for now
    SPI.begin ();

    // Slow down the master a bit
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    }  // end of setup

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

  void loop (void)
    {

    byte a, b, c, d;

    // enable Slave Select
    digitalWrite(SS, LOW);

    transferAndWait ('a');  // add command
    transferAndWait (10);
    a = transferAndWait (17);
    b = transferAndWait (33);
    c = transferAndWait (42);
    d = transferAndWait (0);

    // disable Slave Select
    digitalWrite(SS, HIGH);

    Serial.println ("Adding results:");
    Serial.println (a, DEC);
    Serial.println (b, DEC);
    Serial.println (c, DEC);
    Serial.println (d, DEC);

    // enable Slave Select
    digitalWrite(SS, LOW);

    transferAndWait ('s');  // subtract command
    transferAndWait (10);
    a = transferAndWait (17);
    b = transferAndWait (33);
    c = transferAndWait (42);
    d = transferAndWait (0);

    // disable Slave Select
    digitalWrite(SS, HIGH);

    Serial.println ("Subtracting results:");
    Serial.println (a, DEC);
    Serial.println (b, DEC);
    Serial.println (c, DEC);
    Serial.println (d, DEC);

    delay (1000);  // 1 second delay
    }  // end of loop

โดยทั่วไปแล้วรหัสสำหรับทาสจะทำเกือบทุกอย่างในรูทีนการขัดจังหวะ (เรียกว่าเมื่อมีข้อมูล SPI ขาเข้ามาถึง) มันใช้เวลาไบต์ที่เข้ามาและเพิ่มหรือลบตามที่จำได้ว่า "คำสั่งไบต์" โปรดทราบว่าการตอบสนองจะ "รวบรวม" ในครั้งถัดไปผ่านลูป นี่คือสาเหตุที่อาจารย์ต้องส่งการถ่ายโอน "จำลอง" ครั้งสุดท้ายเพื่อรับคำตอบสุดท้าย

ในตัวอย่างของฉันฉันใช้ลูปหลักเพียงแค่ตรวจสอบเมื่อ SS สูงและล้างคำสั่งที่บันทึกไว้ ด้วยวิธีนี้เมื่อ SS ถูกดึงต่ำอีกครั้งสำหรับธุรกรรมถัดไปไบต์แรกจะถือเป็นไบต์คำสั่ง

น่าเชื่อถือยิ่งขึ้นสิ่งนี้จะเกิดขึ้นเมื่อมีการขัดจังหวะ นั่นคือคุณจะต้องเชื่อมต่อ SS กับอินพุตอินเตอร์รัปต์อย่างใดอย่างหนึ่ง (เช่นบน Uno เชื่อมต่อพิน 10 (SS) ไปยังพิน 2 (อินเทอร์รัปต์อินพุท) หรือใช้อินเตอร์รัปต์ pin-change บนพิน 10

จากนั้นอินเทอร์รัปต์สามารถใช้สังเกตได้เมื่อ SS ถูกดึงต่ำหรือสูง

Slave (ตัวอย่าง)

// what to do with incoming data
volatile byte command = 0;

void setup (void)
  {

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  }  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
  {
  byte c = SPDR;

  switch (command)
    {
    // no command? then this is the command
    case 0:
      command = c;
      SPDR = 0;
      break;

    // add to incoming byte, return result
    case 'a':
      SPDR = c + 15;  // add 15
      break;

    // subtract from incoming byte, return result
    case 's':
      SPDR = c - 8;  // subtract 8
      break;

    } // end of switch

  }  // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
  {

  // if SPI not active, clear current command
  if (digitalRead (SS) == HIGH)
    command = 0;
  }  // end of loop

ตัวอย่างผลลัพธ์

Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34

เอาต์พุตตัววิเคราะห์ลอจิก

สิ่งนี้แสดงระยะเวลาระหว่างการส่งและรับในรหัสด้านบน:

SPI master และ slave timing


ฟังก์ชั่นใหม่ใน IDE 1.6.0 เป็นต้นไป

IDE เวอร์ชัน 1.6.0 มีการเปลี่ยนแปลงวิธีการทำงานของ SPI ในระดับหนึ่ง คุณยังต้องทำ SPI.begin()ก่อนใช้ SPI ที่ตั้งค่าฮาร์ดแวร์ SPI อย่างไรก็ตามตอนนี้เมื่อคุณกำลังจะเริ่มการสื่อสารกับทาสคุณก็ต้องSPI.beginTransaction()ทำการตั้งค่า SPI (สำหรับทาสนี้) ด้วยวิธีที่ถูกต้อง:

  • ความเร็วสัญญาณนาฬิกา
  • ลำดับบิต
  • เฟสนาฬิกาและกระแสไฟฟ้า

SPI.endTransaction()เมื่อคุณทำในการสื่อสารกับทาสที่คุณเรียก ตัวอย่างเช่น:

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));
digitalWrite (SS, LOW);        // assert Slave Select
byte foo = SPI.transfer (42);  // do a transfer
digitalWrite (SS, HIGH);       // de-assert Slave Select
SPI.endTransaction ();         // transaction over

ทำไมต้องใช้ SPI

ฉันจะเพิ่มคำถามเบื้องต้นหนึ่งคำถาม: เมื่อใด / ทำไมคุณจะใช้ SPI ความจำเป็นสำหรับการกำหนดค่าหลายหลักหรือทาสจำนวนมากจะเอียงสเกลไปทาง I2C

นี่เป็นคำถามที่ยอดเยี่ยม คำตอบของฉันคือ:

  • อุปกรณ์บางอย่าง (ค่อนข้างน้อย) รองรับวิธีถ่ายโอน SPI เท่านั้น ตัวอย่างเช่น 74HC595 output shift register, 74HC165 input shift register, ไดรเวอร์ LED MAX7219, และแถบ LED บางอันที่ฉันได้เห็น ดังนั้นคุณอาจใช้เพราะอุปกรณ์เป้าหมายรองรับเท่านั้น
  • SPI เป็นวิธีที่เร็วที่สุดในชิป Atmega328 (และที่คล้ายกัน) อัตราที่เร็วที่สุดที่อ้างถึงข้างต้นคือ 888,888 ไบต์ต่อวินาที เมื่อใช้ I 2 C คุณจะได้รับประมาณ 40,000 ไบต์ต่อวินาทีเท่านั้น ค่าใช้จ่ายของ I 2 C ค่อนข้างมากและหากคุณพยายามเชื่อมต่ออย่างรวดเร็ว SPI เป็นตัวเลือกที่ต้องการ ตระกูลชิปค่อนข้างน้อย (เช่น MCP23017 และ MCP23S17) รองรับทั้ง I 2 C และ SPI บ่อยครั้งคุณจึงสามารถเลือกระหว่างความเร็วและความสามารถในการมีอุปกรณ์หลายชิ้นบนบัสเดียว
  • อุปกรณ์ SPI และ I 2 C นั้นรองรับทั้งฮาร์ดแวร์ใน Atmega328 ดังนั้นคุณอาจจะทำการถ่ายโอนผ่าน SPI พร้อมกันกับ I 2 C ซึ่งจะเพิ่มความเร็วให้คุณ

ทั้งสองวิธีมีที่ของพวกเขา I 2 C ช่วยให้คุณเชื่อมต่ออุปกรณ์จำนวนมากกับบัสเดี่ยว (สายสองเส้นและกราวด์) ดังนั้นจึงเป็นตัวเลือกที่ต้องการหากคุณต้องการสอบถามอุปกรณ์จำนวนมากซึ่งอาจไม่บ่อยนัก อย่างไรก็ตามความเร็วของ SPI อาจเกี่ยวข้องกับสถานการณ์ที่คุณต้องการส่งออกอย่างรวดเร็ว (เช่นแถบ LED) หรืออินพุตอย่างรวดเร็ว (เช่นตัวแปลง ADC)


อ้างอิง


คุณจะครอบคลุมความแปลกประหลาดที่เป็น SPI ของ Due หรือไม่? ที่การกำหนดค่าของพอร์ต SPI ถูกผูกไว้กับพิน SS ที่ใช้และมีพิน SS ของฮาร์ดแวร์ (IIRC) 4 ที่กำหนดให้กับพอร์ต SPI
Majenko

จุดอื่น ๆ เกี่ยวกับการเลือก: บางครั้งคุณไม่มีตัวเลือกเพราะเซ็นเซอร์ที่คุณต้องการ / จำเป็นต้องใช้นั้นมีให้ใช้ในรูปแบบ I2C เท่านั้น
Igor Stoppa

Are you going to cover the weirdness that is the Due's SPI?- ฉันไม่รู้อะไรเกี่ยวกับ SPI ของ Due (นอกเหนือจากการสันนิษฐานว่าโปรโตคอลโดยรวมเหมือนกัน) คุณสามารถเพิ่มการตอบกลับที่ครอบคลุมเนื้อหานั้นได้
Nick Gammon

หนังสือเสียงของคำตอบนี้จะออกเมื่อใดและคุณจะอ่านเอง)
AMADANON Inc.

1
@AMADANONInc อาจจะเป็นมิวสิควิดีโอเหรอ? หรือภาพเคลื่อนไหว ฉันไม่แน่ใจว่าสำเนียงออสเตรเลียของฉันจะเข้าใจได้หรือไม่ : P
Nick Gammon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.