Lesson 20 SPI protocol detailed explanation and bare metal program development analysis

Publisher:雅致人生Latest update time:2020-03-24 Source: eefocusKeywords:SPI Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

Section 001_SPI Protocol Introduction

There are few development boards on the market that are connected to SPI devices, but the SPI protocol is often used in work. We have developed a SPI module with SPI Flash and SPI OLED. OLED is a display.


Our bare board program will involve two parts:


Emulating SPI with GPIO

Using S3C2440 SPI controller

Let's first introduce the SPI protocol. The hardware framework is as follows:

Write the picture description here

SCK: Provides clock

DO: as data output

DI: as data input

CS0/CS1: as chip select

Only one SPI device can be in operation at the same time.


Assume that 2440 transmits a 0x56 data to SPI Flash, the timing is as follows:

Write the picture description here

First, pull CS0 low to select SPI Flash. The binary of 0x56 is 0b0101 0110. Therefore, in each SCK clock cycle, DO outputs the corresponding level. 

SPI Flash reads the level on D0 at the rising edge of each clock cycle.


In the SPI protocol, there are two values ​​to determine the SPI mode. 

CPOL: indicates the initial level of SPICLK, 0 is level, 1 is high level 

CPHA: Indicates the phase, that is, whether the first or second clock edge samples the data, 0 is the first clock edge, 1 is the second clock edge

image.png

We often use mode 0 and mode 3 because they both sample data on the rising edge. We don’t need to care about the initial level of the clock, as long as we collect data on the rising edge.

What polarity should I choose? What format should I choose? Usually, you should refer to the chip manual of the external module. For example, for OLED, check the timing section of its chip manual:
Write the picture description here

We do not need to care about the initial level of SCLK, as long as we ensure that the data is sampled on the rising edge.


Section 002_Using GPIO to implement SPI protocol to operate OLED

Now start writing code and use GPIO to implement SPI protocol operations.


Now we want to operate the OLED, and connect to the OLED through three lines (SCK, DO, CS). There is no DI here because 2440 only transmits data to the OLED but does not receive data.


We need to use GPIO to implement SOC writing data to OLED. This layer is implemented by gpio_spi.c, which is responsible for sending data.


For OLED, there are special instructions and data formats. The data content to be transmitted is implemented in the oled.c layer, which is responsible for organizing the data.


Therefore, we need to implement the above two files.

Write the picture description here

Functions that need to be implemented: first initialize SPI SPIInt(), then initialize OLEDOLEDInit(), and finally display OLEDPrint().


Create a new gpio_spi.c file to implement SPI initialization SPIInt()


void SPIInit(void)

{

    /* Initialize pins */

    SPI_GPIO_Init();

}


Then implement SPI_GPIO_Init(). Here, GPIO is used to implement the SPI protocol. The circuit diagram is as follows:

Write the picture description here

 GPF1 is used as the OLED chip select pin and is set to output;

 GPG2 is used as the FLASH chip select pin and is set to output;

 GPG4 is used as the data/command selection pin of OLED and is set to output;

 GPG5 is used as MISO of SPI and is set as input;

 GPG6 is used as MOSI of SPI and is set as output;

 GPG7 is used as the SPI clock CLK and is set to output;


/* Use GPIO to simulate SPI */

static void SPI_GPIO_Init(void)

{

    /* GPF1 OLED_CSn output */

    GPFCON &= ~(3<<(1*2));

    GPFCON |= (1<<(1*2));

    GPFDAT |= (1<<1);


    /* GPG2 FLASH_CSn output

    * GPG4 OLED_DC output

    * GPG5 SPIMISO input

    * GPG6 SPIMOSI output

    * GPG7 SPICLK output

    */

    GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));

    GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (1<<(6*2)) | (1<<(7*2)));

    GPGDAT |= (1<<2);

}


Create a new oled.c file to initialize OLEDOLEDInit()


void OLEDInit(void)

{

/* Send commands to OLED to initialize */

}


By referring to the OLED data sheet SPEC UG-2864TMBEG01.pdf, you can find out its initialization process and reference initialization code:


void OLEDInit(void)

{

    /* Send commands to OLED to initialize */

    OLEDWriteCmd(0xAE); /*display off*/ 

    OLEDWriteCmd(0x00); /*set lower column address*/ 

    OLEDWriteCmd(0x10); /*set higher column address*/ 

    OLEDWriteCmd(0x40); /*set display start line*/ 

    OLEDWriteCmd(0xB0); /*set page address*/ 

    OLEDWriteCmd(0x81); /*contract control*/ 

    OLEDWriteCmd(0x66); /*128*/ 

    OLEDWriteCmd(0xA1); /*set segment remap*/ 

    OLEDWriteCmd(0xA6); /*normal / reverse*/ 

    OLEDWriteCmd(0xA8); /*multiplex ratio*/ 

    OLEDWriteCmd(0x3F); /*duty = 1/64*/ 

    OLEDWriteCmd(0xC8); /*Com scan direction*/ 

    OLEDWriteCmd(0xD3); /*set display offset*/ 

    OLEDWriteCmd(0x00); 

    OLEDWriteCmd(0xD5); /*set osc division*/ 

    OLEDWriteCmd(0x80); 

    OLEDWriteCmd(0xD9); /*set pre-charge period*/ 

    OLEDWriteCmd(0x1f); 

    OLEDWriteCmd(0xDA); /*set COM pins*/ 

    OLEDWriteCmd(0x12); 

    OLEDWriteCmd(0xdb); /*set vcomh*/ 

    OLEDWriteCmd(0x30); 

    OLEDWriteCmd(0x8d); /*set charge pump enable*/ 

    OLEDWriteCmd(0x14); 

}


Therefore, we must first implement the OLEDWriteCmd() function. For OLED, in addition to the SPI chip select, clock, and data pins, there is also a data/command switching pin.

Write the picture description here

The D/C here refers to the data/command selection pin. When it is at a high level, the OLED believes that it has received data; when it is at a low level, the OLED believes that it has received a command.


For OLED, commands include turning on/off the display, backlight brightness, etc. For specific commands, you can refer to the OLED main control chip manual SSD1306-Revision 1.1 (Charge Pump).pdf. The relevant commands are introduced in 9 COMMAND TABLE.


Therefore, when writing OLEDWriteCmd(), you need to set it to command mode first:


static void OLEDWriteCmd(unsigned char cmd)

{

    OLED_Set_DC(0); /* command */

    OLED_Set_CS(0); /* select OLED */


    SPISendByte(cmd);


    OLED_Set_CS(1); /* de-select OLED */

    OLED_Set_DC(1); /* */

}


That is: first set it to command mode, then chip select OLED, then transmit the command, then restore to the original mode and cancel the chip select.


The chip select function and mode switching function are relatively simple, just set the corresponding high and low levels:


static void OLED_Set_DC(char val)

{

    if (val)

        GPGDAT |= (1<<4);

    else

        GPGDAT &= ~(1<<4);

}


static void OLED_Set_CS(char val)

{

    if (val)

        GPFDAT |= (1<<1);

    else

        GPFDAT &= ~(1<<1);

}


The remaining function is SPISendByte(), which belongs to the SPI protocol and is placed in gpio_spi.c:


void SPISendByte(unsigned char val)

{

    int i;

    for (i = 0; i < 8; i++)

    {

        SPI_Set_CLK(0);

        SPI_Set_DO(val & 0x80);

        SPI_Set_CLK(1);

        val <<= 1;

    }


}


The data sent must meet the timing requirements of SPI, refer to the previous introduction:

Write the picture description here

First set CLK to low, then the data pin outputs the highest bit of the data, then CLK is high, and during the rising edge of CLK, OLED reads one bit of data. Then shift left one bit, moving the original 7th bit to the 8th bit, repeat 8 times, and the transmission is completed.


Then complete SPI_Set_CLK() and SPI_Set_DO():



static void SPI_Set_CLK(char val)

{

    if (val)

        GPGDAT |= (1<<7);

    else

        GPGDAT &= ~(1<<7);

}


static void SPI_Set_DO(char val)

{

    if (val)

        GPGDAT |= (1<<6);

    else

        GPGDAT &= ~(1<<6);

}


At this point, SPI initialization and OLED initialization are basically completed, and the next step is the OLED display part.


First understand the principle of OLED display:



OLED is 128 pixels long and 64 pixels wide. Each pixel is represented by one bit, which means it is on when it is 1 and off when it is 0.


Each byte of data Datax controls 8 pixels in each column and stores the Data data in the video memory.


The next step is to write the data to the video memory. How to write the data to the video memory can be divided into two problems:


①How to send address 

②How to send data


The manual of the OLED master controller introduces three address modes. The one we often use is the page addressing mode (Page addressing mode (A[1:0]=10xb)), which divides the 64 rows of video memory into 8 pages, each page corresponds to 8 rows; after selecting a page, then select a column, and then you can write data into it. Each time you write a data, the address will increase by 1, and it will automatically jump to the leftmost position after writing to the rightmost position.


The page address and column address are sent through commands, where the column address is sent twice, the low byte is sent first, and then the high byte is sent.


Assume that the size of each character data is 8x16. If the first character position is (page,col), the adjacent right side is (page,col+8), and the coordinates of jumping to the next line after a line is filled is (page+2,col).


/* page: 0-7

 * col : 0-127

 * Character: 8x16 pixels

 */

void OLEDPrint(int page, int col, char *str)

{

    int i = 0;

    while (str[i])

    {

        OLEDPutChar(page, col, str[i]);

        col += 8;

        if (col > 127)

        {

            col = 0;

            page += 2;

        }

        i++;

    }

}


As long as there is data in the character array str[i], OLEDPutChar(page, col, str[i]) is called to display the first character at the specified position, and then the position is moved to the right by the size of a character. If the end of the line is encountered, a line break is performed, and all characters are displayed in sequence.


Now we start to implement the most important OLEDPutChar() function. Displaying a character on the OLED requires the following steps:


a. Get the font


b.Send to OLED


We can obtain the fonts by searching relevant information on the Internet and put the font array oled_asc2_8x16[95][16] in oledfont.c. The characters start with spaces, so subtracting a space each time will give us the character we want.


As shown in the figure, a character first starts at (page, col) and displays 8 bits of data, then wraps to (page+1, col) and displays 8 bits of data.

Write the picture description here

/* page: 0-7

 * col : 0-127

 * Character: 8x16 pixels

 */

void OLEDPutChar(int page, int col, char c)

[1] [2] [3]
Keywords:SPI Reference address:Lesson 20 SPI protocol detailed explanation and bare metal program development analysis

Previous article:Lesson 019 I2C protocol detailed explanation and bare metal program analysis
Next article:ARM's PWM module pulse width modulation and ultrasonic system design

Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号