STM32掌机教程5,程序框架,随机,加命与升级

发布者:hxcp18最新更新时间:2019-05-31 来源: eefocus关键字:STM32  掌机教程  程序框架  加命与升级 手机看文章 扫描二维码
随时随地手机看文章

随机生成地鼠

  随机数是游戏里边非常重要的组成部分,贪吃蛇随机刷新下一个食物,俄罗斯方块随机生成下一个方块,大富翁扔骰子,都是随机的。甚至微信群红包,金额也是随机的。正是因为这些事件不可预测,游戏才充满趣味性。我们地鼠的生成,当然也要随机。

  然而,计算机产生的随机数,都是“伪随机”。伪,指的是说它是随机的,但是却都是有规律可循的。对于C语言,可以直接调用一个随机数生产函数srand()。但是这个函数需要种子。随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就不会变。也就是说,伪随机数也是某种对应映射的产物,而这个自变量就是种子。

  如果你每次调用srand()时都提供相同的种子值,那么,你将会得到相同的随机数序列。因此要想产生看似“更随机的随机数”的关键,就是找个靠谱的种子。


  对于计算机系统来说,经常用时间做种子,因为大概率每次调用程序的时间是不一样的。单片机系统可以使用定时器,把定时器中的计数值作为种子。计数值飞快自加,可以认为每次需要生成随机数的时候,计数值都是不可预测的,也就达到了生成随机数的目的。

  我选用了定时器4来生成种子。定义了一个全局的变量作为计数值。定时器大约每隔1us每溢出一次,变量自增。

  定时器4的初始化与中断服务


//timer.c

void TIM4_Seed_Init(void)

{

  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

NVIC_InitTypeDef NVIC_InitStructure;


RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能

//定时器TIM3初始化

TIM_TimeBaseStructure.TIM_Period = 71; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值

TIM_TimeBaseStructure.TIM_Prescaler =0; //设置用来作为TIMx时钟频率除数的预分频值

TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式

TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

  TIM_ClearITPendingBit(TIM4, TIM_IT_Update  );  //清除TIMx更新中断标志 

TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM4中断,允许更新中断


//中断优先级NVIC设置

NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;  //TIM3中断

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;  //先占优先级0级

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能

NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器


TIM_Cmd(TIM4, ENABLE);  //使能TIMx  

}

//定时器4中断服务程序

void TIM4_IRQHandler(void)   //TIMx中断

{

if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)  //检查TIMx更新中断发生与否

{

TIM_ClearITPendingBit(TIM4, TIM_IT_Update  );  //清除TIMx更新中断标志 

time_us++;

}

}


  然后在主函数中调用初始化函数,并把地鼠编号递增的代码改为1~8之间随机。


//main.c

u32 time_us = 0;         //随机生成地鼠,用到的随机数种子

TIM4_Seed_Init();


//main.c    if(life)

if(next_flg)  //需要生成下一个地鼠

{

    next_flg = 0;

// mouse++;

// if(mouse > 8)//1到8

// mouse = 1;

    u8 i = 0;    //从随机数到地鼠用的临时变量

    srand(time_us);

    i = rand()%8 +1;

    mouse = i;

......

}


  主函数中这个if条件语句太长了,不适合阅读,因此把随机生成地鼠与点亮地鼠灯的功能提取函数。


//main.c    if(life)

if(next_flg)  //需要生成下一个地鼠

{

    next_flg = 0;

    mouse = random_num();       //产生随机数

    CreatMouse(mouse);           //随机生成地鼠

    timeout_flg = 0;

    TIM_SetCounter(TIM2, 0);//定时器清零

}


//按照参数,点亮某个LED

void CreatMouse(u8 mouse)

{

AllLED_OFF();  //先把灯全都关掉

delay_ms(50);  //然后稍微延时,避免地鼠刷新到同一个位置时,看不出来

switch(mouse)

{

case 1: SLED1 = 0;break;

case 2: SLED2 = 0;break;

case 3: SLED3 = 0;break;

case 4: SLED4 = 0;break;

case 5: SLED5 = 0;break;

case 6: SLED6 = 0;break;

case 7: SLED7 = 0;break;

case 8: SLED8 = 0;break;

default: break;

}

}


//使用定时器的计数值生成随机数

u8 random_num(void)

{

u8 i = 0;    //从随机数到地鼠用的临时变量

srand(time_us);

i = rand()%8 +1;

return i;

}



  编译并运行,可以发现地鼠是随机生成的。考虑到有可能两个地鼠生成到同一个位置,所以打中每次生成地鼠之前先关掉LED灯是很有必要的。


加命

  一般在游戏里,得到一定的分数以后都会加命,我们也来定义一个功能:得分每次超过整百,都加一条命。

  在打中地鼠的时候会加分,所以我们把加命的逻辑写在加分之后。需要注意这么个情况:假如现在得分是102,加命,再打中一个地鼠后,假如分数是108,不应该加命,应该等到分数大于200后,再加命。因此需要定义一个变量,帮助判断是否需要加命。


//main.c

u8 add_life_cnt = 1;       //用于增加生命计数,每到100的整数倍以后加命

//while(1)  if(life)

key = KEY_Scan(0);

if(key)       //如果按下按键

{

    next_flg = 1;   //不论打的对不对,都要生产下一个地鼠

    TIM_SetCounter(TIM2, 0);//不论打中的地鼠对不对,定时器都清零

    if(key == mouse)//正确打中地鼠   加分,生成下一个地鼠

    {

        score+= level;

        showNumber(56,6,score,DEC,8,FONT_16_EN);

        if(score/100 == add_life_cnt)     //一定积分以后加命

        {

                add_life_cnt++;

                life++;

                showNumber(56,2,life,DEC,8,FONT_16_EN);

        }

    }

    else //打错   减命

    {

        life--;

        showNumber(56,2,life,DEC,8,FONT_16_EN);

    }

}                             



  在测试的时候,可以把加命的分数设置的小一点,比如20:if(score/20 == add_life_cnt) //一定积分以后加命 。

  为了让代码更方便阅读与维护,我把加命的操作提取出函数来。


//加命并显示

void add_life(void)

{

add_life_cnt++;

life++;

showNumber(56,2,life,DEC,8,FONT_16_EN);

}


  然后加命的判断分支里就只需要一行代码了:


if(score/100 == add_life_cnt)    //一定积分以后加命

{

    add_life();

}

升级

  难度升级以后,每个地鼠出生停留的时间都会变短,我设置一个数组,用于存放地鼠停留的时间,从2s到100ms——我还真不信有人反应能这么快。博尔特反应速度133ms。


  我的思路是,每击中10个地鼠难度增加,难度增加其实也就是数组的索引+1,然后更新定时器2的溢出时间。

  首先要定义数组,变量,并修改定时器2初始化。


//main.c

#define LEVEL_UP_CNT  10    //击中x个地鼠难度增加

//不同难度对应的时间,(数值+1)/10 = 时间ms

u16 level1_time_arr[15] = {19999,14999,11999,9999,7999,5999,4999,3999,2999,1999,1799,1599,1399,1199,999};


u8 level_cnt = 0;         //击中的地鼠数量,用于升级计数


TIM2_Int_Init(level1_time_arr[level_cnt],7199);


  编写一个函数用于提升难度。


//难度提升并显示

void level_up(void)

{

level_cnt = 0;

level++;

if(level>14)//默认14关

level = 14;

TIM_SetAutoreload(TIM2,level1_time_arr[level]);

showNumber(56,4,level,DEC,8,FONT_16_EN);

}


  然后在打中地鼠的逻辑里来判断是否需要升级。注意,有时候升级与加命的条件会同时满足,这当然没什么弊端,知识考虑到后续会增加BGM,BGM最好持续一段时间,所以这两种情况先不同时处理。


//main.c    if(key == mouse)

   else if(++level_cnt > LEVEL_UP_CNT) //升级与加命不同时处理

    {

        level_up();

    }


提取函数

  为了让主函数看起来尽可能简洁,我把屏幕显示,加分,减命的操作也提取出了函数。主函数最好能只体现业务逻辑。


int main(void)

{

LED_Init();

KEY_Init();

delay_init();

initIIC();

initOLED();

TIM4_Seed_Init();


FirstScreen();      //显示完屏幕内容以后,再开启打地鼠计时用的定时器

show_opt();


TIM2_Int_Init(level1_time_arr[level_cnt],7199);

while(1)

{

if(life)//还有命

{

if(next_flg)  //需要生成下一个地鼠

{

next_flg = 0;

mouse = random_num();       //产生随机数

CreatMouse(mouse);           //随机生成地鼠

TIM_SetCounter(TIM2, 0);//定时器清零

timeout_flg = 0;

}

if(timeout_flg)  //超时,减命,生成下一个地鼠

{

timeout_flg = 0;

next_flg = 1;

sub_life();

}

key = KEY_Scan(0);

if(key)       //如果按下按键

{

next_flg = 1;   //不论打的对不对,都要生产下一个地鼠

TIM_SetCounter(TIM2, 0);//不论打中的地鼠对不对,定时器都清零

if(key == mouse)//正确打中地鼠   加分,生成下一个地鼠

{

add_score();

if(score/100 == add_life_cnt)    //一定积分以后加命

{

add_life();

}

else if(++level_cnt > LEVEL_UP_CNT) //升级与加命不同时处理

{

level_up();

}

}

else //打错   减命

{

sub_life();

}

}

}

else//没命了

{

}

}

}

  以下是提取出来的函数。


//按照level加分并显示

void add_score(void)

{

score += level;

showNumber(56,6,score,DEC,8,FONT_16_EN);

}

//减命并显示

void sub_life(void)

{

life--;

showNumber(56,2,life,DEC,8,FONT_16_EN);

}

//设置固定显示的内容

void FirstScreen(void)

{

//显示大LOGO

formatScreen(0x00);

showImage(0,0,128,8,Y_LOGO_ENUM);

delay_ms(1000);

//显示汉字

formatScreen(0x00);

// showString(0,0,"yoodao",FONT_16_EN);

showCNString(0,0,"小极客打地鼠掌机",FONT_16_CN);

//显示生命、难度与分数

showString(0,2,"life:",FONT_16_EN);

showString(0,4,"level:",FONT_16_EN);

showString(0,6,"score:",FONT_16_EN);

}

//设置屏幕上的参数

void show_opt(void)

{

showNumber(56,2,life,DEC,8,FONT_16_EN);

showNumber(56,4,level,DEC,8,FONT_16_EN);

showNumber(56,6,score,DEC,8,FONT_16_EN);

}


到此,打地鼠程序的基本框架就列好了,也可以来玩打地鼠了。接下来增加背景音乐。


关键字:STM32  掌机教程  程序框架  加命与升级 引用地址:STM32掌机教程5,程序框架,随机,加命与升级

上一篇:STM32掌机教程6,电子琴
下一篇:STM32掌机教程4,STM32驱动OLED屏幕

推荐阅读最新更新时间:2024-11-21 05:42

STM32—IWDG看门狗
背景: STM32的看门狗有2个:独立看门狗IWDG和窗口看门狗WWDG本文主要介绍STM32的IWDG。 内容: IWDG主要性能 (1)自由运行的递减计数器; (2)时钟由独立的RC振荡器提供(可在停止和待机模式下工作),LSI为其时钟源,STOP和SLEEP模式仍可运行; (3)看门狗被激活后,在计数器计数至0x000的的时产生复位。 下图为看门狗的框图。 上图中,IWDG由4个寄存器控制,PR,SR,RLR和KR。下面继续分析STM32的HAL库中IWDG的使用。 IWDG_HandleTypeDef hiwdg;定义一个IWDG_HandleTypeDef类型的结构体,查看其结构体,如
[单片机]
<font color='red'>STM32</font>—IWDG看门狗
STM32单片的FSMC对TFT的驱动
一、FSMC之我见 开始只是谈到别人对FSMC的理解,注意这里只讨论FSMC控制TFT,也就是在FSMC的NORPSRAM模式控制LCD,所以我们以下的分析都是基于这种模式的。 1、我们之前通过使用GPIO来模拟8080/6800时序从而达到驱动彩屏的,同样需要明白的一点就是我们也只是使用FSMC来模拟8080/6800时序,只不过这个读写速度有些快(使用了总线嘛),仅此而已! 简单一点就是:8080是通过“读使能(RE)”和“写使能(WE)”两条控制线进行读写操作。 6800是通过“总使能(E)”和“读写选择(W/R)”两条控制线进行 2、那么了解到FSMC的三总线如下! 数据线:这个可以分为8位的和16位,这个不难
[单片机]
<font color='red'>STM32</font>单片<font color='red'>机</font>的FSMC对TFT的驱动
s3c2440裸机-电阻触摸屏-3-触摸屏TSC初始化和中断服务程序框架
1. ADC中断产生流程 中断源: 这里是ADC和TSC共用一个中断源。 SRCPND表示哪个中断源产生了中断请求。 INTMODE:配置中断模式 配置中断屏蔽寄存器 中断挂起寄存器(用来显示当前优先级最高的、正在发生的中断, 需要清除对应位) 从SRCPND寄存器可以读到ADC和TSC复用的同一个中断源,那么如何区分呢? 可以从SUBSRCPND寄存器配置,如下: 当bit 9被置1时,表示TSC中断。那么我们需要打开subsrcmask寄存器 所以TSC中断的产生流程如下: 2. TSC编程实现   ①初始化T
[单片机]
STM32之PWM学习笔记 ---PWM原理
一. STM32F103通用定时器简介: 通用定时器是一个可编程预分频器驱动的16位自动装载计数器(好难记,,)。 适用于: 测量输入信号的脉冲长度(输入捕获)、产生输出波形(输出比较和PWM)。 每个定时器都是完全独立的,都可以同步操作。 ---------------------以下为复制粘贴------------------------------ STM32 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能特点包括: ①位于低速的APB1总线上(APB1) ②16 位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)。 ③16 位可编程(可以实时修改)
[单片机]
记<font color='red'>STM32</font>之PWM学习笔记 ---PWM原理
stm32之RCC学习笔记
stm32芯片的所有片上外设都需要手动设置时钟。 三种不同的时钟源可被用来驱动系统时钟(SYSCLK): HSI振荡器时钟 由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差。(所以通常不用与提供SYSCLK) HSE振荡器时钟: 高速外部时钟信号,由HSE外部晶体/陶瓷谐振器(较常用)或者HSE用户外部时钟两种方式产生 PLL时钟 时钟源输入,内部PLL可以用来倍频HSI RC的输出时钟或HSE晶体输出时钟,一旦PLL被激活,这些参数就不能被改动。 二级时
[单片机]
<font color='red'>stm32</font>之RCC学习笔记
STM32使用keil串口输出中文乱码问题
问题状态:已解决 问题描述:在进行串口实验时,我们发现使用串口调试助手输出的中文全是乱码(不是文件中的中文乱码,是串口输出的时候出现乱码),见以下图片: 解决方案: 1.首先在你的文件中找到main.c文件,然后使用记事本打开,然后保存另存为,选择ANSI编码(原来默认的是UTF-8),替换原来文件。 2.然后重新编译工程,下载到开发板,再打开串口调试助手,然后我们发现问题已经解决。 (这里注意:如果不成功,重启开发板试试,波特率设置115200,太高也会乱码)
[单片机]
<font color='red'>STM32</font>使用keil串口输出中文乱码问题
stm32库函数记录
一、系统初始化函数执行完毕各时钟的状态 二、三类(总线上的)时钟函数 2.1、外设时钟使能函数 void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState); void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewStat e); void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewStat e); 此3 个时钟使能函数也是 STM32
[单片机]
<font color='red'>stm32</font>库函数记录
Keil实现对意法半导体STM32互联系列的支持
Keil公司宣布其产品Keil MDK-ARM(微控制器开发套件)及RL-ARM(实时库)实现了对意法半导体STM32互联系列的支持。Keil公司同时推出了新的MCBSTEM32C评估板和开发套件。 STM32 互联系列基于ARM® Cortex™-M3处理器,具有全速USB OTG、两个CAN2.0B接口、10/100以太网(含对IEEE1588精密时间协议 (PTP)的硬件支持)。该产品同STM32家族的其他产品采用同样的外设,因此能够轻松实现项目移植,并有高达256KB的闪存和64KB的SRAM。 MDK-ARM 最新版本的的MDK-ARM支持所有的STM32产品,它包括配置文件、设备专有视图以
[单片机]
Keil实现对意法半导体<font color='red'>STM32</font>互联系列的支持
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved