基于stm32 标准库spi驱动st7789(使用DMA)

DMA

DMA 直接内存搬运技术,使数据不经过cpu,直接从内存搬运到spi的发送的寄存器里面,这样做的好处是减少cpu的负担,而且能大大提升显示屏的刷新速率

使用spi直接驱动ST7789显示屏

最开始我是用spi直接驱动 显示屏幕,但我发现即使是使用spi的最大频率发送数据,刷屏的速率依旧很慢

代码:

#include "delay.h"
#include "sys.h"
#include "st7789.h"
 int main(void)
 { 
	 delay_init();	    	 //ÑÓʱº¯Êý³õʼ»¯	  
	 initlcd();
	 while(1){
	 fillScreen(0xf800);
   //delay_us(100);
	 fillScreen(0);
	// delay_us(100);
	 }
}

<st7789.h>

#include "sys.h" 
#define DC    PBout(11) //DC	  
void initlcd();
void writeData(u8 data);
void writeCommand(u8 data);
void fillScreen(u16 color);
void SPI1_Init(void);
void SPI1_SetSpeed(u8 SpeedSet);   
u8 SPI1_ReadWriteByte(u8 TxData);

<st7789.c>


#include "st7789.h"
#include "delay.h"

SPI_InitTypeDef  SPI_InitStructure;
//spi1的初始化
void SPI1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
    
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE );	
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //¸´ÓÃÍÆÍìÊä³ö
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

 	GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//
	SPI_Init(SPI1, &SPI_InitStructure);  //
 
	SPI_Cmd(SPI1, ENABLE); //
	
	SPI1_ReadWriteByte(0xff);//	 
}   

 //设置spi的传输速率
void SPI1_SetSpeed(u8 SpeedSet)
{
	SPI_InitStructure.SPI_BaudRatePrescaler = SpeedSet ;
  	SPI_Init(SPI1, &SPI_InitStructure);
	SPI_Cmd(SPI1,ENABLE);
} 
//spi 读写
u8 SPI1_ReadWriteByte(u8 TxData)
{		
	u8 retry=0;				 	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //
		{
		retry++;
		if(retry>200)return 0;
		}			  
	SPI_I2S_SendData(SPI1, TxData); 
	retry=0;

	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//
		{
		retry++;
		if(retry>200)return 0;
		}	  						    
	return SPI_I2S_ReceiveData(SPI1); //				    
}

//初始化显示屏
void initlcd(){
 	RCC->APB2ENR|=1<<3;//时钟使能							 
	GPIOB->CRH&=0XFFFF0FFF;//设置为输出模式
	GPIOB->CRH|=0X00003000;
	GPIOB->ODR|=1<11;     //dc high

    SPI1_Init();
	SPI1_SetSpeed(SPI_BaudRatePrescaler_2);	
	
	
    writeCommand(0x01);
	delay_us(150);
	writeCommand(0x11);
	delay_us(120);
	writeCommand(0x3A);
	writeData(0x55);
	writeCommand(0x36);
	writeData(0x00);
	writeCommand(0x21);
	writeCommand(0x13);
	writeCommand(0x29);
	
}
void writeData(u8 data){
   DC = 1;
   SPI1_ReadWriteByte(data);
   
}

void writeCommand(u8 cmd){
	 DC = 0;
   SPI1_ReadWriteByte(cmd);
}

void fillScreen(u16 color){
  u16 i ,j;
  writeCommand(0x2A);
  writeData(0);
  writeData(0);
  writeData(0);
  writeData(240);
  writeCommand(0X2B);
  writeData(0);
  writeData(0);
  writeData(0X01);
  writeData(0X40);
  writeCommand(0X2C);
  for(i = 0 ; i<240 ; i++){
	  for(j = 0 ; j<320 ; j++){
	       writeData(color>>8);  
          writeData(color);			
	}
	}
}

结果

刷新频率约 1秒3帧
在这里插入图片描述

使用spi加dma驱动ST7789显示屏

在使dma搬运数据后刷新速率有了明显的提升

代码

#include "delay.h"
#include "sys.h"
#include "st7789.h"   	 
 int main(void)
 { 
	 delay_init();	    	 	  
	 initlcd();
	 while(1){
	   fillScreen(0xf800);
	   fillScreen(0xffff);
	 }
}

<st7789.h>

#include "sys.h"	 
#define DC    PBout(11) //DC	  
void initlcd();
void writeData(u8 data);
void writeCommand(u8 data);
void fillScreen(u16 color);
void SPI1_Init(void);	
void SPI1_SetSpeed(u8 SpeedSet);   
u8 SPI1_ReadWriteByte(u8 TxData);
void MYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);

<st7789.c>

#include "st7789.h"
#include "delay.h"
#include "sys.h"
u8 SendBuff[480];
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;    
//配置dma
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
 	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//ʹÄÜDMA´«Êä
	
    DMA_DeInit(DMA_CHx);   //将dma1的某通道
	DMA1_MEM_LEN=cndtr;
	DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //dma 要搬运到的外设地址
	DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //dma要搬运的内存的地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //搬运方向, 从内存到外设
	DMA_InitStructure.DMA_BufferSize = cndtr; //要搬运的内存的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //  传输过程中外设的基地址不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //传输过程中内存地址递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为八位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//数据宽度为八位 
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //正常传输模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级设置
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //没有内存到内存的传输
	DMA_Init(DMA_CHx, &DMA_InitStructure);  //
	  	
} 
//使能dma1的通道3,因为spi输出对应的是此通道
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
	DMA_Cmd(DMA_CHx, DISABLE );    
 	DMA_SetCurrDataCounter(DMA1_Channel3,DMA1_MEM_LEN);
 	DMA_Cmd(DMA_CHx, ENABLE);  
}	  

 


SPI_InitTypeDef  SPI_InitStructure;
//spi1的初始化
void SPI1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
  
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE );	
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

 	GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_CRCPolynomial = 7;	
	SPI_Init(SPI1, &SPI_InitStructure); ´æÆ÷
 
	SPI_Cmd(SPI1, ENABLE); 
	
	SPI1_ReadWriteByte(0xff);	 
}   
  
void SPI1_SetSpeed(u8 SpeedSet)
{
	SPI_InitStructure.SPI_BaudRatePrescaler = SpeedSet ;
  	SPI_Init(SPI1, &SPI_InitStructure);
	SPI_Cmd(SPI1,ENABLE);
} 


u8 SPI1_ReadWriteByte(u8 TxData)
{		
	u8 retry=0;				 	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //
		{
		retry++;
		if(retry>200)return 0;
		}			  
	SPI_I2S_SendData(SPI1, TxData); 
	retry=0;

	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//
		{
		retry++;
		if(retry>200)return 0;
		}	  						    
	return SPI_I2S_ReceiveData(SPI1); 				    
}


void initlcd(){

 	RCC->APB2ENR|=1<<3;//?????IO PORTC?? 							 
	GPIOB->CRH&=0XFFFF0FFF;//PC11/12 
	GPIOB->CRH|=0X00003000;
	GPIOB->ODR|=1<11;     //PC11,12 ???
  
  SPI1_Init();
	SPI1_SetSpeed(SPI_BaudRatePrescaler_2);	
	//配置dma
	MYDMA_Config(DMA1_Channel3,(u32)&SPI1->DR,(u32)SendBuff,480);
	
  writeCommand(0x01);
	delay_us(150);
	writeCommand(0x11);
	delay_us(120);
	writeCommand(0x3A);
	writeData(0x55);
	writeCommand(0x36);
	writeData(0x00);
	writeCommand(0x21);
	writeCommand(0x13);
	writeCommand(0x29);
	
}
void writeData(u8 data){
   DC = 1;
   SPI1_ReadWriteByte(data);
   
}

void writeCommand(u8 cmd){
	 DC = 0;
   SPI1_ReadWriteByte(cmd);
}

void fillScreen(u16 color){
	u16 i ,j;
	//DC = 0;
  writeCommand(0x2A);
  writeData(0);
  writeData(0);
	writeData(0);
	writeData(240);
  writeCommand(0X2B);
  writeData(0);
  writeData(0);
	writeData(0X01);
	writeData(0X40);
  writeCommand(0X2C);
	DC = 1;
	for(j=0 ;j<480;){
      SendBuff[j] = color>>8;
		  SendBuff[j+1] = color;
		  j += 2;
	}

  for(i = 0 ; i<320 ; i++){
		
		  SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,ENABLE); //????1?DMA?? 
	      MYDMA_Enable(DMA1_Channel3);
		
  while(1){
		if(DMA_GetFlagStatus(DMA1_FLAG_TC3)!=RESET)//µÈ´ýͨµÀ4´«ÊäÍê³É
				{
					DMA_ClearFlag(DMA1_FLAG_TC3);//Çå³ýͨµÀ4´«ÊäÍê³É±êÖ¾
					break; 
		        }
					}			
	
	}

}

结果

在这里插入图片描述
刷新速率约一秒十多帧

连线

DC ------------ PB11
CLK----------- PA5
MISO--------- PA6
MOSI--------- PA7
CS------------ GND

引用

dma和spi部分参考正点原子代码,示例使用的主控芯片是stm32f103

### STM32 标准库 SPI 使用 DMA 示例代码及教程 #### 关于STM32标准库使用SPIDMA配合的重要性 对于嵌入式系统设计而言,利用DMA(直接存储器访问)控制器可以显著提高数据传输效率并减轻CPU负载。当涉及到如SPI这样的串行外设接口时,结合DMA进行数据交换能够确保更快速度的数据处理以及更低功耗的操作模式[^1]。 #### 实现高效稳定的SPI+DMA全双工通信实例分析 针对具体应用场景——例如在STM32F407芯片上构建一个支持全双工操作的SPI从设备——可以通过配置DMA通道来同步完成接收和发送任务。这种做法不仅简化了编程逻辑,还增强了系统的实时性能表现[^3]。 #### 基于STM32标准库实现SPI驱动ST7789显示屏案例解析 为了进一步说明如何运用DMA优化显示更新过程,在另一个实际例子中展示了怎样借助DMA特性加速向ST7789屏幕推送图像帧的过程。这里的关键在于设置好相应的硬件参数之后,让DMA负责将缓冲区内的像素值自动转移到SPI模块用于后续渲染工作,从而减少了软件层面干预的需求[^4]。 以下是基于上述原理的一个简单C语言程序片段,它演示了如何初始化SPI总线并通过DMA机制执行连续性的读/写命令: ```c #include "stm32f4xx_hal.h" // 定义全局变量 extern SPI_HandleTypeDef hspi1; uint8_t txBuffer[64]; /* 发送缓冲 */ uint8_t rxBuffer[64]; /* 接收缓冲 */ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){ GPIO_InitTypeDef GPIO_InitStruct = {0}; if(spiHandle->Instance==SPI1){ __HAL_RCC_GPIOA_CLK_ENABLE(); /**SPI1 GPIO Configuration PA5 ------> SPI1_SCK PA6 ------> SPI1_MISO PA7 ------> SPI1_MOSI */ GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置DMA流... } } int main(void){ // 初始化系统... // 启动DMA传输前准备数据... memset(txBuffer, 0xAA, sizeof(txBuffer)); // 开始DMA传输... HAL_StatusTypeDef status = HAL_OK; status = HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)txBuffer,(uint8_t *)rxBuffer,sizeof(txBuffer)); while(HAL_GetTick() < 1000); // 等待一段时间 // 结束后的清理工作... } ``` 这段代码主要完成了以下几个方面的工作: - 设置了必要的GPIO引脚作为SPI信号线路; - 准备了一组测试用的数据集`txBuffer`用来模拟真实环境中可能遇到的信息传递情况; - 调用了`HAL_SPI_TransmitReceive_DMA()`函数启动一次完整的双向DMA传送流程;
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值