SPI是一种高速、全双工、同步串行总线,SPI与I2C对比: SPI是全双工,I2C是半双工 SPI只能有一个主机,I2C支持多主机多从机 I2C占用更少的GPIO,更节省资源
SPI的数据位宽更灵活,可以根据需要选择多位数数据宽度
SPI协议没有响应机制,主机无法得知从机是否接收到所发的数据,需采用一些方法来防止数据丢帧
SPI协议可以做到非常高的速率,每一个SCLK都可以进行数据的传输,通过引入CRC等校验方法,可以即高速传输数据,又能保持数据的准确度
I2C通过器件地址来选择从机,从机数量的增加不会导致GPIO的增加,而SPI通过CS片选信号选择从机,每增加一个从机就要多占用一个GPIO;也可以通过加入译码器来实现多从机控制
SPI、I2C都应用于板内器件短距离通讯 SPI总线详解可参考:
SPI Block Guide V04.01.pdf
(428.89 KB)
此篇主要介绍GPIO软件模拟实现SPI来驱动0.96寸OLED显示屏 硬件连接 GND —— GND VCC —— 3.3V DO —— PA5
DI —— PA7
RES —— PB1
DC —— PB0
CS —— PA4 软件代码 SPI代码: //SPI初始化
void SPI_Initial(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//根据GPIO组初始化GPIO时钟
RCC_AHBPeriphClk_Enable( RCC_SPI_CS | RCC_SPI_SCK | RCC_SPI_MOSI | RCC_SPI_MISO, ENABLE);
//GPIO_CS初始化设置
GPIO_InitStructure.Pins = PIN_SPI_CS;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(PORT_SPI_CS, &GPIO_InitStructure);
//GPIO_SCK初始化设置
GPIO_InitStructure.Pins = PIN_SPI_SCK;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(PORT_SPI_SCK, &GPIO_InitStructure);
//GPIO_MISO初始化设置
GPIO_InitStructure.Pins = PIN_SPI_MISO;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
GPIO_Init(PORT_SPI_MISO, &GPIO_InitStructure);
//GPIO_MOSI初始化设置
GPIO_InitStructure.Pins = PIN_SPI_MOSI;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(PORT_SPI_MOSI, &GPIO_InitStructure);
SPI_CS_DISABLE;
SPI_SCK_HIGH;
SPI_MOSI_HIGH;
}
//SPI写一字节数据,TxData --- 发送的字节数据
void SPI_WriteByte(unsigned char TxData)
{
unsigned char cnt;
for(cnt = 0; cnt < 8; cnt++)
{
SPI_SCK_LOW; //时钟 - 低
Delay_Us(1);
if(TxData & 0x80) //发送数据
SPI_MOSI_HIGH;
else
SPI_MOSI_LOW;
TxData <<= 1;
Delay_Us(1);
SPI_SCK_HIGH; //时钟 - 高
Delay_Us(1);
}
}
//SPI读一字节数据,读回来的字节数据
unsigned char SPI_ReadByte(void)
{
unsigned char cnt;
unsigned char RxData = 0;
for(cnt = 0; cnt < 8; cnt++)
{
SPI_SCK_LOW; //时钟 - 低
Delay_Us(1);
RxData <<= 1;
if(SPI_MISO_READ) //读取数据
{
RxData |= 0x01;
}
SPI_SCK_HIGH; //时钟 - 高
Delay_Us(1);
}
return RxData;
}
OLED代码: //向设备写控制命令
static void OLED_Write_CMD(unsigned char cmd)
{
#ifdef HW_I2C
unsigned char tx_buf[BUF_SIZE] = {0x00, cmd};
I2C_MasterSendDataToSlave(CW_I2C2, tx_buf, BUF_SIZE);
#endif
#ifdef SW_I2C
I2C_Start();
I2C_Send_Byte(0x78);
I2C_Wait_Ack();
I2C_Send_Byte(0x00);
I2C_Wait_Ack();
I2C_Send_Byte(cmd);
I2C_Wait_Ack();
I2C_Stop();
#endif
#ifdef HW_SPI
OLED_CS_LOW;
OLED_DC_LOW;
while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_TXE) == RESET);
SPI_SendData(SPI_MASTER, cmd);
while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_RXNE) == RESET);
SPI_ReceiveData(SPI_MASTER);
OLED_CS_HIGH;
#endif
#ifdef SW_SPI
OLED_CS_LOW;
OLED_DC_LOW;
SPI_WriteByte(cmd);
OLED_CS_HIGH;
#endif
}
//向设备写数据
static void OLED_Write_Date(unsigned char date)
{
#ifdef HW_I2C
unsigned char tx_buf[BUF_SIZE] = {0x40, date};
I2C_MasterSendDataToSlave(CW_I2C2, tx_buf, BUF_SIZE);
#endif
#ifdef SW_I2C
I2C_Start();
I2C_Send_Byte(0x78);
I2C_Wait_Ack();
I2C_Send_Byte(0x40);
I2C_Wait_Ack();
I2C_Send_Byte(date);
I2C_Wait_Ack();
I2C_Stop();
#endif
#ifdef HW_SPI
OLED_CS_LOW;
OLED_DC_HIGH;
while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_TXE) == RESET);
SPI_SendData(SPI_MASTER, date);
while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_RXNE) == RESET);
SPI_ReceiveData(SPI_MASTER);
OLED_CS_HIGH;
#endif
#ifdef SW_SPI
OLED_CS_LOW;
OLED_DC_HIGH;
SPI_WriteByte(date);
OLED_CS_HIGH;
#endif
}
//坐标设置
static void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_Write_CMD(0xB0 + y);
OLED_Write_CMD(((x & 0xF0) >> 4) | 0x10);
OLED_Write_CMD(x & 0x0F);
}
//开启OLED显示
static void OLED_Display_On(void)
{
OLED_Write_CMD(0x8D); //SET DCDC命令
OLED_Write_CMD(0x14); //DCDC ON
OLED_Write_CMD(0xAF); //DISPLAY ON
}
//关闭OLED显示
static void OLED_Display_Off(void)
{
OLED_Write_CMD(0x8D); //SET DCDC命令
OLED_Write_CMD(0x10); //DCDC OFF
OLED_Write_CMD(0xAE); //DISPLAY OFF
}
//OLED清屏
void OLED_Clear(void)
{
unsigned char cnt, count;
for(cnt = 0; cnt < 8; cnt++)
{
OLED_Write_CMD(0xB0 + cnt);
OLED_Write_CMD(0x00);
OLED_Write_CMD(0x10);
for(count = 0; count < 128; count++)
{
OLED_Write_Date(0x00);
}
}
}
//OLED清行
void OLED_Clear_Row(unsigned char n)
{
unsigned char count;
OLED_Write_CMD(0xB0 + n);
OLED_Write_CMD(0x00);
OLED_Write_CMD(0x10);
for(count = 0; count < 128; count++)
{
OLED_Write_Date(0x00);
}
}
//OLED填满屏幕
void OLED_Fill(void)
{
unsigned char cnt, count;
for(cnt = 0; cnt < 8; cnt++)
{
OLED_Write_CMD(0xB0 + cnt); //设置页地址(0~7)
OLED_Write_CMD(0x00); //设置显示位置—列低地址
OLED_Write_CMD(0x10); //设置显示位置—列高地址
for(count = 0; count < 128; count++)
{
OLED_Write_Date(0x01);
}
}
}
//指定位置显示一个字符
//x:0~127
//y:0~63
//chr:字符
//mode:0,反白显示;1,正常显示
//size:选择字体 16/12
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr, unsigned char size)
{
unsigned char offset = 0, cnt = 0;
offset = chr - ' '; //计算偏移量
if(x > 128 - 1)
{
x = 0;
y = y + 2;
}
if(size == 16)
{
OLED_Set_Pos(x, y);
for(cnt = 0; cnt < 8; cnt++)
{
OLED_Write_Date(F8x16[offset * 16 + cnt]);
}
OLED_Set_Pos(x, y + 1);
for(cnt = 0; cnt < 8; cnt++)
{
OLED_Write_Date(F8x16[offset * 16 + cnt + 8]);
}
}
else
{
OLED_Set_Pos(x, y);
for(cnt = 0; cnt < 6; cnt++)
{
OLED_Write_Date(F6x8[offset][cnt]);
}
}
}
unsigned int oled_pow(unsigned char m, unsigned char n)
{
unsigned int result = 1;
while(n--)
{
result *= m;
}
return result;
}
//指定位置显示一个数字
//x,y:起点坐标
//num:数值(0~4294967295)
//len:数字的位数
//size:字体大小
//mode:模式 0,填充模式;1,叠加模式
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len, unsigned char size)
{
unsigned char cnt, temp;
unsigned char show = 0;
for(cnt = 0; cnt < len; cnt++)
{
temp = (num / oled_pow(10, len - cnt - 1)) % 10;
if(show == 0 && cnt < (len - 1))
{
if(temp == 0)
{
OLED_ShowChar(x + (size / 2) * cnt, y, ' ', size);
continue;
}
else
{
show = 1;
}
}
OLED_ShowChar(x + (size / 2) * cnt, y, temp + '0', size);
}
}
//指定位置显示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *chr, unsigned char size)
{
unsigned char cnt = 0;
while(chr[cnt] != '\0')
{
OLED_ShowChar(x, y, chr[cnt], size);
x += 8;
if(x > 120)
{
x = 0;
y += 2;
}
cnt++;
}
}
//显示汉字
void OLED_ShowCHinese(unsigned char x, unsigned char y, unsigned char no)
{
unsigned char cnt, addr = 0;
OLED_Set_Pos(x, y);
for(cnt = 0; cnt < 16; cnt++)
{
OLED_Write_Date(Hzk[2 * no][cnt]);
addr++;
}
OLED_Set_Pos(x, y + 1);
for(cnt = 0; cnt < 16; cnt++)
{
OLED_Write_Date(Hzk[2 * no + 1][cnt]);
addr++;
}
}
//显示图片
/*
@brief 显示图片
@param x0:起始列地址
y0:起始页地址
x1:终止列地址
y1:终止页地址
BMP[]:存放图片代码的数组
@retval 无
*/
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[])
{
unsigned int j = 0; //定义变量
unsigned char x, y; //定义变量
if(y1 % 8 == 0)
{
y = y1 / 8; //判断终止页是否为8的整数倍
}
else
{
y = y1 / 8 + 1;
}
for(y = y0; y < y1; y++) //从起始页开始,画到终止页
{
OLED_Set_Pos(x0, y); //在页的起始列开始画
for(x = x0; x < x1; x++) //画x1 - x0 列
{
OLED_Write_Date(BMP[j++]); //画图片的点
}
}
}
//显示动图
/*
@brief 显示动图
@param x0:起始列地址
y0:起始页地址
x1:终止列地址
y1:终止页地址
k: 帧个数
m: 单帧数组大小
BMP[][m]:存放动图代码的数组
@retval 无
*/
void OLED_DrawGIF(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char k, int m, const unsigned char GIF[][m])
{
unsigned int j = 0; //定义变量
unsigned char x, y, i; //定义变量
if(y1 % 8 == 0)
{
y = y1 / 8; //判断终止页是否为8的整数倍
}
else
{
y = y1 / 8 + 1;
}
for (i = 0; i < k; i++) //从第一帧开始画
{
j = 0;
for(y = y0; y < y1; y++) //从起始页开始,画到终止页
{
OLED_Set_Pos(x0, y); //在页的起始列开始画
for(x = x0; x < x1; x++) //画x1 - x0 列
{
OLED_Write_Date(GIF[i][j++]); //画图片的点
}
}
//Delay_Ms(80);
}
}
#if defined SW_SPI || defined HW_SPI
//SPI驱动方式DC、RES引脚初始化
void OLED_SPI_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//根据GPIO组初始化GPIO时钟
RCC_AHBPeriphClk_Enable( RCC_OLED_DC | RCC_OLED_RES, ENABLE);
//GPIO_DC初始化设置
GPIO_InitStructure.Pins = PIN_OLED_DC;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(PORT_OLED_DC, &GPIO_InitStructure);
//GPIO_RES初始化设置
GPIO_InitStructure.Pins = PIN_OLED_RES;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(PORT_OLED_RES, &GPIO_InitStructure);
OLED_RES_LOW;
Delay_Ms(200);
OLED_RES_HIGH;
}
#endif
//OLED初始化
void OLED_Init(void)
{
#ifdef SW_I2C
I2C_Init();
#endif
#ifdef HW_I2C
I2C_Init();
#endif
#ifdef SW_SPI
SPI_Initial();
OLED_SPI_GPIO_Init();
#endif
#ifdef HW_SPI
SPI_Initial();
OLED_SPI_GPIO_Init();
#endif
Delay_Ms(200);
OLED_Write_CMD(0xAE); //display off
OLED_Write_CMD(0x00); //set low column address
OLED_Write_CMD(0x10); //set high column address
OLED_Write_CMD(0x40); //set start line address
OLED_Write_CMD(0xB0); //set page address
OLED_Write_CMD(0x81); //contract control
OLED_Write_CMD(0xFF); //128
OLED_Write_CMD(0xA1); //set segment remap
OLED_Write_CMD(0xA6); //normal / reverse
OLED_Write_CMD(0xA8); //set multiplex ratio(1 to 64)
OLED_Write_CMD(0x3F); //1/32 duty
OLED_Write_CMD(0xC8); //Com scan direction
OLED_Write_CMD(0xD3); //set display offset
OLED_Write_CMD(0x00); //
OLED_Write_CMD(0xD5); //set osc division
OLED_Write_CMD(0x80); //
OLED_Write_CMD(0xD8); //set area color mode off
OLED_Write_CMD(0x05); //
OLED_Write_CMD(0xD9); //Set Pre-Charge Period
OLED_Write_CMD(0xF1); //
OLED_Write_CMD(0xDA); //set com pin configuartion
OLED_Write_CMD(0x12); //
OLED_Write_CMD(0xDB); //set Vcomh
OLED_Write_CMD(0x30); //
OLED_Write_CMD(0x8D); //set charge pump enable
OLED_Write_CMD(0x14); //
OLED_Write_CMD(0xAF); //turn on oled panel
}
运行测试
测试代码
CW32F030CxTx_SW_SPI_OLED.zip
(261.28 KB)
|