一、SPI

1.1 简介

SPI英文全称Serial Peripheral Interface,即串行外围设备接口,是一种高速、全双工同步串行通信总线。

1.2 信号线

  • SCK(Serial Clock):串行时钟线,由主设备产生,用于同步数据传输。
  • MOSI(Master Output Slave Input):主机输出从机输入线,主设备通过这条线发送数据给从设备。
  • MISO(Master Input Slave Output):主机输入从机输出线,主设备通过这条线接收从设备发送的数据。
  • SS(Slave Select):从机选择线(每个从机一根选择线),用于选择与主设备进行通信的从设备。通常情况下,SS线为低电平有效,即当SS线为低电平时,选中对应的从设备进行通信。

image-20250305140335105

1.3 开始与结束

当SS从高电平拉低到低电平,这个算是起始的一个时序。而SS从低电平拉高到高电平,就算是结束的时序。

img

1.4 发送和接收字节

发送接收字节看似是两个时序,但是在SPI中却是同一个时序,因为SPI的机制是==我们发送一个字节,并且接收一个字节==(哪怕我们并不需要接收数据)。反过来看也可以是我们接收一个字节,并且发送一个字节(哪怕这个字节是无用的数据)。

image-20250305140438435

img

交换bit

img

在SCK上升沿的时候,移出MOSI的数据,在SCK下降沿的时候读取MISO的数据。

需要在SCK上升沿之前把需要发送的数据位放置在MOSI线上

SCK下降沿的时候马上读取MISO线上的数据位

(实际上下降沿和读取应该是同时的,但是我们软件模拟没法同时,但是效果是一样的)

1.6 不同模式下的通信

image-20250305141745720

image-20250305141814533

image-20250305141718015

image-20250305141639698

image-20250305141859718

image-20250305142011451

image-20250305142031698

二、TFT-LCD

image-20250305142759302

引脚 功能
GND 接地
VCC 供电(3.3V-5V)
TP_INT 接3v3上拉,
TP_SDA TP触摸芯片数据引脚
TP_SCL TP触摸芯片时钟引脚
LCD_RST 低电平TFT复位
LCD_MOSI 数据输入引脚
LCD_CLK LCD时钟引脚
LCD_CS 片选信号,低电平使能
LCD_DC 区分接受的是数据还是命令(低电平命令,高电平数据)
LCD_BLK 背光(可常接3.3V)

HAL_SPI_Transmit()

1
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

**SPI_HandleTypeDef \*hspi**:

  • 指向 SPI 句柄 的指针。这个句柄包含了关于 SPI 外设的配置信息,必须在使用之前通过 HAL_SPI_Init() 初始化。

**uint8_t \*pData**:

  • 指向要发送的数据的指针。pData 是一个数据缓冲区,包含要通过 SPI 发送的数据。数据以字节的形式存储。

**uint16_t Size**:

  • 发送数据的大小,以字节为单位。Size 表示要发送的字节数。

**uint32_t Timeout**:

  • 传输操作的超时时间,以毫秒为单位。如果在指定的时间内传输未完成,函数将返回超时错误。

LCD_GPIO_Init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void LCD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};

__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitStructure.Pin = BLK_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//速度50MHz
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA
HAL_GPIO_WritePin(GPIOA, BLK_PIN, GPIO_PIN_SET);

GPIO_InitStructure.Pin = RES_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//速度50MHz
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOB
HAL_GPIO_WritePin(GPIOB, RES_PIN, GPIO_PIN_SET);

GPIO_InitStructure.Pin = DC_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//速度50MHz
HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC
HAL_GPIO_WritePin(GPIOC, DC_PIN, GPIO_PIN_SET);

GPIO_InitStructure.Pin = CS_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//速度50MHz
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); //初始化GPIOD
HAL_GPIO_WritePin(GPIOD, CS_PIN, GPIO_PIN_SET);

}

写一个字节数据 LCD_Writ_Bus()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void LCD_Writ_Bus(u8 dat) 
{
//hard SPI
HAL_SPI_Transmit(&hspi1,&dat,1,1);

//soft SPI
/*
u8 i;
for(i=0;i<8;i++)
{
LCD_SCLK_Clr();
if(dat&0x80)
{
LCD_MOSI_Set();
}
else
{
LCD_MOSI_Clr();
}
LCD_SCLK_Set();
dat<<=1;
}
*/
}

写两个字节数据 LCD_WR_DATA()

1
2
3
4
5
6
7
8
9
10
void LCD_WR_DATA(u16 dat)
{
// LCD_Writ_Bus(dat>>8);
// LCD_Writ_Bus(dat);
uint8_t temp[2];
temp[0]=(dat>>8)&0xff;
temp[1]=dat&0xff;
HAL_SPI_Transmit(&hspi1,temp,2,1);

}

写命令 LCD_WR_REG()

1
2
3
4
5
6
void LCD_WR_REG(u8 dat)
{
LCD_DC_Clr();//写命令
LCD_Writ_Bus(dat);
LCD_DC_Set();//写数据
}

设置起始和初始地址 LCD_Address_Set()

1
2
3
4
5
6
7
8
9
10
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1);
LCD_WR_DATA(x2);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1);
LCD_WR_DATA(y2);
LCD_WR_REG(0x2c);//储存器写
}

设置显示区域:该函数通过设置列地址和行地址,定义了一个矩形区域 (x1, y1)(x2, y2),然后准备向该区域写入图像数据或颜色。

显示区域绘制:在调用 LCD_Address_Set() 后,LCD 屏幕的指定区域就被设置好了,之后可以通过其他函数(例如 LCD_WriteColor() 或类似的函数)向该区域写入具体的颜色或图像数据,从而实现绘制操作。

LCD常用函数

1.LCD_Fill()区域颜色填充

1
2
3
4
5
6
7
8
9
10
11
12
void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color)
{
u16 i,j;
LCD_Address_Set(xsta+OFFSET_X,ysta+OFFSET_Y,xend+OFFSET_X-1,yend-1+OFFSET_Y);//设置显示范围
for(i=ysta;i<yend;i++)
{
for(j=xsta;j<xend;j++)
{
LCD_WR_DATA(color);
}
}
}

2.指定位置画点LCD_DrawPoint()

1
2
3
4
5
void LCD_DrawPoint(u16 x,u16 y,u16 color)
{
LCD_Address_Set(x,y,x,y);//设置光标位置
LCD_WR_DATA(color);
}

3.画线LCD_DrawLine()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color)
{
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_y;}
if(delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴
else distance=delta_y;
for(t=0;t<distance+1;t++)
{
LCD_DrawPoint(uRow,uCol,color);//画点
xerr+=delta_x;
yerr+=delta_y;
if(xerr>distance)
{
xerr-=distance;
uRow+=incx;
}
if(yerr>distance)
{
yerr-=distance;
uCol+=incy;
}
}
}

4.画矩形LCD_DrawRectangle()

1
2
3
4
5
6
7
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2,u16 color)
{
LCD_DrawLine(x1,y1,x2,y1,color);
LCD_DrawLine(x1,y1,x1,y2,color);
LCD_DrawLine(x1,y2,x2,y2,color);
LCD_DrawLine(x2,y1,x2,y2,color);
}

5.画圆Draw_Circle()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Draw_Circle(u16 x0,u16 y0,u8 r,u16 color)
{
int a,b;
a=0;b=r;
while(a<=b)
{
LCD_DrawPoint(x0-b,y0-a,color); //3
LCD_DrawPoint(x0+b,y0-a,color); //0
LCD_DrawPoint(x0-a,y0+b,color); //1
LCD_DrawPoint(x0-a,y0-b,color); //2
LCD_DrawPoint(x0+b,y0+a,color); //4
LCD_DrawPoint(x0+a,y0-b,color); //5
LCD_DrawPoint(x0+a,y0+b,color); //6
LCD_DrawPoint(x0-b,y0+a,color); //7
a++;
if((a*a+b*b)>(r*r))//判断要画的点是否过远
{
b--;
}
}
}

6.写汉字LCD_ShowChinese()

1
2
3
4
5
6
7
8
9
10
11
12
13
void LCD_ShowChinese(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
while(*s!=0)
{
if(sizey==12) LCD_ShowChinese12x12(x,y,s,fc,bc,sizey,mode);
else if(sizey==16) LCD_ShowChinese16x16(x,y,s,fc,bc,sizey,mode);
else if(sizey==24) LCD_ShowChinese24x24(x,y,s,fc,bc,sizey,mode);
else if(sizey==32) LCD_ShowChinese32x32(x,y,s,fc,bc,sizey,mode);
else return;
s+=2;
x+=sizey;
}
}

入口数据:x,y显示坐标

​ *s 要显示的汉字串

​ fc 字的颜色

​ bc 字的背景色

​ sizey 字号 可选 16 24 32

​ mode: 0非叠加模式 1叠加模式

7.显示单个字符LCD_ShowChar()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void LCD_ShowChar(u16 x,u16 y,u8 num,u16 fc,u16 bc,u8 sizey,u8 mode)
{
u8 temp,sizex,t,m=0;
u16 i,TypefaceNum;//一个字符所占字节大小
u16 x0=x;
sizex=sizey/2;
TypefaceNum=(sizex/8+((sizex%8)?1:0))*sizey;
num=num-' '; //得到偏移后的值
LCD_Address_Set(x,y,x+sizex-1,y+sizey-1); //设置光标位置
for(i=0;i<TypefaceNum;i++)
{
if(sizey==12)temp=ascii_1206[num][i]; //调用6x12字体
else if(sizey==16)temp=ascii_1608[num][i]; //调用8x16字体
else if(sizey==24)temp=ascii_2412[num][i]; //调用12x24字体
else if(sizey==32)temp=ascii_3216[num][i]; //调用16x32字体
else return;
for(t=0;t<8;t++)
{
if(!mode)//非叠加模式
{
if(temp&(0x01<<t))LCD_WR_DATA(fc);
else LCD_WR_DATA(bc);
m++;
if(m%sizex==0)
{
m=0;
break;
}
}
else//叠加模式
{
if(temp&(0x01<<t))LCD_DrawPoint(x,y,fc);//画一个点
x++;
if((x-x0)==sizex)
{
x=x0;
y++;
break;
}
}
}
}
}

8.显示字符串LCD_ShowString()

1
2
3
4
5
6
7
8
9
void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 fc,u16 bc,u8 sizey,u8 mode)
{
while(*p!='\0')
{
LCD_ShowChar(x,y,*p,fc,bc,sizey,mode);
x+=sizey/2;
p++;
}
}

9.显示数字mypow()

1
2
3
4
5
6
u32 mypow(u8 m,u8 n)
{
u32 result=1;
while(n--)result*=m;
return result;
}

10.显示整数变量LCD_ShowIntNum()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void LCD_ShowIntNum(u16 x,u16 y,u16 num,u8 len,u16 fc,u16 bc,u8 sizey)
{
u8 t,temp;
u8 enshow=0;
u8 sizex=sizey/2;
for(t=0;t<len;t++)
{
temp=(num/mypow(10,len-t-1))%10;
if(enshow==0&&t<(len-1))
{
if(temp==0)
{
LCD_ShowChar(x+t*sizex,y,' ',fc,bc,sizey,0);
continue;
}else enshow=1;

}
LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
}
}

11.显示两位小数变量LCD_ShowFloatNum1()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void LCD_ShowFloatNum1(u16 x,u16 y,float num,u8 len,u16 fc,u16 bc,u8 sizey)
{
u8 t,temp,sizex;
u16 num1;
sizex=sizey/2;
num1=num*100;
for(t=0;t<len;t++)
{
temp=(num1/mypow(10,len-t-1))%10;
if(t==(len-2))
{
LCD_ShowChar(x+(len-2)*sizex,y,'.',fc,bc,sizey,0);
t++;
len+=1;
}
LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
}
}

12.显示图片LCD_ShowPicture()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void LCD_ShowPicture(u16 x,u16 y,u16 length,u16 width,const u8 pic[])
{
u16 i,j;
u32 k=0;
LCD_Address_Set(x,y,x+length-1,y+width-1);
for(i=0;i<length;i++)
{
for(j=0;j<width;j++)
{
LCD_WR_DATA8(pic[k*2]);
LCD_WR_DATA8(pic[k*2+1]);
k++;
}
}
}