完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
文章目录 一、认识其本质 (一)认识SPI
(三)TFT优势
二、所需材料 三、底层建筑 (一)模拟SPI
/**************************************************************************** * 名 称: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); } 初始化GPIO //****************************************************************** //函数名: 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); } 初始化TFT //****************************************************************** //函数名: 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) { } } ———————————————— 版权声明:本文为CSDN博主「__海阔天空__」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_39432978/article/details/81873689
**************************************************************************** * 名 称: 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) { } }
(二)硬件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); } 编写发送函数,只要发送的时候将数据送入发送BUFF,数据就能够自行移位发送出去,但是将数据送入BUFF之前要判断发送区是否空,否则会覆盖之前的数据,导致数据丢失 /**************************************************************************** * 名 称: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; //返回收到的数据 } 修改写入数据的底层函数,并且兼容之前的IO口模拟,当硬件SPI不好使的时候可以使用IO口模拟,设置是否选择使用高速模式 /**************************************************************************** * 名 称: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:低速模式 设置好了之后可以开始测试了,把STM32的SPI时钟调到最高,发现显示确实快了很多,延迟小了很多,然后问题就来了,想看一下这时候的时钟到底是多少,就用逻辑分析仪测了一下,瞬间绝望,这什么鬼,怎么啥都没有,难道顺序错的,可是显示都是正常的啊 ———————————————— 版权声明:本文为CSDN博主「__海阔天空__」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_39432978/article/details/81873689
回到顶部 1、关于NSS
四、上层建筑
(一)写入图片 1、原理
2、实现代码 /**************************************************************************** * 名 称: 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); } } 显示效果还是挺好的,毕竟TFT嘛,手机拍照太垃圾。。。 ———————————————— 版权声明:本文为CSDN博主「__海阔天空__」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_39432978/article/details/81873689
2、写入变量 写入一个整形变量 /**************************************************************************** * 名 称: 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); } } 回到顶部 3、写入中文 汉字的编码方式: 每个英文字符对应一个字节,这就是ASCII码,如31-‘1’,41-‘A’,‘61’-‘a’.美国人定的标准 汉字采用2字节编码(现在不完全准确),国家制定。现在的标准是GB18030,早期是GB2312-80。前者含盖后者 一个字节是8位,ASCII码最高位是’0’(所以最多128个编码)。 汉字将最高位置为’1’,与ASCII码(英文符号)区隔开。 软件当读取一个字节时,先判断最高位是否为’0’。若是,则作英文符号处理;若不是,再读取下一个字节,两个字节合一处对应一个汉字。如:D1A7-‘学’,CFB0-‘习’。 使用取模软件将要写入的汉字进行取模,软件设置如下,按照如下设置方式进行设置,取模出来的数据不必进行修改就能够直接使用 将取模得到的数据存入字库,先定义一个结构体,结构体的成员包括汉字对应的像素数据和汉字检索的标志,"我"其实是一个16位的数据,也就是汉字在计算机中存储的内码,它的特点是最高位为1,而英文的最高为为0,这为我们区分汉字和英文提供了极大的便利 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 & (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 & (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!nn我爱学习", 16, 1); while(1) { } } 1 回到顶部 (三)自己写printf函数 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的值…,这样一来我们就能把可变的参数编程确定的参数了 2、实现代码 然后就是printf的实现了 /**************************************************************************** * 名 称: my_printf(char* string, ...) * 功 能: 格式化显示字符串 * 入口参数: *string:字符串 * ...:可变参数 * 出口参数: count:字符的总个数(一个中文占两个字符) * 使用范例: int result; * int a = 666; * result = my_printf("Hello World!n%dn%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 != ' |