در این مقاله به معرفی و امکانات پروتکلهای ارتباطی SPI به در ESP32 با استفاده از Arduino IDE پرداخته ایم. در ابتدا نگاهی داریم به پینهای SPI سیستم ESP32، نحوهی اتصال دستگاههای SPI، تعریف پینهای سفارشی SPI، نحوهی استفاده از چند دستگاه SPI و غیره.
جدول عناوین:
-
پروتکل ارتباطی ESP32 SPI
-
دستگاههای جانبی ESP32 SPI
-
پینهای ESP32 SPI
-
استفاده از پینهای سفارشی ESP32 SPI
-
استفاده از چندین دستگاه SPI در ESP32
-
چندین دستگاه SPI (bus یکسان، پین CS متفاوت)
-
استفاده از دو رابط SPI bus (استفادهی همزمان از HSPI و VSPI)
موضوع این مقاله آموزشی برنامهنویسی ESP32 با استفاده از Arduino core است، پس حتماً افزونه ESP32 رو روی Arduino IDE نصب کنین. علاوه بر این، میتونین برای برنامهنویسی بوردهاتون با استفاده از Arduino core از VS Code به همراه PlatformIO استفاده کنین.
پروتکل ارتباطی ESP32 SPI
SPI مخفف Serial Peripheral Interface هست. SPI یک پروتکل همزمان داده سری (Synchronous serial data protocol) هست که میکروکنترلرها از اون برای برقراری ارتباط با یک یا بیش از یک دستگاه جانبی استفاده میکنن. برای مثال، بورد ESP32 با یک سنسور که از SPI پشتیبانی میکنه (یا یک میکروکنترلر دیگه) ارتباط برقرار میکنه.
در ارتباطات SPI، همیشه یک controller (که بهش master هم میگن) داریم که دستگاههای جانبی ( که بهشون slaves هم میگن) رو کنترل میکنه. دادهها به طور همزمان ارسال و دریافت میشن. به عبارت دیگه، master میتونه دادههایی به slave ارسال کنه و slave هم همزمان میتونه دادههایی به master ارسال کنه.
میتونین فقط یک master داشته باشین که همون میکروکنترلر (ESP32) هست، اما میتونین چند تا slave داشته باشین. Slave میتونه یک سنسور، یک کارت microSD، یک میکروکنترلر دیگه و غیره باشه. به عبارت دیگه، میتونین یک ESP32 رو به چند تا سنسور وصل کنین، اما همون سنسور رو نمیتونیم به طور همزمان به چند تا بورد ESP32 وصل کنین.
رابط SPI
برای ارتباط SPI به چهار خط نیاز دارین:
-
MISO: Master In Slave Out
-
MOSI: Master Out Slave In
-
SCK: Serial Clock
-
CS/SS: Chip Select ( از این خط زمانی استفاده میشه که چند تا دستگاه جانبی روی یک SPI bus استفاده میشن و بخوایم یک دستگاه رو انتخاب کنیم)
در دستگاهی که فقط slave داره (مثل سنسور، نمایشگر و غیره) ممکنه از اصطلاحات متفاوتی استفاده بشه:
-
ممکنه MISO با نام SDO (Serial Data Out) استفاده بشه
-
MOSI هم ممکنه با نام SDI (Serial Data In) استفاده بشه
دستگاههای جانبی ESP32 SPI
ESP32 شامل چهار دستگاه جانبی SPI هست: SPI0، SPI1، SPI2 (که معمولاً بهش HSPI میگن) و SPI3 (که معمولاً بهش VSPI میگن).
SP0 و SP1 به صورت داخلی و برای ارتباط با یک flash memory تعبیه شده استفاده میشه و برای کارای دیگه نباید ازشون استفاده کنین.
برای ارتباط با دستگاههای دیگه میتونین از HSPI و VSPI استفاده کنین. HSPI و VSPI سیگنالهای bus مستقل دارن و هر bus میتونه 3 تا SPI slave رو اجرا کنه.
پینهای SPI پیشفرض ESP32
در بسیاری از بوردهای ESP32 پینهای پیشفرض SPI از قبل مشخص شدن. نمودار پینها برای خیلی از بوردها به شکل زیره:
اخطار: بسته به نوع بوردی که استفاده میکنین، ممکنه پینهای پیشفرض SPI متفاوت باشن. برای همین حتماً طرح پایه (pinout) بوردی که ازش استفاده میکنین رو بررسی کنین. علاوه بر این، بعضی از بوردها پینهای SPI از پیش تعیین شده ندارن، برای همین باید اونا رو کدنویسی کنین.
نکته: معمولاً، زمانیکه پینها مشخص نشده باشن، بورد در زمان برقراری ارتباط SPI با تنظیمات پیشفرض از پینهای VSPI استفاده میکنه.
چه از قبل پینهای بورد مشخص شده باشن چه نه، همیشه امکان کدنویسیشون وجود داره.
یافتن پینهای پیشفرض SPI در بورد ESP32
اگر از پینهای پیشفرض SPI بورد خودتون مطمئن نیستین، با بارگذاری این کد میتونین پینها رو پیدا کنین.
//Find the default SPI pins for your board //Make sure you have the right board selected in Tools > Boards void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.print("MOSI: "); Serial.println(MOSI); Serial.print("MISO: "); Serial.println(MISO); Serial.print("SCK: "); Serial.println(SCK); Serial.print("SS: "); Serial.println(SS); } void loop() { // put your main code here, to run repeatedly: }
نکته: معمولاً، زمانیکه پینها مشخص نشده باشن، بورد در زمان برقراری ارتباط SPI با تنظیمات پیشفرض از پینهای VSPI استفاده میکنه.
بعد از بارگذاری کد، Serial Monitor, RST بورد رو باز کنین؛ در این قسمت، پینهای SPI بهتون نمایش داده میشن.
استفاده از پینهای SPI سفارشیِ بورد ESP32
زمانیکه از کتابخانهها برای ارتباط با دستگاههای جانبی استفاده میکنین، به سادگی میتونین از پینهای SPI سفارشی استفاده کنین چون میتونین اونا رو به عنوان آرگومان به library constructor انتقال بدین.
برای مثال، نگاهی بندازین به کد زیر که با استفاده از کتابخانه Adafruit_BME280 با سنسور BME280 ارتباط برقرار میکنه.
#include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include <SPI.h> #define BME_SCK 25 #define BME_MISO 32 #define BME_MOSI 26 #define BME_CS 33 #define SEALEVELPRESSURE_HPA (1013.25) //Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI unsigned long delayTime; void setup() { Serial.begin(9600); Serial.println(F("BME280 test")); bool status; // default settings // (you can also pass in a Wire library object like &Wire2) status = bme.begin(); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } Serial.println("-- Default Test --"); delayTime = 1000; Serial.println(); } void loop() { printValues(); delay(delayTime); } void printValues() { Serial.print("Temperature = "); Serial.print(bme.readTemperature()); Serial.println(" *C"); // Convert temperature to Fahrenheit /*Serial.print("Temperature = "); Serial.print(1.8 * bme.readTemperature() + 32); Serial.println(" *F");*/ Serial.print("Pressure = "); Serial.print(bme.readPressure() / 100.0F); Serial.println(" hPa"); Serial.print("Approx. Altitude = "); Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA)); Serial.println(" m"); Serial.print("Humidity = "); Serial.print(bme.readHumidity()); Serial.println(" %"); Serial.println(); }
با اجرای فرمان زیر به سادگی میتونین پینهای سفارشی SPI رو به library constructor منتقل کنین.
Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
من از پینهای SPI زیر (نه پینهای پیشفرض) استفاده کردم و همهچی طبق انتظار پیش رفت:
#define BME_SCK 25 #define BME_MISO 32 #define BME_MOSI 26 #define BME_CS 33
اگر از کتابخانه استفاده نمیکنین یا اگه کتابخانهای که ازش استفاده میکنین پینهای موجود در library constructor رو قبول نمیکنه، ممکنه لازم باشه SPI bus رو خودتون مقداردهی کنین. در این حالت، لازمه که متد ()SPI.begin رو در ()setup فراخوانی کنین و پینهای SPI رو به عنوان آرگومان میپذیرن:
SPI.begin(SCK, MISO, MOSI, SS);
استفاده از ESP32 به همراه چندین دستگاه SPI
همانطور که دیدیم، میتونین در بورد ESP32 از دو تا SPI bus مختلف استفاده کنین و هر bus میتونه به سه تا دستگاه جانبی مختلف متصل بشه. به عبارت دیگه، میتونیم 6 تا دستگاه SPI رو به بورد ESP32 وصل کنیم. در صورتیکه میخواید از دستگاههای بیشتری استفاده کنین، میتونین از یک SPI multiplexer استفاده کنین.
چندین دستگاه SPI
(bus یکسان، پین CS متفاوت)
البته دقت کنید هر دستگاه جانبی از یک پین CS متفاوت استفاده میکنه بنابراین میتونین از یک SPI bus یکسان برای اتصال چندین دستگاه SPI استفاده کنین.
برای انتخاب دستگاه جانبی که قصد دارین باهاش ارتباط برقرار کنین، باید پین CS رو روی LOW تنظیم کنین. برای مثال، فرض کنین دستگاه جانبی 1 و دستگاه جانبی 2 رو دارین. همچنین برای خواندن از دستگاه جانبی 1، پین CS رو روی LOW تنظیم کنین (در اینجا با CS_1 نشون داده شده).
digitalWrite(CS_1, LOW); // enable CS pin to read from peripheral 1 /* use any SPI functions to communicate with peripheral 1 */
حالا شاید بخواهید همزمان از دستگاه جانبی 2 هم بخوانید. برای این کار باید پین CS دستگاه جانبی 1 روی HIGH تنظیم کنین و اونو غیرفعال کنین و پین CS دستگاه جانبی 2 رو روی LOW تنظیم کنین و اونو فعال کنین.
digitalWrite(CS_1, HIGH); // disable CS pin from peripheral 1 digitalWrite(CS_2, LOW); // enable CS pin to read from peripheral 2 /* use any SPI functions to communicate with peripheral 2 */
استفاده از دو SPI bus interface در بورد ESP32
(استفادهی همزمان از HSPI و VSPI)
برای اینکه بتونیم به طور همزمان با چند تا دستگاه جانبی SPI ارتباط برقرار کنیم، میتونیم از دو SPI bus بورد ESP32 (یعنی HSPI و VSPI) استفاده کنیم. برای این کار، میتونین از پینهای پیشفرض HSPI و VSPI یا پینهای سفارشی استفاده کنین
در مرحله بعد برای استفاده همزمان از HSPI و VSPI کافیه:
1) اول، کتابخانه SPI رو به کُد اضافه کنین:
#include <SPI.h>
2) دو شی SPIClass رو با دو تا نام متفاوت (یکی در HSPI bus و یکی در VSPI bus) مقداردهی کنین. برای مثال:
vspi = new SPIClass(VSPI); hspi = new SPIClass(HSPI);
3) متد ()begin رو در اون شیها فراخونی کنین.
vspi.begin(); hspi.begin();
در صورت نیاز میتونین پینهای سفارشی رو به متد ()begin منتقل کنین.
vspi.begin(VSPI_CLK, VSPI_MISO, VSPI_MOSI, VSPI_SS); hspi.begin(HSPI_CLK, HSPI_MISO, HSPI_MOSI, HSPI_SS);
4) در آخر، باید پینهای SS رو به عنوان خروجی تعیین کنین. برای مثال:
pinMode(VSPI_SS, OUTPUT); pinMode(HSPI_SS, OUTPUT);
از فرمانهای معمول برای ارتباط با دستگاههای SPI (مهم نیست از متدهای کتابخانه سنسور استفاده میکنین یا از متدهای کتابخانه SPI) استفاد کنین.
برای آشنایی بیشتر با نحوهی استفاده از چندین SPI bus در کتابخانه Arduino-esp32 SPI به مثال زیر توجه کنین:
/* The ESP32 has four SPi buses, however as of right now only two of * them are available to use, HSPI and VSPI. Simply using the SPI API * as illustrated in Arduino examples will use VSPI, leaving HSPI unused. * * However if we simply intialise two instance of the SPI class for both * of these buses both can be used. However when just using these the Arduino * way only will actually be outputting at a time. * * Logic analyser capture is in the same folder as this example as * "multiple_bus_output.png" * * created 30/04/2018 by Alistair Symonds */ #include <SPI.h> // Define ALTERNATE_PINS to use non-standard GPIO pins for SPI bus #ifdef ALTERNATE_PINS #define VSPI_MISO 2 #define VSPI_MOSI 4 #define VSPI_SCLK 0 #define VSPI_SS 33 #define HSPI_MISO 26 #define HSPI_MOSI 27 #define HSPI_SCLK 25 #define HSPI_SS 32 #else #define VSPI_MISO MISO #define VSPI_MOSI MOSI #define VSPI_SCLK SCK #define VSPI_SS SS #define HSPI_MISO 12 #define HSPI_MOSI 13 #define HSPI_SCLK 14 #define HSPI_SS 15 #endif #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 #define VSPI FSPI #endif static const int spiClk = 1000000; // 1 MHz //uninitalised pointers to SPI objects SPIClass * vspi = NULL; SPIClass * hspi = NULL; void setup() { //initialise two instances of the SPIClass attached to VSPI and HSPI respectively vspi = new SPIClass(VSPI); hspi = new SPIClass(HSPI); //clock miso mosi ss #ifndef ALTERNATE_PINS //initialise vspi with default pins //SCLK = 18, MISO = 19, MOSI = 23, SS = 5 vspi->begin(); #else //alternatively route through GPIO pins of your choice vspi->begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_SS); //SCLK, MISO, MOSI, SS #endif #ifndef ALTERNATE_PINS //initialise hspi with default pins //SCLK = 14, MISO = 12, MOSI = 13, SS = 15 hspi->begin(); #else //alternatively route through GPIO pins hspi->begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_SS); //SCLK, MISO, MOSI, SS #endif //set up slave select pins as outputs as the Arduino API //doesn't handle automatically pulling SS low pinMode(vspi->pinSS(), OUTPUT); //VSPI SS pinMode(hspi->pinSS(), OUTPUT); //HSPI SS } // the loop function runs over and over again until power down or reset void loop() { //use the SPI buses spiCommand(vspi, 0b01010101); // junk data to illustrate usage spiCommand(hspi, 0b11001100); delay(100); } void spiCommand(SPIClass *spi, byte data) { //use it as you would the regular arduino SPI API spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0)); digitalWrite(spi->pinSS(), LOW); //pull SS slow to prep other end for transfer spi->transfer(data); digitalWrite(spi->pinSS(), HIGH); //pull ss high to signify end of data transfer spi->endTransaction(); }
جمعبندی
در این مقاله نشون دادیم که چجوری میشه به کمک Arduino core، ارتباط SPI رو به همراه ESP32 استفاده کرد، در حالیکه ESP32 به عنوان یک کنترلر (master) عمل میکنه.
به طور خلاصه، ESP32 چهار تا SPI bus داره، اما فقط از دو تای اونا – یعنی HSPI و VSPI – میشه برای کنترل دستگاههای جانبی استفاده کرد. در بیشتر بوردهای ESP32،GPIOهای HSPI و VSPI از قبل مشخص شدن، اما میتونین با کدنویسی این پینها رو تغییر بدین.
اگه میخواین چند تا دستگاه جانبی SPI رو به طور همزمان راهاندازی کنین میتونین به طور همزمان از busهای HSPI و VSPI استفاده کنین یا مادامی که پینهای CS اونا به یک GPIO متفاوت وصل شده میتونین از چند تا دستگاه جانبی در همان bus استفاده کنین.
در این مقاله آموزشی مثالها رو با شرح جزئیات دقیق توضیح ندادیم چون سنسورها، کتابخانهها و موارد کاربرد متفاوتی داریم. اما، الان نحوهی اتصال یک یا چند تا دستگاه SPI به بورد ESP32 رو میدونین.
برای آشنایی بیشتر با درایور SPI Master به این لینک مراجعه کنین.
در این نوشتار، نحوهی تنظیم ESP32 به عنوان یک SPI slave رو توضیح ندادیم، برای آشنایی بیشتر با این مورد، این مثالها رو بررسی کنین.