手头有一种HX8347主控的屏幕,有RGB和3线SPI接口,这块屏原本的设计是SPI是配置接口,RGB接口是MCU驱动接口。但是RGB接线太多,用起来还是比较复杂。SPI接口尽管是配置接口,也可以用来驱动屏幕。不过是3线SPI,缺少DC引脚,DC需要用发送数据的方式解决。
根据HX8347的手册
就是在发送数据前,需要先发送一个字节,如发送0×70,表示后面跟的是指令;发送0×72,表示后面跟的是数据。
一、SPI配置
apm32f4xx_spi_cfg.c
void DAL_SPI_Config(void)
{
/* Configures SPI1 */
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7U;
if (DAL_SPI_Init(&hspi1) != DAL_OK)
{
DAL_ErrorHandler();
}
}
SPI1设置为SPI主设备,波特率最高只能设到SPI_BAUDRATEPRESCALER_4
void DAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_InitStruct = {0U};
if(hspi->Instance == SPI1)
{
/* Enable peripheral clock */
__DAL_RCM_SPI1_CLK_ENABLE();
/* Enable peripheral GPIO clock */
__DAL_RCM_GPIOA_CLK_ENABLE();
/* Configure the SPI pin */
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
DAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/* DMA Init */
hdma2.Instance = DMA2_Stream2;
hdma2.Init.Channel = DMA_CHANNEL_2;
hdma2.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma2.Init.PeriphInc = DMA_PINC_DISABLE;
hdma2.Init.MemInc = DMA_MINC_ENABLE;
hdma2.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma2.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma2.Init.Mode = DMA_NORMAL;
hdma2.Init.Priority = DMA_PRIORITY_LOW;
hdma2.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
//hdma2.XferCpltCallback = DMA_Callbackup;
if (DAL_DMA_Init(&hdma2) != DAL_OK)
{
DAL_ErrorHandler();
}
//用于在 HAL 库的数据结构之间建立链接
__DAL_LINKDMA(&hspi1,hdmatx,hdma2);
/* SPI1 interrupt Init */
DAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
DAL_NVIC_EnableIRQ(SPI1_IRQn);
}
DAL_SPI_MspInit中需要设置复用引脚,只设置了SCL和SDA(MOSI),CS需要自己控制。试了好久,引脚的速率最高只能GPIO_SPEED_FREQ_MEDIUM。
另外DMA也需要在这里设置。中断设置不是必须。
二、DMA配置
apm32f4xx_dma_cfg.c
void DAL_DMA_Config(void)
{
__DAL_RCM_DMA2_CLK_ENABLE();
DAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
DAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
}
只有时钟和中断。其他在apm32f4xx_spi_cfg.c中配置。
三、HX8347配置
1、配置CS、RST、背光引脚
void LCD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
__DAL_RCM_GPIOA_CLK_ENABLE();
GPIO_InitStructure.Pin = LCD_CS|LCD_RST|LCD_LED;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
DAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
}
2、向SPI总线写一个BYTE
void SPI_WriteData(unsigned char Data)
{
DAL_SPI_Transmit_DMA(&hspi1,&Data,1);
while(DAL_DMA_GetState(&hdma2)==DAL_DMA_STATE_BUSY);
}
3、写指令
void Lcd_WriteIndex(unsigned char Index)
{
LCD_CS_CLR;
SPI_WriteData(0x70);
SPI_WriteData(Index);
LCD_CS_SET;
}
4、写数据
void Lcd_WriteData(unsigned char Data)
{
LCD_CS_CLR;
SPI_WriteData(0x72);
SPI_WriteData(Data);
LCD_CS_SET;
}
5、写寄存器
void Lcd_Write_REG(unsigned char Index,unsigned char Data)
{
Lcd_WriteIndex(Index);
Lcd_WriteData(Data);
}
6、设置画图输出范围
void LCD_SetWindow(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{
//SC
Lcd_Write_REG(0x02,x1>>8); // Column address start2
Lcd_Write_REG(0x03,(uint8_t)x1); // Column address start1
//EC
Lcd_Write_REG(0x04,x2>>8); // Column address end2
Lcd_Write_REG(0x05,(uint8_t)x2); // Column address end1
//SP
Lcd_Write_REG(0x06,y1>>8); // Row address start2
Lcd_Write_REG(0x07,(uint8_t)y1); // Row address start1
//EP
Lcd_Write_REG(0x08,y2>>8); // Row address end2
Lcd_Write_REG(0x09,(uint8_t)y2); // Row address end1
//写0x22到index register,那么下次send data就会直接被写到graphic ram
Lcd_WriteIndex(0x22);
}
7、DMA方式清屏
void FillRect_DMA(uint16_t color)
{
int16_t j;
LCD_SetWindow(0, 0,239,319);
LCD_CS_CLR;
SPI_WriteData(0x72);
for(j=0 ;j<Buf_Size;j+=2){
TxData[j] = color>>8;
TxData[j+1] = color;
}
for(j = 0 ; j<320/(Buf_Size/480) ; j++){
DAL_SPI_Transmit_DMA(&hspi1,TxData,Buf_Size);
while(DAL_DMA_GetState(&hdma2)==DAL_DMA_STATE_BUSY);
}
LCD_CS_SET;
}
8、写汉字及ASCII
void Gui_DrawFont_GBK16_DMA(unsigned int x, unsigned int y, unsigned int fc, unsigned int bc,unsigned char *s)
{
unsigned char i,j;
unsigned short k,x0,y0;
x0=x;
y0=y;
x=0;
y=0;
DMA_BUFFER_RESET(bc); //清缓存
while(*s)
{
if((*s) < 128)
{
k=*s;
if (k==13)
{
x=x0;
y+=16;
}
else
{
if (k>32) k-=32; else k=0;
for(i=0;i<16;i++)
for(j=0;j<8;j++)
{
if(asc16[k*16+i]&(0x80>>j)) Gui_DrawPoint_DMA(x+j,y+i,fc);
else
{
if (fc!=bc) Gui_DrawPoint_DMA(x+j,y+i,bc);
}
}
x+=8;
}
s++;
}
else
{
for (k=0;k<hz16_num;k++)
{
if ((hz16[k].Index[0]==*(s))&&(hz16[k].Index[1]==*(s+1)))
{
for(i=0;i<16;i++)
{
for(j=0;j<8;j++)
{
if(hz16[k].Msk[i*2]&(0x80>>j)) Gui_DrawPoint_DMA(x+j,y+i,fc);
else {
if (fc!=bc) Gui_DrawPoint_DMA(x+j,y+i,bc);
}
}
for(j=0;j<8;j++)
{
if(hz16[k].Msk[i*2+1]&(0x80>>j)) Gui_DrawPoint_DMA(x+j+8,y+i,fc);
else
{
if (fc!=bc) Gui_DrawPoint_DMA(x+j+8,y+i,bc);
}
}
}
}
}
s+=2;x+=16;
}
}
LCD_SetWindow(0,y0,239,y0+16);
LCD_CS_CLR;
SPI_WriteData(0x72);
DAL_SPI_Transmit_DMA(&hspi1,TxData,Buf_Size);
while(DAL_DMA_GetState(&hdma2)==DAL_DMA_STATE_BUSY);
LCD_CS_SET;
}
四、效果
五、总结
1、SPI+DMA网上其实已经写了很多,但通常是基于标准库的,基于HAL(DAL)库还是比较少。
2、这个屏比较特殊3线SPI,需要稍微特殊处理下。
3、SPI的设置速率不是越快越好,太快HX8347跟不上,需要找到一个平衡点,尽可能提高速度。