SPI, IDC, UART, CAN

In this article we will consider such interaction protocols as SPI, IDC, UART, CAN

SPI

SPI protocol is a synchronous serial interface used to exchange data between microcontrollers and various peripheral devices, such as sensors, SD cards, memory modules, and so on. It was developed by the company Motorola in the 1980s to enable high-speed, direct and efficient data transmission. Since then, SPI has become widespread and popular due to its simplicity.

The very essence of SPI is master-slave architecture, where one device acts as a master, managing the exchange, and one or more devices work as slaves. Data exchange occurs along four main lines:

  1. MOSI (Master Out Slave In) — line for data transfer from master to slave.

  2. MISO (Master In Slave Out) — line for data transmission from slave to master.

  3. SCK (Serial Clock) — synchronization line controlled by the master.

  4. SS (Slave Select) — slave selection line, also controlled by the master.

The master generates a clock signal on the SCK line, determining the rate of data exchange. In this case, data is transmitted simultaneously in both directions via MOSI and MISO lines. Each slave is assigned a unique SS line, and when the master wants to communicate with a particular slave, he activates it by sending a low signal to the corresponding SS line.

Technical specifications

Data transfer rate can vary widely depending on the specific microcontroller and peripheral, but is typically in the range of a few kilobits per second to several megabits. But here you need to consider that high speed may require shorter connections and higher quality wiring due to signal integrity problems.

Operating modes are defined by four combinations of clock phase and polarity (CPOL and CPHA), which allows you to fine-tune the protocol to the requirements of a specific device.

Multiplexing SS lines allows one master to control multiple slaves using individual slave select lines or address decoders to save microcontroller pins.

Implementation example

Let's look at the classic example of SPI, based on a situation where you need to connect and work with an SD card using a microcontroller, where SPI finds its application due to the need for high-speed data exchange.

Target: Integrate SD card with microcontroller to read and write data. An SPI interface is used for this.

Components:

  • Microcontroller (eg Arduino Uno)

  • SD card and SD card reader

  • Wires for connection

Step 1: Connecting the SD card module to the microcontroller

  1. MOSI The microcontroller is connected to the MOSI of the SD card module.

  2. MISO The microcontroller is connected to the MISO module of the SD card.

  3. SCK The microcontroller is connected to the SCK of the SD card module.

  4. SS or sometimes referred to as the CS of the microcontroller is connected to the CS of the SD card module.

  5. Power and ground are connected accordingly.

Step 2: Configure and initialize SPI in code

For Arduino it might look something like this:

#include <SPI.h>
#include <SD.h>

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // ждем подключения серийного порта
  }
  
  Serial.print("Инициализация SD-карты...");
  
  if (!SD.begin(10)) { // пин CS подключен к 10 пину
    Serial.println("инициализация не удалась!");
    return;
  }
  Serial.println("инициализация успешна.");
}

void loop() {
  //  код для работы с SD-картой
}

The code initializes the SPI interface and SD card using the SPI and SD libraries provided by Arduino. IN setup() The SD card connection is checked to see if it is successfully connected. IN loop() You can realize reading or writing data to the SD card.

Step 3: Read and Write Data

To work with files on the SD card, use the functions SD.open(), file.read(), file.write() and others provided by the SD library.

I2C

I2Cwhich is often pronounced as ay-to-si, was developed Philips (now NXP Semiconductors) in the early 1980s as a means to facilitate communication between chips inside televisions.

I2C is a two-wire, synchronous serial protocol. It uses two lines for communication:

I2C supports multi-master configuration, which allows multiple masters to control the bus without interfering with each other.

Each slave device on the I2C bus has a unique address, which is used by the master to initiate communication. Addresses can be 7-bit or 10-bit.

Data exchange process:

  1. Start: The master initiates the transfer by sending a START condition, then transmits the slave address with a read/write bit.

  2. Addressing: If a slave with the corresponding address is present on the bus, it responds with an acknowledgment signal (ACK).

  3. Data transfer: After receiving the ACK, the master or slave (depending on the operation) begins transmitting data, with an acknowledgment after each byte.

  4. Stop: The master ends the transfer by sending a STOP condition.

Technical parameters:

I2C supports several speeds: standard (100 kbps), fast (400 kbps), and fast+ (1 Mbps). There are also experimental implementations with even higher speeds, but they are usually rarely used.

I2C uses a technique of pulling up lines to the power supply through resistors.

In a multi-master configuration, if two masters start transmitting at the same time, an arbitration mechanism is used to determine which master will continue to control the bus.

Well, as already mentioned, devices can have 7-bit or 10-bit addresses.

Implementation example

For example, you need to connect a temperature sensor that uses I2C to communicate with the Arduino microcontroller, and read temperature data from it.

Components:

  • Microcontroller (Arduino Uno, Nano, or any other with I2C support)

  • Temperature sensor on I2C (for example, TMP102)

  • Connecting wires

Connecting the sensor to the microcontroller:

  1. Connect the VCC of the sensor to 5V on the Arduino (or to 3.3V if the sensor is 3.3V).

  2. Connect the GND of the sensor to GND on the Arduino.

  3. Connect the SDA (data) of the sensor to A4 on the Arduino Uno (or the corresponding SDA pin on other boards).

  4. Connect the SCL (clock signal) of the sensor to A5 on the Arduino Uno (or the corresponding SCL pin on other boards).

Microcontroller programming:

#include <Wire.h>

// адрес датчика TMP102
const int tempSensorAddress = 0x48; 

void setup() {
  Wire.begin(); // инициализация I2C
  Serial.begin(9600); // начало серийной связи для вывода данных
}

void loop() {
  // запрос на чтение 2 байтов из датчика температуры
  Wire.requestFrom(tempSensorAddress, 2); 
  if(Wire.available() == 2) {
    byte MSB = Wire.read(); // старший байт
    byte LSB = Wire.read(); // младший байт

    // преобразование в температуру в соответствии с документацией датчика
    int temp = ((MSB << 8) | LSB) >> 4; 

    // преобразование в градусы Цельсия
    float celsius = temp * 0.0625;
    Serial.print("Температура: ");
    Serial.print(celsius);
    Serial.println(" C");
  }
  delay(1000); // задержка для читаемости
}

After downloading the program to the microcontroller, open the port monitor in the Arduino IDE to see the current temperature read by the sensor. This data will be updated once per second.

UART

UART became the basis for many early forms of computer interfaces, including RS-232which was common for communication between computers and peripheral devices.

A UART works by transmitting and receiving data through two wires: TX (transmit) and RX (receive).

The UART transmits data asynchronously using a start bit, stop bits, data bits, and an optional parity bit. Data is sent sequentially, one bit at a time, starting with a start bit, followed by data bits, a parity bit (if used), and ending with stop bits.

UART data transfer speed is usually measured in baud (bits per second). The baud rate of both sides must be matched for correct operation. Common speeds include 9600, 19200, 38400, 57600, 115200 and higher.

The standard configuration is 8 data bits, although other settings (eg 7 data bits) are possible. The parity bit (can be odd, even, or none) is used to detect errors in the transmitted data.

Unlike I2C and SPI interfaces, UART does not have strict physical layer requirements.

A UART is typically used for half-duplex communication, which means that either data can be sent or received at a time.

Many UART interfaces have built-in transmit and receive buffers.

Implementation

For example, you need to exchange the string “Hello, UART!” between two microcontrollers.

Equipment:

  • 2 microcontrollers (for example, Arduino Uno, STM32, ESP32, etc.).

  • Wires for connecting TX/RX pins and ground between microcontrollers.

Connection:

  1. Microcontroller 1:

    • TX -> RX Microcontroller 2

    • RX <- TX Microcontroller 2

    • GND connects to GND Microcontroller 2

  2. Microcontroller 2:

    • RX connected to TX Microcontroller 1

    • TX connected to RX Microcontroller 1

    • GND connects to GND Microcontroller 1

Let's run the first transmitter:

void setup() {
  // инициализация Serial порта со скоростью 9600 бод.
  Serial.begin(9600);
}

void loop() {
  // отправка сообщения на Микроконтроллер 2
  Serial.println("Привет, UART!");
  // задержка, чтобы не перегружать канал сообщениями.
  delay(2000); // задержка 2 секунды
}

Receiver:

void setup() {
  // инициализация Serial порта со скоростью 9600 бод.
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    // чтение полученной строки и вывод в Serial Monitor.
    String received = Serial.readStringUntil('\n');
    Serial.print("Получено сообщение: ");
    Serial.println(received);
  }
}

The work will look like this:

  1. Transmitter initializes its Serial port and sends the string “Hello, UART!” in a loop. on Microcontroller 2 every two seconds.

  2. Receiver also initializes its Serial port and listens for incoming data. As soon as the data arrives, it reads it up to the newline character ('\n'), and then outputs the received message to its Serial Monitor.

CAN

CAN was developed in the early 1980s by Bosch for the needs of the automotive industry. CAN is a multi-node, serial communication protocol that allows multiple devices (nodes) on a bus to communicate with each other without the need for a master control device. This is a decentralized protocol that is designed to operate in environments with high electromagnetic interference.

CAN uses two wires to transmit data (CAN_H and CAN_L). The voltage difference between CAN_H and CAN_L is used to determine the logic levels. This is what allows CAN to operate in conditions of strong electromagnetic interference.

All nodes on the bus can initiate data transfer. If two or more nodes simultaneously attempt to send messages, a priority-based arbitration mechanism is used to allow the message with the higher priority to be transmitted first.

Frame formats:

CAN supports different frame types:

  • Data frame: Transfers data from sender to recipient.

  • Request frame: Request to send data.

  • Error frame: sent by the node that detected the error.

  • Overload frame: Indicates that a delay is required before the next data transmission.

CAN supports a variety of data rates, typically from 125 kbps to 1 Mbps. The speed affects the maximum possible length of the tire: the higher the speed, the shorter the tire. For example, at 1 Mbit/s the maximum bus length is about 40 meters, while at 125 kbit/s it can reach more than 500 meters.

Each message on the CAN network has ID, which determines its priority during arbitration. There are two identifier formats: standard (11 bits) and extended (29 bits).

Implementation example

It is necessary to organize the exchange of messages between two nodes (Node A and Node B) via the CAN interface.

Equipment:

  • 2 microcontrollers with CAN support (for example, STM32F103C8T6).

  • CAN transceivers (for example, SN65HVD230), one for each microcontroller.

  • Power supply circuit for microcontrollers and transceivers.

  • Wires for connecting microcontrollers to transceivers and connecting CAN_H and CAN_L lines between transceivers.

Connection:

  1. Connect the CAN_H and CAN_L outputs of the first transceiver to the CAN_H and CAN_L inputs of the second transceiver, respectively.

  2. Connect TX and RX of the microcontroller to CAN TX and CAN RX of the transceiver.

  3. Connect power and ground to microcontrollers and transceivers.

For Node A and Node B we will write simple code that will send and receive CAN messages. Let's assume that the HAL library for STM32 is used.

Node A:

#include "stm32f1xx_hal.h"
#include "main.h"
#include "can.h"

CAN_HandleTypeDef hcan;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CAN_Init(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_CAN_Init();

  CAN_TxHeaderTypeDef TxHeader;
  uint8_t message[5] = {'H', 'e', 'l', 'l', 'o'};
  uint32_t TxMailbox;

  TxHeader.DLC = 5; // Длина сообщения
  TxHeader.IDE = CAN_ID_STD;
  TxHeader.RTR = CAN_RTR_DATA;
  TxHeader.StdId = 0x123; // Идентификатор сообщения

  while (1)
  {
    if(HAL_CAN_AddTxMessage(&hcan, &TxHeader, message, &TxMailbox) == HAL_OK) {
      // Сообщение успешно отправлено
    }
    HAL_Delay(1000); // Задержка 1 секунда
  }
}

Node B:

#include "stm32f1xx_hal.h"
#include "main.h"
#include "can.h"

CAN_HandleTypeDef hcan;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CAN_Init(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_CAN_Init();

  CAN_RxHeaderTypeDef RxHeader;
  uint8_t receivedMessage[5];

  while (1)
  {
    if(HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, receivedMessage) == HAL_OK) {
      // Сообщение успешно принято
      // Можно добавить код для обработки сообщения
    }
  }
}

Let's compare these protocols in a table for clarity and reinforcement:

Characteristic

SPI

I2C

UART

CAN

Connection type

Synchronous

Synchronous

Asynchronous

Synchronous

Number of wires

4 or more (MOSI, MISO, SCK, SS for each slave)

2 (SDA and SCL)

2 (TX and RX)

2 (CAN_H and CAN_L)

Multi-master

No (usually one master)

Yes

Not applicable

Yes

Transmission speed

Up to several tens of Mbit/s

Up to 5 Mbit/s (in High-Speed ​​mode)

Typically up to 1 Mbit/s

Up to 1 Mbit/s

Range

Several meters

Up to several meters (up to 1m for High-Speed)

Tens of meters (depending on speed)

Up to 1 km (at low speeds)

Addressing

Along SS lines

By address on the bus

Not required

By message ID

Complexity

Medium (requires management of multiple lines for multi-slave configurations)

Medium (difficulty increases with the number of devices on the bus)

Low (easy to implement)

High (requires fine tuning of parameters)

Interference immunity

Low to medium (depending on implementation)

Average

Average

High

Arbitration support

No

Yes (via collision and retry mechanism)

Not applicable

Yes (lossless arbitrage)

Application

High speed transmission in closed systems

Connecting peripherals and sensors

Simple serial communication

Automotive networks, industrial networks

The specific choice of protocol depends on many factors, such as data transfer speed, range, number of connected devices, resistance to interference, and so on.

Finally, I would like to recommend you free webinar, where colleagues from OTUS will talk about important aspects of providing power for stationary and wearable devices. You will learn about the various typical voltage levels used in modern electronics and the methods for implementing them. Battery management principles needed to optimize life and efficiency and much more.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *