×

具有物联网作弊功能的数字骰子

消耗积分:0 | 格式:zip | 大小:0.00 MB | 2023-06-14

刘英

分享资料个

描述

#25projectsofchristmas

数字物联网作弊骰子

M5STickC-plus 具有内置加速度计。和一个显示器。有了这个,您可以轻松地对数字骰子进行编程,当您摇动设备时会产生新的值。此外,M5STickC-plus 还拥有一个 WiFi 芯片,可以让它连接到互联网。这就是物联网进入游戏的地方:想象一下和你的朋友玩骰子,看看谁在酒吧买单。估计最远的人必须支付。您摇动 M5Stick 并将其正面朝下放在桌子上。每个人都必须猜测一个值,但您首先检查您的智能手机以查看滚动的值,因为您的钱包中没有足够的钱来支付账单。

骰子展示

两个骰子的值应该显示在显示器上,骰子应该看起来像骰子:

pYYBAGOlFhqAVX-xAAASCHOZg5Y625.png
M5StickC-plus 带两个骰子
 

为此,首先将没有点的骰子图像加载为背景图像。

pYYBAGOlFiCACuYAAAAPNGNCaKA602.png
空模具的背景图像
 

现在,根据随机生成的 1 到 6 之间的值,必须在骰子的区域上绘制点。为此,6 个可能值的点坐标存储在一个 3 维数组中:

// Array to define the position of the dots on the dice
int dot_positions[7][6][2] {
    {}, // 0
    {{55,55}}, // 1
    {{25,25},{85,85}}, // 2
    {{25,25},{55,55},{85,85}}, // 3
    {{25,25},{85,25},{25,85},{85,85}}, // 4
    {{25,25},{85,25},{55,55},{25,85},{85,85}},// 5
    {{25,25},{25,55},{25,85},{85,25},{85,55},{85,85}}, // 6
};

我认为这些值需要解释得更详细一点:M5StickC-plus 的显示器分辨率为135*240 像素空间足以容纳两个大小为 110x110 像素的芯片图像。原点在左上角,模具上每个可能的点位置的坐标可以这样定义:

pYYBAGOlFiOAeh7QAABveXR98Og883.png
模具上的点位置
 

如果现在掷出 2,则必须在显示屏上的相应位置绘制两个圆圈。为了让骰子的点位于正确的位置,坐标必须在显示器上移动骰子的目标位置:

pYYBAGOlFiWAUp9UAABhXa2Qoos258.png
将值 2 绘制为骰子的示例
 

dot_positions []数组包含骰子可以取的所有 6 个值对应的圆的位置。然后可以使用它来编写一个简单的函数,为 1 到 6 之间的每个数值绘制相应的点排列:

// function to draw the dice
void draw_dice(int16_t x, int16_t y, int dice_value) {
    // M5StickC-plus Display size is 135x240
    // Dice size is 110x110
    M5.Lcd.pushImage(x, y, 110, 110, (uint16_t *)Dice_background);
    if(dice_value > 0 && dice_value < 7){
        for(int dot_index = 0; dot_index < dice_value; dot_index++) {
            M5.Lcd.fillCircle(x+dot_positions[dice_value][dot_index][0],
                              y+dot_positions[dice_value][dot_index][1],
                              DOT_SIZE, TFT_BLACK);
        }
    }
}

如上所述,对值为 2 的骰子的函数调用将如下所示:

draw_dice(12,9, 2);

现在我们可以画骰子了,但是应用程序仍然需要在一个好的过程中运行。所以我们需要一个“过程序列控制东西功能的东西” ,或者换句话说:我们需要一个状态机

有限状态机

所谓的“状态机”是过程序列的行为模型。它由有限数量的状态组成,因此通常称为有限状态机 (FSM)。简单地说:基于当前状态和给定输入,状态机执行状态转换并产生输出。这听起来比实际更复杂。对于骰子软件,我们可以定义三个简单的状态:

poYBAGOlFiqAX2LNAABY1P09OvQ701.png
骰子软件的基本状态
 

软件应保持在启动状态,直到检测到设备晃动。然后结果应该只在摇动停止时显示。因此,软件应保持在摇动状态,直到检测到摇动结束。最后,软件应保持显示状态,直到按下 M5Stick 上的按钮。这是状态机的基本原理。

为了表示骰子程序的流程控制,代码中定义了以下状态:

//  0 = start with printing text
#define START_STATE 0
// 10 = waiting for start shaking
#define WAIT_STATE 10
// 20 = generate random numbers and wait for stop shaking
#define SHAKE_STATE 20
// 30 = display dice #1 value
#define DISPLAY1_STATE 30
// 40 = display dice #2 value
#define DISPLAY2_STATE 40
// 50 = wait for button press to start new game
#define BUTTON_STATE 50

操作顺序的详细图形表示如下所示:

pYYBAGOlFi2AIhCqAACQNTvDq5w068.png
骰子软件的状态机
 

状态机实现

有几种方法可以实现状态机。由于该软件只需要一个简单的状态机,因此也可以通过一个简单的switch()函数来实现:

// state machine cases
switch(process_State) {
    // ******** START_STATE = start with printing text
    case START_STATE  :
        ...
        // next state
        process_State = WAIT_STATE;
    break;
    // ******** WAIT_STATE = waiting for acceleration (start shaking)
    case WAIT_STATE  :
        ...
        process_State = SHAKE_STATE;
    break;
    // ******** SHAKE_STATE = generate random numbers and wait for stop shaking
    case SHAKE_STATE  :
        ...
        process_State = DISPLAY1_STATE;
    break;
    // ******** DISPLAY1_STATE = display dice #1
    case DISPLAY1_STATE  :
        ...
        process_State = DISPLAY2_STATE;
    break;
    // ******** DISPLAY2_STATE = display dice #2
    case DISPLAY2_STATE  :
        ...
        process_State = BUTTON_STATE;
    break;
    // ******** BUTTON_STATE = wait for button press to start new roll
    case BUTTON_STATE  :
        ...
        process_State = START_STATE;
    break;
}

在每个“开关状态”中,都实现了相应的功能。详情请查看完整的源代码。基本上这就是全部。不像一开始听起来那么复杂,对吧?

如果你想学习如何处理背景图像,请查看我的“M5Stack Screen Capture”项目或我的“M5Stack Christmas Snow Globe”项目。

震动检测

集成加速度传感器 (IMU) 测量 3 个轴(X、Y 和 Z)的加速度:

pYYBAGOlFjCAD5KGAAApHplb1Fg344.png
3轴加速度传感器
 

因此,左右晃动可以通过沿 X 轴的加速度来确定。但不应检测到敲击、撞击或敲击,只有实际的摇晃。一种简单的方法是在很短的时间间隔内进行两次测量。然后,这些值的绝对差异可以用作设备加速程度的指标。方向无关紧要(向左或向右),因为计算的是绝对值。但是,该值也会检测到设备上的敲击。为了防止这种情况,可以计算 10 次测量的移动平均值,从而过滤掉高频数据,如敲击或敲击。

poYBAGOlFjeAALKeAAB6zl7XE7k038.png
用于抖动检测的过滤原始日期
 

非阻塞解决方案

如果你编写一个函数,以 100ms 的间隔测量加速度 10 次来计算平均值,那么整个软件将被阻塞 10*100ms = 1 秒。情况不妙。人们应该始终避免这种阻塞功能。与其在函数内部循环进行 10 次测量,不如让函数只进行一次测量,然后将结果存储在全局变量中。在状态机内,函数在WAIT_STATESHAKE_STATE内被调用,这导致状态机的非阻塞流。

移动平均过滤功能

通常人们会通过将单个值相加然后将总和除以相加值的数量来计算平均值:

poYBAGOlFjuAUgtqAAAUl4z_Vsw280.jpg
平均值的算术计算
 

此方法适用于逐块计算平均值:您只需将多个值相加,然后将最后的总和除以值的数量。对于下一个块,您再次从总结新值开始。但是,如果要计算每个新值的平均值(移动平均值),则必须删除列表的第一个值并在末尾添加新值。编写这样的方法既麻烦又在数学上是不必要的。

另一种(顺便说一下,在数学上相同)方法是将最后计算的平均值乘以减 1 的值的数量,然后将新值添加到其中,并将该总和除以要平均的值的数量:

poYBAGOlFj-ACw6GAAAZLkS4rrU241.jpg
移动平均线计算
 

与存储 10 个值的列表相比,此方法速度更快,所需内存更少。

从技术上讲,这是一种低通滤波器,其背后的理论几乎可以变得任意复杂,但是对于这个简单的程序,可以通过反复试验很好地确定合适的值。要平均的值的数量值越高,过滤掉的高频信号就越多,但产生的延迟也就越多。检测抖动的函数的实现如下所示:

int mean_accX_n = 8;

.....

float get_horizontal_shaking(){
    // two values of X-acceleration
    float accX_1, accX_2;
    // Y and Z values are needed for function call
    float accY, accZ;
    // differential value of X-acceleration
    float accX_d = 0;
    // get the first and the second sensor data
    M5.Imu.getAccelData(&accX_1,&accY,&accZ);
    delay(100);
    M5.Imu.getAccelData(&accX_2,&accY,&accZ);
    // calculate the absolute differential value
    accX_d = fabs(accX_2 - accX_1);
    // building the mean value (Moving average calculation)
    mean_accX_d = ((mean_accX_d * (mean_accX_n-1)) + accX_d)/mean_accX_n;
    return mean_accX_d;
}

返回值大于 3 表示设备已摇晃。小于 1 的值表示晃动已停止:

pYYBAGOlFkKAYiNlAACnepPCmF0638.png
带有阈值的过滤数据
 

有了这个,骰子软件就可以运行了,你可以为酒吧账单滚动了。唯一缺少的是作弊功能。

物联网作弊

只需几行简单的代码,M5StickC 就变成了一个 WiFi 接入点:

// WiFi network configuration:
char wifi_ssid[] = "M5StickC-plus";
char wifi_key[] = "1234567890";
IPAddress ip(192, 168, 0, 1);
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);

WiFiClient myclient;
WiFiServer server(80);

void web_server_init(){
    WiFi.mode(WIFI_AP);
    WiFi.softAP(wifi_ssid, wifi_key);
    WiFi.softAPConfig(ip, gateway, subnet);
    WiFi.begin();
    // Start TCP/IP-Server
    server.begin();
}

现在您可以将手机连接到 M5Stick 创建的 WiFi 接入点。同时,Web 服务器正在等待您使用浏览器连接到 IP 地址。

我已经在我的项目“ M5ATOM ENV 迷你数据监视器”中描述了 Web 服务器的功能。对于骰子网页,我还使用动态 HTML 代码通过外部 Java 脚本请求包含立方体的值:

<script>
    window.onload = function(){
        document.getElementById('dice1').innerHTML = dice1value;
        document.getElementById('dice2').innerHTML = dice2value;
    };
script>
<script type="text/javascript" src="dicevalue.js">script>

掷出的两个骰子的值被定义为全局变量。当 Web 浏览器请求 Java 脚本文件“ dicevalue.js ”时,Web 服务器会根据当前值生成它:

case GET_dicevalue: {
    client.println("HTTP/1.1 200 OK");
    client.println("Content-type:application/javascript");
    client.println();
    client.printf("var dice1value = %c;\n", dice1_html_value);
    client.printf("var dice2value = %c;\n", dice2_html_value);
    break;
}

其他一切都与我的项目“M5ATOM ENV 迷你数据监视器”中的描述完全相同但是,在这段代码中,我将 Web 服务器功能外包给了外部文件。这使骰子软件的代码保持简单和干净。带有接入点的 Web 服务器只需要在 setup() 函数中初始化,并在每次运行时在 loop() 函数中调用:

void setup() {
    M5.begin();

    ...

    // init and start the web-server for the dice-web-page
    web_server_init();
    delay(3000);
}


void loop() {
    M5.update();

    ...

    // check for web browser requests
    web_server_update();
    delay(20);
}

现在你可以带着空钱包去酒吧和朋友喝一杯。但是请确保 M5Stick 的电池始终充满电,因为如果有人拿出真正的骰子,您必须依靠您的运气而不是您的智能手机。

反馈

我希望你喜欢这个简短有趣的项目,并且这段代码可以证明对你们中的一些人有用。如果您有任何问题或意见,请随时给我留言。

享受掷骰子的乐趣!

问候,

汉斯-君特


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !