系列文章目录
STM32F103RCT6+0.99寸TFT圆屏 硬件SPI显示(初级)
STM32F103RCT6+0.99寸TFT圆屏 硬件SPI+DMA显示(中级)
STM32F103RCT6+0.99寸TFT圆屏 硬件SPI+外部FLASH显示(高级)
前言
上一篇文章使用软件SPI成功显示,但是这个仅限于新手理解操作,只能显示静态图片,不能显示速度快的动态图片,动态显示效果不好,所以在上篇的基础,本文章将软件SPI改成硬件SPI(不知道的可以去看我上一篇的STM32F103RCT6+0.99寸TFT圆屏 软件SPI显示(初级)_中景园tft屏幕-CSDN博客),传输速度提高一倍,再采用DMA传输,速度再次提高一倍。
一、DMA简介
DMA,Direct Memory Access—直接存储器存取,也称为成组数据传送,也称为直接内存操作。DMA在数据传送过程中,没有保存现场、恢复现场之类的工作,主要功能是用来搬数据,但是不需要占用CPU, 即在传输数据的时候,CPU可以干其他的事情,类似多线程。
由于CPU根本不参加传送操作,因此就省去了CPU取指令、取数、送数等操作。内存地址修改、传送字 个数的计数等等,也不是由软件实现,而是用硬件线路直接实现的。所以DMA方式能满足高速I/O设备的要求,也有利于CPU效率的发挥。
数据传输支持从外设到存储器或者存储器到存储器, 这里的存储器可以是SRAM或者是FLASH。DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,DMA2有5个通道,DMA2只存在于大容量产品和互联型产品中。
想要使用DMA传输数据,必须先DMA请求。
下面是DMA请求映像表:
DMA传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。
举个例子,你可以理解DMA为外援,当你有搬砖、跑步任务时,你不可能同时做这两个任务,你可以叫外援帮你完成搬砖任务,你只需要外援告诉你搬了多少砖、完成进度等就行,然后你去完成跑步任务就行,这跟软件的多线程类似,但是区别就是DMA相当于两个人同时干两件事,而多线程相当于一个人分时干两件事,分时时间越短,就容易让人以为一个人同时干两件事。
二、SPI简介
上一篇文章,忘记介绍SPI,这一篇补上。SPI(Serial Peripheral Interface)是一种同步串行通信接口(高速全双工的通信总线),通常用于电子设备中微控制器与外围设备之间的通信。该接口具有高速通信的特性,常用于嵌入式系统中与传感器、显示器、存储芯片等组件进行通信。
STM32的SPI接口通常包括主、从模式、全双工通信以及多主机支持等特性。使用SPI接口可以实现快速的数据传输,适用于需要高速数据交换的应用场景。
SPI通讯设备之间的常用连接方式如图:
SPI连接只需要4根线,分别是SCK、MOSI、MISO,片选线为SS。SS ( Slave Select):从设备选择信号线,常称为片选信号线,也称为NSS、CS;SCK (Serial Clock):时钟信号线,用于通讯数据同步,它由通讯主机产生,决定了通讯的速率; MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚;MISO (Master Input,,Slave Output):主设备输入/从设备输出引脚。如图所示:
一个主机可以挂载多个从机,通过片选SS选择不同从机传输数据。
SPI通讯时序如下图(SPOL=0,SPHA=1):
SPI通信有4种不同的操作模式如下图所示:
(时钟极性) CPOL=0,SCK的空闲状态为低电平;CPOL=1,SCK的空闲状态为高电平。
(时钟相位)CPHA=0,在SCK的第一个跳变沿(上升或下降)数据被采样; CPHA=1,在SCK的第二个跳变沿(上升或下降)数据被采样。SPI 主设备与从设备通信的时钟相位和极性应该一致。
三、硬件设计
引脚改了,改成主控芯片的硬件SPI脚,STM32F103RCT6与0.99寸TFT圆屏硬件连接图如下:
STM32F103RCT6 | 0.99寸TFT圆屏 |
---|---|
3.3V | VCC |
GND | GND |
PB5 | SDA3_MOSI |
PB3 | SCL3_SCLK |
PC13 | RES1 |
PC14 | DC1 |
PB9 | CS1_L |
PC15 | BLK1 |
将图片转成二进制数组步骤可以参考STM32F103RCT6+0.99寸TFT圆屏 软件SPI显示(初级)_中景园tft屏幕-CSDN博客
,这里不再重复论述。
四、软件解析
采用的是硬件SPI,并用了DMA,所以速度特别快,本程序是鉴于中景园的程序上修改。这里按.c文件介绍。
①lcd_init
先配置TFT屏的引脚,按上面讲的硬件设计对应配置:
void LCD1_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, ENABLE); //使能A端口时钟
//RES1、DC1、BLK1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
//CS1_L、CS1_R
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9);
}
②spi.c
配置硬件SPI
void SPI3_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);//使能GPIOA
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //改变指定管脚的映射,JTAG的其他引脚PA15 PB3、PB4有其他用途,设置关闭JTAG部分功能,保留SWD功能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);//使能SPI1
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_3|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOA
GPIO_SetBits(GPIOB,GPIO_Pin_3|GPIO_Pin_5);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;//只发送模式
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置SPI工作模式:主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//设置SPI数据大小:8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟空闲时SCLK位高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//串行同步时钟空第二个时钟沿捕获
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS信号由软件管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;//波特率预分频值:波特率预分频值为4
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//数据传输高位先行
SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式
SPI_Init(SPI3,&SPI_InitStructure);//初始化SPI
SPI_Cmd(SPI3, ENABLE);//使能SPI
}
③dma.c
配置SPI的DMA,可以查看DMA请求映像表,SPI3对应的DMA是DMA2通道2,所以要配置DMA2通道2,不过要分成两种,MYDMA2_Config1是直接全部数据发送,用于发送单一数据量,比如发送单一颜色的数据,在这里用于显示单个颜色;MYDMA2_Config是边发送边传输,用于发送不同数据量,比如不同发送不同颜色的数据,在这里用于同时显示多个颜色,即显示图片。
/************************DMA2***************************/
u16 DMA2_MEM_LEN;//保存DMA每次数据传送的长度
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MYDMA2_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE); //使能DMA传输
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
DMA2_MEM_LEN=cndtr;
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
}
void MYDMA2_Config1(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE); //使能DMA传输
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
DMA2_MEM_LEN=cndtr;
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //内存地址寄存器不变
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
}
//开启一次DMA传输
void MYDMA2_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_Cmd(DMA_CHx, DISABLE );
DMA_SetCurrDataCounter(DMA2_Channel2,DMA2_MEM_LEN);
DMA_Cmd(DMA_CHx, ENABLE);
}
③lcd1.c
lcd1.c主要是0.99寸TFT屏显示程序,字符、字符串、中文字符、数字、图片等显示程序,这里就不一 一介绍了。
在指定区域填充颜色函数:这里用到MYDMA2_Config1来配置
/******************************************************************************
函数说明:在指定区域填充颜色
入口数据:xsta,ysta 起始坐标
xend,yend 终止坐标
color 要填充的颜色
返回值: 无
******************************************************************************/
void LCD1_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color)
{
u16 color1[1];
u16 num;
color1[0]=color;
num=(xend-xsta)*(yend-ysta);
LCD1_Address_Set(xsta,ysta,xend-1,yend-1);//设置显示范围
LCD_CS1_L_Clr();
LCD_CS1_R_Clr();
SPI3->CR1|=1<<11;//设置SPI16位传输模式
SPI_Cmd(SPI3, ENABLE);//使能SPI
MYDMA2_Config1(DMA2_Channel2,(u32)&SPI3->DR,(u32)color1,num);
SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Tx,ENABLE);
MYDMA2_Enable(DMA2_Channel2);
while(1)
{
if(DMA_GetFlagStatus(DMA2_FLAG_TC2)!=RESET)//等待通道4传输完成
{
DMA_ClearFlag(DMA2_FLAG_TC2);//清除通道3传输完成标志
break;
}
}
LCD_CS1_L_Set();
LCD_CS1_R_Set();
SPI3->CR1=~SPI3->CR1;
SPI3->CR1|=1<<11;
SPI3->CR1=~SPI3->CR1;//设置SPI8位传输模式
SPI_Cmd(SPI3, ENABLE);//使能SPI
}
显示图片函数:这里用到MYDMA2_Config来配置
/******************************************************************************
函数说明:显示图片
入口数据:x,y起点坐标
length 图片长度
width 图片宽度
pic[] 图片数组
返回值: 无
******************************************************************************/
void LCD1_ShowPicture(u16 x,u16 y,u16 length,u16 width,const u8 pic[])
{
u16 num;
num=length*width*2;
LCD1_Address_Set(x,y,x+length-1,y+width-1);
LCD_CS1_L_Clr();
LCD_CS1_R_Clr();
MYDMA2_Config(DMA2_Channel2,(u32)&SPI3->DR,(u32)pic,num);
SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Tx,ENABLE);
MYDMA2_Enable(DMA2_Channel2);
while(1)
{
if(DMA_GetFlagStatus(DMA2_FLAG_TC2)!=RESET)//等待通道4传输完成
{
DMA_ClearFlag(DMA2_FLAG_TC2);//清除通道3传输完成标志
break;
}
}
LCD_CS1_L_Set();
LCD_CS1_R_Set();
}
④main.c
主要是初始化LCD显示函数,动态显示眼睛。图片也是转成二进制数组,把数组用DMA发给LCD,即可显示图片,添加了延时,不然显示太快了,主要是保证眼睛的显示效果。
最终显示效果如下:
总结
通过和上一篇的通过软件SPI对比,肉眼看不出一帧一帧的显示图片了,软件SPI能明显感觉得到是一帧一帧的显示图片,硬件SPI+DMA显示图片很流畅,适合动态显示图片。
各位如果觉得有不足之处或者有疑问,可以在评论上提建议和讨论,有空的话,会解答和修改不足之处。
代码链接如下:
https://download.csdn.net/download/weixin_45534031/89599085?spm=1001.2014.3001.5503