【学习过程】
5月10日,我收到了面包板社区发来的快递,我怀着非常激动的心情打开了这个快递,见到了板子,参见网址https://mbb.eet-china.com/blog/3876349-406843.html。
从5月13日开始,我利用闲暇时间,每天学习一个小时。要是周末有时间的话,我会多学一点。因为我这么多年来已经不学习相关知识了,对这部分知识已经很陌生了,希望利用面包板社区的活动,把自己年久失修的知识建筑,再一次建好。
5月13日,周一,今天主要任务是开会,下午找板子资料,做好测试板子的准备工作
今天上午开完会后,事情不是很多,我下午的时间在NUCLEO-L412KB开发板相关的资料,主要还是从www.st.com网站上学习相关的知识。
https://www.st.com/content/st_com/en/search.html#q=L412-t=resources-page=1 这个网址有很多的资源,下载学习。
在网站的首页上就有一个醒目的Software Development Tools,在这个里面找到相关的工具和软件。点进去就是一个简单的介绍,英文版如下:
Complex programmable silicon components require a full complementary Ecosystem.
ST and its partners provide an extensive range of Software Development Tools that are increasingly becoming an important criterion in the selection of semiconductor devices.
Microcontrollers and microprocessors have always required assemblers, compilers, and linkers as well as debugging and programming software.
Software developers will also appreciate a wide range of integrated development environments (IDEs) able to configure and initialize the MCU or MPU as well as monitor its behavior in run time.
These tools are available both from ST and from a wide selection of 3rd party suppliers. Most tools are available as free downloads in the following pages while some are available online through the ST distribution network.
翻译成汉语如下:
复杂的可编程半导体组件需要一个完整的互补生态系统。
ST和合作伙伴提供了广泛的软件开发工具,越来越成为选择半导体器件的一个重要标准。
微控制器和微处理器总是需要汇编程序、编译器和链接器以及调试和编程软件。
软件开发人员还用到一系列的集成开发环境(IDEs),这些环境能够配置和初始化MCU或MPU,可以运行时实现实时监控。
这些工具既可从ST供应商和其他第三方供应商下载。大多数工具都可以在下面的页面中免费下载,而有些则可以通过ST网络上在线下载。
意译如下:
我们的ST公司是很厉害的,我们的开发工具非常完全,很多工程师都在使用我们的芯片。
进去之后选择第一个软件,Arm Development Studio。附带的英文解释是:Comprehensive embedded toolchain for any Arm-based device: Eclipse-based IDE/debugger, C/C++ Compiler, CMSIS, performance analyzer, graphics debugger。翻译成汉语是:任何基于arm设备的综合嵌入式工具链:基于Eclipse的IDE/调试器、c/c++编译器、CMSIS、性能分析器、图形调试器。
意译就是:我们的软件很强大。快来使用我啊。
下载完成软件后就是安装。
1.下载软件,双击安装包,进入安装向导界面,点击“Next”。2.勾选“I agree to...”,点击“Next”。3.选择软件和支持包安装路径(可以默认),点击“Next”。4.填写信息(可以随便填写),点击“Next”。5.安装过程需要等待几分钟。6.安装结束时,弹出下图提示,选择“安装”。7.安装完成,点击“Finish”。8.自动更新“支持包”,可以直接退出,自己下载安装。到这里,Keil MDK-ARM就安装完成。9.还有下一步就是破解的任务了,打开注册管理窗口(File -> License Management),并复制CID(备用)。10.打开“注册机”:(1)粘贴上面复制的CID,(2)目标选择ARM,(3)生成注册码,(4)复制注册码。可以新建工程使用了。
打开软件看了一下,和Keil2界面好像区别有点大啊。记得几年前的时候用的是Keil C51,基于uVision IDE的,那时候学习的芯片就是C51,很好用的一个软件,现在发展到了Keil5。打开Keil5的瞬间,就是感觉Keil5比以前的版本高达上了许多,或者对我来说就是难了很多。百度了下keil5怎么建一个工程,一点点的建立一个工程。虽然我十多年没有用这个东西了,但是这个难度对我来说还是可以接受的。
5月14日,周二,昨天软件装好了,今天没有大段的时间来研究板子,可以利用间隙时间看一下资料
工作中,你可能不会讨厌工作有多难,也可能不会抱怨工作多紧急,但是最不好的事情可能就是不一会就有人打断你,并且还是无意义的来打断你。
也没有时间来研究板子,索性看一点STM32L412xx的datasheet,那就翻译一下吧,督促自己更好的理解datasheet中的芯片的解释。正好也有做一个翻译的活动的计划。这个翻译应该不涉及到版权,也可以发布到网站上,让多的英语好的板友(自己定义的,也可以叫自己“板子”),希望管理员不要介意。
此datasheet没有完全翻译,要是全翻译,一个月估计也完成不了,我看到的认为相对重要的部分。有点教师的职业病,总是喜欢划重点。
意法半导体的STM32L4xx微控制器是特色是功能专一和封装紧凑,相对比较低廉的成本预算,为了消费类、工业和医疗应用不仅带来了超低功耗,还有高速的处理速度。新的微控制器在设计的时候,突破各种设计资源的制约,为设计工程师提供了更大的发挥空间,一方面是内核性能,一方面是能耗限制,一方面是空间尺寸,一方面是芯片成本。对于以上的四个方面的限制,意法半导体采用的Flex Power Control技术,这个地方好像是电源控制技术,非常好的,新的微控制器的能效和性能也非常的好,同时达到了类似产品中的最佳性能,在2个不知道名字的测试中表现非常好。研发人员可以利用STM32L4xx微控制器做出很多很好的产品,还可以利用低功耗模式来最大限度地延长电池续航时间。此外,为节省元件和电路板空间,STM32L4xx微控制器提供多钟封装方式,包括5mm x 5mm LQFP 32封装方式,还有2.58mm x 3.07mm WLCSP36封装方式。工作温度范围,-40°C至85°C或125°C,STM32L4xx微控制器耐受恶劣的工作温度环境,ECC闪存纠错和片上SRAM奇偶校验,这两种校验方式大大加强了STM32L4xx微控制器系统的安全性。Quad-SPI接口可以直接连接片外存储器,扩展系统的存储容量。STM32L4xx微控制器还配备丰富的控制外设和通信接口。STM32L4xx微控制器在电源系统方便表现也很好,工作电流最低可以到28μA/ MHz。Quad-SPI接口可以连接高性能的片外存储器,扩展系统的存储容量。STM32L4xx微控制器还配备丰富的控制外设和通信接口。STM32L4xx微控制器还集成了随机数发生器,加密采用AES-256加密算法,加密性能好。
翻译了这么多,其实总结一句话就是写这个STM32L4xx微控制器好的,性能有多强大,工作效率有多高。
5月15日,周三,写代码可能一时也学不会,就找到了其他的代码来测试下
看了一些资料,准备做的时候我发现了我存在一个很大 的问题,这么多年没有学习了,我发现我几乎看不懂代码,更不要说是自己写代码了。在最开始学习单片机的时候,就是看视频,自己写一些简单的代码,还有一个方法就是找到类似的代码,直接移植到自己的芯片上,我发现还是第二个方法要简单方便一些,说做就做,我在网上搜索了一部分关于显示屏显示汉字的代码,就准备开始移植程序。
我在工作的时候,出现了很多问题,我也不知道大家是自己写代码,还是移植代码。我总结一下我的问题,希望大家也可以借鉴下。
虽然看着要实现的功能是一样的,但是芯片的内容大同小异,就会出现各种意想不到的问题,代码移植的错误就在我们这里发生了,代码移植往往就没有策划想象的那么简单了。程序需要抽丝剥茧,小心翼翼地把代码复制来粘贴去的,稍有遗漏就会出问题。特别的,当我们在提出移植程序的时候,我们总是非常想当然的以为这是很简单的一件事情:“在之前的芯片上都不是已经实现功能了吗?照着抄一遍不就OK了?一天的时间搞定如何?”。这时,心里真是有苦难言的感觉,尤其是对于我这种很久没有看程序的人来说,这一行代码是什么意思啊,为什么这里有这么一行代码啊,这样代码为什么要放在这个位置啊,这个Keil5为啥要这么用啊。一系列的问题就这么出现了,很多程序都没有搞清楚,我们就开始程序移植,这是让程序玩火自焚啊!自己都会不知道程序飞到什么地方去了。这简直就是放飞程序啊。
遇到了这么大的问题,我还是第一时间想到了百度,虽然我也不是很喜欢这个,有时候需要找资源的时候,也没有办法,只能用这个,我搜索了下,代码移植是怎么回事?
代码移植一般来讲与代码的可重用性有一定的区别,代码移植往往是将不同平台、不同编译环境的程序代码经过修改转移到自己的系统中运行,这与代码的可重用性有着本质的区别。
这是百度百科上讲到的知识点。
这时候我就有一个问题了,当时在学习的时候不是说代码可以移植的啊?为什么这里说代码移植和可重用性有着本质的区别?难道代码不能这么来用吗?
带着这些问题,我还在搜索相关的资料,总结了以下如何移植程序的一些心得。和大家一起分享。
对于一般的项目来说,我们都需要一定的开发流程,开发流程大概都是这样的。自己先熟悉自己用的硬件,然后按照自己选择的硬件来编写代码。现在很少听见代码移植这样的事情。为什么?其实我也不是很清楚,那时候上学的时候都是这么学的啊,看来是现在的程序越来越大了,压根就不适合移植了,就算是新需求要求抄一个相同项目的代码,这也要求提需求的人,提供详细的文档,经过严格的评审,程序才能移植,然后才能实现需要的功能。
我们自己的想法就是直接拿来用,想COPY一下,也不是很难啊。但是旺旺看起来是很容易的事情,但是代码COPY起来就没那么简单了。和同事聊天的时候就说,代码移植这个只是适合讲课,根本不适合工程实现。这句话简直更新了我的认知啊。有代码的,可以参考,但是一定不能用,因为系统架构的差异,早就决定了,代码移植几乎是不可能的一件事情。
对于学习来说,我学习NUCLEO-L412KB开发板来说,只是简单的实现一个很简单的程序,并且我对程序也不是很熟悉的人,可以通过看别人的代码,适当的修改就可以实现的,那既然可以这样,那问题就又来了,我怎么进行代码移植呢?困难又在哪里?我把我这几天的心得写一下,大家一起分享,写的可能也有不恰当的地方,欢迎大家指正。
代码移植,首先需要有一个类似的项目,这个是完全没有问题的,要是需要一直的代码本身就有问题,你再怎么移植也不会出现好的结果的。这个是前提条件。
NUCLEO-L412KB开发板需要的代码可以从之前的下载,这就要求两者的代码,差异不大的时候,svn merge工具基本上完全可以胜任这件事情。merge是干啥的呢?使用过SVN的都知道,SVN,其实就是一个代码管理的软件,使用它可以很好的解决代码冲突,这个就可以拿来用了。他可以针对一个文件产生不同的版本,每一个版本都对应自身的改动。因为它记录了每一个文件的修改变化(可以理解为文件之间的不同点diff),这对于我们的开发,理解有很好的作用。
NUCLEO-L412KB开发板需要的代码就可以直接从原先的代码中复制就可以了。因为这个时候,代码冲突会比较少,这个阶段代码移植是一件可以迅速完成的事情,遇到代码冲突,修改一下即可。
如果NUCLEO-L412KB开发板需要的代码和搜索到的代码的差异如果非常大,工具合并,可能不是那么好用,可能冲突一大堆,解决这些冲突的时间,还不如手动复制代码来的快一些。
总结下上面两种方法,那就是当某些功能的差异没那么大的时候,先用工具试着合并一下,如果冲突的文件数量大的可怕,那就手动复制;如果冲突量尚在可以接受的范围内,仅对于那些冲突特别严重的文件进行手动复制。工具和手动复制两者结合起来,比完全使用工具和手动复制效率要高一些。
总之,不管是初学者,几乎看不懂代码,就是为了验证的,还是为了工程上的应用,为了简单,快速完成项目,需要移植代码的时候!不过一定要记着:
移植代码必须在对功能理解和看懂要移植代码的前提下进行,否则出问题就抓瞎,没有对错的标准,就没法进行正确性测试。就算工具可以完全搞定,这样做也是必须的,因为出了BUG还得你来改。
5月16日,周四,第一次连接测试
NUCLEO-L412KB开发板上电后没有反应,首先要检查什么? 首先应该确认电源电压是否正常。因为NUCLEO-L412KB开发板的灯是亮的,确定这个电源没有问题。用电压表测量接地引脚跟电源引脚之间的电压,看是否是电源电压是不是正常的。接下来就是检查NUCLEO-L412KB开发板与ILI9341液晶屏之间的接线是不是对的,因为这个是用杜邦线链接的,应该不会出现问题,除非VCC和GND接反的错误。然后再检查晶振,是否起振了,这个方法参照网上的资料,一般用示波器来看晶振引脚的波形,注意,应该使用示波器探头的X10档。另一个办法是,测量复位状态下的IO口电平,首先,按住复位键不放,然后,测量IO口的电压,看是否是高电平,如果不是高电平,则多半是晶振的原因,没有起振。
5月17日,周五,第二次连接测试
找了各种各样的原因好错误,还是没有反应。
5月18日-19日,周末,天津雷阵雨
下雨,还是雷阵雨,雷声滚滚,我没有研究NUCLEO-L412KB,实在是太累了,还没有研究明白,我坐在电脑前总结自己一周的工作,这一周来,出去自己的工作外,还看看NUCLEO-L412KB板子,虽然自己知道的知识是很少的,但是自己也是为了学习申请这个NUCLEO-L412KB板子的。虽然现在自己学习的并不好,也没有前面的人发出来的帖子好,但是我觉得我了解了这些知识,我也是非常开心的。
我坐在办公室的电脑前,我在想,还是找一个年轻老师问一下,这个NUCLEO-L412KB到底是怎么搞的。
5月20日,周一,
有半个月的时间一直在研究自己的程序,到底是什么地方出了问题,这么多年来,好久不看程序,真的是对这些知识都陌生了,连一个显示程序都弄不出来了。惭愧啊。
我的想法就是NUCLEO-L412KB硬件驱动2.2寸液晶显示屏。按照学习来说,这个不是特别的难,但是对于我好多年没有看书学习的人来说,真的是有点难了。看来学业不精。
上午的时候找了一个老师,和我一起看这个,其实也是我学习,找人家来帮忙的。
5月21日,周二,
我们使用的接口是8位并口操作,可以进行常用图形的显示、字符串显示,以及中文显示,由于中文的字库较大,因此我们可以按照自己的需求加入中文字库。
下面主要说下如何加入自己的中文字体,主要修改软件库中的ili9341_font.h文件,因为我们使用的是utf-8编码格式,因此也可以显示其他外文字体。
1、我们先要制作字库(只需要加入自己需要的):
我们使用附件中的软件pctolcd工具制作:
打开软件后,在配置中选择如下:
然后我们在软件中填入自己需要显示的所有中文字符,并选择size大小(请选择16x16和24x24的),点击生成字模,再把生成的字模拷贝到ili9341_font.h文件中的Chinese_code[]数组中,如下:
2、我们需要制作汉字对应的UTF8编码表:
首先我们使用附件中的工具(utf8中文编码生成工具)来生成,我们只需要把你需要的所有汉字填写到该软件目录下的 text.txt 文件中(注意:text.txt文件要保证是一个utf8格式的),然后打开软件选择好汉字文件和utf8编码文件,点击生成编码,即可在coding.txt文件中生成汉字对应的utf8编码表,打开该文件复制编码表填入到ili9341_font.h文件中的Chinese_text[]数组中。经过上面的步骤我们就制作号了自己的汉字字库了,现在我们只需要使用就可以了。
5月22日,周三,
调试
5月23日,周四,
调试
5月24日,周五,
调试
5月25日-26日,周末,完成了最后的测试
最后,我还是在别人的帮助下,参照其他板子的程序,完成这一部分。
#include "bsp_ili9341_lcd.h"
#include "ascii.h"
#include "bsp_sdfs_app.h"
#define DEBUG_DELAY()
void Lcd_Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
/**
* @brief 初始化控制LcD的IO
* @param 无
* @retval 无
*/
void LCD_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能FSMC时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);
/* 使能FSMC对应相应管脚时钟*/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD
| RCC_APB2Periph_GPIOE
| RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 配置LCD复位控制管脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* 配置FSMC相对应的数据线,FSMC-D0~D15: PD 14 15 0 1,PE 7 8 9 10 11 12 13 14 15,PD 8 9 10*/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8 | GPIO_Pin_9 |
GPIO_Pin_10 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 |
GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 |
GPIO_Pin_15;
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* 配置FSMC相对应的控制线
* PD4-FSMC_NOE :LCD-RD
* PD5-FSMC_NWE :LCD-WR
* PD7-FSMC_NE1 :LCD-CS
* PD11-FSMC_A16 :LCD-DC
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 配置LCD背光控制管脚*/
#if 1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
#elif 0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_ResetBits(GPIOD, GPIO_Pin_13);
#endif
}
/**
* @brief LCD FSMC 模式配置
* @param 无
* @retval 无
*/
void LCD_FSMC_Config(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef p;
p.FSMC_AddressSetupTime = 0x02; //地址建立时间
p.FSMC_AddressHoldTime = 0x00; //地址保持时间
p.FSMC_DataSetupTime = 0x05; //数据建立时间
p.FSMC_BusTurnAroundDuration = 0x00;
p.FSMC_CLKDivision = 0x00;
p.FSMC_DataLatency = 0x00;
p.FSMC_AccessMode = FSMC_AccessMode_B; // 一般使用模式B来控制LCD
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
//FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_NOR;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
/* 使能 FSMC Bank1_SRAM Bank */
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);
}
/**
* @brief LCD 软件复位
* @param 无
* @retval 无
*/
void LCD_Rst(void)
{
GPIO_ResetBits(GPIOE, GPIO_Pin_1); //低电平复位
Lcd_Delay(0xAFFf<<2);
GPIO_SetBits(GPIOE, GPIO_Pin_1);
Lcd_Delay(0xAFFf<<2);
}
/**
* @brief 配置lcd初始化寄存器
* @param 无
* @retval 无
*/
void LCD_REG_Config(void)
{
/* Power control B (CFh) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xCF);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x81);
LCD_ILI9341_Parameter(0x30);
/* Power on sequence control (EDh) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xED);
LCD_ILI9341_Parameter(0x64);
LCD_ILI9341_Parameter(0x03);
LCD_ILI9341_Parameter(0x12);
LCD_ILI9341_Parameter(0x81);
/* Driver timing control A (E8h) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xE8);
LCD_ILI9341_Parameter(0x85);
LCD_ILI9341_Parameter(0x10);
LCD_ILI9341_Parameter(0x78);
/* Power control A (CBh) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xCB);
LCD_ILI9341_Parameter(0x39);
LCD_ILI9341_Parameter(0x2C);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x34);
LCD_ILI9341_Parameter(0x02);
/* Pump ratio control (F7h) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xF7);
LCD_ILI9341_Parameter(0x20);
/* Driver timing control B */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xEA);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
/* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xB1);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x1B);
/* Display Function Control (B6h) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xB6);
LCD_ILI9341_Parameter(0x0A);
LCD_ILI9341_Parameter(0xA2);
/* Power Control 1 (C0h) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xC0);
LCD_ILI9341_Parameter(0x35);
/* Power Control 2 (C1h) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0xC1);
LCD_ILI9341_Parameter(0x11);
/* VCOM Control 1(C5h) */
LCD_ILI9341_CMD(0xC5);
LCD_ILI9341_Parameter(0x45);
LCD_ILI9341_Parameter(0x45);
/* VCOM Control 2(C7h) */
LCD_ILI9341_CMD(0xC7);
LCD_ILI9341_Parameter(0xA2);
/* Enable 3G (F2h) */
LCD_ILI9341_CMD(0xF2);
LCD_ILI9341_Parameter(0x00);
/* Gamma Set (26h) */
LCD_ILI9341_CMD(0x26);
LCD_ILI9341_Parameter(0x01);
DEBUG_DELAY();
/* Positive Gamma Correction */
LCD_ILI9341_CMD(0xE0); //Set Gamma
LCD_ILI9341_Parameter(0x0F);
LCD_ILI9341_Parameter(0x26);
LCD_ILI9341_Parameter(0x24);
LCD_ILI9341_Parameter(0x0B);
LCD_ILI9341_Parameter(0x0E);
LCD_ILI9341_Parameter(0x09);
LCD_ILI9341_Parameter(0x54);
LCD_ILI9341_Parameter(0xA8);
LCD_ILI9341_Parameter(0x46);
LCD_ILI9341_Parameter(0x0C);
LCD_ILI9341_Parameter(0x17);
LCD_ILI9341_Parameter(0x09);
LCD_ILI9341_Parameter(0x0F);
LCD_ILI9341_Parameter(0x07);
LCD_ILI9341_Parameter(0x00);
/* Negative Gamma Correction (E1h) */
LCD_ILI9341_CMD(0XE1); //Set Gamma
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x19);
LCD_ILI9341_Parameter(0x1B);
LCD_ILI9341_Parameter(0x04);
LCD_ILI9341_Parameter(0x10);
LCD_ILI9341_Parameter(0x07);
LCD_ILI9341_Parameter(0x2A);
LCD_ILI9341_Parameter(0x47);
LCD_ILI9341_Parameter(0x39);
LCD_ILI9341_Parameter(0x03);
LCD_ILI9341_Parameter(0x06);
LCD_ILI9341_Parameter(0x06);
LCD_ILI9341_Parameter(0x30);
LCD_ILI9341_Parameter(0x38);
LCD_ILI9341_Parameter(0x0F);
/* memory access control set */
DEBUG_DELAY();
LCD_ILI9341_CMD(0x36);
LCD_ILI9341_Parameter(0xC8); /*竖屏 左上角到(起点)到右下角(终点)扫描方式*/
DEBUG_DELAY();
/* column address control set */
LCD_ILI9341_CMD(0X2A);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0xEF);
/* page address control set */
DEBUG_DELAY();
LCD_ILI9341_CMD(0X2B);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x01);
LCD_ILI9341_Parameter(0x3F);
/* Pixel Format Set (3Ah) */
DEBUG_DELAY();
LCD_ILI9341_CMD(0x3a);
LCD_ILI9341_Parameter(0x55);
/* Sleep Out (11h) */
LCD_ILI9341_CMD(0x11);
Lcd_Delay(0xAFFf<<2);
DEBUG_DELAY();
/* Display ON (29h) */
LCD_ILI9341_CMD(0x29);
}
/**
* @brief lcd初始化,如果要用到lcd,一定要调用这个函数
* @param 无
* @retval 无
*/
void LCD_Init(void)
{
LCD_GPIO_Config();
LCD_FSMC_Config();
LCD_Rst();
LCD_REG_Config();
}
/* 设置液晶GRAM的扫描方向
* 当设置成不同的扫描模式时, page(即x) 跟 column(即y) 的值是会改变的
*/
void Lcd_GramScan( uint16_t option )
{
switch(option)
{
case 1:
{/* 左上角->右下脚 显示中英文时用的是这种模式 */
LCD_ILI9341_CMD(0x36);
LCD_ILI9341_Parameter(0xC8);
LCD_ILI9341_CMD(0X2A);
LCD_ILI9341_Parameter(0x00); /* x start */
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00); /* x end */
LCD_ILI9341_Parameter(0xEF);
LCD_ILI9341_CMD(0X2B);
LCD_ILI9341_Parameter(0x00); /* y start */
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x01); /* y end */
LCD_ILI9341_Parameter(0x3F);
}break;
case 2:
{/* 左下角->右上角 显示摄像头图像时用的是这种模式 */
LCD_ILI9341_CMD(0x36);
LCD_ILI9341_Parameter(0x68);
LCD_ILI9341_CMD(0X2A);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x01);
LCD_ILI9341_Parameter(0x3F);
LCD_ILI9341_CMD(0X2B);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0xEF);
}break;
case 3:
{/* 右下角->左上角 显示BMP图片时用的是这种模式 */
LCD_ILI9341_CMD(0x36);
LCD_ILI9341_Parameter(0x28);
LCD_ILI9341_CMD(0X2A);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x01);
LCD_ILI9341_Parameter(0x3F);
LCD_ILI9341_CMD(0X2B);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0xEF);
}break;
case 4:
{/* 左下角->右上角 显示BMP图片时用的是这种模式 */
LCD_ILI9341_CMD(0x36);
LCD_ILI9341_Parameter(0x48);
LCD_ILI9341_CMD(0X2A);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0xEF);
LCD_ILI9341_CMD(0X2B);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x00);
LCD_ILI9341_Parameter(0x01);
LCD_ILI9341_Parameter(0x3F);
}break;
}
/* write gram start */
LCD_ILI9341_CMD(0x2C);
}
/* 以上为LCD底层函数
*------------------------------------------------------------------------------------------------------
*----------------------------------------我是分割线----------------------------------------------------|
*------------------------------------------------------------------------------------------------------
* 以下为LCD应用函数
*/
void LCD_Clear(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color)
{
uint32_t i = 0;
/* column address control set */
LCD_ILI9341_CMD(0X2A);
LCD_ILI9341_Parameter( x >> 8 ); /* 先高8位,然后低8位 */
LCD_ILI9341_Parameter( x & 0xff ); /* column start */
LCD_ILI9341_Parameter( (x+width-1) >> 8 ); /* column end */
LCD_ILI9341_Parameter( (x+width-1) & 0xff );
/* page address control set */
LCD_ILI9341_CMD(0X2B);
LCD_ILI9341_Parameter( y >> 8 ); /* page start */
LCD_ILI9341_Parameter( y & 0xff );
LCD_ILI9341_Parameter( (y+height-1) >> 8); /* page end */
LCD_ILI9341_Parameter( (y+height-1) & 0xff);
/* memory write */
LCD_ILI9341_CMD(0x2c);
for( i=0; i < width*height; i++ )
{
LCD_WR_Data( color );
//Delay(0x0FFf);
}
}
void LCD_SetCursor(uint16_t x, uint16_t y)
{
LCD_ILI9341_CMD(0X2A); /* 设置X坐标 */
LCD_ILI9341_Parameter(x>>8); /* 先高8位,然后低8位 */
LCD_ILI9341_Parameter(x&0xff); /* 设置起始点和结束点*/
LCD_ILI9341_Parameter(x>>8);
LCD_ILI9341_Parameter(x&0xff);
LCD_ILI9341_CMD(0X2B); /* 设置Y坐标*/
LCD_ILI9341_Parameter(y>>8);
LCD_ILI9341_Parameter(y&0xff);
LCD_ILI9341_Parameter(y>>8);
LCD_ILI9341_Parameter(y&0xff);
}
// _ _ _ _ _ _
// | |
// | |
// | |
// | |
// | |
// | |
// | |
// | |
// | |
// | |
// | |
// | |
// - - - - - -
void LCD_OpenWindow(uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
LCD_ILI9341_CMD(0X2A); /* 设置X坐标 */
LCD_ILI9341_Parameter( x >> 8 ); /* 先高8位,然后低8位 */
LCD_ILI9341_Parameter( x & 0xff ); /* 设置起始点和结束点*/
LCD_ILI9341_Parameter( (x+width-1) >> 8 );
LCD_ILI9341_Parameter( (x+width-1) & 0xff );
LCD_ILI9341_CMD(0X2B); /* 设置Y坐标*/
LCD_ILI9341_Parameter( y >> 8 );
LCD_ILI9341_Parameter( y & 0xff );
LCD_ILI9341_Parameter( (y+height-1) >> 8);
LCD_ILI9341_Parameter( (y+height-1) & 0xff);
}
void LCD_SetPoint(uint16_t x , uint16_t y , uint16_t color)
{
LCD_SetCursor(x, y);
LCD_ILI9341_CMD(0x2c); /* 写数据 */
LCD_WR_Data(color);
}
uint16_t LCD_RD_data(void)
{
uint16_t R=0, G=0, B=0 ;
R = *(__IO uint16_t *)Bank1_LCD_D; /*FIRST READ OUT DUMMY DATA*/
R = *(__IO uint16_t *)Bank1_LCD_D; /*READ OUT RED DATA */
B = *(__IO uint16_t *)Bank1_LCD_D; /*READ OUT BLACK DATA*/
G = *(__IO uint16_t *)Bank1_LCD_D; /*READ OUT GREEN DATA*/
return (((R>>11)<<11) | ((G>>10)<<5) | (B>>11));
}
uint16_t LCD_GetPoint(uint16_t x , uint16_t y)
{
uint16_t temp;
LCD_SetCursor(x, y);
LCD_ILI9341_CMD(0x2e); /* 读数据 */
temp=LCD_RD_data();
return (temp);
}
/*-------------------------------------------------------------------------------------------------------*/
/* column 240
_ _ _ _ _ _
page| |
| |
| |
| |
| |
| | 320
| |
| |
| |
| |
| |
| |
- - - - - -
*/
/* 显示一个字符
* 大小为 12(宽度)* 6(高度)
*/
void LCD_DispChar(uint16_t x, uint16_t y, uint8_t ascii, uint16_t color)
{
uint16_t page, column, temp, i;
i = ascii - ' ';
LCD_OpenWindow(x, y, STR_WIDTH, STR_HEIGHT);
LCD_ILI9341_CMD(0X2C);
for( page=0; page < STR_HEIGHT; page++ )
{
temp = asc2_1206[page];
for( column=0; column < STR_WIDTH; column++ )
{
if( temp & 0x01 )
{
LCD_WR_Data( color );
}
else
{
LCD_WR_Data( BACKGROUND );
}
temp >>= 1;
}/* 一行写完 */
}/* 全部写完 */
}
/* 显示字符串 */
void LCD_DispStr(uint16_t x, uint16_t y, uint8_t *pstr, uint16_t color)
{
while( *pstr != '\0' )
{
if( x > (COLUMN-STR_WIDTH) )
{
x = 0;
y += STR_HEIGHT;
}
if( y > (PAGE-STR_HEIGHT) )
{
x = 0;
y = 0;
}
LCD_DispChar(x, y, *pstr, color);
x += STR_WIDTH;
pstr++;
}
}
// temp = 345 length = 0
// 34 1
// 3 2
// 0 3
// 0 6 12
// ____ ____ ____
/* 显示变量
* 大小为 12(宽度)* 6(高度)
*/
void LCD_DisNum(uint16_t x, uint16_t y, uint32_t num, uint16_t color)
{
uint32_t length = 0, temp = 0;
temp = num;
if( temp == 0 )
{
LCD_DispChar(x, y, '0', color);
return;
}
while( temp )
{// 得到num的长度
temp /= 10;
length ++;
}
while( num )
{
/* 从个位开始显示 */
LCD_DispChar((x+STR_WIDTH*(length--)-STR_WIDTH), y, (num%10)+'0', color);
num /= 10;
}
}
/*------------------------------------------------------------------------------------------------------*/
/* column 240
_ _ _ _ _ _
page| |
| |
| |
| |
| |
| | 320
| |
| |
| |
| |
| |
| |
- - - - - -
*/
/* 显示一个汉字
* 大小为 16(宽度)* 16(高度),共32个字节
* 取模顺序为:高位在前,低位在后
*/
void LCD_DispCH(uint16_t x, uint16_t y, const uint8_t *pstr, uint16_t color)
{
uint8_t page , column;
uint8_t buffer[32];
uint16_t tmp_char=0;
LCD_OpenWindow(x, y, CH_WIDTH, CH_HEIGHT);
LCD_ILI9341_CMD(0X2C);
GetGBKCode_from_sd(buffer,pstr); /* 取字模数据 */
for(page=0; page< CH_HEIGHT; page++)
{
/* 取出两个字节的数据,在lcd上即是一个汉字的一行 */
tmp_char=buffer[page*2];
tmp_char=(tmp_char<<8);
tmp_char|=buffer[2*page+1];
for (column=0; column< CH_WIDTH; column++)
{
if ( tmp_char & (0x01<<15) ) /* 高位在前 */
{
LCD_WR_Data(color);
}
else
{
LCD_WR_Data(BACKGROUND);
}
tmp_char <<= 1;
}
}
}
/*
* 显示一串汉字
*/
void LCD_DispStrCH(uint16_t x, uint16_t y, const uint8_t *pstr, uint16_t color)
{
while( *pstr != '\0' )
{
if( x > (COLUMN-CH_WIDTH) )
{
x = 0;
y += CH_HEIGHT;
}
if( y > (PAGE-CH_HEIGHT) )
{
x = 0;
y = 0;
}
LCD_DispCH(x, y, pstr, color);
pstr +=2; /* 一个汉字两个字节 */
x += CH_WIDTH;
}
}
/*
* 中英文混合显示,不能显示中文标点符号
* 中文大小:16*16
* 英文大小:12*6
*/
void LCD_DispEnCh(uint16_t x, uint16_t y, const uint8_t *pstr, uint16_t color)
{
while(*pstr != '\0')
{
if(*pstr <= 126) /* 字符串 */
{
if( x > (COLUMN-STR_WIDTH) )
{
x = 0;
y += STR_HEIGHT;
}
if( y > (PAGE-STR_HEIGHT) )
{
x = 0;
y = 0;
}
LCD_DispChar(x, y, *pstr, color);
x += STR_WIDTH;
pstr++;
}
else /* 汉字 */
{
if( x > (COLUMN-CH_WIDTH) )
{
x = 0;
y += CH_HEIGHT;
}
if( y > (PAGE-CH_HEIGHT) )
{
x = 0;
y = 0;
}
LCD_DispCH(x, y, pstr, color);
pstr +=2;
x += CH_WIDTH;
}
}
}
/* -------------------------------------------end of file----------------------------------------------- */
好像程序有点多啊,其实很多都是注释文件,
【学习心得】
NUCLEO-L412KB开发板是的在面包板社区申请到的一个开发板,虽然我这么长的时间的学习,自己有一点小的心得和大家一起分享。
首先感谢面包板社区给我学习的机会,当然这个学习的过程,对我来说意义重大,我是很久没有学习了,我继续来学习的。对于你们学习好的人来说,这个也不会需要这么长的时间完成,但是对于我来说是不一样 的意义。其次,感谢帮助的我所有人,包括面包板社区的管理员,兢兢业业的工作,还督促我们完成测试任务,非常感谢管理员付出的劳动,非常感谢管理员对于这次活动的支持。第三,就是感谢赞助者,是您的赞助,才让我们有了 这么好的一次学习的机会,我学习了这个NUCLEO-L412KB开发板,我就更有兴趣学习别的,是你们给了我重新学习st的机会,谢谢你们。
在这么长的时间,学习了NUCLEO-L412KB开发板的基本知识,包括单运算器、控制器和寄存器,他们是如何工作的,在微处理器内部运算器、控制器、寄存器之间是相互连接的,由控制器向各部分发布操作命令,运算器接到命令后进行相应运算,并将运算后结果存入相应的寄存器中。
NUCLEO-L412KB开发板要学习的内容
虽然我只是完成了一点点内容,但是NUCLEO-L412KB开发板是是非常强大的。
在NUCLEO-L412KB开发板的实验中,只是实现了显示汉字,别的都没有完成,我在未来的时间内,好好学习NUCLEO-L412KB开发板的使用,争取在未来的时间内,转变成一个有一定知识的人。
自己学习了这么长时间的单片机或者说是这一个月的NUCLEO-L412KB开发板学习,自己掌握的知识还是比较少的。在未来的一段时间内,我自己的时间还是相对较多的,我重新学习NUCLEO-L412KB开发板和单片机相关的知识。
首先,学习单片机要有必须的基础:电子技术方面要有数字电路和模拟电路等方面的理论基础,个性是数字电路;编程语言要求汇编语言或C语言。要想成为单片机高手,推荐初学者首先学习汇编语言,学的差不多的时候,转入C语言学习。尽管汇编语言属于低级语言,编程效率低,但是较C语言具有目标代码简短,占用内存少,执行速度快等优点,更重要的是能使初学者尽快熟悉单片机的内部结构,并能对其进行精确的控制。汇编语言在单片机教材里面都会涉及,不需要单独购买教材和学习。C语言是一门学问,有很多专业书籍来讲解,并且对我们今后的编程生涯有绝对的好处,因此要深入学习,千万不要自以为看了某某的视频教程就以为掌握了C语言,那只是C语言的一部分。在那里给大家推荐一本单片机C语言程序设计参考书,马忠梅等著,北京航空航天大学出版社出版的《单片机的C语言应用程序设计》,要求C语言基础。如果没学过C语言,推荐学习清华大学谭浩强编写的C语言程序设计,这本书写的不错,通俗易懂。
其次,是单片机教材选取。单片机是一门十分重视实践的技术,不能总是看书,但要学习它首先应看书,对单片机引脚、内部结构、寄存器和原理有必须详细了解和感官认识,它的是怎样工作的,能干些什么?刚开始时,也许你看不明白,但这并不要紧,因为你还缺乏实践经验。此刻单片机应用广泛,因此各个厂家分别推出了自己的单片机,按内部结构体系派系分:51系列、PIC系列、AVR系列、摩托罗拉等等……我们没必要每样都学!因为他们的编程方法和调试过程以及内部指令结构有必须的相似,只要学精通一款就OK了!尤其是用C语言编程,就几乎不用分什么派系,但是我们要选取一款有代表性的知识范围广,并且入门容易,书籍多。一般来说,MCS-51系列单片机已经得到广泛的普及和应用,市场上它的资料也比较多,用的人也很多。给大家推荐一些参考书,学习时只需要一本就足够拉。书名:《新编MCS-51单片机应用设计》,哈尔滨工业大学出版,作者:张毅刚;书名:《单片机原理及应用》,高等教育出版社,作者:张毅刚等;书名:《单片机高级教程:应用与设计》,北京航空航天大学出版社,作者:何立民。相关教材还有很多,在这不一一列举。虽然之前学习了,现在已经忘记了,NUCLEO-L412KB开发板也是我未来学习的最好的一个板子,记住NUCLEO-L412KB开发板。
然后,是开发工具和开发环境的选取。选取一块适宜的学习板,对于初学者来说一般无力理解,如果经济条件允许、本人又对单片机很感兴趣、有从事相关工作意向的话,鼓励大家购买。随便说一句,学习板功能要求太全,具有流水灯、数码管、独立键盘、矩阵键盘、AD或DA、液晶、蜂鸣器等就差不多啦,毕竟,功能齐全的价格比较高。仿真器对单片机初学者来说既是那么耳熟,同时又有些陌生,这主要是因为市场上传统的仿真器价格都在千元以上,对经济不是十分宽裕的人来说是不小的开支。同时仿真器是用来提高调试程序效率的,有了单片机教程板以后,先看下指导说明书,熟悉一下学习板,开卷有益。以后就得靠自己多练习了,将学习板与电脑连接好,先学会开发软件的使用,然后从最简单的流水灯实验做起,按照你自己的意愿控制流水灯,当你完成时,你会发现这是多么惬意的事情。太好玩了,你会觉得这不是在学习,而是在玩,当你发现,单片机能够按照你编写的程序工作时,你会觉得十分兴奋,比做什么事情都开心,这样你会慢慢迷上单片机,真的。不少网站上说搞定某个实验,就恭维的告诉你一声”恭喜你,学会了”自己学会了单片机,这有点可笑,这只能说明你算过关了,对单片机有了必须了解和会使用它了。但是单片机能完成的功能太多了,尤其是对外围器件的控制,综合起来能设计出许多意想不到的产品。因此除了入门外,精通可千万别轻易说出口。
最后,在熟练掌握和应用后,那能够说对于单片机方面的硬件你已经入门了,剩下的就是自己练习设计电路,不断的积累经验。最终,自己完全设计具有个人风格的电路,产品,这样你就是单片机高手拉。只要过了第一关,后面的路就好走多了,万事开头难,大家可能都听过。时下多家电子类的报刊杂志如:《电子制作》《无线电》《电子报》《电子世界》都开设了详细的单片机教程专栏,组重要是面包板社区对大家学习NUCLEO-L412KB开发板还有其他的开发板都有,并且还是免费的。单片机的朋友来说帮忙很大,能够说此刻的单片机教程环境是最好的,有网络,有书籍,有报刊杂志,还有视频教程,元件的采购方面也十分充足,相关的器材又多有便宜。
记得那时候刚接触单片机的时候,我根本不知道这个小黑疙瘩是干啥的,伴随着后面的学习,慢慢的知道他就是一个CPU、存储单元和I/O口组成,也就是一个非常小的电脑。其实本科阶段我就是了解这么多,真正开始使用单片机还是在研究生阶段,那时候为了完成任务,几乎每天都是实验室和单片机为伴。也总结下研究生阶段的学习心得与大家一起交流。
1、先迈出第一步。不管是什么事情,只要走了第一步了,后面的事情就好说了。最开始的时候,我连KEIL软件都不会使用,只能看英文的说明书,那就慢慢的啃。就这样一点一点的,学会了单片机的入门。
2、需要什么知识,那就去恶补什么知识。单片机的书,一般都很厚,估计学霸也不想从头到尾看完,就算坚持看下来,估计自己掌握的也不是很多。我的建议就是需要什么知识,那就去学习什么知识,需要什么类型的芯片,那就去学习什么芯片。
3、程序要自己动手写。现在网络也发达了,很多程序都能在晚上找到现成的程序,再加上单片机的移植性非常好,导致很多人都懒得动手去写程序了,我觉得这个不是一个好的习惯。我建议还是认认真真的去写一下这个程序,因为不知道什么地方的分号就会忘记,不知道什么地方就会出错,自己写一遍也会增加自己的单片机的理解。
4、学会调试程序。很多人都说,调试程序还不如我自己再写一遍,其实也是这样的,很多时候写的程序都不知道什么地方会出现问题,这就需要我们会调试程序。调试程序要比会写程序更重要,只有找到了调试程序的方法,说明你对这个程序有了非常深刻的了解。如果实在是调试不出来,可以到网上求助,但是求助后一点要搞清楚自己什么地方没有注意,什么地方自己还没有掌握。
5、培养自己做项目的能力。教材上的案例都是非常基础、非常简单的,只要做个项目才能知道自己真实的水平。因为项目涉及到的知识非常的全面,做项目要远远的大于从书本上学习的知识。
6、最后一点,坚持。任何事情,开始的时候很难,中间的过程很难,但是最后的结局会很美好,很多人都倒在了中间过程。要时刻给自己打点鸡血,保持自己学习的动力。
curton 2019-6-10 12:04
面包板社区管理员 2019-6-10 08:39