SPI通讯协议示例

0x01 SPI通讯特点

SPI在接线方面会拥有片选引脚、时钟引脚、数据引脚,其中数据引脚又分为 MISO和MOSI,分别对应的是 “Master IN Slave Out”(主机输入从机输出)“Master Out Slave IN”(主机输出从机输出),在一些只需要使用MOSI引脚的设备例如OLED显示屏上,通常会将其标注为SDA引脚,因为无需读取数据,所以这个引脚自然也就成了唯一的数据引脚。

而使用SPI的方式进行通信,只需要CS引脚来区分所选择的设备。例如我要读取sd卡中的内容,并将其传输至OLED屏幕上进行显示,那么我可以先为OLED屏幕分配SPI1,随后再为sd卡增选一只spi引脚。CS引脚在使能后(有的设备是高电平选中,有的设备是低电平选中),设备会认为被选中,从而可以向其发送或读取数据。

在使用SPI进行通讯时,具体配置与设备的时钟极性和时钟相位有关。

CPHA(时钟相位):决定第一次电平跳变采样或第二次电平跳变采样;
CPOL(时钟极性):决定SCLK在空闲时的电平状态。

0x01 硬件SPI示例

在实际使用中,数据手册一般都不会告知你使用的是SPI协议通信,需要自行去体会,下面随便找一个数据手册为例。
在这里插入图片描述
如何判断其使用的是SPI协议进行通信的呢?

可以看到图中有一个CS引脚,又有一只SCLK引脚,所以基本可以判断其使用的是SPI通讯协议了。

通过对这个时序图进行分析,我们马上能知道: CS管脚是低电平选中,所以在你仅有一个SPI设备的情况下,你可以直接把SPI引脚进行接地处理。

随后对SCLK这根线进行分析,我们可以看到时钟总线,在没有进行信号传输的时候,是处在一个低电平的状态,依此可以得到 时钟极性应配置为0

继续观察 SCLK时序,可以看到在一个下降的箭头处有一个小箭头,这说明在与此设备的通信中,数据是在 SCLK低电平是时有效,并且也可以知道,此设备允许 一次传输16位数据

根据这些,可以完成对SPI的配置,以下是GD32的SPI配置示例。

	/* SPI parameter config */
	spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; //全双工模式
	spi_init_struct.device_mode = SPI_MASTER;//本机为主机模式
	spi_init_struct.frame_size = SPI_FRAMESIZE_16BIT;//传输16位数据
	spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;//空闲为低电平,第1个下降沿有效(从0开始编号)
	spi_init_struct.nss = SPI_NSS_SOFT;//CS引脚由软件控制
	spi_init_struct.prescale = SPI_PSC_16;//分频速率
	spi_init_struct.endian = SPI_ENDIAN_MSB;//大端模式传输
	spi_init(SPI_TC, &spi_init_struct);

如此一来,即便是拿到了一个完全陌生的设备,只要拿到了时序图,便也可以快速上手了。

0x02 软件SPI示例

例如GD32的硬件SPI,是比较稳定的,可以使用DMA进行传输。但是某些单片机的SPI并不稳定例如STM32。

现在我们来随便找一个常用的OLED显示屏,ssd1306芯片的数据手册,使用软件SPI的方式来模拟SPI时序。
在这里插入图片描述
如图,可以获知,CS低电平有效,SCLK空闲状态为高电平,并且在第二个跳变沿生效,一次传输8位数据,D/C引脚为区分数据与命令的引脚,此项为区分数据与命令的作用。网上很多OLED屏的驱动例程,通常都会封装一个写命令函数和写数据函数。

可以自己编写一个使用STM32驱动 ssd1306屏幕的驱动库,看看能否达到想要的效果。

以下为网上广为流传的驱动代码,可以提供参考

#include "bsp_oled.h"
#include "bsp_oled.h"
#include "bsp_delay.h"
#include "stdlib.h"
#include "oledfont.h"  	 

u8 OLED_GRAM[144][8];


//反显函数
void OLED_ColorTurn(u8 i)
{
	if(i==0)
		{
			OLED_WR_Byte(0xA6,OLED_CMD);//正常显示
		}
	if(i==1)
		{
			OLED_WR_Byte(0xA7,OLED_CMD);//反色显示
		}
}

//屏幕旋转180度
void OLED_DisplayTurn(u8 i)
{
	if(i==0)
	{
		OLED_WR_Byte(0xC8,OLED_CMD);//正常显示
		OLED_WR_Byte(0xA1,OLED_CMD);
	}
	if(i==1)
	{
		OLED_WR_Byte(0xC0,OLED_CMD);//反转显示
		OLED_WR_Byte(0xA0,OLED_CMD);
	}
}


//开启OLED显示 
void OLED_DisPlay_On(void)
{
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x14,OLED_CMD);//开启电荷泵
	OLED_WR_Byte(0xAF,OLED_CMD);//点亮屏幕
}

//关闭OLED显示 
void OLED_DisPlay_Off(void)
{
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x10,OLED_CMD);//关闭电荷泵
	OLED_WR_Byte(0xAE,OLED_CMD);//关闭屏幕
}

//设置OLED亮度
void OLED_SetBrightness(uint8_t xNUM)
{
	OLED_WR_Byte(0x81,OLED_CMD);//设置亮度命令
	OLED_WR_Byte(0x81,OLED_CMD);//亮度0x00-0xFF
}

//更新显存到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();//更新显示
}

//画点 
//x:0~127
//y:0~63
//t:1 填充 0,清空	
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
	u8 i,m,n;
	i=y/8;
	m=y%8;
	n=1<<m;
	if(t){OLED_GRAM[x][i]|=n;}
	else
	{
		OLED_GRAM[x][i]=~OLED_GRAM[x][i];
		OLED_GRAM[x][i]|=n;
		OLED_GRAM[x][i]=~OLED_GRAM[x][i];
	}
}

//画线
//x1,y1:起点坐标
//x2,y2:结束坐标
void OLED_DrawLine(u8 x1,u8 y1,u8 x2,u8 y2,u8 mode)
{
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,distance;
	int incx,incy,uRow,uCol;
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1;
	uRow=x1;//画线起点坐标
	uCol=y1;
	if(delta_x>0)incx=1; //设置单步方向 
	else if (delta_x==0)incx=0;//垂直线 
	else {incx=-1;delta_x=-delta_x;}
	if(delta_y>0)incy=1;
	else if (delta_y==0)incy=0;//水平线 
	else {incy=-1;delta_y=-delta_x;}
	if(delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y;
	for(t=0;t<distance+1;t++)
	{
		OLED_DrawPoint(uRow,uCol,mode);//画点
		xerr+=delta_x;
		yerr+=delta_y;
		if(xerr>distance)
		{
			xerr-=distance;
			uRow+=incx;
		}
		if(yerr>distance)
		{
			yerr-=distance;
			uCol+=incy;
		}
	}
}
//x,y:圆心坐标
//r:圆的半径
void OLED_DrawCircle(u8 x,u8 y,u8 r)
{
	int a, b,num;
    a = 0;
    b = r;
    while(2 * b * b >= r * r)      
    {
        OLED_DrawPoint(x + a, y - b,1);
        OLED_DrawPoint(x - a, y - b,1);
        OLED_DrawPoint(x - a, y + b,1);
        OLED_DrawPoint(x + a, y + b,1);
 
        OLED_DrawPoint(x + b, y + a,1);
        OLED_DrawPoint(x + b, y - a,1);
        OLED_DrawPoint(x - b, y - a,1);
        OLED_DrawPoint(x - b, y + a,1);
        
        a++;
        num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
        if(num > 0)
        {
            b--;
            a--;
        }
    }
}


//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size1:选择字体 6x8/6x12/8x16/12x24
//mode:0,反色显示;1,正常显示
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1,u8 mode)
{
	u8 i,m,temp,size2,chr1;
	u8 x0=x,y0=y;
	if(size1==8)size2=6;
	else size2=(size1/8+((size1%8)?1:0))*(size1/2);  //得到字体一个字符对应点阵集所占的字节数
	chr1=chr-' ';  //计算偏移后的值
	for(i=0;i<size2;i++)
	{
		if(size1==8)
			  {temp=asc2_0806[chr1][i];} //调用0806字体
		else if(size1==12)
        {temp=asc2_1206[chr1][i];} //调用1206字体
		else if(size1==16)
        {temp=asc2_1608[chr1][i];} //调用1608字体
		else if(size1==24)
        {temp=asc2_2412[chr1][i];} //调用2412字体
		else return;
		for(m=0;m<8;m++)
		{
			if(temp&0x01)OLED_DrawPoint(x,y,mode);
			else OLED_DrawPoint(x,y,!mode);
			temp>>=1;
			y++;
		}
		x++;
		if((size1!=8)&&((x-x0)==size1/2))
		{x=x0;y0=y0+8;}
		y=y0;
  }
}


//显示字符串
//x,y:起点坐标  
//size1:字体大小 
//*chr:字符串起始地址 
//mode:0,反色显示;1,正常显示
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 size1,u8 mode)
{
	while((*chr>=' ')&&(*chr<='~'))//判断是不是非法字符!
	{
		OLED_ShowChar(x,y,*chr,size1,mode);
		if(size1==8)x+=6;
		else x+=size1/2;
		chr++;
  }
}

//m^n
u32 OLED_Pow(u8 m,u8 n)
{
	u32 result=1;
	while(n--)
	{
	  result*=m;
	}
	return result;
}

//显示数字
//x,y :起点坐标
//num :要显示的数字
//len :数字的位数
//size:字体大小
//mode:0,反色显示;1,正常显示
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size1,u8 mode)
{
	u8 t,temp,m=0;
	if(size1==8)m=2;
	for(t=0;t<len;t++)
	{
		temp=(num/OLED_Pow(10,len-t-1))%10;
			if(temp==0)
			{
				OLED_ShowChar(x+(size1/2+m)*t,y,'0',size1,mode);
      }
			else 
			{
			  OLED_ShowChar(x+(size1/2+m)*t,y,temp+'0',size1,mode);
			}
  }
}

//显示汉字
//x,y:起点坐标
//num:汉字对应的序号
//mode:0,反色显示;1,正常显示
void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1,u8 mode)
{
	u8 m,temp;
	u8 x0=x,y0=y;
	u16 i,size3=(size1/8+((size1%8)?1:0))*size1;  //得到字体一个字符对应点阵集所占的字节数
	for(i=0;i<size3;i++)
	{
		if(size1==16)
				{temp=Hzk1[num][i];}//调用16*16字体
		else if(size1==24)
				{temp=Hzk2[num][i];}//调用24*24字体
		else if(size1==32)       
				{temp=Hzk3[num][i];}//调用32*32字体
		else if(size1==64)
				{temp=Hzk4[num][i];}//调用64*64字体
		else return;
		for(m=0;m<8;m++)
		{
			if(temp&0x01)OLED_DrawPoint(x,y,mode);
			else OLED_DrawPoint(x,y,!mode);
			temp>>=1;
			y++;
		}
		x++;
		if((x-x0)==size1)
		{x=x0;y0=y0+8;}
		y=y0;
	}
}

//num 显示汉字的个数
//space 每一遍显示的间隔
//mode:0,反色显示;1,正常显示
void OLED_ScrollDisplay(u8 num,u8 space,u8 mode)
{
	u8 i,n,t=0,m=0,r;
	while(1)
	{
		if(m==0)
		{
	    OLED_ShowChinese(128,24,t,16,mode); //写入一个汉字保存在OLED_GRAM[][]数组中
			t++;
		}
		if(t==num)
			{
				for(r=0;r<16*space;r++)      //显示间隔
				 {
					for(i=1;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=1;i<144;i++)   //实现左移
		{
			for(n=0;n<8;n++)
			{
				OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
			}
		}
		OLED_Refresh();
	}
}

//x,y:起点坐标
//sizex,sizey,图片长宽
//BMP[]:要写入的图片数组
//mode:0,反色显示;1,正常显示
void OLED_ShowPicture(u8 x,u8 y,u8 sizex,u8 sizey,u8 BMP[],u8 mode)
{
	u16 j=0;
	u8 i,n,temp,m;
	u8 x0=x,y0=y;
	sizey=sizey/8+((sizey%8)?1:0);
	for(n=0;n<sizey;n++)
	{
		 for(i=0;i<sizex;i++)
		 {
				temp=BMP[j];
				j++;
				for(m=0;m<8;m++)
				{
					if(temp&0x01)OLED_DrawPoint(x,y,mode);
					else OLED_DrawPoint(x,y,!mode);
					temp>>=1;
					y++;
				}
				x++;
				if((x-x0)==sizex)
				{
					x=x0;
					y0=y0+8;
				}
				y=y0;
		}
	 }
}

#if OLED_COMMUNICATION_MODE == IIC
void OLED_WR_Byte(u8 dat,u8 cmd)
{	
	u8 i;			  
	if(cmd)
	  OLED_DC_Set();
	else 
	  OLED_DC_Clr();		  
	OLED_CS_Clr();
	for(i=0;i<8;i++)
	{			  
		OLED_SCL_Clr();
		if(dat&0x80)
		   OLED_SDA_Set();
		else 
		   OLED_SDA_Clr();
		OLED_SCL_Set();
		dat<<=1;   
	}				 		  
	OLED_CS_Set();
	OLED_DC_Set();   	  
}

//OLED的初始化
void OLED_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);	 //使能A端口时钟
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|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);	  //初始化GPIOA
 	GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_15);
	
	OLED_RES_Clr();
	delay_ms(200);
	OLED_RES_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_Clear();
	OLED_WR_Byte(0xAF,OLED_CMD);
}

#elif OLED_COMMUNICATION_MODE == SPI
void OLED_WR_Byte(u8 dat,u8 cmd)
{	
	u8 i;			  
	if(cmd)
	  OLED_DC_Set();
	else 
	  OLED_DC_Clr();		  
	OLED_CS_Clr();
	for(i=0;i<8;i++)
	{			  
		OLED_SCL_Clr();
		if(dat&0x80)
		   OLED_SDA_Set();
		else 
		   OLED_SDA_Clr();
		OLED_SCL_Set();
		dat<<=1;   
	}				 		  
	OLED_CS_Set();
	OLED_DC_Set();   	  
}

//OLED的初始化
void OLED_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);	 //使能A端口时钟
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|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);	  //初始化GPIOA
 	GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_15);
	
	OLED_RES_Clr();
	delay_ms(200);
	OLED_RES_Set();
	
	OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel 		关闭OLED面板
	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) 设置起始行地址 设置映射 RAM 显示起始行(0x00~0x3F)
	OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register    设置对比度控制寄存器
	OLED_WR_Byte(0xCF,OLED_CMD);//--Set SEG Output Current Brightness 设置 SEG 输出电流亮度
	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) 设置多路复用比(1 至 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) 设置显示偏移移映射 RAM 计数器 (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 设置分频比,将时钟设置为 100 帧/秒
	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 将预充电设置为15个时钟,将放电设置为1个时钟
	OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration 设置 COM 引脚硬件配置
	OLED_WR_Byte(0x12,OLED_CMD);
	OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh 设置 VCOMH
	OLED_WR_Byte(0x40,OLED_CMD);//--Set VCOM Deselect Level 设置 VCOM 取消选择级别
	OLED_WR_Byte(0x20,OLED_CMD);//--Set Page Addressing Mode (0x00/0x01/0x02) 设置页面寻址模式 (0x00/0x01/0x02)
	OLED_WR_Byte(0x02,OLED_CMD);//
	OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable 设置电荷泵启用(0X14)/禁用(0X10)
	OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable 
	OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5) 禁用整个显示屏开启 (0xa4/0xa5)
	OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 禁用反向显示开启 (0xa6/a7)
	OLED_Clear();
	OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel 		开启OLED面板
}
#else
	#error "OLED_COMMUNICATION_MODE" is no define, Please define OLED_COMMUNICATION_MODE to IIC or SPI!
#endif

SPI(Serial Peripheral Interface)是一种同步的串行通信协议,常用于微处理器和外围设备之间的通信。下面是一个简单的SPI通讯协议代码示例,以向AD5541写入数据为例: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/spi/spidev.h> #define SPI_DEVICE "/dev/spidev0.0" #define SPI_MODE SPI_MODE_0 #define SPI_BITS_PER_WORD 8 #define SPI_SPEED_HZ 1000000 int main(int argc, char *argv[]) { int spi_fd, ret; unsigned char tx_buf[3]; // 打开SPI设备 spi_fd = open(SPI_DEVICE, O_RDWR); if (spi_fd < 0) { printf("Failed to open SPI device!\n"); return -1; } // SPI模式设置 ret = ioctl(spi_fd, SPI_IOC_WR_MODE, &SPI_MODE); if (ret == -1) { printf("Failed to set SPI mode!\n"); return -1; } // SPI字长设置 ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &SPI_BITS_PER_WORD); if (ret == -1) { printf("Failed to set SPI bits per word!\n"); return -1; } // SPI时钟频率设置 ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED_HZ); if (ret == -1) { printf("Failed to set SPI speed!\n"); return -1; } // 向AD5541写入数据 tx_buf[0] = 0x01; // 控制字节 tx_buf[1] = 0x12; // 数据高8位 tx_buf[2] = 0x34; // 数据低8位 ret = write(spi_fd, tx_buf, 3); if (ret == -1) { printf("Failed to write to SPI device!\n"); return -1; } // 关闭SPI设备 close(spi_fd); return 0; } ``` 在以上代码中,需要根据实际情况修改SPI_DEVICE、SPI_MODE、SPI_BITS_PER_WORD和SPI_SPEED_HZ这四个参数,以及向AD5541写入数据的内容。在SPI通讯中,通常需要在每个传输周期中发送一个控制字节(用于指定读写、寄存器地址等),然后再发送相应的数据。传输的数据是8位字节,可以通过SPI_IOC_WR_BITS_PER_WORD来设置字长。SPI_IOC_WR_MAX_SPEED_HZ则用于设置时钟频率,以控制数据传输速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IoT_H2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值