STM32:SPI 与 TFT

您所在的位置:网站首页 spi3线和4线区别 STM32:SPI 与 TFT

STM32:SPI 与 TFT

2024-07-15 20:29:16| 来源: 网络整理| 查看: 265

STM32系列博客:  STM32学习之旅① 开发环境搭建 STM32学习之旅② 固件库的使用及工程模板的建立 STM32学习之旅③ 从点灯到代码移植 STM32学习之旅④ USART串口和上位机通信 STM32学习之旅⑤ SPI控制TFT,从底层到底层的设计 目录:

一、认识其本质 (一)认识SPI (二)常见显示器 (三)TFT优势 二、所需材料 三、底层建筑 (一)模拟SPI (二)硬件SPI 1、关于NSS 四、上层建筑 (一)写入图片 1、原理 2、实现代码 (二)写入文字 1、写入英文 2、写入变量 3、写入中文 (三)自己写printf函数 1、原理 2、实现代码 五、缓冲区 (一)硬件隔离 (二)实现程序 (三)数据帧和数据包 (四)变与不变 六、程序设计 (一)程序设计一般思路 1、自上而下 2、自下而上 (二)最后 一、认识其本质

(一)认识SPI

SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI 总线是Motorola公司推出的同步串行接口技术,(关于同步还是异步,主要看通信双方的时钟线是否连在一起)。SPI由四根线完成数据传输,分别是SCK(时钟)、MOSI(主出从入)、MISO(主入从出)和SSEL(片选)。通讯时序图如下(当然很多SPI器件的数据手册都会给出响应的时序图):  SPI主要特点有:

可以同时发出和接收串行数据,是一种全双工的通信; 可以当作主机或从机工作 提供频率可编程时钟 发送结束中断标志 写冲突保护 总线竞争保护等 结构简单 传输速度快,通常可以达到几兆到几十兆每秒,常用于数据量比较大的传输。 主要缺点:

需要占用较多的接口线 只能有一个主机 没有应答机制 (二)常见显示器

关于常见的LCD、OLED、TFT (三)TFT优势

TFT(Thin Film Transistor)是薄膜晶体管的缩写。是有源矩阵液晶的一种。是最好的LCD彩色显示器之一,TFT式显示器具有高响应度、高亮度、高对比度等优点,其显示效果接近CRT式显示器

TFT上的每个液晶像素点都是由集成在像素点后面的薄膜晶体管来驱动,因此反应时间较快,

并且可视角度大,一般可达130度左右

色彩丰富,可支持65536色

回到顶部

二、所需材料

TFT显示屏,看着高大上的电容屏真的买不起。。。

逻辑分析仪,对没错贫穷限制人的想象,不过24M的采样率测个SPI还是勉强能够应付

取模工具, 密码:tjwu 三、底层建筑

(一)模拟SPI

STM32内部外设自带SPI接口,为什么还要用IO口去模拟呢?原因就是移植性,用IO口模拟的程序移植性是最好的,无论是51、msp430还是stm32,或者是DSP,都可以用IO口实现,我们知道经典的51单片机是没有SPI接口的,这时候就需要IO口模拟去实现;在初期写显示驱动程序的时候,IO口模拟出错的可能性比直接用SPI接口小很多,可以大大提高我们写驱动的效率;另外,用IO口模拟SPI还可以让我们对SPI的读写时序有更深刻的认识。 这是TFT手册中对引脚的描述 由设备最短高电平保持时间,可以粗略计算出该设备能够兼容的SPI最高速度为1/(15*2ns)=33MHz,可见SPI是一种高速协议,然而STM32中SPI2的最高速度只支持16MHz,没有能发挥TFT的最高性能 

根据以下时序图可以用软件模拟出数据传输的时序,并对TFT进行控制 /**************************************************************************** * 名    称: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 #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)     {

    } } 可以看到一整块屏幕都变成红色的了

写入了一堆0XF800,怪不得花儿那样红 回到顶部

(二)硬件SPI

通过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 #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时钟调到最高,发现显示确实快了很多,延迟小了很多,然后问题就来了,想看一下这时候的时钟到底是多少,就用逻辑分析仪测了一下,瞬间绝望,这什么鬼,怎么啥都没有,难道顺序错的,可是显示都是正常的啊

后来仔细想了想,信号里面的奈奎斯特采样定理给了我点启发(论学以致用的重要性),由奈奎斯特采样定理可知,将模拟信号转化为数字信号的时候,采样的频率至少要是被测信号频率的2倍才不会导致失真,但是实际使用的时候一般设置采样频率为2.56~4倍,根据STM32官方数据手册知道SPI的最高时钟可以达到18MHz,可怜的我的逻辑分析仪最高采样频率才是24MHz,又去淘宝店家查了以下这个逻辑分析仪测量带宽只有5MHz。。。好了,确实是贫穷限制了人的想象,等我有钱了一定把你家淘宝店都买下来,好吧,我有钱还在赶这玩意儿,醒醒吧

将SPI时钟调到8分频,这一波总算能测了,不过看上去还是有点失真,测了一下频率8分频是4MHz,那么最高2分频对应的应该是16MHz,也就是说SPI2的最高频率是16MHz,目前还不知道怎么调到18MHz,不过16MHz也够用了。 回到顶部

1、关于NSS

前面提到过这个NSS,被这个坑了一整天。。。这个其实是STM32SPI自带的片选信号,NSS可以选择软件控制模式和硬件控制模式,所谓软件控制就是另外配置一个IO口来作为片选信号,和IO口模拟类似,发送数据前先将CS拉低,再把数据送入发送BUFF,发完之后再拉高;但是我担心的是这样拉低又拉高的过程占用时间,导致效率降低,然后一直在寻找能够自动完成片选的方法,之前一直以为硬件SPI的CS 能够自动拉高和拉低,但是发现我错了。以下这幅图可以清楚的告诉你为什么 配置硬件NSS模式,将其设置为输出使能,只需要将SSOE为使能就行了,配置代码如下,红色圈起来的部分,没错,注释掉的那一句就是失传多年的寄存器操作(和下面那一句是等价的),这可是我查阅了数据手册找到的寄存器表,结果你告诉我固件库是有相应的函数的。。。固件库真牛逼,看来是我老了。。。

好了,然后再去测时序,发现CS在初始化完之后被拉低了,但是不再被拉高,这样也是能够完成传输的,但是这和直接将CS接地有什么区别。。。正如ST官方手册所说,硬件NSS输出模式只能用于单主机通信,但是为了数据传输避免干扰,我还是选择了软件模式,慢就慢一点吧,反正也不会慢到哪去,毕竟STM3272MHz主频摆在那里 到这一步说明硬件的SPI已经配置好了,可以开始我们的上层建筑了 回到顶部

四、上层建筑

底层建筑搭得好不好往往决定了上层建筑的好坏。之前已经成功将一整块屏幕填充上指定的颜色,但是这仅仅是小小的实验而已,实际使用时我们通常用TFT来显示一些文字和图片,显示文字和图片就涉及到一些算法了,这一部分的好坏往往取决于设计者的软件水平,而作为嵌入式设计这也是最核心的部分,和硬件相关的部分仅仅是为上层建筑搭建根基,但是有根基还是不能够住人的,底层设计的好坏才是决定这个设计的总体价值。而一个优秀的嵌入式工程师总能够将整个系统的差异局部化和最小化,以提高系统的移植性去适应不同的底层建筑。当然市面上越来越多的嵌入式系统都将器件的差异集中到一部分,还有各种解决方案的推出为嵌入式开发者提供了更大的便利。 回到顶部

(一)写入图片

1、原理

这款TFT是128*128像素的显示器,每一个像素点都直接由内部逻辑控制,每个像素点可以显示65536种颜色。要显示图片的时候就要将要显示的图片先取模,找出该图片每一个像素点对应的色彩数据,将其存入数组

打开取模工具,打开要显示的图片,设置好之后点保存,就会生成128*128*2=32768个数据的数组,这里有个小tips,修改图像数据可以使用微软自带的画图工具(当然有ps大佬要用Photoshop来弄这个我也没意见),有些教程说要转成bmp格式才能够显示,试了一下发现.jpg也行

新建一个.h文件,用来存放要显示的图片,将取模得到的数据存入.h文件 2、实现代码

然后再程序中将数据显示出来,设置显示窗口->将数据挨个写入TFT /**************************************************************************** * 名    称: 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         Gui_Drawbmp16(0, 0, 128, 128, gImage_bear_dolls);     } } 显示效果还是挺好的,毕竟TFT嘛,手机拍照太垃圾。。。 回到顶部

(二)写入文字

显示图片相对于显示文字来说用的还是比较少的,一般是装逼用的。那么我们就来搞点实用的东西吧 1、写入英文

写入一个英文字符,由之前写入一个点到写入一个英文字符 /**************************************************************************** * 名    称: 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 = ' '))//判断是不是非法字符!     {         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!诞生了,骚气的红色字体,对了还可以给背景加颜色的

确实骚气十足 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[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函数

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%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)     {     } } float类型的参数正在想办法。。。 回到顶部

五、缓冲区

(一)硬件隔离

之所以硬件隔离,是因为外设是慢速设备,二我们的MCU(单片机)是高速设备,在写入数据的过程中会出现等待,这就造成了堵塞,解决方案是将硬件隔离开来,方法是建立一个缓冲区,上层建筑(发送程序)每次只往缓冲区写入数据,而底层驱动每次从缓冲区读取数据,然后通过中断发送出去,通过判断缓冲区是否为空可以判断数据是否发送完成,从而可以进入低功耗模式节省功耗。之所以这样做因为我们发现利用硬件SPI的时候只有每次往发送BUFF中写入数据的时刻是需要MCU进行控制,而其他时间硬件是可以自己完成的,通过建立缓冲区可以实现高速设备(MCU)和低速设备的缓冲,同时降低减少堵塞,大大提高了程序的效率。 (二)实现程序

先定义缓存buff /**********************************缓冲区管理*********************************/ #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);     } } 利用中断读取缓存中的数据,由于spi相对于mcu来说是一个慢速的设备,缓存可以起到缓冲的二作用,从而解决了由于mcu等待spi造成的堵塞 /**************************************************************************** * 名    称: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);//将中断关闭         }     } } 由于中断中需要判断总线是否忙,然后再从缓存中将数据送入发送buff,所以需要开启标志位使能SPI2->CR1 |= (1 状态机建模->确定接口,用模拟实现的方法将程序模块化->最后完善事件处理程序

优点:思路清晰

2、自下而上

从硬件底层出发,一步步完善硬件驱动->控制寄存器来达到需要的效果->将驱动程序封装,确定接口->完善模块

优点,底层设计完善,易调试 ---------------------  作者:__海阔天空__  来源:CSDN  原文:https://blog.csdn.net/qq_39432978/article/details/81873689  版权声明:本文为博主原创文章,转载请附上博文链接!



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭