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:
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:
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
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:
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.
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:
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.
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:
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.
/* page: 0-7
* col : 0-127
* Character: 8x16 pixels
*/
void OLEDPutChar(int page, int col, char c)
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
- Popular Resources
- Popular amplifiers
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- Ranking of installed capacity of smart driving suppliers from January to September 2024: Rise of independent manufacturers and strong growth of LiDAR market
- Industry first! Xiaopeng announces P7 car chip crowdfunding is completed: upgraded to Snapdragon 8295, fluency doubled
- P22-009_Butterfly E3106 Cord Board Solution
- Keysight Technologies Helps Samsung Electronics Successfully Validate FiRa® 2.0 Safe Distance Measurement Test Case
- Innovation is not limited to Meizhi, Welling will appear at the 2024 China Home Appliance Technology Conference
- Innovation is not limited to Meizhi, Welling will appear at the 2024 China Home Appliance Technology Conference
- Huawei's Strategic Department Director Gai Gang: The cumulative installed base of open source Euler operating system exceeds 10 million sets
- Download from the Internet--ARM Getting Started Notes
- Learn ARM development(22)
- Learn ARM development(21)
- Differences between CPU and CLA and Error Handling Techniques
- EEWORLD University Hall ---- Pattern Recognition National University of Defense Technology Cai Xuanping
- ST32F103x Read the sensor value on X-NUCLEO-IKS01A3 via I2C
- Need help designing a CAN communication solution between two MCUs!
- 【NUCLEO-WL55JC2 Review 1】 Overview of STM32WL Chip
- Friends who are familiar with MP2307DN, please take a look!
- General welding specifications for MINI manufacturers
- Why constant current source simulation does not produce constant current
- [National Technology N32G457 Review] 1. Unboxing + Lighting
- The second article is a simple comparison between GD32VF103C START and ST official routines