STM32 与 AS608 指纹模块的调试与应用

前言

在嵌入式系统中,指纹识别作为一种生物识别技术,广泛应用于门禁系统、考勤机、智能锁等场景。本文将分享如何在 STM32F103C8T6 开发板上使用 AS608 指纹模块,实现指纹的录入和识别功能。
在这里插入图片描述

硬件准备

  • STM32F103C8T6 开发板
  • AS608 指纹模块
  • OLED 显示屏(用于显示提示信息)
  • 杜邦线若干

硬件连接

确保硬件连接正确,以下是主要的连接:

  • AS608 指纹模块与 STM32 的连接:

    • AS608 TXD(发送端) ==> STM32 PA3(USART2 RX)
    • AS608 RXD(接收端) ==> STM32 PA2(USART2 TX)
    • AS608 VCC ==> 3.3V
    • AS608 GND ==> GND
  • OLED 显示屏与 STM32 的连接:

    按照 OLED 屏幕的接线要求进行连接,一般使用 I2C 或 SPI 接口。

软件设计

项目结构

  • main.c:主程序入口,负责整体流程控制。
  • AS608.c / AS608.h:封装与 AS608 指纹模块的通信与操作函数。
  • 其他外设驱动文件:如 OLED 显示屏的驱动代码。

主程序流程

  1. 初始化外设:OLED 显示屏、USART2(用于与 AS608 通信)等。
  2. 等待 3 秒:给用户准备时间。
  3. 指纹录入
    • 提示用户放置手指。
    • 获取指纹图像,并生成特征文件。
    • 提示用户再次放置手指,重复上述步骤。
    • 合成指纹模板并存储到指纹库中。
  4. 等待 2 秒
  5. 指纹识别
    • 提示用户放置手指。
    • 获取指纹图像,生成特征文件。
    • 在指纹库中搜索匹配的指纹。
    • 显示识别结果(匹配的指纹 ID 或未找到)。

代码实现

1. main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "AS608.h"

int main(void)
{
    // 初始化外设
    OLED_Init();
    AS608_Init();

    OLED_ShowString(1, 1, "AS608:");

    while(1)
    {
        Delay_s(3);  // 等待3秒

        OLED_ShowString(2, 1, "Enrolling...");
        if (AS608_Enroll() == 0)
        {
            OLED_ShowString(2, 1, "Enroll OK    ");
        }
        else
        {
            OLED_ShowString(2, 1, "Enroll Failed");
        }

        Delay_s(2);  // 再等待2秒(总共5秒)

        OLED_ShowString(2, 1, "Identifying...");
        uint16_t id;
        if (AS608_Identify(&id) == 0)
        {
            OLED_ShowString(2, 1, "ID:");
            OLED_ShowNum(2, 4, id, 5);
        }
        else
        {
            OLED_ShowString(2, 1, "Not Found    ");
        }

        Delay_s(2);  // 等待2秒显示结果
    }
}
2. AS608.h
#ifndef __AS608_H
#define __AS608_H

#include "stm32f10x.h"

// 指令代码
#define PS_GetImage          0x01
#define PS_GenChar           0x02
#define PS_Match             0x03
#define PS_Search            0x04
#define PS_RegModel          0x05
#define PS_StoreChar         0x06
#define PS_LoadChar          0x07
#define PS_UpChar            0x08
#define PS_DownChar          0x09
#define PS_UpImage           0x0A
#define PS_DownImage         0x0B
#define PS_DeleteChar        0x0C
#define PS_Empty             0x0D
#define PS_SetSysPara        0x0E
#define PS_ReadSysPara       0x0F
#define PS_SetPwd            0x12
#define PS_VerifyPwd         0x13
#define PS_TemplateNum       0x1D

// 确认码
#define ACK_SUCCESS          0x00

void AS608_Init(void);
uint8_t AS608_Enroll(void);
uint8_t AS608_Identify(uint16_t *id);

#endif /* __AS608_H */
3. AS608.c
#include "AS608.h"
#include "Delay.h"
#include "OLED.h"
#include <stdio.h>

void AS608_Init(void)
{
    // USART2 初始化
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 配置 USART2 引脚
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;  // TX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;  // RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置 USART2
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 57600;  // AS608默认波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStructure);

    USART_Cmd(USART2, ENABLE);
}

void AS608_SendByte(uint8_t Byte)
{
    USART_SendData(USART2, Byte);
    while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}

void AS608_SendArray(uint8_t *Array, uint16_t Length)
{
    for (uint16_t i = 0; i < Length; i++)
    {
        AS608_SendByte(Array[i]);
    }
}

uint8_t AS608_ReceiveByte(void)
{
    uint32_t timeout = 0xFFFFF; // 增加超时时间
    while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET)
    {
        if (--timeout == 0)
        {
            return 0xFF;  // 超时返回
        }
    }
    return USART_ReceiveData(USART2);
}

// 发送指令包
void AS608_SendCommand(uint8_t instruction_code, uint8_t *parameters, uint16_t parameter_length)
{
    uint16_t packet_length = parameter_length + 2; // instruction code + parameters
    uint8_t packet[12 + parameter_length];
    uint16_t checksum = 0;

    // 包头
    packet[0] = 0xEF;
    packet[1] = 0x01;
    // 地址
    packet[2] = 0xFF;
    packet[3] = 0xFF;
    packet[4] = 0xFF;
    packet[5] = 0xFF;
    // 包标识
    packet[6] = 0x01; // 指令包
    // 包长度
    packet[7] = (packet_length >> 8) & 0xFF;
    packet[8] = packet_length & 0xFF;
    // 指令码
    packet[9] = instruction_code;
    // 参数
    for (uint16_t i = 0; i < parameter_length; i++)
    {
        packet[10 + i] = parameters[i];
    }
    // 计算校验和
    checksum = packet[6] + packet[7] + packet[8];
    for (uint16_t i = 9; i < 10 + parameter_length; i++)
    {
        checksum += packet[i];
    }
    // 校验和
    packet[10 + parameter_length] = (checksum >> 8) & 0xFF;
    packet[11 + parameter_length] = checksum & 0xFF;

    // 发送包
    AS608_SendArray(packet, 12 + parameter_length);
}

// 接收响应包
uint8_t AS608_ReceivePacket(uint8_t *buffer, uint16_t *length)
{
    uint16_t idx = 0;
    uint16_t i;
    uint16_t checksum = 0;

    // 读取包头和地址,共6字节
    for (i = 0; i < 6; i++)
    {
        buffer[idx++] = AS608_ReceiveByte();
    }
    // 检查包头和地址
    if (buffer[0] != 0xEF || buffer[1] != 0x01)
    {
        return 1; // 包头错误
    }
    // 读取包标识和长度,共3字节
    for (i = 0; i < 3; i++)
    {
        buffer[idx++] = AS608_ReceiveByte();
    }
    uint16_t packet_length = (((uint16_t)buffer[7] << 8) | buffer[8]) - 2; // 减去校验和长度
    // 读取包内容和校验和
    for (i = 0; i < packet_length + 2; i++)
    {
        buffer[idx++] = AS608_ReceiveByte();
    }
    *length = idx;
    // 计算校验和
    for (i = 6; i < idx - 2; i++)
    {
        checksum += buffer[i];
    }
    uint16_t received_checksum = ((uint16_t)buffer[idx - 2] << 8) | buffer[idx - 1];
    if (checksum != received_checksum)
    {
        return 2; // 校验和错误
    }
    return 0; // 成功
}

// 指纹录入函数
uint8_t AS608_Enroll(void)
{
    uint8_t ack;
    uint8_t buffer[64];
    uint16_t length;
    uint16_t page_id = 0; // 假设将指纹存储在ID为0的位置
    uint8_t retry;

    // 提示放置手指
    OLED_ShowString(2, 1, "Place Finger ");
    // Step 1: 获取图像
    retry = 0;
    do
    {
        AS608_SendCommand(PS_GetImage, NULL, 0);
        ack = AS608_ReceivePacket(buffer, &length);
        if (ack == 0 && buffer[9] == ACK_SUCCESS)
        {
            break;
        }
        else if (ack == 0 && buffer[9] == 0x02)
        {
            OLED_ShowString(3, 1, "No Finger    ");
        }
        else
        {
            OLED_ShowString(3, 1, "GetImage Err ");
        }
        Delay_ms(500);
        retry++;
    } while (retry < 10);

    if (retry >= 10)
    {
        return 1; // 超时
    }

    // Step 2: 生成特征文件到CharBuffer1
    uint8_t param[1] = {0x01}; // Buffer1
    AS608_SendCommand(PS_GenChar, param, 1);
    ack = AS608_ReceivePacket(buffer, &length);
    if (ack != 0 || buffer[9] != ACK_SUCCESS)
    {
        return 1; // 错误
    }

    // 提示移开手指
    OLED_ShowString(2, 1, "Remove Finger");
    Delay_s(2);

    // 提示再次放置手指
    OLED_ShowString(2, 1, "Place Again  ");

    // Step 3: 再次获取图像
    retry = 0;
    do
    {
        AS608_SendCommand(PS_GetImage, NULL, 0);
        ack = AS608_ReceivePacket(buffer, &length);
        if (ack == 0 && buffer[9] == ACK_SUCCESS)
        {
            break;
        }
        Delay_ms(500);
        retry++;
    } while (retry < 10);

    if (retry >= 10)
    {
        return 1; // 超时
    }

    // Step 4: 生成特征文件到CharBuffer2
    param[0] = 0x02; // Buffer2
    AS608_SendCommand(PS_GenChar, param, 1);
    ack = AS608_ReceivePacket(buffer, &length);
    if (ack != 0 || buffer[9] != ACK_SUCCESS)
    {
        return 1; // 错误
    }

    // Step 5: 合并特征到模板
    AS608_SendCommand(PS_RegModel, NULL, 0);
    ack = AS608_ReceivePacket(buffer, &length);
    if (ack != 0 || buffer[9] != ACK_SUCCESS)
    {
        return 1; // 错误
    }

    // Step 6: 存储模板到指定ID
    uint8_t store_param[3] = {0x01, (page_id >> 8) & 0xFF, page_id & 0xFF};
    AS608_SendCommand(PS_StoreChar, store_param, 3);
    ack = AS608_ReceivePacket(buffer, &length);
    if (ack != 0 || buffer[9] != ACK_SUCCESS)
    {
        return 1; // 错误
    }

    return 0; // 成功
}

// 指纹识别函数
uint8_t AS608_Identify(uint16_t *id)
{
    uint8_t ack;
    uint8_t buffer[64];
    uint16_t length;
    uint8_t retry;

    // 提示放置手指
    OLED_ShowString(2, 1, "Place Finger ");

    // Step 1: 获取图像
    retry = 0;
    do
    {
        AS608_SendCommand(PS_GetImage, NULL, 0);
        ack = AS608_ReceivePacket(buffer, &length);
        if (ack == 0 && buffer[9] == ACK_SUCCESS)
        {
            break;
        }
        else if (ack == 0 && buffer[9] == 0x02)
        {
            OLED_ShowString(3, 1, "No Finger    ");
        }
        else
        {
            OLED_ShowString(3, 1, "GetImage Err ");
        }
        Delay_ms(500);
        retry++;
    } while (retry < 10);

    if (retry >= 10)
    {
        return 1; // 超时
    }

    // Step 2: 生成特征文件到CharBuffer1
    uint8_t param[1] = {0x01}; // Buffer1
    AS608_SendCommand(PS_GenChar, param, 1);
    ack = AS608_ReceivePacket(buffer, &length);
    if (ack != 0 || buffer[9] != ACK_SUCCESS)
    {
        return 1; // 错误
    }

    // Step 3: 搜索指纹库
    uint8_t search_param[6] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x01}; // Buffer1, 起始页0,页数1
    AS608_SendCommand(PS_Search, search_param, 6);
    ack = AS608_ReceivePacket(buffer, &length);
    if (ack != 0 || buffer[9] != ACK_SUCCESS)
    {
        return 1; // 未找到
    }

    // 获取匹配到的指纹ID
    *id = ((uint16_t)buffer[10] << 8) | buffer[11];

    return 0; // 成功
}

关键问题与解决方案

问题描述

在实现过程中,发现程序一直停留在 Place Finger 提示,无法继续。这表明程序可能在等待指纹模块的响应,但未能收到正确的数据。

可能原因
  1. USART 通讯问题

    • 波特率设置不正确。
    • USART 引脚连接错误。
    • USART 接收函数未正确实现。
  2. 指令包格式错误

    • 指令包中的参数、校验和计算错误。
    • 未正确按照 AS608 通讯协议发送和接收数据。
  3. 指纹模块未正常工作

    • 模块损坏或供电不足。
    • 指纹库已满或需要初始化。
解决方案
  1. 检查 USART 通讯配置

    • 确保波特率设置为 57600,这是 AS608 的默认波特率。

    • 检查 USART2 初始化代码,确保配置正确。

      // 配置 USART2
      USART_InitTypeDef USART_InitStructure;
      USART_InitStructure.USART_BaudRate = 57600;  // AS608默认波特率
      USART_InitStructure.USART_WordLength = USART_WordLength_8b;
      USART_InitStructure.USART_StopBits = USART_StopBits_1;
      USART_InitStructure.USART_Parity = USART_Parity_No;
      USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
      USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
      USART_Init(USART2, &USART_InitStructure);
      
    • 确认引脚连接:

      • PA2(USART2 TX) ==> AS608 RXD
      • PA3(USART2 RX) ==> AS608 TXD
  2. 改进接收函数 AS608_ReceiveByte

    • 增加超时时间,避免因等待时间过短导致接收失败。

      uint8_t AS608_ReceiveByte(void)
      {
          uint32_t timeout = 0xFFFFF; // 增加超时时间
          while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET)
          {
              if (--timeout == 0)
              {
                  return 0xFF;  // 超时返回
              }
          }
          return USART_ReceiveData(USART2);
      }
      
  3. 检查指令包格式和校验和计算

    • 确保发送的指令包符合 AS608 通讯协议,包括包头、地址、包标识、包长度、指令码、参数、校验和。

    • 修改 AS608_SendCommand 函数中的校验和计算方式:

      // 计算校验和
      checksum = packet[6] + packet[7] + packet[8];
      for (uint16_t i = 9; i < 10 + parameter_length; i++)
      {
          checksum += packet[i];
      }
      
  4. 增加调试信息

    • 在关键步骤中,通过 OLED 显示接收到的确认码,便于判断问题所在。

      OLED_ShowString(3, 1, "Ack: ");
      OLED_ShowHexNum(3, 6, buffer[9], 2);
      
  5. 增加超时和错误处理机制

    • 在等待指纹输入和接收模块响应时,增加重试次数,避免程序陷入死循环。

      uint8_t retry = 0;
      do
      {
          // 发送指令并接收响应
          // ...
          retry++;
      } while (retry < 10);
      
  6. 检查指纹模块工作状态

    • 确认指纹模块的供电电压为 3.3V,且电源足够稳定。

    • 如果指纹库已满,尝试清空指纹库:

      uint8_t AS608_EmptyLibrary(void)
      {
          uint8_t buffer[32];
          uint16_t length;
      
          AS608_SendCommand(PS_Empty, NULL, 0);
          uint8_t ack = AS608_ReceivePacket(buffer, &length);
          if (ack == 0 && buffer[9] == ACK_SUCCESS)
          {
              return 0; // 成功
          }
          else
          {
              return 1; // 失败
          }
      }
      
  7. 使用最小化测试代码进行验证

    • 编写简单的测试程序,仅发送 PS_GetImage 指令,查看是否能收到正确的响应。

      int main(void)
      {
          // 初始化外设
          OLED_Init();
          AS608_Init();
      
          OLED_ShowString(1, 1, "AS608 Test:");
      
          while(1)
          {
              OLED_ShowString(2, 1, "Testing...   ");
              AS608_SendCommand(PS_GetImage, NULL, 0);
              uint8_t buffer[32];
              uint16_t length;
              uint8_t ack = AS608_ReceivePacket(buffer, &length);
              if (ack == 0)
              {
                  OLED_ShowString(3, 1, "Ack: ");
                  OLED_ShowHexNum(3, 6, buffer[9], 2);
                  if (buffer[9] == ACK_SUCCESS)
                  {
                      OLED_ShowString(4, 1, "GetImage OK ");
                  }
                  else
                  {
                      OLED_ShowString(4, 1, "GetImage Fail");
                  }
              }
              else
              {
                  OLED_ShowString(3, 1, "No Response  ");
              }
              Delay_s(2);
          }
      }
      

总结

通过对硬件连接、USART 通讯配置、指令包格式、接收函数以及错误处理机制的逐一检查和改进,成功实现了 AS608 指纹模块在 STM32 上的指纹录入和识别功能。

在调试过程中,遇到类似的问题时,应从硬件和软件两个方面入手,逐步排查。同时,增加调试信息和错误处理机制,可以大大提高调试效率。

注意事项

  • 确保指纹模块的电源供电稳定。
  • 遵循 AS608 通讯协议,正确组建指令包和解析响应包。
  • USART 接收函数需要考虑超时和异常情况。

参考资料

  • AS608 指纹模块数据手册
  • STM32F10x 标准外设库参考手册
  • UART 通讯协议和调试方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/941876.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux Shell 基础教程⑧

Shell 教程 Shell 是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言。 Shell 是指一种应用程序&#xff0c;这个应用程序提供了一个界面&#xff0c;用户通过这个界面访问操作系统内核的服务。 Ke…

网络刷卡器的功能和使用场景

网络刷卡器是一种连接互联网的设备&#xff0c;能够通过网络将读取到的各种卡片信息传输至服务器进行处理。这类刷卡器通常支持多种类型的卡片&#xff0c;如银行卡、身份证、会员卡、公交卡等&#xff0c;并运用现代信息技术确保数据的安全性和高效性&#xff0c;功能十分强大…

Centos7下的根口令重置与GRUB修复

目录 1. 利用GRUB进入单用户模式重置根口令&#xff1b; 步骤较多方法 步骤较少方法&#xff1a;这里主要是把重新以rw方式挂载的步骤换为了在编辑模式直接修改 2. 利用Linux系统安装光盘进入急救模式重置根口令&#xff1b; 3. 如果GRUB损坏&#xff0c;利用Linu…

赋能新一代工业机器人-望获实时linux在工业机器人领域应用案例

在工业4.0蓬勃发展的当下&#xff0c;工业机器人作为制造业转型升级的中流砥柱&#xff0c;正朝着超精密、极速响应的方向全力冲刺。然而&#xff0c;为其适配理想的望获实时Linux系统&#xff0c;却犹如寻找开启宝藏之门的关键钥匙&#xff0c;成为众多企业在智能化进程中的棘…

“无需代码,一句需求,立刻看到你的创意变成网页”==>前端AI工具 “V0”

想象一下&#xff0c;一个能帮你跳过所有烦人的代码编写过程&#xff0c;直接根据你的需求生成页面的 AI&#xff01;没错&#xff0c;这就是 v0&#xff01;你只需要用自然语言描述你想要的界面&#xff0c;v0 就会挥一挥它的“魔法鼠标”&#xff0c;立刻生成漂亮的 UI 代码。…

C语言(一)——初识C语言

目录 简单认识一段代码 数据类型 变量和常量 变量的作用域和变量的生命周期 常量 字符串 转义字符 注释 函数 数组 操作符 关键字 结构体 结构的声明 结构成员的类型 结构体变量的初始化 结构体传参 简单认识一段代码 main()函数是程序的入口&#xff0c;所以…

频繁拿下定点,华玉高性能中间件迈入商业化新阶段

伴随着智能驾驶渗透率的快速增长&#xff0c;中国基础软件市场开始进入黄金窗口期。 近日&#xff0c;华玉通软&#xff08;下称“华玉”&#xff09;正式获得某国内头部轨道交通产业集团的智能化中间件平台定点项目。这将是华玉在基础软件领域深耕和商业化发展过程中的又一重…

怎么学习数据结构与算法?

数据结构与算法 提及数据结构与算法&#xff0c;许多人可能会不自觉地皱起眉头。似乎在不知不觉中&#xff0c;以字节跳动为代表的一批公司&#xff0c;在面试环节开始了一场针对算法的连环盘问。若非事先系统地刷过一系列算法题目&#xff0c;想要轻松通过这一关&#xff0c;…

MySQL通过日志恢复数据的步骤

试验环境&#xff1a;Windows Server2012 r2、MySql-8.0.27-winx64。 1、先检查MySQL有没有开启binlog日志 通过下面的SQL命令查看MySQL是否开启日志以及日志文件的位置&#xff1a; show variables like %log_bin% 执行结果如下图所示&#xff1a; 图中&#xff0c;log_bi…

react+antd的Table组件编辑单元格

需求&#xff1a;新增加一行&#xff0c;且单元格可以编辑 场景&#xff1a;真实的业务需求&#xff08;antd 3 版本react&#xff09; 效果图&#xff1a;1. 默认增加一行时&#xff0c;第一列是下拉选择框&#xff0c;第2 3列是TextArea&#xff0c;图1 2. 当下拉选择的数据不…

基于Springboot的数码产品抢购系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实…

LabVIEW电机控制中的主动消抖

在LabVIEW电机控制系统中&#xff0c;抖动现象&#xff08;如控制信号波动或机械振动&#xff09;会影响系统的稳定性和精度。通过使用主动消抖算法&#xff0c;可以有效降低抖动&#xff0c;提高控制性能。本文将介绍几种主流的主动消抖算法&#xff0c;并结合具体应用案例进行…

连续自成核退火热分级(SSA)技术表征共聚聚丙烯(PP)分子链结构

共聚聚丙烯是一种多相多组分高分子体系&#xff0c;体系中同时存在多种链组成、序列结构和相结构。研究表明&#xff0c;共聚聚丙烯中除了均聚聚丙烯外&#xff0c;还有乙丙无规共聚物&#xff08;又称乙丙橡胶&#xff0c;EPR&#xff09;及不同序列长度的乙丙嵌段共聚物&…

游戏AI实现-寻路算法(Dijkstra)

戴克斯特拉算法&#xff08;英语&#xff1a;Dijkstras algorithm&#xff09;&#xff0c;又称迪杰斯特拉算法、Dijkstra算法&#xff0c;是由荷兰计算机科学家艾兹赫尔戴克斯特拉在1956年发现的算法。 算法过程&#xff1a; 1.首先设置开始节点的成本值为0&#xff0c;并将…

C# OpenCV机器视觉:缺陷检测

在一个阳光明媚的早晨&#xff0c;阿强正准备享受他的一杯咖啡&#xff0c;突然接到了老板的电话。“阿强&#xff0c;我们的生产线出现了问题&#xff01;有几个产品的质量不合格&#xff0c;客户投诉不断&#xff01;你能不能想办法解决这个问题&#xff1f;” 阿强一听&…

模型 ADDIE(分析、设计、开发、实施、评估)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。分析、设计、开发、实施、评估教学法。 1 模型ADDIE(分析、设计、开发、实施、评估)的应用 1.1 个人IP私域运营体系构建 在个人IP私域运营领域&#xff0c;ADDIE模型被应用于构建一个系统的运营体系…

【微信小程序】3|首页搜索框 | 我的咖啡店-综合实训

首页-搜索框-跳转 引言 在微信小程序中&#xff0c;首页的搜索框是用户交互的重要入口。本文将通过“我的咖啡店”小程序的首页搜索框实现&#xff0c;详细介绍如何在微信小程序中创建和处理搜索框的交互。 1. 搜索函数实现 onClickInput函数在用户点击搜索框时触发&#x…

upload-labs-master第21关超详细教程

目录 环境配置解题思路利用漏洞 操作演示 环境配置 需要的东西 phpstudy-2018 链接&#xff1a; phpstudy-2018 提取码&#xff1a;0278 32 位 vc 9 和 11 运行库 链接&#xff1a; 运行库 提取码&#xff1a;0278 upload-labs-master 靶场 链接&#xff1a; upload-lasb-ma…

Elasticsearch:确保业务规则与语义搜索无缝协作

作者&#xff1a;来自 Elastic Kathleen DeRusso 利用查询规则与语义搜索和重新排序相结合的强大功能。 更多阅读&#xff1a; Elasticsearch 8.10 中引入查询规则 - query rules Elasticsearch 查询规则现已正式发布 - query rules 你是否知道查询规则&#xff08;query ru…

mysql联表查询

创建多个表&#xff0c;语句如下&#xff1a; CREATE DATABASE /*!32312 IF NOT EXISTS*/sg_security /*!40100 DEFAULT CHARACTER SET utf8mb4 */;USE sg_security;/*Table structure for table sys_menu */DROP TABLE IF EXISTS sys_menu;CREATE TABLE sys_menu (id bigint(2…