STM32_SD卡的SDIO通信_DMA读写

本篇,将使用CubeMX+Keil,创建一个SD卡的DMA读写工程。

目录

一、简述

二、CubeMX 配置 SDIO + DMA

三、Keil 编辑代码

四、实验效果


实现效果,如下图:


一、简述

上篇已简单介绍了SD、SDIO,本篇不再啰嗦,有兴趣的可翻看上篇:

STM32_SD卡的SDIO通信_基础读写-CSDN博客

使用SD卡存储数据,常用的两种方式:

  • DMA读写(不带文件系统):逻辑简单、代码体积小,适合只做数据存储的应用。
  • 模拟U盘方式(FATFS+USB):方便电脑端文件存储,但代码体积大、文件解释麻烦。

两种方式各有优劣,得按硬件限制、方案需求而进行选择。

本篇示范的是:不带FATFS的DMA读写。


二、CubeMX 配置 SDIO + DMA

新建工程部分,略过。

1、使能SDIO

  • Mode :  选择 SD 4 bits Wide bus ;
  • DMA Settings :  添加SDIO_RX、SDIO_TX这两项;  其它参数默认;
  • 其它参数 :F4系列不用修改配置,默认即可。F103系列,需把时钟分频系数修改为 6,即SDIOCLK Clock divide factor这一项,默认0,修改为6, 不然会通信失败。

2、开启SDIO全局中断,修改中断优先级

3、时钟设置

进入时钟树配置页面。

如果之前没配置过SDIO、USB,这时就会弹窗:是否自动配置所需时钟?

选择:NO ,手动修改即可。

不推荐 Yes,因为它将针对已使能的SDIO、USB进行必须值的配置,而已设置好的系统时钟,将会被修改成其它值。

F4系列,如果板子用25M晶振,使用下图配置即可;如果是8M晶振,修改晶振、分频两处为8即可。

重点:箭头所指的Q值,它用于控制USB 、SDIO和随机数生成器的时钟,这个时钟必须是 48M !  因此,这里设置为 7; 

好了,已完成配置。

重新生成工程,即可!


三、Keil 编辑代码

1、打开keil 工程,先重新编译一次。

  • 正常情况,编译是0 Error的。
  • 如果有Error,  应该是新建工程时,路径、名称有中文了,重新开建工程,改为英文即可。

2、重要修改:SD卡的初始化,使用 1-bit 模式

CubeMX生成的SDIO初始化代码,有一个bug,需要手动修改,操作如下: 

  • 右击 main.c 文件中函数 MX_SDIO_SD_Init(), 
  • 在弹出菜单中:Go To Ddfinition Of ...;  将跳转到SD卡初始化函数内部;

跳转到 sdio.c文件的 MX_SDIO_SD_Init()函数内部后,

把下图位置中的 4B,改为 1B ;

它下面还有一个4B,不用修改,只修改刚才那个即可。不要改错位置了!

重要!CubeMX每次重新生成后,都要手动修改一次。如果不修改,初始化过程会导致程序卡死。

3、DMA 读写函数 介绍

我们在上篇介绍SD卡的基础读写时,使用的4个函数,如下:

这4个函数,在本工程中,还是可用的。

1、获取SD卡信息
HAL_SD_CardInfoTypeDef pCardInfo = {0};          // SD卡信息结构体
HAL_SD_GetCardInfo(&hsd, &pCardInfo);            // 获取 SD 卡的信息

2、读数据
HAL_SD_ReadBlocks(&hsd, aOldData, 7, 2, 3000);   //  SD卡的句柄、数据、块地址、块数量、超时ms

3、写数据
HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000)  //  SD卡的句柄、数据、块地址、块数量、超时ms

4、擦除数据
HAL_SD_Erase(&hsd, 7, 8)  //  SD卡的句柄、块起始地址、块结束地址

本篇通过DMA读写,将使用以下两个读写函数:

(和基础读写的函数名称近似,只是多了后辍"_DMA")

4、读取数据_通过DMA
HAL_SD_ReadBlocks_DMA(&hsd, aOldData, 7, 2);    // 读取SD卡指定块的数据; 参数:SD句柄、数据地址、块起始地址、需要读取的块数量;

5、写入数据_通过DMA
HAL_SD_WriteBlocks_DMA(&hsd, aTestData, 7, 2);  // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;

注意机制的不同:

  • 基础读、写函数: HAL_SD_ReadBlocks() 和 HAL_SD_WriteBlocks()  ,它俩是”死等式“执行的,即,读写完成、操作超时,才能结束函数、并返回执行状态,执行下一行代码。如果数据量大,会"卡"程序。
  • DMA读、写函数:HAL_SD_ReadBlocks_DMA() 和 HAL_SD_WriteBlocks_DMA() ,将按函数的参数配置、启动DMA后,就会退出函数,执行下一行代码。

4、中断回调函数

通过DMA读、写后,有两种方式可以知道读写操作是否完成:

  1. 通过HAL_SD_GetState(&hsd)获取状态;
  2. 通过它俩的中断回调函数。本篇将使用中断回调函数的方式,这样操作性更好;

中断回调函数需要自行手写,可以写在 it.c 或 main.c底部 ,它俩分别是:

1、DMA TX 传输完成 中断回调函数

void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
    printf("写入完成\r\n");
}

2、DMA RX 传输完成 中断回调函数
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
    printf("读取完成\r\n");
}

5、具体读写操作示例

首先说明一点,本示例示范如何通过DMA读写,而示范代码中的状态判断,只是提供一个思路,这种写法是不高效的,需按自身方案作出适合的修改。

第一步:新建两个变量,作为DMA读写完成的标志。

在main()的上方,新建两个变量,作为读写结束的标志:

volatile uint8_t myFlagSDReadReady = 0;    // 读取传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
volatile uint8_t myFlagSDWriteReady = 0;   // 写入传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。

技巧:

这里用了volatile关键字修饰变量。

因为示范代码中有几段 while (myFlagSDReadReady == 0) 这样的空循环判断变量状态变化。

而CubeMX生成的工程,默认编译优化等级是3。编译时会把这一段中的变量误判为是不变的,编译运行后,就会导致循环体“卡死”。

使用 volatile 修饰,是告诉编译器不要优化它,每次循环时都必须从内存中读一次这变量的真实值。

编写完成后,位置如下图:

第二步:在main() 下方,即/* USER CODE BEGIN 4 */下面,编写需要的两个中断回调函数:

/*DMA Tx传输完成中断回调函数*/
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
    myFlagSDWriteReady = 1;   // 写入传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
}

/*DMA Rx传输完成中断回调函数*/
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
    myFlagSDReadReady = 1;    // 读取传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
}

编写完成后,位置如下图:

第三步:在 main()函数内,/* USER CODE BEGIN 2 */ 注释下方,编写读写代码:

有点点长,建议直接复制。

其实,有效的读写只是简单的几行而已,这里写了一堆,只是为了展示。

代码里已附详细注释,整体流程是:

  • 获取SD卡信息
  • 读取测试块的原数据 
  • 写入测试
  • 擦除测试
  • 写回原数据
    /***************** SD卡读写通信测试 *****************/
    /* 1、获取卡信息,打印到串口助手                    */
    /* 2、读测试:读出测试位置原数据,保存在 aOldData[] */
    /* 3、写测试:在测试的块上,写入指定数据            */
    /*    读出刚才写入的块数据,打印到串口助手观察      */
    /* 4、擦除测试:擦除指定块上的数据                  */
    /*    读出刚才擦除块的数据,打印到串口助手观察      */
    /* 5、写回原数据到指定位置                          */
    /*    读出刚才写入的块数据,打印到串口助手观察      */

    #define SD_TEST_SIZE    1024                                       // 测试数据的字节数,刚好是2个块大小:2x512
    static uint8_t aOldData[SD_TEST_SIZE] = {0};                       // 用于存放旧数据,先读出来,测试完了,再把旧数据写回去
    static uint8_t aTestData[SD_TEST_SIZE] = {0};                      // 临时缓存,用来存放测试数据
    HAL_SD_CardInfoTypeDef pCardInfo = {0};                            // SD卡信息结构体

    uint8_t status = HAL_SD_GetCardState(&hsd);                        // SD卡状态标志值
    if (status == HAL_SD_CARD_TRANSFER)
    {
        /* 1、获取卡信息,打印到串口助手 */
        HAL_SD_GetCardInfo(&hsd, &pCardInfo);                          // 获取 SD 卡的信息
        printf("\r1、获取SD卡信息 ... \r\n");
        printf("卡类型:%d \r\n", pCardInfo.CardType);                 // 类型返回:0-SDSC、1-SDHC/SDXC、3-SECURED
        printf("卡版本:%d \r\n", pCardInfo.CardVersion);              // 版本返回:0-CARD_V1、1-CARD_V2
        printf("块数量:%d \r\n", pCardInfo.BlockNbr);                 // 可用的块数量
        printf("块大小:%d \r\n", pCardInfo.BlockSize);                // 每个块的大小; 单位:字节
        printf("卡容量:%lluG \r\n", ((unsigned long long)pCardInfo.BlockSize * pCardInfo.BlockNbr) / 1024 / 1024 / 1024);  // 计算卡的容量

        HAL_Delay(1000);                                               // 重要:稍作延时再开始读写测试; 避免有些仿真器烧录期间的多次复位,短暂运行了程序,导致下列读写数据不完整。

        /* 2、读测试:读出测试位置原数据,保存在 aOldData[] */
        printf("\r2、读取测试块的原数据 ... \r\n");
        memset(aOldData, 0, SD_TEST_SIZE);                             // 清0数组的数据
        myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.
        HAL_SD_ReadBlocks_DMA(&hsd, aOldData, 7, 2);                   // 读取SD卡指定块的数据; 参数:SD句柄、数据地址、块起始地址、需要读取的块数量;
        while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
        for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 原始数据
            printf("%X ",  aOldData[i]);
        printf("\r\n");

        /* 3-1、写测试:在测试的块上写入数据 */
        printf("\r3、SD卡 写入测试 ...\r\n");
        memset(aTestData, 0x8, SD_TEST_SIZE);                          // 整个数组填充指定值,作为测试写入的数据
        myFlagSDWriteReady = 0;                                        // 写入传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.
        HAL_SD_WriteBlocks_DMA(&hsd, aTestData, 7, 2);                 // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;
        while (myFlagSDWriteReady == 0);                               // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
        printf("对指定块写入结束! \r写入的数据是:\n");
        for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 写入的数据
            printf("%X ",  aTestData[i]);
        printf("\r\n");
        /* 3-2、读出现在块内的数据 */
        printf("\r现在块内的数据是:\r\n");
        memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据
        myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.
        HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2);                  // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量;
        while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
        for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 写入后块内现在数据
            printf("%X ",  aTestData[i]);
        printf("\r\n");

        /* 4-1、擦除测试:擦除指定块上的数据  */
        printf("\r4、擦除块测试 ...\r\n");
        if (HAL_SD_Erase(&hsd, 7, 8) == HAL_OK)                        // 擦除SD卡上的数据; 参数:SD结构体、块的起始地址、块的结束地址
        {
            while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束
            printf("擦除 成功! \r\n");
        }
        else
        {
            printf("擦除 失败! \r\n");
        }
        /* 4-2、读取,擦除后指定块上的数据  */
        printf("擦除后,现在块内的数据是:\r\n");
        memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据
        myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1.
        HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2);                  // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量;
        while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
        for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 块内现在的数据
            printf("%X ",  aTestData[i]);
        printf("\r\n");

        /* 5-1、写回测试块上的原始数据 */
        printf("\r5、写回原数据 ...\r\n");
        //memset(aOldData, 0, SD_TEST_SIZE);                           // 这行是备用的,为了测试后写入特定数据
        myFlagSDWriteReady = 0;                                        // 写入传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.
        HAL_SD_WriteBlocks_DMA(&hsd, aOldData, 7, 2);                  // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;
        while (myFlagSDWriteReady == 0);                               // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
        printf("写入结束! \n");
        /* 5-2、读取现在块内的数据 */
        printf("现在块内的数据是: \r\n");
        memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据
        myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1.
        HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2);                  // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量;
        while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
        for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 块内现在的数据
            printf("%X ",  aTestData[i]);
        printf("\r\n\r\n");

        printf("SD卡 读写测试结束!\r\n");
    }

编写完成后,位置如下图:

至此,代码编写完成,可以编译、烧录了。


四、实验效果

程序运行后,串口助手输出如下:

如有错漏 ,望指正~~~!

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

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

相关文章

互联网行业常用12个数据分析指标和八大模型

本文目录 前言 一、互联网线上业务数据分析的12个指标 1. 用户数据&#xff08;4个&#xff09; (1) 存量&#xff08;DAU/MAU&#xff09; (2) 新增用户 (3) 健康程度&#xff08;留存率&#xff09; (4) 渠道来源 2. 用户行为数据&#xff08;4个&#xff09; (1) 次数/频率…

【学术投稿-2025年计算机视觉研究进展与应用国际学术会议 (ACVRA 2025)】从计算机基础到HTML开发:Web开发的第一步

会议官网&#xff1a;www.acvra.org 简介 2025年计算机视觉研究进展与应用&#xff08;ACVRA 2025&#xff09;将于2025年2月28-3月2日在中国广州召开&#xff0c;将汇聚世界各地的顶尖学者、研究人员和行业专家&#xff0c;聚焦计算机视觉领域的最新研究动态与应用成就。本次…

【Unity踩坑】Unity项目管理员权限问题(Unity is running as administrator )

问题描述&#xff1a; 使用Unity Hub打开或新建项目时会有下面的提示。 解决方法&#xff1a; 打开“本地安全策略”&#xff1a; 在Windows搜索栏中输入secpol.msc并回车&#xff0c;或者从“运行”对话框&#xff08;Win R&#xff0c;然后输入secpol.msc&#xff09;启…

开发板上Qt运行的环境变量的三条设置语句的详解

在终端中运行下面三句命令用于配置开发板上Qt运行的环境变量&#xff1a; export QT_QPA_GENERIC_PLUGINStslib:/dev/input/event1 export QT_QPA_PLATFORMlinuxfb:fb/dev/fb0 export QT_QPA_FONTDIR/usr/lib/fonts/设置成功后可以用下面的语句检查设置成功没有 echo $QT_QPA…

【PyQt】pyqt小案例实现简易文本编辑器

pyqt小案例实现简易文本编辑器 分析 实现了一个简单的文本编辑器&#xff0c;使用PyQt5框架构建。以下是代码的主要功能和特点&#xff1a; 主窗口类 (MyWindow): 继承自 QWidget 类。使用 .ui 文件加载用户界面布局。设置窗口标题、状态栏消息等。创建菜单栏及其子菜单项&…

Java 数据库连接池:HikariCP 与 Druid 的对比

Java 数据库连接池&#xff1a;HikariCP 与 Druid 的对比 数据库连接池&#xff1a;HikariCP 1. 卓越的性能表现 HikariCP 在数据库连接池领域以其卓越的性能脱颖而出。 其字节码经过精心优化&#xff0c;减少了不必要的开销&#xff0c;使得连接获取和释放的速度极快。 在…

PHP实现混合加密方式,提高加密的安全性(代码解密)

代码1&#xff1a; <?php // 需要加密的内容 $plaintext 授权服务器拒绝连接;// 1. AES加密部分 $aesKey openssl_random_pseudo_bytes(32); // 生成256位AES密钥 $iv openssl_random_pseudo_bytes(16); // 生成128位IV// AES加密&#xff08;CBC模式&#xff09…

Turing Complete-3位解码器

要求如下&#xff1a; 就是搭建一个3-8译码器 思路一&#xff1a; 使用四种判断来解决问题。 判断一&#xff1a;3个输入中是否有0个绿色。 解决办法&#xff1a;三个输入通过三输入或门再取反。 判断二&#xff1a;3个输入中是否有1个绿色&#xff0c;并确定是输入1、输入…

我主编的电子技术实验手册(24)——RL并联电路

本专栏是笔者主编教材&#xff08;图0所示&#xff09;的电子版&#xff0c;依托简易的元器件和仪表安排了30多个实验&#xff0c;主要面向经费不太充足的中高职院校。每个实验都安排了必不可少的【预习知识】&#xff0c;精心设计的【实验步骤】&#xff0c;全面丰富的【思考习…

手写MVVM框架-模板渲染1

虚拟dom创建好了&#xff0c;依赖也收集好了&#xff0c;这个时候就该去渲染dom了&#xff0c;把页面上的 { {name}} 渲染成具体的值。 渲染之前我们给原型上添加一个render方法 //代码在src/core/render.jsexport function renderMixin(MiniVue) {MiniVue.prototype.$render …

人类心智逆向工程:AGI的认知科学基础

文章目录 引言:为何需要逆向工程人类心智?一、逆向工程的定义与目标1.1 什么是逆向工程?1.2 AGI逆向工程的核心目标二、认知科学的四大支柱与AGI2.1 神经科学:大脑的硬件解剖2.2 心理学:心智的行为建模2.3 语言学:符号与意义的桥梁2.4 哲学:意识与自我模型的争议三、逆向…

【C语言篇】“三子棋”

一、游戏介绍 三子棋&#xff0c;英文名为 Tic - Tac - Toe&#xff0c;是一款简单而经典的棋类游戏。游戏在一个 33 的棋盘上进行&#xff0c;两名玩家轮流在棋盘的空位上放置自己的棋子&#xff08;通常用 * 和 # 表示&#xff09;&#xff0c;率先在横、竖或斜方向上连成三个…

尝试ai生成figma设计

当听到用ai 自动生成figma设计时&#xff0c;不免好奇这个是如何实现的。在查阅了不少资料后&#xff0c;有了一些想法。参考了&#xff1a;在figma上使用脚本自动生成色谱 这篇文章提供的主要思路是&#xff1a;可以通过脚本的方式构建figma设计。如果我们使用ai 生成figma脚本…

【玩转 Postman 接口测试与开发2_015】第12章:模拟服务器(Mock servers)在 Postman 中的创建与用法(含完整实测效果图)

《API Testing and Development with Postman》最新第二版封面 文章目录 第十二章 模拟服务器&#xff08;Mock servers&#xff09;在 Postman 中的创建与用法1 模拟服务器的概念2 模拟服务器的创建2.1 开启侧边栏2.2 模拟服务器的两种创建方式2.3 私有模拟器的 API 秘钥的用法…

Java面试题2025-并发编程基础(多线程、锁、阻塞队列)

并发编程 一、线程的基础概念 一、基础概念 1.1 进程与线程A 什么是进程&#xff1f; 进程是指运行中的程序。 比如我们使用钉钉&#xff0c;浏览器&#xff0c;需要启动这个程序&#xff0c;操作系统会给这个程序分配一定的资源&#xff08;占用内存资源&#xff09;。 …

Android学习19 -- 手搓App

1 前言 之前工作中&#xff0c;很多时候要搞一个简单的app去验证底层功能&#xff0c;Android studio又过于重型&#xff0c;之前用gradle&#xff0c;被版本匹配和下载外网包折腾的堪称噩梦。所以搞app都只有找应用的同事帮忙。一直想知道一些简单的app怎么能手搓一下&#x…

实验六 项目二 简易信号发生器的设计与实现 (HEU)

声明&#xff1a;代码部分使用了AI工具 实验六 综合考核 Quartus 18.0 FPGA 5CSXFC6D6F31C6N 1. 实验项目 要求利用硬件描述语言Verilog&#xff08;或VHDL&#xff09;、图形描述方式、IP核&#xff0c;结合数字系统设计方法&#xff0c;在Quartus开发环境下&#xff…

< 自用文儿 > 下载 MaxMind GeoIP Databases 对攻击的 IP 做 地理分析

起因 两个 VPM/VPS&#xff0c;安装了 fail2ban 去拦截密码穷举攻击。每天的记录都在增长&#xff0c;以前复制屏幕输出就行&#xff0c;一屏的内容还容易粘贴出来的。昨天已经过 500 条&#xff0c;好奇 fail2ban 是如何存储这些内容的&#xff1f;就发现它在使用 SQLite3 数…

通过IP地址查看终端具体位置

最近关于deepseek的热度不断&#xff0c;无论是deepseek的优秀性价比和能力&#xff0c;以及deepseek遭受到海外攻击然后国内反击的AI上甘岭&#xff0c;随之而来的是铺天盖地的营销号&#xff0c;要擦亮自己的眼睛&#xff0c;要勇于质疑和证实网络言论的真实性。 其中一点&am…

【Linux探索学习】第二十八弹——信号(下):信号在内核中的处理及信号捕捉详解

Linux学习笔记&#xff1a; https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 在前面我们已经学习了有关信号的一些基本的知识点&#xff0c;包括&#xff1a;信号的概念、信号产生和信号处理等&#xff0c;今天我们重…