背景
需要控制几块SPI通讯接口的屏幕显示内容。因此需要配置芯片SPI接口,软件方式此前已经轻松实现,但是出于性能考虑,使用硬件方式是更优的方案。
浏览了华大提供的代码例子,均是全双工场景下两个芯片相互发送接收数据调用的SPI_TransReceive函数,案例对于我需要的单方发送场景不太合适。
同时例子里发送的数据内容是固定的,在实际使用中还需重新修改。
本文会提供一种更简单易用的配置方式,方便单发送场景下的SPI硬件配置,同时不会涉及到屏幕控制的实际指令,仅包含对SPI接口相关进行配置,方便您快速移植代码。
设计思路
1、简单的屏幕控制需求,无需配置过于复杂,简单易用即可,因此采用了阻塞式的轮询方案进行SPI通讯。
2、对于SPI通讯来说,最快速和占用资源最小的方式应该是采用DMA+中断的方式进行配置,但是这样配置和调用会比较麻烦,另一方面小型SPI屏幕本身性能有限,芯片发送指令速度再快,屏幕显示的速度还是没有什么差别的,因此无需配置过于复杂。
代码实现
1、SPI初始化
下面代码中需要注意:
1、无需接收数据因此SPI_MISO接口无需配置;
2、SPI_SS接口对应屏幕的片选CS,在主机模式下,460芯片似乎不会自动控制该引脚拉高和拉低,因此将该引脚作为普通的GPIO初始化,以进行手动的拉低和拉高;
3、stcSpiInit.u32SpiMode用于控制SPI工作模式,关系到MOSI和MISO的波形,该配置是导致SPI通讯成功与否的重要内容,460有0~3共4种工作模式。
c
void InitSPI(void)
{
stc_spi_init_t stcSpiInit;//SPI CONFIG
stc_irq_signin_config_t stcIrqSignConfig;//RX/TX
stc_gpio_init_t stcGpioInit;//GPIO
//GPIO初始化
(void)GPIO_StructInit(&stcGpioInit);
stcGpioInit.u16PinDrv = PIN_HIGH_DRV;
//(void)GPIO_Init(IPS_SPI_SS_PORT, IPS_SPI_SS_PIN, &stcGpioInit);
(void)GPIO_Init(IPS_SPI_SCK_PORT, IPS_SPI_SCK_PIN, &stcGpioInit);
(void)GPIO_Init(IPS_SPI_MOSI_PORT, IPS_SPI_MOSI_PIN, &stcGpioInit);
//(void)GPIO_Init(SPI_MISO_PORT, SPI_MISO_PIN, &stcGpioInit);
/* 配置引脚Func模式 */
//GPIO_SetFunc(IPS_SPI_SS_PORT, IPS_SPI_SS_PIN, IPS_SPI_SS_FUNC);
GPIO_SetFunc(IPS_SPI_SCK_PORT, IPS_SPI_SCK_PIN, IPS_SPI_SCK_FUNC);
GPIO_SetFunc(IPS_SPI_MOSI_PORT, IPS_SPI_MOSI_PIN, IPS_SPI_MOSI_FUNC);
//GPIO_SetFunc(SPI_MISO_PORT, SPI_MISO_PIN, SPI_MISO_FUNC);
/* 配置 SPI */
FCG_Fcg1PeriphClockCmd(IPS_SPI_CLK, ENABLE);
SPI_StructInit(&stcSpiInit);
stcSpiInit.u32WireMode = SPI_4_WIRE;//四线SPI
stcSpiInit.u32TransMode = SPI_SEND_ONLY;//仅发送模式(也可配置全双工)
stcSpiInit.u32MasterSlave = IPS_EXAMPLE_SPI_MASTER_SLAVE;//主从机模式
stcSpiInit.u32Parity = SPI_PARITY_INVD;//奇偶校验模式,配置无校验
stcSpiInit.u32SpiMode = SPI_MD_3;//SPI工作模式,空闲状态低电平、上升沿采样,下降沿变化
stcSpiInit.u32BaudRatePrescaler = SPI_BR_CLK_DIV256;//速率分频
stcSpiInit.u32DataBits = SPI_DATA_SIZE_8BIT;//数据位
stcSpiInit.u32FirstBit = SPI_FIRST_MSB;//最高有效位先传输,D7、D6...D0顺序
stcSpiInit.u32FrameLevel = SPI_1_FRAME;//向寄存器写入1位数据后立即推送至TX
(void)SPI_Init(IPS_SPI_UNIT, &stcSpiInit);
SPI_Cmd(IPS_SPI_UNIT,ENABLE);
//SPI_IntCmd(SPI_UNIT, (SPI_INT_TX_BUF_EMPTY), ENABLE);//仅启用TX
//SPI_IntCmd(SPI_UNIT, (SPI_INT_TX_BUF_EMPTY | SPI_INT_RX_BUF_FULL), ENABLE);
/* TX NVIC configure */
// stcIrqSignConfig.enIntSrc = SPI_TX_IRQ_SRC;
// stcIrqSignConfig.enIRQn = SPI_TX_IRQ_NUM;
// stcIrqSignConfig.pfnCallback = &SPI_TransCompleteCallback;
// (void)INTC_IrqSignIn(&stcIrqSignConfig);
// NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
// NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
// NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);
/* RX NVIC configure */
// stcIrqSignConfig.enIntSrc = SPI_RX_IRQ_SRC;
// stcIrqSignConfig.enIRQn = SPI_RX_IRQ_NUM;
// stcIrqSignConfig.pfnCallback = &SPI_ReceiveCompleteCallback;
// (void)INTC_IrqSignIn(&stcIrqSignConfig);
// NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
// NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT - 1);
// NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);
//普通IO初始化
(void)GPIO_StructInit(&stcGpioInit);
stcGpioInit.u16PinState = PIN_STAT_RST;//默认置0
stcGpioInit.u16PinDir = PIN_DIR_OUT;//输出模式
(void)GPIO_Init(IPS_SPI_RES_PORT, IPS_SPI_RES_PIN, &stcGpioInit);
(void)GPIO_Init(IPS_SPI_DC_PORT, IPS_SPI_DC_PIN, &stcGpioInit);
(void)GPIO_Init(IPS_SPI_CS_PORT, IPS_SPI_CS_PIN, &stcGpioInit);
stcGpioInit.u16PinState = PIN_STAT_SET;//背光默认置1常亮
(void)GPIO_Init(IPS_SPI_BLK_PORT, IPS_SPI_BLK_PIN, &stcGpioInit);
IPS_CS_HIGH;//片选默认拉高
}
2、初始化代码对应的头文件声明内容
c
/* SPI definition */
#define IPS_EXAMPLE_SPI_MASTER_SLAVE (SPI_MASTER)
#define IPS_SPI_UNIT (CM_SPI1)
#define IPS_SPI_CLK (FCG1_PERIPH_SPI1)
#define IPS_SPI_TX_IRQ_SRC (INT_SRC_SPI1_SPTI)
#define IPS_SPI_TX_IRQ_NUM (INT013_IRQn)
#define IPS_SPI_RX_IRQ_SRC (INT_SRC_SPI1_SPRI)
#define IPS_SPI_RX_IRQ_NUM (INT014_IRQn)
/*SPI通讯配置引脚,4个,屏幕可不配置MISO*/
///* SS(CS) */
//#define IPS_SPI_SS_PORT (GPIO_PORT_B)
//#define IPS_SPI_SS_PIN (GPIO_PIN_09)
//#define IPS_SPI_SS_FUNC (GPIO_FUNC_42)
/* CS */
#define IPS_SPI_CS_PORT (GPIO_PORT_B)
#define IPS_SPI_CS_PIN (GPIO_PIN_09)
/* SCK(CLK/SCL) */
#define IPS_SPI_SCK_PORT (GPIO_PORT_A)
#define IPS_SPI_SCK_PIN (GPIO_PIN_07)
#define IPS_SPI_SCK_FUNC (GPIO_FUNC_43)
/* MOSI(Master Out Slave In/SDA/主机输出从机输入) */
#define IPS_SPI_MOSI_PORT (GPIO_PORT_A)
#define IPS_SPI_MOSI_PIN (GPIO_PIN_06)
#define IPS_SPI_MOSI_FUNC (GPIO_FUNC_40)
/* MISO(Master In Slave Out/主机输入从机输出) = PC5 */
//#define IPS_SPI_MISO_PORT (GPIO_PORT_C)
//#define IPS_SPI_MISO_PIN (GPIO_PIN_05)
//#define IPS_SPI_MISO_FUNC (GPIO_FUNC_41)
/* 额外的功能性引脚 */
/* RES */
#define IPS_SPI_RES_PORT (GPIO_PORT_A)
#define IPS_SPI_RES_PIN (GPIO_PIN_05)
/* DC */
#define IPS_SPI_DC_PORT (GPIO_PORT_A)
#define IPS_SPI_DC_PIN (GPIO_PIN_04)
/* BLK */
#define IPS_SPI_BLK_PORT (GPIO_PORT_A)
#define IPS_SPI_BLK_PIN (GPIO_PIN_03)
#define IPS_CS_LOW ( GPIO_ResetPins(IPS_SPI_CS_PORT, IPS_SPI_CS_PIN) )
#define IPS_CS_HIGH ( GPIO_SetPins(IPS_SPI_CS_PORT, IPS_SPI_CS_PIN) )
/* TFT屏相关引脚高低电平控制 */
//额外的通用引脚
#define IPS_DC_LOW ( GPIO_ResetPins(IPS_SPI_DC_PORT, IPS_SPI_DC_PIN) )
#define IPS_DC_HIGH ( GPIO_SetPins(IPS_SPI_DC_PORT, IPS_SPI_DC_PIN) )
#define IPS_RES_LOW ( GPIO_ResetPins(IPS_SPI_RES_PORT, IPS_SPI_RES_PIN) )
#define IPS_RES_HIGH ( GPIO_SetPins(IPS_SPI_RES_PORT, IPS_SPI_RES_PIN) )
#define IPS_BLK_LOW ( GPIO_ResetPins(IPS_SPI_BLK_PORT, IPS_SPI_BLK_PIN) )
#define IPS_BLK_HIGH ( GPIO_SetPins(IPS_SPI_BLK_PORT, IPS_SPI_BLK_PIN) )
3、SPI数据发送接口
为确保发送数据的时序,发送前需要等待SPI_FLAG_TX_BUF_EMPTY状态,发送后需要等待SPI_FLAG_IDLE状态确保发送完成。
否则数据还未发送完成就拉高CS会导致硬件SPI还未发送出刚写入的数据就通知屏幕停止接收数据,导致数据帧截断。
经测试,SPI_FLAG_TX_BUF_EMPTY可以不等待,但是必须等待SPI_FLAG_IDLE状态,出于严谨考虑,两个状态都进行等待较好。
c
void IPS_SPI_WriteData_M(u8 Data)
{
//CS_LOW; /* 拉低片选 */
while(RESET == SPI_GetStatus(IPS_SPI_UNIT,SPI_FLAG_TX_BUF_EMPTY));
SPI_WriteData(IPS_SPI_UNIT, Data);
/* 等待 SPI 完全发送完成(关键) */
while (RESET == SPI_GetStatus(IPS_SPI_UNIT, SPI_FLAG_IDLE));
//CS_HIGH; /* 拉高片选 */
}
如下为屏幕指令写入接口和数据写入接口,通过DC脚的状态不同来控制屏幕区分下面发送的是指令还是数据。
c
/**
* @brief 液晶屏写指令.
* @param Data[in] 指令数据
* @retval None
*/
void TFT_WriteCmd(u8 Data)
{
IPS_DC_LOW;
IPS_CS_LOW; /* 拉低片选 */
IPS_SPI_WriteData_M(Data);
IPS_CS_HIGH; /* 拉高片选 */
IPS_DC_HIGH;
}
/**
* @brief 液晶屏写数据.
* @param Data[in] 数据
* @retval None
*/
void TFT_WriteData(u8 Data)
{
IPS_DC_HIGH;
IPS_CS_LOW; /* 拉低片选 */
IPS_SPI_WriteData_M(Data);
IPS_CS_HIGH; /* 拉高片选 */
IPS_DC_HIGH;
}
/**
* @brief 液晶屏写数据.
* @param Data[in] 2字节数据
* @retval None
*/
void TFT_WriteData_16(u32 Data)
{
IPS_DC_HIGH;
IPS_CS_LOW; /* 拉低片选 */
IPS_SPI_WriteData_M(Data>>8);
IPS_SPI_WriteData_M(Data&0xFF);
IPS_CS_HIGH; /* 拉高片选 */
IPS_DC_HIGH;
}