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-07 23:21

STM32的ADC的原理使用
一、ADC的原理及定义 Analog-to-Digital Converter的缩写。中文译名:模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。其实就是一个取样、量化、编码的一个过程。 典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。比如电量、光照传感器等常用。 ---------------------------------------------------------------------------------------------------------------------------------------------------------
[单片机]
<font color='red'>STM32</font>的ADC的原理<font color='red'>与</font>使用
stm32 adc的dma传输案例
dma不多说了,原理啥的网上一大推,源码也有注释,理解起来很容易 案例是传输adc的4个通道,需要多个或者其它的,照着修改就行。 #define ADC1_DR_Address ((u32)0x40012400+0X4C) //ADC数据的地址 u16 ADC_buf ; //DMA传输BUF void adc_dma_init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2
[单片机]
<font color='red'>stm32</font> adc的dma传输案例
STM32 ISP烧录过程
 STM32在芯片生产过程中内嵌了一段引导程序,其作用就是通过串口将程序下载到Flash中,为以后的软件更新提供了极大的便利,用户不需要利用仿真口进行下载程序,从而极大的提高了工作效率。 STM32复位之后,如果检测到Boot1引脚为低电平,boot0引脚为高电平,芯片就执行内部固话的ISP引导程序,接收来自上位机的命令和数据。整个烧录过程如下图所示: ISP的过程: 1.芯片复位 在给STM32复位之前,首先要确定BOOT0,BOOT1引脚的状态.通过各种方式,先让BOOT0处于高电平状态,BOOT1处于低电平状态,然后在RST脚上产生一个负脉冲,STM32就能进入ISP状态.注意,复位之后,一定要延时一定的时间,让IS
[单片机]
STM32 Keil中关于stlink的调试 下载设置
1.首先找到魔法棒,或者右键项目,选择第一个Options of........ 2.找到Debug ,选择stlink下载 点击setting 3.选择模式为SW模式,点击确定,进入下一步 4.点击Utilities选项卡,先取消use debug driver ,然后再选择选择ST-Link Debugger,点击Settings 5.首先先是要打勾,然后根据芯片flash的大小添加相应的信息,最后点击确定 即可
[单片机]
<font color='red'>STM32</font> Keil中关于stlink的调试 下载设置
stm32 0.96寸OLED时钟程序,万年历,大字体
下午闲着没事,在我们的51hei论坛找了一个小玩意儿(具体谁的我找不着了,抱歉哈),修改了一下。但是他的程序之前写的时候可能太古老了,所以我就稍微修改完善了一下。用的是stm32自带的RTC时钟。硬件连接很简单,当然程序也是比较简单的,只写了温度(DS18B20),stm32自带RTC和OLED显示,大家可自行删改功能。 硬件连接: SDA --》PB13 SCL --》PB12 DS18B20----》PA15 OLED和DS18B20直接5V供电就成,代码和工具都在最后,需要的小伙伴自行下载吧。 效果如下: 单片机源程序如下: #include sys.h #include usart.h
[单片机]
<font color='red'>stm32</font> 0.96寸OLED时钟<font color='red'>程序</font>,万年历,大字体
经典_STM32_ADC多通道采样的例子
STM32 ADC多通道转换 描述:用ADC连续采集11路模拟信号,并由DMA传输到内存。ADC配置为扫描并且连续转换模式,ADC的时钟配置为12MHZ。在每次转换结束后,由DMA循环将转换的数据传输到内存中。ADC可以连续采集N次求平均值。最后通过串口传输出最后转换的结果。 程序如下: #i nclude stm32f10x.h //这个头文件包括STM32F10x所有外围寄存器、位、内存映射的定义 #i nclude eval.h //头文件(包括串口、按键、LED的函数声明) #i nclude SysTickDelay.h #i nclude UART_INTERFACE.h #i nclude stdio.h #d
[单片机]
stm32的定时器输入捕获输出比较
明确一点对比AD的构造,stm32有3个AD,每个AD有很多通道,使用哪个通道就配置成哪个通道,这里定时器也如此,有很多定时器TIMx,每个定时器有很多CHx(通道),可以配置为输入捕捉-------测量频率用,也可以配置为输出比较--------输出PWM使用 输入捕捉:可以用来捕获外部事件,并为其赋予时间标记以说明此事件的发生时刻。 外部事件发生的触发信号由单片机中对应的引脚输入(具体可以参考单片机的datasheet),也可以通过模拟比较器单元来实现。 时间标记可用来计算频率,占空比及信号的其他特征,以及为事件创建日志,主要是用来测量外部信号的频率。 输出比较:定时器中计数寄存器在初始化完后会自动的计数。从botto
[单片机]
<font color='red'>stm32</font>的定时器输入捕获<font color='red'>与</font>输出比较
STM32之八定时器中断
STM32的通用定时器是一个通过可编程预分频器(PSC)驱动的16 位自动装载计数器(CNT)构成。STM32的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32的每个通用定时器都是完全独立的,没有互相共享的任何资源。 STM3的通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括: 1)16位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。 2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535之间
[单片机]
<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