一、SPI时序通信
在此功能中,采用的是四线SPI,我们在开发过程中,应该去寻扎数据手册里面的通信时序图,才能使得单片机利用四线SPI和OLED进行通信的功能操作。
小结:在时序通信的过程张,高位先发,且当时钟线SCLK为低电平的时候,就可以吧数据放到数据线上面将其数据发(SDIN)。
二、SPI的分类
1.软件SPI
对于软件SPI的定义是:采用高低电平模拟SPI通信时序来完成发送或者接收数据。对于软件SPIeryan1,这样的通信方式在于移植灵活,引脚使用灵活,只需要任意四个I/O口就可以进行功能的完成,但是其缺点也很明显,那就是速度比较慢。
2.硬件SPI
对于硬件SPI的解释是,由单片机内部自主控制数据的发送与接收,硬件SPI的优点是速度快,单片机可以自主控制,我们只要完成将数据给到单片机,就可以了。缺点是:接口引脚相对固定,没有那么灵活。但是这是开发过程中最为经典的一部分,往下都是介绍硬件SPI.
三、硬件SPI
1、SPI的特性
硬件SPI特性有:是一个三线全双工的同步传输,可以带有或者不带第三条双线数据线的双线单工同步传输、可以进行8或者16位传输帧格式选择,能搞实现主机或者从机操作,并且支持多个主控模式,可以进行编程的时钟极性和相位,可以进行数据顺序编程,MSB在前或者LSB在前,可以触发中断的专用发送和收发标准,支持可靠通信的硬件CRC。
2、对四大引脚进行解释说明
MISO引脚:主设备输入,从设备输出引脚,该引脚的功能是在从机模式下发送数据,在主机模式下接收数据;
MOSI引脚:主设备输出,从设备输入引脚,该引脚的功能时在主机模式下发送数据,在从机模式下接收数据;
SCK引脚:是串口时钟引脚,该引脚的功能时主设备输出,从设备的输入;
NSS引脚:名为片选引脚,该功能是从设备选择,这一一个可以进行选择的引脚,用来选择主机、从机设备,可以让主设备进行单独的和特定的设备进行通信,并且可以避免和数据线发生冲突,片选引脚,我们一般不采用硬件的片选,我们可以进行手动对片选的控制。
3、库函数初始化
初始化引脚--
需要关闭PA15的JTAG功能,因为PA15复位I/O口不可用--
GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitTypeDef SPI2_SCLK_MOSI; SPI_InitTypeDef SPI_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//JTAG默认为下载接口 我们需要通过复用为普通的IO GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//使其生效为普通IO口
初始化结构体
SPI2_SCLK_MOSI.GPIO_Mode = GPIO_Mode_AF_PP; //设置功能模式为推挽复用功能 SPI2_SCLK_MOSI.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15; //设置引脚配置 SPI2_SCLK_MOSI.GPIO_Speed= GPIO_Speed_50MHz; //设置速度 GPIO_Init(GPIOB, &SPI2_SCLK_MOSI); //初始化spi2的sclk_mosi方面 SPI2_SCLK_MOSI.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为上升沿 SPI2_SCLK_MOSI.GPIO_Pin = GPIO_Pin_14; // SPI2_SCLK_MOSI.GPIO_Speed= GPIO_Speed_50MHz; //a072O+45 GPIO_Init(GPIOB, &SPI2_SCLK_MOSI); SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//2分频最大速度36M SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//第二个时钟边沿的上升沿采样 SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//CLK空闲时为高电平 SPI_InitStruct.SPI_CRCPolynomial = 7;//STM32的SPI带硬件ecc SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//8位数据位 SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工模式 SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位在前先发 SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主机模式 SPI_InitStruct.SPI_NSS = SPI_NSS_Hard;//CS硬件管理 我们自己控制 SPI_Init(SPI2,&SPI_InitStruct); SPI_Cmd(SPI2, ENABLE);
完整代码
OLED.c
#include "oled.h" #include "stdlib.h" #include "oledfont.h" #include "delay.h" u8 OLED_GRAM[144][8]; u8 SPI2_Send_Byte(u8 data) { while(!(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE)==SET));//获取发送完成标志位 SPI_I2S_SendData(SPI2, data);//spi发送一个字节 while(!(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)==SET));//获取接收完成标志位 return SPI_I2S_ReceiveData(SPI2);//spi读一个字节 } void OLED_WR_Byte(u8 dat,u8 cmd) { u8 i; if(cmd) OLED_DC_Set(); else OLED_DC_Clr(); OLED_CS_Clr(); SPI2_Send_Byte(dat); OLED_CS_Set(); OLED_DC_Set(); } //更新显存到OLED void OLED_Refresh(void) { u8 i,n; for(i=0;i<8;i++) { OLED_WR_Byte(0xb0+i,OLED_CMD); //设置行起始地址 OLED_WR_Byte(0x00,OLED_CMD); //设置低列起始地址 OLED_WR_Byte(0x10,OLED_CMD); //设置高列起始地址 for(n=0;n<128;n++) OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); } } //清屏函数 void OLED_Clear(void) { u8 i,n; for(i=0;i<8;i++) { for(n=0;n<128;n++) { OLED_GRAM[n][i]=0;//清除所有数据 } } OLED_Refresh();//更新显示 } //配置写入数据的起始位置 void OLED_WR_BP(u8 x,u8 y) { OLED_WR_Byte(0xb0+y,OLED_CMD);//设置行起始地址 OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);//设置列起始地址 OLED_WR_Byte((x&0x0f),OLED_CMD); } //显示单个字符 //x:0~127 //y:0~7 //chr:传入的字符 void OLED_ShowChar(u8 x,u8 y,char chr,u8 size)//chr = 'A' chr = 65 { int i; u8 j; u8 page = size/8;//计算字符12 16 24 大小所需要显示页数 if(size%8 !=0) page+=1;//有余数 说明还要加多一页 for(j=0;j<page;j++) { OLED_WR_BP(x,y+j);//先确定开始位置 for(i=0;i<size/2;i++) { if(size == 12) OLED_WR_Byte(F12x6[(chr-32)*12+i + j*size/2],OLED_DATA); else if(size == 16) OLED_WR_Byte(F16x8[(chr-32)*16+i + j*size/2],OLED_DATA); else if(size == 24) OLED_WR_Byte(F24x12[(chr-32)*36+i + j*size/2],OLED_DATA); } } } //显示一串字符 void OLED_Show_String(u8 x,u8 y,char *ch,u8 size) { u8 page = size/8;//计算字符12 16 24 大小所需要显示页数 if(size%8 !=0) page+=1;//有余数 说明还要加多一页 while(*ch != 0) { OLED_ShowChar(x,y,*ch,size); x = x+size/2;//移动位置 显示下一个字符 ch++; if(x>127) { x = 0;//坐标归0 下一行显示 y+=page;//页数相加 } } } //OLED显示一个汉字 void OLED_Show_One_Chinese(u8 x,u8 y,char ch[]) { u16 i = 0; int k; while(1) { if((ch[0] == GB2312_font[i][0]) && (ch[1]==GB2312_font[i][1])) { OLED_WR_BP(x,y); for(k=0;k<16;k++) { OLED_WR_Byte(GB2312_16x16[i*32+k],OLED_DATA);//发送字符数据的上半部分 } OLED_WR_BP(x,y+1);//页数+1 改变显示位置 for(;k<32;k++) { OLED_WR_Byte(GB2312_16x16[i*32+k],OLED_DATA);//发送字符数据的上半部分 } break; } i++; if(i >= (sizeof(GB2312_font)/3)) break;//超过字库大小 说明没有搜索到相应的汉字 } } //13397813910 //OLED的初始化 //BMP[]:要写入的图片数组 void OLED_ShowPicture(u8 x0,u8 y0,u8 x1,u8 y1,u8 BMP[]) { u16 j=0; u8 x=0,y=0; if(y%8==0)y=0; else y+=1; for(y=y0;y<y1;y++) { OLED_WR_BP(x0,y); for(x=x0;x<x1;x++) { OLED_WR_Byte(BMP[j],OLED_DATA); j++; } } } //实现滚动的功能 void OLED_ScrollDisplay(u8 num,u8 space) { u8 i,n,t=0,m=0,r; while(1) { if(m==0) { OLED_Show_One_Chinese(10,2,6); //写入一个汉字保存在OLED_GRAM[][]数组中 t++; } if(t==num) { for(r=0;r<16*space;r++) //显示间隔 { for(i=0;i<144;i++) { for(n=0;n<8;n++) { OLED_GRAM[i-1][n]=OLED_GRAM[i][n]; } } OLED_Refresh(); } t=0; } m++; if(m==16){m=0;} for(i=0;i<144;i++) //实现左移 { for(n=0;n<8;n++) { OLED_GRAM[i-1][n]=OLED_GRAM[i][n]; } } OLED_Refresh(); } } void OLED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitTypeDef SPI2_SCLK_MOSI; SPI_InitTypeDef SPI_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//JTAG默认为下载接口 我们需要通过复用为普通的IO GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//使其生效为普通IO口 //PA4---复位 PA15 命令/数据 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能A端口时钟,开启时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_15; //配置引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOD3,6 GPIO_SetBits(GPIOA,GPIO_Pin_4|GPIO_Pin_15); //设置空闲时间为高电平 //PB7---片选线 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能A端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //设置引脚配置引脚7 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOD3,6 GPIO_SetBits(GPIOB,GPIO_Pin_7); //空闲默认高电平 SPI2_SCLK_MOSI.GPIO_Mode = GPIO_Mode_AF_PP; //设置功能模式为推挽复用功能 SPI2_SCLK_MOSI.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15; //设置引脚配置 SPI2_SCLK_MOSI.GPIO_Speed= GPIO_Speed_50MHz; //设置速度 GPIO_Init(GPIOB, &SPI2_SCLK_MOSI); //初始化spi2的sclk_mosi方面 SPI2_SCLK_MOSI.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为上升沿 SPI2_SCLK_MOSI.GPIO_Pin = GPIO_Pin_14; // SPI2_SCLK_MOSI.GPIO_Speed= GPIO_Speed_50MHz; //a072O+45 GPIO_Init(GPIOB, &SPI2_SCLK_MOSI); SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//2分频最大速度36M SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//第二个时钟边沿的上升沿采样 SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//CLK空闲时为高电平 SPI_InitStruct.SPI_CRCPolynomial = 7;//STM32的SPI带硬件ecc SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//8位数据位 SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工模式 SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位在前先发 SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主机模式 SPI_InitStruct.SPI_NSS = SPI_NSS_Hard;//CS硬件管理 我们自己控制 SPI_Init(SPI2,&SPI_InitStruct); SPI_Cmd(SPI2, ENABLE); Delay_ms(200); OLED_RST_Clr();//复位 Delay_ms(200); OLED_RST_Set(); OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel OLED_WR_Byte(0x00,OLED_CMD);//---set low column address OLED_WR_Byte(0x10,OLED_CMD);//---set high column address OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常 OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常 OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64) OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F) OLED_WR_Byte(0x00,OLED_CMD);//-not offset OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration OLED_WR_Byte(0x12,OLED_CMD); OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02) OLED_WR_Byte(0x02,OLED_CMD);// OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5) OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) OLED_WR_Byte(0xAF,OLED_CMD); OLED_Clear(); }