打印
[STM32F1]

STM32例程+俄罗斯方块游戏

[复制链接]
8086|38
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
一路向北lm|  楼主 | 2017-9-23 19:09 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 一路向北lm 于 2017-9-23 21:32 编辑

ST又来活动,这次不要错过了,想得到一块开发板,使用STM32F103ZET6开发板实现俄罗斯方块小游戏。代码已上传,包含电路原理图。

游戏截图1.png (211.48 KB )

游戏截图1.png

游戏截图2.png (189.43 KB )

游戏截图2.png

俄罗斯方块游戏.rar

630.74 KB

原理图.pdf

146.79 KB

评论
ZSGhhh 2022-5-28 23:06 回复TA
为什么我下载进去不能用,我用的板子也是STM32F103ZET6 
来自 2楼
一路向北lm|  楼主 | 2017-9-23 19:20 | 只看该作者
首先来了解下LCD  屏 :
TFT-LCD 又叫做薄膜晶体管液晶显示器,常用的液晶屏接口很多种,8 位、 9 位、16位、18 位都有。而常用的通信模式呢,主要有 6800 模式和 8080 模式两种,今天呢,我们来讲的是 8080 模式。如果大家接触过 LCD1602 或者 LCD12864 等,那么就会发现 8080 模式的时序呢,其实跟 LCD1602 或者 LCD12864 的读写时序是差不多的。8080 接口有 5 条基本的控制线和多条数据线,数据线的数量主要看液晶屏使用的是几位模式,有 8 根、9 根、16 根、18 根四种类型。它们的功能如图3, 8080 接口模式的时序如图4:

01在 WR 跳变为低电平之后,液晶屏开始读取总线上面的数据。如果使用 IO 口模拟写入的时候,可以先在总线上面写入数据,然后在跳变 WR,以保证当读取的时候,总线上面的数据是稳定的。
02在 RD 跳变为低电平之后,液晶屏放置数据到总线上面。从上图,我们就可以很清晰的看得出,液晶屏的读写时序了,大家操作的时候,可以使用单片机 IO 口模拟它的时序进行操作。不过我们开发板上面的单片机自带有 FSMC 模块,我们可以直接使用 FSMC模块来操作液晶屏,接下来我们给大家介绍一下 FSMC 模块。





3.png (79.26 KB )

3.png

4.PNG (223.84 KB )

4.PNG

使用特权

评论回复
来自 3楼
一路向北lm|  楼主 | 2017-9-23 19:57 | 只看该作者
本帖最后由 一路向北lm 于 2017-9-23 19:58 编辑

再来研究下FSMC 模拟 LCD  彩屏 8080 :
引脚数在 100 脚以上的 STM32F103 芯片都带有 FSMC 的接口,而什么是 FSMC 呢?FSMC 即灵活的静态存储控制器,能够与同步或异步存储器和 16 位 PC 存储卡接口,FSMC 的接口支持包括 SRAM、NAND FLASH、NOR FLASH、 PSRAM 等存储器。说白了呢,就是可以当操作 SRAM、NAND FLASH、NORFLASH 和 PSRAM 等存储器的时候,我们不必自己操作 IO 口模拟这些存储器的操作时序了,使用 FSMC 模块可以直接帮我们读写这些存储器。而为什么可以用 FSMC 来控制 LCD 液晶屏呢?我们知道用来控制LCD 液晶屏的信号线主要有:
1) CS:用于片选的选择。
2) RS:用于选择命令或者数据。
3) WR:写能。
5) RESET:复位端使能。
4) RD:读使。
理解了,上面的 FSMC 如何能够模拟 LCD 彩屏 8080 模式之后,我们对照,我们开
发板上面的彩屏接口,就可以很好的理解了。我们这里使用 FSMC_NE4 作片选 CS,使用
FSMC_A10(即地址线 10)作数据命令选择 RS。






使用特权

评论回复
来自 4楼
一路向北lm|  楼主 | 2017-9-23 21:14 | 只看该作者
接下来我们对FSMC  进行操作从而实现对LCD的控制从 FSMC 的角度看,可以把外部存储器划分为固定大小为 256M 字节的四个存储块,如图1 也就是说,当我们操作外部存储器的时候,只要操作单片机中对应的地址,那么 FSMC模块,就可以自动帮我们操作外部存储器,相当于把外部存储器挂在了单片机的地址总线上面了。而我们这里使用的是 NOR 模式来模拟 LCD 液晶屏的 8080 时序,而在 NOR/PSRAM中,有四块存储块,我们使用的是 FSMC_NE4,所以使用的是第四块存储块,就是地址从0x6C000000 到 0x6FFFFFFF 的存储块。现在我们使用地址线 A10 来作数据命令选择端RS,那么当我们要写入数据的时候,地址线 A10 就要输出为 1,无论地址是什么,只要地址线 A10 输出为 1,就可以了;而当我们要写入命令的时候,正好相反,地址线 A10 输出为 0。






1.png (98 KB )

1.png

使用特权

评论回复
来自 5楼
一路向北lm|  楼主 | 2017-9-23 21:20 | 只看该作者
本帖最后由 一路向北lm 于 2017-9-23 21:27 编辑

接下来看下关于lcd操作需要用到的库函数:

1. FSMC_NORSRAMInit() 函数:
我 们 使 用 的 是 NORSRAM 模 式 , 所 以 我 们 使 用 的 初 始 化 函 数 是FSMC_NORSRAMInit()函数。
这 个 函 数 有 一 个 输 入 参 数 , 这 个 输 入 参 数 是 一 个FSMC_NORSRAMInitTypeDef 结构体,这个结构体很复杂,它一共有 15 个成员,接下来我
们来一一介绍这些成员:
1) FSMC_Bank:选择你要使用的 bank,我们使用的是 NOR 模式的 FSMC_NE4,也就是第 1 个 bank 的第 4 个存储块,所以这里我们设置为FSMC_Bank1_NORSRAM4。
2) FSMC_MemoryType:选择你要使用的内存类型,我们使用的是 NOR FLASH 或 者SRAM 都 可 以 , 我 们 选 择 设 置 为 : FSMC_MemoryType_SRAM。
3) FSMC_MemoryDataWidth:选择你使用的内存数据宽度,我们这里选择 16 位长度:FSMC_MemoryDataWidth_16b。
4) FSMC_WriteOperation:这里设置是是否打开写使能,这个肯定要打开,所以设置为:FSMC_WriteOperation_Enable。
5) FSMC_ExtendedMode:是否使用拓展模块,也就是读写的数据是否需要不一样,LCD彩屏读取的速度,一般都比写入慢,为了写入的效率最高,所以我们这里选择使用拓展模块:FSMC_ExtendedMode_Enable
6) FSMC_DataAddressMux:是否复用地址线和数据线,我们这里不复用:FSMC_DataAddressMux_Disable。
7) FSMC_ReadWriteTimingStruct:当没有使用拓展模块时,也就是读写时序不分开时,设置读写时序,也就是速度。而这个参数的设置又是一个 FSMC_NORSRAMTimingInitTypeDef 结构,这个结构体有 7 个成员:
   a) FSMC_AddressSetupTime:设置地址建立时间:我们设置为 3 个时钟周期,即:0x02。注意这里没错,3 个时钟周期是 0x02,因为这里设置是从         0x00 开始的,表示一个时钟周期。
   b) FSMC_AddressHoldTime:地址保持时间,为了方面外部存储器,更精确的读取地址的设置。这里我们设置为一个时钟周期得了:0x00。
   c) FSMC_DataSetupTime:数据建立时间,我们这里设置为 6 个时钟周期,即:0x05。
   d) FSMC_DataLatency:数据保持时间,我们这里设置为 1 个时钟周期: 0x00。
   e) FSMC_BusTurnAroundDuration:总线恢复时间,我们也设置为 1 个时钟周期:0x00。
   f) FSMC_CLKDivision:设置 FSMC 的时钟分频,我们设置为:0x01。
  g) FSMC_AccessMode:设置异步访问模式,也就是读写时序分开时的模式,我们使用 B模式,所以设置为:FSMC_AccessMode_B。
以上就是 FSMC_ReadWriteTimingStruct 这个成员的设置结构体参数。
8) FSMC_WriteTimingStruct:设置写时序,这个设置其实也是使用上面FSMC_ReadWriteTimingStruct 这个成员的结构体参数来设置。
9) 剩 下 的 还 有 7 个 参 数 , FSMC_BurstAccessMode 、FSMC_WaitSignalPolarity、FSMC_AsynchronousWait、FSMC_WrapMode、 FSMC_WaitSignalActive、FSMC_WaitSignal、FSMC_WriteBurst。这些参数在成组模式同步模式才需要设置,大家可以参考中文参考手册
了解我们这里不用设置。




使用特权

评论回复
来自 6楼
一路向北lm|  楼主 | 2017-9-23 21:23 | 只看该作者
本帖最后由 一路向北lm 于 2017-9-23 21:27 编辑

TFT LED例程代码
1.GPIO初始化
/***************************************************************************
*
*函数名:TFT_GPIO_Config
*输 入:无
*输 出:无
*功 能:初始化 TFT 的 IO 口。
***************************************************************************
*/
void TFT_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 打开时钟使能 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE
| RCC_APB2Periph_GPIOG, ENABLE);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
/* FSMC_A10(G12) 和 RS(G0)*/
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4
| GPIO_Pin_5 | GPIO_Pin_8 | 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(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);
}
2.FSMC初始化
void TFT_FSMC_Config(void)
{
/* 初始化函数 */
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTiming;
/* 设置读写时序,给 FSMC_NORSRAMInitStructure 调用 */
/* 地址建立时间,3 个 HCLK 周期 */
FSMC_NORSRAMTiming.FSMC_AddressSetupTime = 0x02;
/* 地址保持时间,1 个 HCLK 周期 */
FSMC_NORSRAMTiming.FSMC_AddressHoldTime = 0x00;
/* 数据建立时间,6 个 HCLK 周期 */
FSMC_NORSRAMTiming.FSMC_DataSetupTime = 0x05;
/* 数据保持时间,1 个 HCLK 周期 */
FSMC_NORSRAMTiming.FSMC_DataLatency = 0x00;
/* 总线恢复时间设置 */
FSMC_NORSRAMTiming.FSMC_BusTurnAroundDuration = 0x00;
/* 时钟分频设置 */
FSMC_NORSRAMTiming.FSMC_CLKDivision = 0x01;
/* 设置模式,如果在地址/数据不复用时,ABCD 模式都区别不大 */
FSMC_NORSRAMTiming.FSMC_AccessMode = FSMC_AccessMode_B;
/*设置 FSMC_NORSRAMInitStructure 的数据*/
/* FSMC 有四个存储块(bank),我们使用第一个(bank1) */
/* 同时我们使用的是 bank 里面的第 4 个 RAM 区 */
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;
/* 这里我们使用 SRAM 模式 */
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
/* 使用的数据宽度为 16 位 */
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
/* 设置写使能打开 */
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
/* 选择拓展模式使能,即设置读和写用不同的时序 */
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable;
/* 设置地址和数据复用使能不打开 */
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
/* 设置读写时序 */
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_NORSRAMTiming;
/* 设置写时序 */
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_NORSRAMTiming;
/* 打开 FSMC 的时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
/*!< Enable FSMC Bank1_SRAM Bank */
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);
}

3 .  写命令程序
void TFT_WriteCmd(uint16_t cmd)
{
TFT->TFT_CMD = cmd >> 8;
TFT->TFT_CMD = cmd & 0x00FF;
}
我们的 LCD 彩屏使用的是 8 位数据口,所以要写两次。
4.  写数据程序
void TFT_WriteData(u16 dat)
{
TFT->TFT_DATA = dat >> 8;
TFT->TFT_DATA = dat & 0x00FF;
}
5 .  初始化函数
初始化函数比较长,这里就不贴出来了,大家可以查看例程里面的初始化函数,
TFT_Init()。在初始化函数里面要设置很多东西,一般来说我们就不用一一去详细了解了,
如果想要修改相应的设置,可以去翻查 LCD 彩屏的数据手册,进行设置。
6.  设置窗口函数
void TFT_SetWindow(uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd)
{
TFT_WriteCmd(0x0210);
TFT_WriteData(xStart);
TFT_WriteCmd(0x0211);
TFT_WriteData(xEnd);
TFT_WriteCmd(0x0212);
TFT_WriteData(yStart);
TFT_WriteCmd(0x0213);
TFT_WriteData(yEnd);
TFT_WriteCmd(0x0200);
TFT_WriteData(xStart);
TFT_WriteCmd(0x0201);
TFT_WriteData(yStart);
TFT_WriteCmd(0x0202);
}
这个是一个很重要的函数,这个函数可以设置你要显示的窗口范围。
7.  清屏函数
/****************************************************************************
* Function Name : TFT_ClearScreen
* Description : 将 LCD 清屏成相应的颜色
* Input : color:清屏颜色
* Output : None
* Return : None
****************************************************************************/
void TFT_ClearScreen(uint16_t color)
{
uint16_t i, j ;
TFT_SetWindow(0, 0, TFT_XMAX, TFT_YMAX);  //作用区域
for(i=0; i<TFT_XMAX+1; i++)
{
for (j=0; j<TFT_YMAX+1; j++)
{
TFT_WriteData(color);
}
}
}


使用特权

评论回复
来自 7楼
一路向北lm|  楼主 | 2017-9-26 17:02 | 只看该作者
以下是俄罗斯方块LCD显示屏的驱动代码,包括了 lcd_driver.C与 lcd_driver.h 两个文件,在主函数调用即可。

lcd.rar

3.6 KB

使用特权

评论回复
来自 8楼
一路向北lm|  楼主 | 2017-9-26 17:11 | 只看该作者
接下来就是关于GUI的显示,主程序中调用 GUI_DisplayInit 初始化函数在TFT上显示我们看到的图画。void GUI_DisplayInit(void){
        GUI_ShowPicture(0, 0, 240, 320);  
}
关于GUI的驱动函数我们可以看到有 asciicode  chfont  font  gui  picture Tfont 文件组成关于代码可以自行阅读,这里会调用即可。
结合着LCD的驱动我们就可以在TFT指定位置上面显示我们想要的图片汉子的字符。



gui.rar

132.65 KB

使用特权

评论回复
评论
ZSGhhh 2022-5-28 22:28 回复TA
确实厉害 
来自 9楼
一路向北lm|  楼主 | 2017-9-26 17:18 | 只看该作者
按键部分:调用button.h即可
void button_init()
{
        GPIO_InitTypeDef  GPIO_InitStructure;
         
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE, ENABLE);//使能PB\E端口时钟
        GPIO_InitStructure.GPIO_Pin = k_up;                          
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;          //下拉输入
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIO_ResetBits(GPIOA,GPIO_Pin_0);

        GPIO_InitStructure.GPIO_Pin = k_left|k_down|k_right;                          
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          //上拉输入
        GPIO_Init(GPIOE, &GPIO_InitStructure);

}
u8 keyscan(u8 mode)                  //mode=0 单次按,mode=1连续按
{
        static u8 key_up=1,i=0;
        if(mode)
        {
                key_up=1;       
        }
        if(key_up&&(K_UP==1||K_LEFT==0||K_DOWN==0||K_RIGHT==0))       
        {
                delay_ms(10);
                i++;
                if(i==8)
                {
                        i=0;
                        key_up=0;
                        if(K_UP==1)
                        {
                                return K_UP_PRESS;
                        }
                        else if(K_LEFT==0)
                        {
                                return K_LEFT_PRESS;       
                        }
                        else if(K_DOWN==0)
                        {
                                return K_DOWN_PRESS;       
                        }
                        else if(K_RIGHT==0)
                        {
                                return K_RIGHT_PRESS;       
                        }
                }       
        }
        else if(K_UP==0&&K_LEFT==1&&K_DOWN==1&&K_RIGHT==1)
        {
                key_up=1;       
        }
        return 0;
}

u8 keyscan1(u8 mode)                  //mode=0 单次按,mode=1连续按
{
        static u8 key_up=1;
        if(mode)
        {
                key_up=1;       
        }
        if(key_up&&(K_UP==1||K_LEFT==0||K_DOWN==0||K_RIGHT==0))       
        {
                delay_ms(10);
               
                        key_up=0;
                        if(K_UP==1)
                        {
                                return K_UP_PRESS;
                        }
                        else if(K_LEFT==0)
                        {
                                return K_LEFT_PRESS;       
                        }
                        else if(K_DOWN==0)
                        {
                                return K_DOWN_PRESS;       
                        }
                        else if(K_RIGHT==0)
                        {
                                return K_RIGHT_PRESS;       
                        }
                       
        }
        else if(K_UP==0&&K_LEFT==1&&K_DOWN==1&&K_RIGHT==1)
        {
                key_up=1;       
        }
        return 0;
}


使用特权

评论回复
评论
ZSGhhh 2022-5-28 22:29 回复TA
666 
来自 10楼
一路向北lm|  楼主 | 2017-9-26 17:20 | 只看该作者
主函数部分:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "button.h"
#include "lcd_driver.h"
#include "gui.h"
#include "tim.h"
#include "tetris.h"



void GUI_DisplayInit(void)
{
        GUI_ShowPicture(0, 0, 240, 320);  
}

int main(void)
{       
        u8 i;
        delay_init();
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        led_init();
        button_init();
        TFT_Init();
        TFT_ClearScreen(GREEN);
        GUI_DisplayInit();
        TIM4_Init(10,7199);
        while(key!= K_UP_PRESS) //等待按键K_UP按下
        {
                GUI_Show12ASCII(20,10,"Press K_UP key to Enter...",RED,WHITE);
                delay_ms(200);
                GUI_Show12ASCII(20,10,"                          ",RED,WHITE);
                delay_ms(200);
        }
        TIM_Cmd(TIM4, DISABLE);  //失能TIMx
        uart_init(9600);
        score_buf[0]=Game.score/100000+0x30;
        score_buf[1]=Game.score%100000/10000+0x30;
        score_buf[2]=Game.score%100000%10000/1000+0x30;
        score_buf[3]=Game.score%100000%10000%1000/100+0x30;
        score_buf[4]=Game.score%100000%10000%1000%100/10+0x30;
        score_buf[5]=Game.score%100000%10000%1000%100%10+0x30;
        score_buf[6]='\0';

        level_buf[0]=Game.level/100+0x30;
        level_buf[1]=Game.level%100/10+0x30;
        level_buf[2]=Game.level%100%10+0x30;
        level_buf[3]='\0';
        Show_TetrisFace();
        Start_Game();
        TIM3_Init(5000,7199);
       
        while(1)
        {       
                  key=keyscan(1);
                switch(key)
                {
                        case K_LEFT_PRESS:MoveLeft();break;
                        case K_RIGHT_PRESS:MoveRight();break;
                        case K_DOWN_PRESS:DownFast();break;
                        case K_UP_PRESS:Transform();break;
                //        case RESET:ResetGame();break;       
                        default :
                                                        break;
                }
                i++;
                if(i==15)
                {
                        i=0;
                        led0=~led0;
                }
                delay_ms(10);               
        }

使用特权

评论回复
11
ningling_21| | 2017-9-23 20:17 | 只看该作者
楼主继续

使用特权

评论回复
12
dingbo95| | 2017-9-23 21:08 | 只看该作者
可以的,代码烧进去就可以玩游戏了哈。

使用特权

评论回复
13
chenqiang10| | 2017-9-23 21:34 | 只看该作者
厉害了,不知道我的开发板可不可以用这个代码?该怎么修改。

使用特权

评论回复
14
junpeng324| | 2017-9-23 21:52 | 只看该作者
是时候分享一波了,看来大家为了得到板子都很疯狂啊。

使用特权

评论回复
15
一路向北lm|  楼主 | 2017-9-25 20:22 | 只看该作者
junpeng324 发表于 2017-9-23 21:52
是时候分享一波了,看来大家为了得到板子都很疯狂啊。

快来加入吧,你要分享什么?

使用特权

评论回复
16
一路向北lm|  楼主 | 2017-9-25 20:24 | 只看该作者
chenqiang10 发表于 2017-9-23 21:34
厉害了,不知道我的开发板可不可以用这个代码?该怎么修改。

修改对应对应的管脚,TFT需要特别注意下。

使用特权

评论回复
17
一路向北lm|  楼主 | 2017-9-25 20:25 | 只看该作者
dingbo95 发表于 2017-9-23 21:08
可以的,代码烧进去就可以玩游戏了哈。

是的,可以happy一波了。

使用特权

评论回复
18
junpeng324| | 2017-9-25 21:18 | 只看该作者
一路向北lm 发表于 2017-9-25 20:22
快来加入吧,你要分享什么?

现在在做一个WiFi物联网的东西。

使用特权

评论回复
19
gujiamao12345| | 2017-9-26 13:55 | 只看该作者
不错!!
学习下!

使用特权

评论回复
20
山东电子小菜鸟| | 2017-9-26 18:24 | 只看该作者
学习下 谢谢

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

275

主题

3784

帖子

76

粉丝