嵌入式驱动开发详解3(pinctrl和gpio子系统)

news2024/11/26 16:16:35

文章目录

  • 前言
  • pinctrl子系统
    • pin引脚配置
    • pinctrl驱动详解
  • gpio子系统
    • gpio属性配置
    • gpio子系统驱动
    • gpio子系统API函数
    • 与gpio子系统相关的of函数
  • pinctrl和gpio子系统的使用
    • 设备树配置
    • 驱动层部分
    • 用户层部分

前言

如果不用pinctrl和gpio子系统的话,我们开发驱动时需要先在设备树或者驱动文件中对pin进行配置成相应的功能引脚,然后如果是gpio功能的话就需要进行gpio初始化,如果是其他外设的话就需要进行其他外设的初始化,因为经常将引脚配置为gpio模式,因此linux内核针对pin的配置特意推出了pinctrl子系统,针对gpio的配置推出了gpio子系统,对于一些其他的外设模式有其他特定的子系统,这里不做讲解。

pinctrl子系统

大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的 GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要配置 pin 的电气特性,比如上/下 拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置 方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引 入的,pinctrl 子系统主要工作内容如下:

  • ①、获取设备树中 pin 信息。
  • ②、根据获取到的 pin 信息来设置 pin 的复用功能
  • ③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始 化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为 drivers/pinctrl。

pin引脚配置

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要 根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。
在dtsi 文件,有一个叫做 iomuxc 的节点就是配置对应的pin引脚信息,但是这个文件里面只设置了iomuxc寄存器的起始地址,剩下的具体配置是在具体的设备树dts文件中追加的,结合起来之后如下所示:

iomuxc: iomuxc@020e0000 
{
	compatible = "fsl,imx6ul-iomuxc";
	reg = <0x020e0000 0x4000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {
		pinctrl_hog_1: hoggrp-1 {
			fsl,pins = < 
				MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 
				MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 
				MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 
			>; 
			......
		};
	}; 
};

对于一个 PIN 的配置主要包括两方面, 一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。所以在每个子节点中类似UART1_RTS_B的定义就对应的设置好了 UART1_RTS_B 的复用功能和 UART1_RTS_B 的电气特性。例如,我们可以在pinfunc.h 这个头文件找到UART1_RTS_B的定义如下:

#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0

0x0090 0x031C 0x0000 0x5 0x0,这 5 个值的含义如下所示:

<mux_reg conf_reg input_reg mux_mode input_val>

三个reg就是对应前面所提到的iomuxc的基地址的偏移值,前三个刚好对应上复用功能寄存器,电气属性寄存器和输入寄存器,第四个和第五个就是复用功能寄存器的值和输入寄存器的值,而电气属性寄存器的值放在了宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19的后面0x17059 。

pinctrl驱动详解

关于pinctrl驱动的具体实现我这里不做过多赘述,我们只需要掌握怎么把设备树的pin给配置好就行,配置好之后pinctrl驱动就会自动的把引脚配置成想要的状态。由于内核都封装好了,所以只需大概将一下实现流程。
在这里插入图片描述如上图所示,pinctrl子系统的实现也是通过platform实现的,当pinctrl驱动与对应的compatible(在刚刚提到的pin配置的节点信息的最前面设置好了)匹配上之后就会执行probe函数,probe函数就会根据节点下面的pin配置给对应的寄存器写入对应的值。

gpio子系统

如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系 统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动 开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API 函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开 发者使用 GPIO。

gpio属性配置

/* 根节点下linux内核中的gpio1属性配置*/
/ {
…………
	soc {
	…………
		gpmi: gpmi-nand@01806000{
		…………
			aips1: aips-bus@02000000 {
			…………
				gpio1: gpio@0209c000 {
				compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
				reg = <0x0209c000 0x4000>;
				interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
					     <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
				gpio-controller;
				#gpio-cells = <2>;
				interrupt-controller;
				#interrupt-cells = <2>;
				};
			};
		};
	};
};

/* 根节点下手动添加某个设备节点*/
/ {
…………
	beep{
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "hbb-beep";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_beep>;
		beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};
};

如上所示, GPIO子系统配置时是根据根节点下linux内核中的gpio1属性配置的,但是还需要gpio编号才能知道去配置哪个gpio,因此我们需要自己手动在根节点下手动添加某个设备节点(这里的节点跟上一个帖子的节点不同,这里仅仅只是设备树的节点,可以通过cd /proc/device-tree命令查看设备树节点是否添加成功) ,通过在自己写的驱动从设备树中读取出设备编号,然后将此编号通过gpio子系统的API函数输入来操作配置GPIO寄存器即可。在上面自己手动添加的节点中:

pinctrl-0 = <&pinctrl_beep>; //这个是前面提到的pinctrl属性配置
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>; //这个就是gpio的编号设置,因此前面的beep-gpio跟驱动有关联

很多初次接触设备树的驱动开发人员很容易因为这个小问题栽了大跟头!因为我们所使用 的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体厂商提供的设 备树是根据自己官方开发板编写的,很多 PIN 的配置和我们所使用的开发板不一样。比如 A 这 个引脚在官方开发板接的是 I2C 的 SDA,而我们所使用的硬件可能将 A 这个引脚接到了其他 的外设,比如 LED 灯上,接不同的外设,A 这个引脚的配置就不同。一个引脚一次只能实现一 个功能,如果 A 引脚在设备树中配置为了 I2C 的 SDA 信号,那么 A 引脚就不能再配置为 GPIO, 否则的话驱动程序在申请 GPIO 的时候就会失败。
检查 PIN 有没有被其他外设使用包括两个方 面:
①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。

gpio子系统驱动

在drivers/gpio/目录下的.c文件就是不同芯片厂商的gpio驱动文件,跟pinctrl子系统一样,通过linux驱动的分层与分离、平台设备驱动相关内容封装好了,我们做驱动开发只要能掌握GPIO子系统的API函数的使用就行,后期如果有想法深入研究内核的开发,可以仔细研究一下具体是怎么实现的。

gpio子系统API函数

对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定 的 GPIO,gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,gpio 子系统提供的常用的 API 函数有下面几个:
gpio_request :用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request 进行申请,函数原型如下:

int gpio_request(unsigned gpio, const char *label)

gpio_free:如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:

void gpio_free(unsigned gpio)

gpio_direction_input:此函数用于设置某个 GPIO 为输入,函数原型如下所示:

int gpio_direction_input(unsigned gpio)

gpio_direction_output:此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio, int value)

gpio_get_value:此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:

#define gpio_get_value __gpio_get_value 
int __gpio_get_value(unsigned gpio)

gpio_set_value:此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下

#define gpio_set_value __gpio_set_value 
void __gpio_set_value(unsigned gpio, int value)

以上只说明了有哪几个函数,具体函数怎么用可自行百度更加详细。

与gpio子系统相关的of函数

首先展示五个跟设备树的节点获取相关的五个OF函数:
of_find_node_by_name:过节点名字查找指定的节点;
of_find_node_by_type:通过 device_type 属性查找指定的节点
of_find_compatible_node:根据 device_type 和 compatible 这两个属性查找指定的节点
of_find_matching_node_and_match:通过 of_device_id 匹配表来查找指定的节点
of_find_node_by_path:通过路径来查找指定的节点,这个是我们经常用的方法。

前面的GPIO子系统的函数可以配置gpio的寄存器,但是在驱动程序中需要先读取 gpio 属性内容,因此Linux 内核提供了几个与 GPIO 有关 的 OF 函数,常用的几个 OF 函数如下所示:
of_get_named_gpio:此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号, 此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编 号,此函数在驱动中使用很频繁! 函数原型如下:

int of_get_named_gpio(struct device_node *np, const char *propname, int index)

np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index:GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO 的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值:正值,获取到的 GPIO 编号;负值,失败。

还有一些其他的跟gpio相关的of函数,但是不怎么常用,比如of_gpio_named_count和of_gpio_count,可以获取pio配置的数量。

pinctrl和gpio子系统的使用

下面将展示一个具体的例子来说明pinctrl子系统和gpio子系统具体怎么用的。

设备树配置

设备树的配置可以看前面pinctrl的配置和在设备树根文件下对gpio信息的配置

驱动层部分

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>  //copy
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h> 
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define BEEP_CNT 1
#define BEEP_NAME "beep"
#define BEEP_ON 1
#define BEEP_OFF 0

struct beep_dev{
	dev_t devid; 
	int major;
	int minor;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	struct device_node *nd;
	int beep_gpio;
};

struct beep_dev beep;

static int beep_open(struct inode *inode, struct file *file)
{
	file->private_data = &beep;
	return 0;
}

static ssize_t beep_write(struct file *file, const char __user *buf,
				size_t count, loff_t *off)
{
	unsigned char status;
	int ret;
	struct beep_dev *dev = file->private_data;
	ret = copy_from_user(&status,buf,count);
	if(ret < 0){
		printk("kernel write failed!!!");
		return -1;
	}
	printk("device write%d\r\n",status);
	if(status == BEEP_ON){
		gpio_set_value(dev->beep_gpio, 0);
	}else if(status == BEEP_OFF){
		gpio_set_value(dev->beep_gpio, 1);
	}
	return 0;
}

int beep_release(struct inode *inode, struct file *file)
{
	return 0;

}

static const struct file_operations beep_fops = {
	.owner	= THIS_MODULE,
	.open	= beep_open,
	.write	= beep_write,
	.release = beep_release,
};

static int __init gpioled_init(void)
{
	int ret;
	beep.nd = of_find_node_by_path("/beep");
	if(beep.nd == NULL){
		printk("beep node cant not find!!\r\n");
		ret = -1;
		goto fail_node;
	}else{
		printk("beep node found!!\r\n");
	}
	beep.beep_gpio = of_get_named_gpio(beep.nd,"beep-gpio",0);
	if(beep.beep_gpio < 0){
		printk("cant not get beep-gpio\r\n");
		ret = -1;
		goto fail_node;
	}
	printk("beep-gpio-num=%d\r\n",beep.beep_gpio);

	ret = gpio_direction_output(beep.beep_gpio,1);
	if(ret < 0){
		printk("can`t set gpio!!!\r\n");
	}

	if(beep.major){
		beep.devid = MKDEV(beep.major,beep.minor);
		ret = register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);
	}else{
		ret = alloc_chrdev_region(&beep.devid,0,BEEP_CNT,BEEP_NAME);
		beep.major = MAJOR(beep.devid);
		beep.minor = MINOR(beep.devid);
		printk("alloc_chrdev_region major=%d minor=%d\r\n",beep.major, beep.minor);
	}
	if (ret < 0) {
		printk("Could not register\r\n");
		goto fail_devid;
	}
	beep.cdev.owner = THIS_MODULE;
	cdev_init(&beep.cdev, &beep_fops);
	ret = cdev_add(&beep.cdev,beep.devid,BEEP_CNT);
	if(ret < 0){
		printk("Could not cdev\r\n");
		goto fail_cdev;
	}
	beep.class = class_create(THIS_MODULE,BEEP_NAME);
	if(IS_ERR(beep.class)){
		ret = PTR_ERR(beep.class);
		goto fail_class;
	}
	beep.device = device_create(beep.class,NULL,beep.devid,NULL,BEEP_NAME);
	if(IS_ERR(beep.device)){
		ret = PTR_ERR(beep.device);
		goto fail_device;
	}
	return 0;
fail_device:
	class_destroy(beep.class);
fail_class:
	cdev_del(&beep.cdev);
fail_cdev:
	unregister_chrdev_region(beep.devid,BEEP_CNT);
fail_devid:
fail_node:
	return ret;
}

static void __exit gpioled_exit(void)
{
	gpio_set_value(beep.beep_gpio,1);
	printk("beep_exit\r\n");
	cdev_del(&beep.cdev);
	unregister_chrdev_region(beep.devid,BEEP_CNT);

	device_destroy(beep.class,beep.devid);
	class_destroy(beep.class);
}

module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");	
MODULE_AUTHOR("hbb");

用户层部分

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include "string.h"


int main(int argc,char *argv[])
{
    int fd,ret;
    char *filename;
    unsigned char databuf;
    if(argc != 3){
        printf("Error Usage!!!\r\n");
        return -1;
    }
    filename = argv[1];
    fd = open(filename ,O_RDWR);
    if(fd < 0){
        printf("file %s open failed!\r\n",filename);
        return -1;
    }
    databuf = atoi(argv[2]);
    ret = write(fd,&databuf,sizeof(databuf));
    if(ret < 0){
        printf("BEEP control failed\r\n");
        close(fd);
        return -1;
    }
    ret = close(fd);
    if(ret < 0){
        printf("file %s close failed! \r\n",filename);
        return -1;
    }
    return 0;
}

到这里对pinctrl和gpio子系统的大致说明就结束了,后面有新的相关的重要的内容会继续进行更新。
对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32C011开发(1)----开发板测试

STM32C011开发----1.开发板测试 概述硬件准备视频教学样品申请源码下载参考程序生成STM32CUBEMX串口配置LED配置堆栈设置串口重定向主循环演示 概述 STM32C011F4P6-TSSOP20 评估套件可以使用户能够无缝评估 STM32C0 系列TSSOP20 封装的微控制器功能&#xff0c;基于 ARM Corte…

【漏洞复现】|百易云资产管理运营系统/mobilefront/c/2.php前台文件上传

漏洞描述 湖南众合百易信息技术有限公司&#xff08;简称&#xff1a;百易云&#xff09;成立于2017年是一家专注于不动产领域数字化研发及服务的国家高新技术企业&#xff0c;公司拥有不动产领域的数字化全面解决方案、覆盖住宅、写字楼、商业中心、专业市场、产业园区、公建、…

Spring Boot 实战:基于 Validation 注解实现分层数据校验与校验异常拦截器统一返回处理

1. 概述 本文介绍了在spring boot框架下&#xff0c;使用validation数据校验注解&#xff0c;针对不同请求链接的前端传参数据&#xff0c;进行分层视图对象的校验&#xff0c;并通过配置全局异常处理器捕获传参校验失败异常&#xff0c;自动返回校验出错的异常数据。 2. 依赖…

量子神经网络

感知机只是一个神经元&#xff0c;若有多个神经元共同作用&#xff0c;则构成神经网络。目前&#xff0c;最常见的量子神经网络模型为基于参数化量子线路的量子神经网络&#xff0c;该模型用参数化量子线路代替神经网络结构&#xff0c;使用经典优化算法更新参数化量子线路的参…

非交换几何与黎曼ζ函数:数学中的一场革命性对话

非交换几何与黎曼ζ函数&#xff1a;数学中的一场革命性对话 非交换几何&#xff08;Noncommutative Geometry, NCG&#xff09;是数学的一个分支领域&#xff0c;它将经典的几何概念扩展到非交换代数的框架中。非交换代数是一种结合代数&#xff0c;其中乘积不是交换性的&…

VUE3项目 关于金额:分转化为元 ;元转化为分;

1.在components 文件夹下新建moneyHandle.ts 文件 2.ts文件中写如下代码&#xff08;保留两位小数&#xff09; //分转化为元 - 正则解决精度 export const regFenToYuan (fen:any) >{var num fen;numfen*0.01;num;var reg num.indexOf(.) >-1 ? /(\d{1,3})(?(?:…

从0开始学习Linux——搭建自己的专属Linux系统

温馨提示本篇文章字数超过5000字&#xff01; 往期目录&#xff1a; 1、从0开始学习Linux——Linux简介&安装https://blog.csdn.net/diamond_5446/article/details/141190487 上一个章节我们简单了解了Linux&#xff0c;并且安装好了虚拟机以及下载好了Centos镜像&#…

《硬件架构的艺术》笔记(五):低功耗设计

介绍 能量以热量形式消耗&#xff0c;温度升高芯片失效率也会增加&#xff0c;增加散热片或风扇会增加整体重量和成本&#xff0c;在SoC级别对功耗进行控制就可以减少甚至可能消除掉这些开支&#xff0c;产品也更小更便宜更可靠。本章描述了减少动态功耗和静态功耗的各种技术。…

浅谈网络 | 传输层之TCP协议

目录 TCP 包头格式TCP 的三次握手TCP 的四次挥手TCP 的可靠性与"靠谱"的哲学TCP流量控制TCP拥塞控制 上一章我们提到&#xff0c;UDP 就像我们小时候一样简单天真&#xff0c;它相信“网之初&#xff0c;性本善&#xff0c;不丢包&#xff0c;不乱序”&#xff0c;因…

华为无线AC+AP组网实际应用小结

之前公司都是使用的H3C的交换机、防火墙以及无线AC和AP的&#xff0c;最近优化下无线网络&#xff0c;说新的设备用华为的&#xff0c;然后我是直到要部署的当天才知道用华为设备的&#xff0c;就很无语了&#xff0c;一点准备没有&#xff0c;以下为这次的实际操作记录吧&…

七、SElinux

一、SElinux简介 SELinux是Security-Enhanced Linux的缩写&#xff0c;意思是安全强化的linuxSELinux 主要由美国国家安全局(NSA)开发&#xff0c;当初开发的目的是为了避免资源的误用传统的访问控制在我们开启权限后&#xff0c;系统进程可以直接访问当我们对权限设置不严谨时…

Paddle Inference部署推理(一)

一&#xff1a;Paddle Inference推理 简介 Paddle Inference 是飞桨的原生推理库&#xff0c;提供服务器端的高性能推理能力。由于 Paddle Inference 能力直接基于飞桨的训练算子&#xff0c;因此它支持飞桨训练出的所有模型的推理。 Paddle Inference 功能特性丰富&#xff…

【数据分享】2001-2023年我国30米分辨率冬小麦种植分布栅格数据(免费获取)

小麦、玉米、水稻等各类农作物的种植分布数据在农业、环境、国土等很多专业都经常用到&#xff01; 本次给大家分享的是我国2001-2023年逐年的30米分辨率冬小麦种植分布栅格数据&#xff01;数据格式为TIFF格式&#xff0c;数据坐标为GCS_WGS_1984。该数据包括我国11个省份的冬…

类和对象plus版

一.类的定义 1.1类定义的格式 图中class为关键字&#xff0c;Stack为类的名字&#xff0c;用{}框住类的主体&#xff0c;类定义完后&#xff1b;不能省略。 为了区分成员变量&#xff0c;一般习惯在成员变量前面或后面加一个特殊标识&#xff0c;_或者m_ 1.2访问限定符 c采用…

jquery-picture-cut 任意文件上传(CVE-2018-9208)

目录 1、漏洞描述 2、访问ip&#xff1a;port 3、一句话木马&#xff1a;exploit.php 4、上传一句话木马 5、中国蚁剑连接成功 6、拿到flag 1、漏洞描述 jQuery是一个快速、简洁的JavaScript框架&#xff0c;是继Prototype之后又一个优秀的JavaScript代码库&#xff08;框…

【Spring Boot】# 使用@Scheduled注解无法执行定时任务

1. 前言 在 Spring Boot中&#xff0c;使用Scheduled注解来定义定时任务时&#xff0c;定时任务不执行&#xff1b;或未在规定时间执行。 import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;Component public c…

LM2904运算放大器的应用:测电池电压

在电子设备的广泛应用中&#xff0c;电池作为便携设备的能量来源&#xff0c;其电压监测显得尤为关键。LM2904作为一款低功耗、高增益带宽积和高共模抑制比的双运算放大器&#xff0c;非常适用于电池电压的测量与监测。本文详细介绍了LM2904在电池电压测量方面的应用&#xff0…

电子学习中的关键游戏化元素

游戏化彻底改变了电子学习领域&#xff0c;提供了一种使学习具有吸引力、互动性和有效性的方法。通过将类似游戏的功能集成到教育平台中&#xff0c;教育工作者可以增强动力&#xff0c;提高知识记忆&#xff0c;并创造动态的学习体验。游戏化的关键要素为设计与学习者产生共鸣…

【网络通信】数据集合集!

本文将为您介绍经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 RITA 更新时间&#xff1a;2024-11-22 访问地址: GitHub 描述&#xff1a; RITA 是一个用于网络流量分析的开源框架。 该框架以 TSV 或 JSON 格式提取 Zeek 日志&#xff0c;目前支…

竞赛经验:关于不记得字母表,如何知道字母顺序qwq

利用ASCII码算出码值再转成字符即可 #include <bits/stdc.h> using namespace std;int main() {for(int i 1; i < 30; i){cout << char(ai) << ;} }结果&#xff1a; ps:大意了&#xff0c;本想用电脑目录&#xff0c;但没考虑到会有文件不存在导致缺…