menu icon
Remote Battery Level Monitoring

Part 3: Remote Battery Level Monitoring - SPI Communications

GIT Repository : view
Posted on : 29 Sep 2024
Tag(s) : PIC16F1459, SPI, Remote-Monitoring, ESP8266

In this article, we continue building our remote battery monitoring system by adding a crucial component: SPI communication between the ESP8266 WiFi Module and the PIC16 microcontroller. In this setup, the ESP8266 will serve as the master controller, sending the battery level it retrieves via WiFi to the PIC16 microcontroller, which will act as the slave device.

System Overview

Before diving into the technical details, let's take a moment to visualize the overall system. Below is the hardware diagram, which outlines all the important blocks in our system:

Overview of the remote battery level system
Overview of the remote battery monitoring system

This article focuses specifically on the communication between the ESP8266 and the PIC16 microcontroller. For more information on the other subsystems, be sure to check out the related articles linked at the end of this post.

SPI Basics

Let’s start with a brief overview of SPI (Serial Peripheral Interface). SPI is one of several communication protocols used to transfer data between devices. In an SPI system, the master device initiates communication, while the slave devices respond only when spoken to by the master. One of SPI's advantages is its full-duplex capability, meaning that data can be sent and received simultaneously.

SPI uses four main lines for communication:

Each of these lines has a specific role in data transmission, and their names make their functions self-explanatory.

Circuit Diagram

Below is the schematic diagram showing the connections of our system on the PIC microcontroller :

Circuit Diagram for PIC16 Battery System
PIC16 Circuit Diagram for the Remote Battery Monitoring system

The SPI pin connections between the ESP8266 and the PIC16F159 microcontroller are as follows:

SPI Pin Connections
SPI Pin Connections between ESP8266 and PIC16 Microcontroller

At this stage, we are not using the Slave Select (SS) line, as we only have two devices communicating with each other.

The circuit board, with the different components highlighted, is shown below:

Circuit board
Prototype circuit board

Configuring SPI on the PIC16 Microcontroller

SPI on the PIC16 microcontroller is controlled by the Master Synchronous Serial Port (MSSP) module. To set up SPI, we need to configure three main registers:

In addition to configuring these registers, it is necessary to set the appropriate pins for input/output using the TRISx registers. Additionally, we need to enable SPI interrupts through the PIE1 register.

PIC16 SPI Registers
Summary of SPI Registers on the PIC16F159

SPI Clock Modes

SPI supports four different clock modes, depending on the configuration of the clock polarity (CKP) and clock edge select (CKE) bits. Selecting the correct clock mode is crucial to ensure that data is sampled correctly.

In our setup, we are using CKP = 0, CKE = 1.

The code snippet below shows how we configure the MSSP module for slave mode on the PIC16F159:

void setSPIMode() {
    // Set SPI Pins (B6, B4, C7, C6)
    
    TRISBbits.TRISB6 = 1; // CLK
    TRISBbits.TRISB4 = 1; // SDI
    TRISCbits.TRISC7 = 0; // SDO
    TRISCbits.TRISC6 = 1; // SS
    
    // Configure SSP1CON1<5:0>
    SSP1CON1 = 0b00100101;
    
    // (7) WCOL = 0 [No collision]
    // (6) SSPOV = 0 [No overflow] -> Data must be read from buffer
    // (5) SSPEN = 1 [Enable SPI serial port]
    // (4) CKP = 0 [Clock Polarity] -> Idle state is Low
    // SSPM<3:0> = 0101 [SPI Slave mode] -> clock = SCK pin, SS pin control disabled
    
    SSP1STATbits.BF = 0;
    SSP1STATbits.CKE = 1; // Transmit occurs on transition from active to idle clock state
    SSP1CON3bits.BOEN = 0; // [Buffer overwrite disabled], -> If New data is received while buffer is full, the SSPOV bit of the SSP1CON register is set
    
    PIE1bits.SSP1IE = 1; // MSSP Interrupts enabled
    PIR1bits.SSP1IF = 0; // Clear interrupt flag
}

Configuring SPI on the ESP8266

For the ESP8266, we use the Arduino framework to simplify the code and reduce development time. Fortunately, the ESP8266 comes with an SPI library, which makes adding SPI into our project straightforward. We will use PlatformIO IDE to include and use this library in our project.

Data Transmission Payload

The ESP8266 will continuously transmit the battery level data to the PIC16 microcontroller via the SPI interface. The data payload format is as follows:

Data Payload
Data payload format

In this format, the characters ‘A’ and ‘Z’ mark the start and end of each transmission, so that the PIC16 knows when a full data packet has been sent.

Data Reception on the PIC Microcontroller

The PIC16 receives data from the SPI bus and stores it in the SSPBUF buffer. Once the buffer is full, an interrupt is triggered, allowing the microcontroller to read the data. Below is the code for our interrupt service routine (ISR) that handles data reception:

// Our Interrupt Service Routine
void __interrupt() ISR(void) {
    if ( PIR1bits.SSP1IF ) { // SPI data reception
        
        // Clear the interrupt flag
        PIR1bits.SSP1IF = 0;

        if( SSP1CON1bits.SSP1OV == 1 ){
            // There is a data overflow error. Discard the received byte
            SPI_Read();
            SSP1CON1bits.SSP1OV = 0; // clear overflow flag
        }else{
            // Read the received data from SPI Buffer
            received_data = SSPBUF;
            
            // check input data state
            if( received_data == 'A'){
                // start of transmit sequence
                spi_index_count = 0;
            }
            
            if( spi_index_count < 5 ){
                spi_tmp_buffer[spi_index_count] = received_data;
                spi_index_count++;
            }
            
            if( spi_index_count >= 5 ){
                if( received_data == 'Z'){
                    // End of transmit sequence
                    
                    // Copy contents into our main buffer
                    size_t len = sizeof(spi_tmp_buffer)/sizeof(spi_tmp_buffer[0]);
                    
                    for( int x = 0; x < len; x++){
                        spi_input_buffer[x] = spi_tmp_buffer[x];
                    }
                    
                }
            } 
        }
    }
}

Complete Code

The complete code for this project is available on my GitHub here.

Video Demo

Conclusion

In this article, we successfully established SPI communication between the ESP8266 and the PIC16 microcontroller. With this communication link in place, the ESP8266 can now transmit real-time battery data to the PIC16.

In the next part of the project, we will focus on building the automatic charging subsystem, ensuring that the monitored battery can be efficiently recharged when its level gets too low. Stay tuned!

Other article related to this project