目录:
SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI 总线是Motorola公司推出的同步串行接口技术,(关于同步还是异步,主要看通信双方的时钟线是否连在一起)。SPI由四根线完成数据传输,分别是SCK(时钟)、MOSI(主出从入)、MISO(主入从出)和SSEL(片选)。通讯时序图如下(当然很多SPI器件的数据手册都会给出响应的时序图):
SPI主要特点有:
TFT(Thin Film Transistor)是薄膜晶体管的缩写。是有源矩阵液晶的一种。是最好的LCD彩色显示器之一,TFT式显示器具有高响应度、高亮度、高对比度等优点,其显示效果接近CRT式显示器
TFT上的每个液晶像素点都是由集成在像素点后面的薄膜晶体管来驱动,因此反应时间较快,
并且可视角度大,一般可达130度左右
色彩丰富,可支持65536色
回到顶部
TFT显示屏,看着高大上的电容屏真的买不起。。。
逻辑分析仪,对没错贫穷限制人的想象,不过24M的采样率测个SPI还是勉强能够应付
取模工具, 密码:4h7h
/****************************************************************************
* 名 称:void SPIv_WriteData(u8 Data)
* 功 能:STM32_模拟SPI写一个字节数据底层函数
* 入口参数:Data
* 出口参数:无
* 说 明:STM32_模拟SPI读写一个字节数据底层函数
****************************************************************************/
void SPIv_WriteData(u8 Data)
{
unsigned char i=0;
for ( i = 8; i > 0; i --)
{
LCD_SCL_CLR;
if ( Data & 0x80)
LCD_SDA_SET; //输出数据
else
LCD_SDA_CLR;
LCD_SCL_SET;
Data <<= 1;
}
}
//******************************************************************
//函数名: LCD_WR_DATA
//功能: 向液晶屏总线写入写8位数据
//输入参数:Data:待写入的数据
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_WR_DATA(u8 data)
{
LCD_CS_CLR; //软件控制片选信号
LCD_RS_SET;
SPIv_WriteData(data);
LCD_CS_SET; //软件控制片选信号
}
//******************************************************************
//函数名: LCD_WR_DATA
//功能: 向液晶屏总线写入写8位数据
//输入参数:Data:待写入的数据
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_WR_DATA(u8 data)
{
LCD_CS_CLR; //软件控制片选信号
LCD_RS_SET;
SPIv_WriteData(data);
LCD_CS_SET; //软件控制片选信号
}
//******************************************************************
//函数名: LCD_WR_REG
//功能: 向液晶屏总线写入写16位指令
//输入参数:Reg:待写入的指令值
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_WR_REG(u16 data)
{
LCD_CS_CLR; //软件控制片选信号
LCD_RS_CLR;
SPIv_WriteData(data);
LCD_CS_SET; //软件控制片选信号
}
//******************************************************************
//函数名: LCD_WriteReg
//功能: 写寄存器数据
//输入参数:LCD_Reg:寄存器地址
// LCD_RegValue:要写入的数据
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_WriteReg(u16 LCD_Reg, u16 LCD_RegValue)
{
LCD_WR_REG(LCD_Reg);
LCD_WR_DATA(LCD_RegValue);
}
//******************************************************************
//函数名: LCD_WriteRAM_Prepare
//功能: 开始写GRAM
// 在给液晶屏传送RGB数据前,应该发送写GRAM指令
//输入参数:无
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_WriteRAM_Prepare(void)
{
LCD_WR_REG(lcddev.wramcmd);
}
//******************************************************************
//函数名: LCD_Reset
//功能: LCD复位函数,液晶初始化前要调用此函数
//输入参数:无
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_RESET(void)
{
#ifdef LCD_RST
LCD_RST_CLR;
#endif
LCD_WR_REG(0x01);
delay_ms(100);
#ifdef LCD_RST
LCD_RST_SET;
#endif
LCD_WR_REG(0x01);
delay_ms(50);
}
//******************************************************************
//函数名: LCD_GPIOInit
//功能: 液晶屏IO初始化,液晶初始化前要调用此函数
//输入参数:无
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_GPIOInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB ,ENABLE);
#ifdef LCD_RST
GPIO_InitStructure.GPIO_Pin = LCD_LED| LCD_RS| LCD_CS | LCD_SCL | LCD_SDA | LCD_RST;
#else
GPIO_InitStructure.GPIO_Pin = LCD_LED| LCD_RS| LCD_CS | LCD_SCL | LCD_SDA;
#endif
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//******************************************************************
//函数名: LCD_Init
//功能: LCD初始化
//输入参数:无
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_Init(void)
{
LCD_GPIOInit();//使用模拟SPI
LCD_RESET(); //液晶屏复位
//************* Start Initial Sequence **********//
//开始初始化液晶屏
LCD_WR_REG(0x11);//Sleep exit
delay_ms (120);
//ST7735R Frame Rate
LCD_WR_REG(0xB1);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x2D);
LCD_WR_REG(0xB2);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x2D);
LCD_WR_REG(0xB3);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x2D);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x2D);
LCD_WR_REG(0xB4); //Column inversion
LCD_WR_DATA(0x07);
//ST7735R Power Sequence
LCD_WR_REG(0xC0);
LCD_WR_DATA(0xA2);
LCD_WR_DATA(0x02);
LCD_WR_DATA(0x84);
LCD_WR_REG(0xC1);
LCD_WR_DATA(0xC5);
LCD_WR_REG(0xC2);
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0x00);
LCD_WR_REG(0xC3);
LCD_WR_DATA(0x8A);
LCD_WR_DATA(0x2A);
LCD_WR_REG(0xC4);
LCD_WR_DATA(0x8A);
LCD_WR_DATA(0xEE);
LCD_WR_REG(0xC5); //VCOM
LCD_WR_DATA(0x0E);
LCD_WR_REG(0x36); //MX, MY, RGB mode
LCD_WR_DATA(0xC8);
//ST7735R Gamma Sequence
LCD_WR_REG(0xe0);
LCD_WR_DATA(0x0f);
LCD_WR_DATA(0x1a);
LCD_WR_DATA(0x0f);
LCD_WR_DATA(0x18);
LCD_WR_DATA(0x2f);
LCD_WR_DATA(0x28);
LCD_WR_DATA(0x20);
LCD_WR_DATA(0x22);
LCD_WR_DATA(0x1f);
LCD_WR_DATA(0x1b);
LCD_WR_DATA(0x23);
LCD_WR_DATA(0x37);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x02);
LCD_WR_DATA(0x10);
LCD_WR_REG(0xe1);
LCD_WR_DATA(0x0f);
LCD_WR_DATA(0x1b);
LCD_WR_DATA(0x0f);
LCD_WR_DATA(0x17);
LCD_WR_DATA(0x33);
LCD_WR_DATA(0x2c);
LCD_WR_DATA(0x29);
LCD_WR_DATA(0x2e);
LCD_WR_DATA(0x30);
LCD_WR_DATA(0x30);
LCD_WR_DATA(0x39);
LCD_WR_DATA(0x3f);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x03);
LCD_WR_DATA(0x10);
LCD_WR_REG(0x2a);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x7f);
LCD_WR_REG(0x2b);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x9f);
LCD_WR_REG(0xF0); //Enable test command
LCD_WR_DATA(0x01);
LCD_WR_REG(0xF6); //Disable ram power save mode
LCD_WR_DATA(0x00);
LCD_WR_REG(0x3A); //65k mode
LCD_WR_DATA(0x05);
LCD_WR_REG(0x29);//Display on
LCD_SetParam();//设置LCD参数
LCD_LED_SET;//点亮背光
}
//******************************************************************
//函数名: LCD_DrawPoint
//功能: 在指定位置写入一个像素点数据
//输入参数:(x,y):光标坐标
//返回值: 无
//修改记录:无
//******************************************************************
void LCD_DrawPoint(u16 x,u16 y)
{
LCD_SetCursor(x,y);//设置光标位置
LCD_WR_DATA_16Bit(BLACK);
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
int main()
{
SystemInit(); //初始化系统,系统时钟设定为72MHz
delay_init(); //配置systick,中断时间设置为72000/72000000 = 1us
LCD_Init(); //液晶屏初始化
while(1)
{
LCD_DrawPoint(64, 64);
delay_ms(10);
}
}
这一个如果成功的话,可以屏幕的正中心看到一个小小的像素点,对没错就是一个像素点,很小很小
然后这是写入的时序图,用逻辑分析仪测的,可以看到和LCD_Init()中定义的是一样的
/****************************************************************************
* 名 称: LCD_Clear
* 功 能: LCD全屏填充清屏函数
* 入口参数: Color:要清屏的填充色
* 出口参数: 无
* 说 明:
****************************************************************************/
void LCD_Clear(u16 Color)
{
u16 i,j;
LCD_SetWindows(0, 0, lcddev.width-1, lcddev.height-1);
for (i = 0; i < lcddev.width; i ++)
{
for (j = 0; j < lcddev.height; j ++)
LCD_WR_DATA_16Bit(Color); //写入数据
}
}
/****************************************************************************
* 名 称: LCD_SetWindows
* 功 能: 设置lcd显示窗口,在此区域写点数据自动换行
* 入口参数: xy起点和终点
* 出口参数: 无
* 说 明:
****************************************************************************/
void LCD_SetWindows(u16 xStar, u16 yStar, u16 xEnd, u16 yEnd)
{
#if USE_HORIZONTAL == 1 //使用横屏
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(xStar >> 8);
LCD_WR_DATA(0x00FF & xStar + 32);
LCD_WR_DATA(xEnd >> 8);
LCD_WR_DATA(0x00FF & xEnd + 32);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(yStar >> 8);
LCD_WR_DATA(0x00FF & yStar + 0);
LCD_WR_DATA(yEnd >> 8);
LCD_WR_DATA(0x00FF & yEnd + 0);
#else
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(xStar>>8);
LCD_WR_DATA(0x00FF&xStar+0);
LCD_WR_DATA(xEnd>>8);
LCD_WR_DATA(0x00FF&xEnd+0);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(yStar>>8);
LCD_WR_DATA(0x00FF&yStar+32);
LCD_WR_DATA(yEnd>>8);
LCD_WR_DATA(0x00FF&yEnd+32);
#endif
LCD_WriteRAM_Prepare(); //开始写入GRAM
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
int main()
{
SystemInit(); //初始化系统,系统时钟设定为72MHz
//systick_init(72);
delay_init(); //配置systick,中断时间设置为72000/72000000 = 1us
LCD_Init(); //液晶屏初始化
delay_ms(10);
LCD_Clear(RED);
while(1)
{
}
}
回到顶部
通过IO口模拟,我们成功的驱动了我们的TFT 显示器,说明我们的底层建筑没问题
但是我们发现,在进行屏幕更新的时候还是会有一定的延迟,通过观察我们发现,使用IO口模拟SPI的时候,时钟速度只有1.6MHz,但是TFT的SPI是支持30MHz的,这无疑降低了TFT的性能
为了提高SPI的写入速度,我们可以使用STM32自带的SPI接口,根据STM32的数据手册,STM32SPI的最大速度可达18MHz,尽管没有达到TFT的最高性能,但是对于写一些文字和图片来说,已经勉强能够胜任了
下面就来配置STM32的SPI吧,先配置SPI管脚SCK、SOMI对应的IO口,将其设置为复用推挽输出,然后将RST、RS、CS管脚设置为推挽输出,STM32是自带CS硬件片选接口的,这个后面再来说。然后配置SPI的工作模式,官方手册里面是由介绍的
/****************************************************************************
* 名 称:SPI2_Init(void)
* 功 能:STM32_SPI2硬件配置初始化
* 入口参数:无
* 出口参数:无
* 说 明:STM32_SPI2硬件配置初始化
****************************************************************************/
void SPI2_Init(void)
{
/*SPI初始化*/
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/*配置SPI2管脚对应的GPIO口*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE);
#if USE_HARDNSS //使用硬件NSS
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15 | LCD_CS;
#else
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
#endif
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*IO口初始化*/
#if USE_HARDNSS //使用硬件NSS
#ifdef LCD_RST
GPIO_InitStructure.GPIO_Pin = LCD_LED | LCD_RS | LCD_RST;
#else
GPIO_InitStructure.GPIO_Pin = LCD_LED | LCD_RS;
#endif
#else
#ifdef LCD_RST
GPIO_InitStructure.GPIO_Pin = LCD_LED | LCD_RS | LCD_CS | LCD_RST;
#else
GPIO_InitStructure.GPIO_Pin = LCD_LED | LCD_RS | LCD_CS;
#endif
#endif
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*SPI2配置选项*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //时钟悬空高
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
#if USE_HARDNSS
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; //硬件控制片选信号
#else
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件控制片选信号
#endif
#if SPI_HIGH_SPEED_MODE
/*波特率预分频值为32*/
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
#else
/*波特率预分频值为2*/
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
#endif
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始
/*SPI_CRCPolynomial定义了用于CRC值计算的多项式*/
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI2, &SPI_InitStructure);
//SPI2->CR2 |= (1<<2);
#if USE_HARDNSS
SPI_SSOutputCmd(SPI2, ENABLE);
#endif
/*使能SPI2*/
SPI_Cmd(SPI2, ENABLE);
}
/****************************************************************************
* 名 称:u8 SPI_WriteByte(SPI_TypeDef* SPIx,u8 Byte)
* 功 能:STM32_硬件SPI读写一个字节数据底层函数
* 入口参数:SPIx,Byte
* 出口参数:返回总线收到的数据
* 说 明:STM32_硬件SPI读写一个字节数据底层函数
****************************************************************************/
u8 SPI_WriteByte(SPI_TypeDef* SPIx, u8 Byte)
{
while ((SPIx->SR & SPI_I2S_FLAG_TXE) == RESET); //等待发送区空
SPIx->DR = Byte; //发送一个byte
while ((SPIx->SR & SPI_I2S_FLAG_RXNE) == RESET);//等待接收完一个byte
return SPIx->DR; //返回收到的数据
}
/****************************************************************************
* 名 称:LCD_WR_REG(u16 data)
* 功 能:向液晶屏总线写入写16位指令
* 入口参数:Reg:待写入的指令值
* 出口参数:无
* 说 明:
****************************************************************************/
void LCD_WR_REG(u16 data)
{
#if USE_HARDNSS //硬件控制片选信号
#else
LCD_CS_CLR; //软件控制片选信号
#endif
LCD_RS_CLR;
#if USE_HARDWARE_SPI
SPI_WriteByte(SPI2, data);
#else
SPIv_WriteData(data);
#endif
#if USE_HARDNSS //硬件控制片选信号
#else
LCD_CS_SET; //软件控制片选信号
#endif
}
/****************************************************************************
* 名 称:LCD_WR_DATA(u8 data)
* 功 能:向液晶屏总线写入写8位数据
* 入口参数:Data:待写入的数据
* 出口参数:无
* 说 明:
****************************************************************************/
void LCD_WR_DATA(u8 data)
{
#if USE_HARDNSS //硬件控制片选信号
#else
LCD_CS_CLR; //软件控制片选信号
#endif
LCD_RS_SET;
#if USE_HARDWARE_SPI
SPI_WriteByte(SPI2,data);
#else
SPIv_WriteData(data);
#endif
#if USE_HARDNSS //硬件控制片选信号
#else
LCD_CS_SET; //软件控制片选信号
#endif
}
/****************************************************************************
* 名 称:LCD_DrawPoint_16Bit
* 功 能:8位总线下如何写入一个16位数据
* 入口参数:(x,y):光标坐标
* 出口参数:无
* 说 明:
****************************************************************************/
void LCD_WR_DATA_16Bit(u16 data)
{
#if USE_HARDNSS //硬件控制片选信号
#else
LCD_CS_CLR; //软件控制片选信号
#endif
LCD_RS_SET;
#if USE_HARDWARE_SPI
SPI_WriteByte(SPI2, data >> 8);
SPI_WriteByte(SPI2, data);
#else
SPIv_WriteData(data >> 8);
SPIv_WriteData(data);
#endif
#if USE_HARDNSS //硬件控制片选信号
#else
LCD_CS_SET; //软件控制片选信号
#endif
}
/***********************************用户配置区***************************************/
#define USE_HORIZONTAL 1 //定义是否使用横屏 0:不使用 1:使用.
#define USE_HARDWARE_SPI 1 //1:Enable Hardware SPI 0:USE Soft SPI
#define USE_HARDNSS 0 //使用硬件NSS
#define SPI_HIGH_SPEED_MODE 1 //SPI高速模式 1:高速模式 0:低速模式
回到顶部
回到顶部
回到顶部
/****************************************************************************
* 名 称: LCD_SetWindows
* 功 能: 设置lcd显示窗口,在此区域写点数据自动换行
* 入口参数: xy起点和终点
* 出口参数: 无
* 说 明:
****************************************************************************/
void LCD_SetWindows(u16 xStar, u16 yStar, u16 xEnd, u16 yEnd)
{
#if USE_HORIZONTAL == 1 //使用横屏
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(xStar >> 8);
LCD_WR_DATA(0x00FF & xStar + 32);
LCD_WR_DATA(xEnd >> 8);
LCD_WR_DATA(0x00FF & xEnd + 32);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(yStar >> 8);
LCD_WR_DATA(0x00FF & yStar + 0);
LCD_WR_DATA(yEnd >> 8);
LCD_WR_DATA(0x00FF & yEnd + 0);
#else
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(xStar>>8);
LCD_WR_DATA(0x00FF&xStar+0);
LCD_WR_DATA(xEnd>>8);
LCD_WR_DATA(0x00FF&xEnd+0);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(yStar>>8);
LCD_WR_DATA(0x00FF&yStar+32);
LCD_WR_DATA(yEnd>>8);
LCD_WR_DATA(0x00FF&yEnd+32);
#endif
LCD_WriteRAM_Prepare(); //开始写入GRAM
}
/****************************************************************************
* 名 称: Gui_Drawbmp16(u16 start_x,u16 start_y,const unsigned char *p)
* 功 能: 显示一副16位BMP图像
* 入口参数: start_x,start_y :起点坐标
* length:图像的长度(从左往右)
* width:图像的宽度(从上往下)
* *p :图像数组起始地址
* 出口参数: 无
* 说 明:
****************************************************************************/
void Gui_Drawbmp16(u16 start_x, u16 start_y, u16 length, u16 width, const unsigned char *p)
{
int i;
unsigned char picH, picL;
LCD_SetWindows(start_x, start_y, start_x +length - 1, start_y + width - 1);//窗口设置
for (i = 0; i < length * width; i ++)
{
picL = *(p + i * 2); //数据低位在前
picH = *(p + i * 2 + 1);
LCD_WR_DATA_16Bit(picH << 8 | picL);
}
LCD_SetWindows(0, 0, lcddev.width - 1,lcddev.height - 1);//恢复显示窗口为全屏
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
#include "gui.h"
#include "picture.h"
int main()
{
SystemInit(); //初始化系统,系统时钟设定为72MHz
delay_init(); //配置systick,中断时间设置为72000/72000000 = 1us
LCD_Init(); //液晶屏初始化
delay_ms(10);
while(1)
{
Gui_Drawbmp16(0, 0, 128, 128, gImage_bear_dolls);
}
}
回到顶部
/****************************************************************************
* 名 称: LCD_ShowChar(u16 x, u16 y, u16 fc, u16 bc, u8 num, u8 size, u8 mode)
* 功 能: 显示单个英文字符
* 入口参数: (x,y):字符显示位置起始坐标
* fc:前置画笔颜色
* bc:背景颜色
* num:数值(0-94)
* size:字体大小(12/16)
* mode:模式 0,填充模式;1,叠加模式
* 出口参数: 无
* 说 明:
****************************************************************************/
void LCD_ShowChar(u16 x, u16 y, u16 fc, u16 bc, u8 num, u8 size, u8 mode)
{
u8 temp;
u8 pos, t;
u16 colortemp = POINT_COLOR;
num = num - ' ';//得到偏移后的值
LCD_SetWindows(x, y, x + size / 2 - 1, y + size - 1);//设置单个文字显示窗口
if (!mode) //非叠加方式
{
if (size == 12)
{
for (pos = 0; pos < size; pos ++)
{
temp = asc2_1206[num][pos];//调用1206字体
for (t = 0; t < size / 2; t ++)
{
if (temp & 0x01)
{
LCD_WR_DATA_16Bit(fc);
}
else
{
LCD_WR_DATA_16Bit(bc);
}
temp >>= 1;
}
}
}
else
{
for (pos = 0; pos < size; pos ++)
{
temp = asc2_1608[num][pos];//调用1608字体
for (t = 0; t < size / 2; t ++)
{
if (temp & 0x01)
{
LCD_WR_DATA_16Bit(fc);
}
else
{
LCD_WR_DATA_16Bit(bc);
}
temp >>= 1;
}
}
}
}
else //叠加方式
{
if (size == 12)
{
for (pos = 0; pos < size; pos ++)
{
temp = asc2_1206[num][pos];//调用1206字体
for (t = 0; t < size / 2; t ++)
{
POINT_COLOR = fc;
if (temp & 0x01)
{
LCD_DrawPoint(x + t, y + pos);//画一个点
}
temp >>= 1;
}
}
}
else
{
for (pos = 0; pos < size; pos ++)
{
temp = asc2_1608[num][pos]; //调用1608字体
for (t = 0; t < size / 2; t ++)
{
POINT_COLOR = fc;
if (temp & 0x01)
{
LCD_DrawPoint(x + t, y + pos);//画一个点
}
temp >>= 1;
}
}
}
}
POINT_COLOR = colortemp;
LCD_SetWindows(0, 0, lcddev.width - 1, lcddev.height - 1);//恢复窗口为全屏
}
/****************************************************************************
* 名 称: LCD_ShowString(u16 x,u16 y,u8 size,u8 *p,u8 mode)
* 功 能: 显示英文字符串
* 入口参数: x,y :起点坐标
* size:字体大小
* *p:字符串起始地址
* mode:模式 0,填充模式;1,叠加模式
* 出口参数: 无
* 说 明:
****************************************************************************/
void LCD_ShowString(u16 x, u16 y, u8 *p, u8 size, u8 mode)
{
while ((*p <= '~') && (*p >= ' '))//判断是不是非法字符!
{
if (x > (lcddev.width - 1) || y > (lcddev.height - 1))
return;
LCD_ShowChar(x, y, POINT_COLOR, BACK_COLOR, *p, size, mode);
x += size / 2;
p ++;
}
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
#include "gui.h"
int main()
{
SystemInit(); //初始化系统,系统时钟设定为72MHz
//systick_init(72);
delay_init(); //配置systick,中断时间设置为72000/72000000 = 1us
LCD_Init(); //液晶屏初始化
delay_ms(10);
LCD_Clear(WHITE);
LCD_ShowString(18, 40, "Hello World!", 16, 1);
while(1)
{
}
}
Hello World!
诞生了,骚气的红色字体,对了还可以给背景加颜色的/****************************************************************************
* 名 称: LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size)
* 功 能: 显示单个数字变量值
* 入口参数: x,y :起点坐标len :指定显示数字的位数
* size:字体大小(12,16)
* color:颜色
* num:数值(0~4294967295)
* 出口参数: 无
* 说 明:
****************************************************************************/
void LCD_ShowNum(u16 x, u16 y, u32 num, u8 len, u8 size)
{
u8 t, temp;
u8 enshow = 0;
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 + (size / 2) * t, y, POINT_COLOR, BACK_COLOR, ' ', size, 1);
continue;
}
else
enshow = 1;
}
LCD_ShowChar(x + (size / 2) * t, y, POINT_COLOR, BACK_COLOR, temp + '0', size, 1);
}
}
回到顶部
typedef struct
{
char Msk[32];
unsigned char Index[2];
}typFNT_GB16;
//字体取模:宋体常规小四
const typFNT_GB16 tfont16[]=
{
// 我(0) 爱(1) 学(2) 习(3)
0x04,0x40,0x0E,0x50,0x78,0x48,0x08,0x48,0x08,0x40,0xFF,0xFE,0x08,0x40,0x08,0x44,
0x0A,0x44,0x0C,0x48,0x18,0x30,0x68,0x22,0x08,0x52,0x08,0x8A,0x2B,0x06,0x10,0x02,"我",/*0*/
0x00,0x08,0x01,0xFC,0x7E,0x10,0x22,0x10,0x11,0x20,0x7F,0xFE,0x42,0x02,0x82,0x04,
0x7F,0xF8,0x04,0x00,0x07,0xF0,0x0A,0x10,0x11,0x20,0x20,0xC0,0x43,0x30,0x1C,0x0E,"爱",/*1*/
0x22,0x08,0x11,0x08,0x11,0x10,0x00,0x20,0x7F,0xFE,0x40,0x02,0x80,0x04,0x1F,0xE0,
0x00,0x40,0x01,0x80,0xFF,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00,"学",/*2*/
0x00,0x00,0x7F,0xF8,0x00,0x08,0x00,0x08,0x08,0x08,0x04,0x08,0x02,0x08,0x02,0x08,
0x00,0x68,0x01,0x88,0x0E,0x08,0x70,0x08,0x20,0x08,0x00,0x08,0x00,0x50,0x00,0x20,"习",/*3*/
};
/****************************************************************************
* 名 称: GUI_DrawFont16(u16 x, u16 y, u16 fc, u16 bc, u8 *s,u8 mode)
* 功 能: 显示单个16X16中文字体
* 入口参数: x,y :起点坐标
* fc:前置画笔颜色
* bc:背景颜色
* s:字符串地址
* mode:模式 0,填充模式;1,叠加模式
* 出口参数: 无
* 说 明:
****************************************************************************/
void GUI_DrawFont16(u16 x, u16 y, u16 fc, u16 bc, u8 *s,u8 mode)
{
u8 i, j;
u16 k;
u16 HZnum;
u16 x0 = x;
HZnum = sizeof(tfont16) / sizeof(typFNT_GB16); //自动统计汉字数目
for (k = 0; k < HZnum; k ++)
{
if ((tfont16[k].Index[0] == *(s)) && (tfont16[k].Index[1] == *(s+1)))
{
if (!mode) //非叠加方式
{
LCD_SetWindows(x, y, x + 16 - 1, y + 16 - 1);
for (i = 0; i < 16 * 2; i ++)
{
for (j = 0; j < 8; j ++)
{
if (tfont16[k].Msk[i] & (0x80 >> j))
{
LCD_WR_DATA_16Bit(fc);
}
else
{
LCD_WR_DATA_16Bit(bc);
}
}
}
}
else//叠加方式
{
POINT_COLOR = fc;
for (i = 0; i < 16 * 2; i ++)
{
for (j = 0; j < 8; j ++)
{
if (tfont16[k].Msk[i] & (0x80 >> j))
{
LCD_DrawPoint(x, y);//画一个点
}
x ++;
}
if ((x - x0) == 16)
{
x = x0;
y ++;
}
}
}
break;//查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
}
else
{
continue;
}
}
LCD_SetWindows(0, 0, lcddev.width - 1, lcddev.height - 1);//恢复窗口为全屏
}
然后再主函数中调用它就能进行相应的显示了
然后有了单个中文汉字的显示还不够,因为我们要显示汉字的时候通常不是一个一个的去显示,而是一整个字符串去显示,而且会是中英混合的显示,所以我们再来编写一个字符串的显示函数
/****************************************************************************
* 名 称: Show_Str(u16 x, u16 y, u16 fc, u16 bc, u8 *str,u8 size,u8 mode)
* 功 能: 显示一个字符串,包含中英文显示
* 入口参数: x,y :起点坐标
* fc:前置画笔颜色
* bc:背景颜色
* str :字符串
* size:字体大小
* mode:模式 0,填充模式;1,叠加模式
* 出口参数: 无
* 说 明:
****************************************************************************/
void Show_Str(u16 x, u16 y, u16 fc, u16 bc, u8 *str, u8 size, u8 mode)
{
u16 x0 = x;
u8 bHz = 0; //字符或者中文
while (*str != 0) //数据未结束
{
if (!bHz)
{
if (x > (lcddev.width - size / 2) || y > (lcddev.height - size))
{
y += size;
x = x0;
continue;
}
if (*str > 0x80) //最高为是1,为汉字,否则为英文
bHz = 1; //中文
else //字符
{
if (*str == '\n')//换行符号
{
y += size;
x = x0;
str ++;
continue;
}
else
{
if(size == 12 || size == 16)
{
LCD_ShowChar(x, y, fc, bc, *str, size, mode);
x += size / 2; //字符,为全字的一半
}
else//字库中没有集成16X32的英文字体,用8X16代替
{
LCD_ShowChar(x, y, fc, bc, *str, 16, mode);
x += 8; //字符,为全字的一半
}
}
str ++;
}
}
else//中文
{
if (x > (lcddev.width - size) || y > (lcddev.height - size))
{
y += size;
x = x0;
continue;
}
bHz = 0;//有汉字库
if (size == 32)
GUI_DrawFont32(x, y, fc, bc, str, mode);
else if (size == 24)
GUI_DrawFont24(x, y, fc, bc, str, mode);
else
GUI_DrawFont16(x, y, fc, bc, str, mode);
str += 2;
x += size;//下一个汉字偏移
}
}
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
#include "gui.h"
int main()
{
SystemInit(); //初始化系统,系统时钟设定为72MHz
delay_init(); //配置systick,中断时间设置为72000/72000000 = 1us
LCD_Init(); //液晶屏初始化
delay_ms(10);
LCD_Clear(WHITE);
Show_Str(20, 30, BLUE, YELLOW, "Hello World!\n\n我爱学习", 16, 1);
while(1)
{
}
}
回到顶部
尽管我们的显示函数已经可以显示中英文加变量了,但是我还并不是很满意,因为当遇到多个要显示的变量的时候,要写的可不止是一条显示语句了,我们更希望使用像printf那样的函数,能够用一句话显示多个变量,也比较人性化,所以我们再来编写一个printf函数
为了编写printf函数,我查阅了可变参数的相关知识,打开了stdio.c文件进行参考
为了实现printf函数,我们需要用到一个可变参数,printf(const char* string, ...)
,其中的...
代表的就是可变参数,回想一下函数传参的过程,当函数发生调用的时候,在函数的入口处,系统会把参数依次压入堆栈,解决问题的关键就在这,这个压入堆栈的过程是遵循一定的规则的,即参数从右往左入栈,高地址先入栈,也就是说栈底占领着最高内存地址,先入栈的参数,其地理位置最高。假设调用函数是这样printf("Hello World!", a, b);
那么入栈过程就是b->a->“Hello World!”,因为函数参数进栈以及参数空间地址分配都是"实现相关"的,也就是说入栈的顺序和每个数据所占的内存字节都是对应的,这样一来,一旦我们知道某函数帧的栈上的一个固定参数的位置,我们完全有可能推导出其他变长参数的位置,所以每一个printf函数都会有一个位置已知的参数,那就是字符串,根据字符串就可以推出每个参数的地址 b.addr = a.addr + x_sizeof(a);
解决问题的关键在于如何将可变参数转化为确定的参数,下图很好的说明了函数调用是的传参过程,假设调用函是之这样printf("Hello World!", a, b, c, d, e);
,首先是e先入栈,存放的位置是栈底,对应的地址是高地址;然后是d入栈,其次是c…最后是"Hello World!"
我们发现在printf(const char* string, ...)
中,只有*string的地址是已知的,也就是"Hello World!"对应的地址,如果我们知道其他可变参数的数据类型,我们就可以根据数据存储的地址关系(函数调用时,堆栈的地址是连续的),轻而易举的找出每个可变参数的地址,根据地址就可以取出参数,这时候我们需要借助一组函数,他们在stdarg.h
中:
typedef struct
{
char *_Ap;
} _VA_LIST;
typedef __Va_list va_list;
#define va_start(ap, A) (ap._Ap = _GLB __va_start1())
#define va_end(ap) ((void) 0)
#define va_arg(ap, T) _VA_ARG(ap, T)
va_start(ap, string)
后,ap指针首先指向栈顶元素,也就是"Hello World!"这个字符串,然后计算出这个元素所占用的内存地址,然后ap += sizeof(string);
,也就是ap指向了可变参数的第一个元素,也就是a这个元素;然后使用result = va_arg(ap, int);
,首先是计算出int类型占用的内存,64位系统中是4个字节,也就是ap += 4;
这时ap指针指向堆栈中的下一个元素,也就是b这个int类型的变量,并且返回a的值…,这样一来我们就能把可变的参数编程确定的参数了/****************************************************************************
* 名 称: my_printf(char* string, ...)
* 功 能: 格式化显示字符串
* 入口参数: *string:字符串
* ...:可变参数
* 出口参数: count:字符的总个数(一个中文占两个字符)
* 使用范例: int result;
* int a = 666;
* result = my_printf("Hello World!\n%d\n%s" a, "Good Luck!");
* 说 明: 注意可变参数的输入不用加&
****************************************************************************/
int my_printf(char* string, ...)
{
u8 str_length;
u8 x, y;
x = START_X;
y = START_Y;
u32 u32_temp;
// float num_float;
char char_temp = NULL; //临时变量
char* str_temp = NULL;
va_list ap; //定义char* 变量 ap
int count = 0;
va_start(ap, string);//为arg进行初始化
while(*string != '\0')
{
if (*string < 0x80) //最高为是0,为英文字符,否则为汉字
{
if (*string != '%' && *string != '\n')//为英文字母,照常输出
{
LCD_ShowChar(x, y, PRINTF_FC, PRINTF_BC, *string, PRINTF_SIZE, PRINTF_MODE);
x += PRINTF_SIZE / 2;
string ++;
count ++;
continue;
}
else if (*string == '\n')
{
y += PRINTF_SIZE;//换行
x = 0;
string++;
count ++;
continue;
}
else
{
switch(*++string)
{
case 'd':
u32_temp = va_arg(ap, int);
str_length = LCD_ShowNum(x, y, u32_temp, PRINTF_SIZE);
string ++;
x += str_length * PRINTF_SIZE / 2;
count += str_length;
break;
case 'c':
char_temp = va_arg(ap,int);
LCD_ShowChar(x, y, PRINTF_FC, PRINTF_BC, (u8)char_temp, PRINTF_SIZE, PRINTF_MODE);
x += PRINTF_SIZE / 2;
string ++;
count ++;
break;
case 's':
str_temp = (char*)va_arg(ap,int);//取下一个参数的地址,因为这个是字符串
Show_Str(x, y, PRINTF_FC, PRINTF_BC, (u8*)str_temp, PRINTF_SIZE, PRINTF_MODE);
str_length = strlen(str_temp);
x += str_length * PRINTF_SIZE / 2;
if (x >= lcddev.width)
{
y += (x / lcddev.width) * PRINTF_SIZE;
x %= lcddev.width;
}
string ++;
count += str_length;
break;
// case 'f':
// break;
default: //
LCD_ShowChar(x, y, PRINTF_FC, PRINTF_BC, *--string, PRINTF_SIZE, PRINTF_MODE);
x += PRINTF_SIZE / 2;
LCD_ShowChar(x, y, PRINTF_FC, PRINTF_BC, *++string, PRINTF_SIZE, PRINTF_MODE);
x += PRINTF_SIZE / 2;
string ++;
count ++;
break;
}
}
}
else//中文
{
GUI_DrawFont16(x, y, PRINTF_FC, PRINTF_BC, (u8*)&string, PRINTF_MODE);
string += 2;
x += PRINTF_SIZE;//下一个汉字偏移
continue;
}
}
va_end(ap);//将arg指向空,防止野指针
return count;
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stdarg.h"
#include "timer.h"
#include "gui.h"
int main()
{
SystemInit(); //初始化系统,系统时钟设定为72MHz
delay_init(); //配置systick,中断时间设置为72000/72000000 = 1us
LCD_Init(); //液晶屏初始化
delay_ms(10);
LCD_Clear(WHITE);
int a, b, c, d, e;
a = 1;
b = 2;
c = 3;
d = 4;
e = 5;
my_printf("Hello World!\n%d\n%d\n%d\n%d\n%d", a, b, c, d, e);
while(1)
{
}
}
回到顶部
/**********************************缓冲区管理*********************************/
#define SEND_BUFF_SIZE 9150
#define RECEIVE_BUFF_SIZE 6000
#if USE_SEND_BUFF
typedef struct
{
_Bool first_falg;
u32 data_length;
u32 p_write;
u32 p_read;
_Bool tpye[SEND_BUFF_SIZE];
u8 data[SEND_BUFF_SIZE];
}send_buff_typedef;
extern send_buff_typedef send_buff;
#endif
/******************************以上是缓冲区管理*******************************/
/****************************************************************************
* 名 称:write_buff(_Bool type, u8 data)
* 功 能:向缓冲区写入一字节数据
* 入口参数:type:该字节数据对应的类型,1:数据,0:命令
* data:数据
* 出口参数:0:失败,1:成功
* 说 明:type的值只能为1或0
****************************************************************************/
u8 write_buff(_Bool type, u8 data)
{
while (send_buff.data_length >= SEND_BUFF_SIZE);
SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, DISABLE);
/*写指针等于读指针,说明buff是空的,直接将数据发送出去*/
if (send_buff.first_falg)
{
send_buff.first_falg = 0;
LCD_CS_CLR;
if (type)
{
LCD_RS_SET;
}
else
{
LCD_RS_CLR;
}
SPI2->DR = data;
SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, ENABLE);
return (1);
}
else
{
send_buff.data_length ++;
if (type)
{
send_buff.tpye[send_buff.p_write] = 1;
}
else
{
send_buff.tpye[send_buff.p_write] = 0;
}
send_buff.data[send_buff.p_write] = data;
send_buff.p_write = ++ send_buff.p_write >= SEND_BUFF_SIZE ? 0 : send_buff.p_write;
SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, ENABLE);
return (1);
}
}
/****************************************************************************
* 名 称:SPI2_IRQHandler(void)
* 功 能:SPI2中断函数
* 入口参数:无
* 出口参数:无
* 说 明:
****************************************************************************/
void SPI2_IRQHandler(void)
{
if(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY ) == RESET)
{
LCD_CS_SET;//发送完毕,片选拉高
if (send_buff.data_length > 0)//缓冲区由有待发数据
{
send_buff.data_length --;
LCD_CS_CLR; //开始发送数据,先将片选拉低
if (send_buff.tpye[send_buff.p_read])//判断是数据还是命令
{
LCD_RS_SET;
}
else
{
LCD_RS_CLR;
}
SPI2->DR = send_buff.data[send_buff.p_read];
send_buff.p_read = ++ send_buff.p_read >= SEND_BUFF_SIZE ? 0 : send_buff.p_read;
}
else
{
send_buff.first_falg = 1; //缓冲区空,标志置位,下一个数据可以直接发送
SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_TXE, DISABLE);//将中断关闭
}
}
}
SPI2->CR1 |= (1 << 6); //使能BUSY_FLAG
,没找到对应的控制函数,所以直接按照参考手册上的说明直接控制寄存器我们发现改变显示状态的时候总是一个画面一个画面来改变的,而需要设备发送数据只有这一个画面是连续的,那么我们可以将这一个画面对应的数据打一个包,然后等这一个包缓存完了之后再发出去,相比于一边缓存一边发送的方法,可以大大减少了一个包发送需要的时间,从而提高了一个画面刷新的速度,减少了刷面刷新过程中卡顿的现象。
缺点:刷新延迟,缓存区占用的空间更大
我们发现刷新显示的时候只有与上一个画面不同的部分是需要改变的,而刷新其它相同的部分无疑是在浪费资源,所以我们可以每次刷新的时候只改变与上一个画面不同的部分,方法是在写入缓存区之前进行判断,只将需要改变的部分写入,从而可以减少数据写入的量减少刷新画面需要的时间,提高刷新率,同时也减少了缓冲区占用的内存
缺点:由于判断也需要时间,使得刷新的延迟更加明显了
站在最高层,从最高点出发依次往下设计,首先规划中断和资源->状态机建模->确定接口,用模拟实现的方法将程序模块化->最后完善事件处理程序
优点:思路清晰
从硬件底层出发,一步步完善硬件驱动->控制寄存器来达到需要的效果->将驱动程序封装,确定接口->完善模块
优点,底层设计完善,易调试
注意函数封装、可重入性、移植性、模块之间的耦合性
熟能生巧,只有多练习才能有所提高,光说不练只会原地踏步
**说明:**本文仅作个人技术交流
Good Luck!
回到顶部