ZYNQ之嵌入式驱动开发——字符设备驱动

文章目录

  • Linux驱动程序分类
  • Linux应用程序和驱动程序的关系
  • 简单的测试驱动程序
  • 在petalinux中添加LED驱动
  • 新字符设备驱动

Linux驱动程序分类

驱动程序分为字符设备驱动、块设备驱动和网络设备驱动。
字符设备是按字节访问的设备,比如以一个字节收发数据的串口,字符设备在Linux外设中占比最大。
块设备的特点是按一定格式存取的数据,具体的格式由文件系统决定。块设备以存储设备为主,存储设备的特点是以存储块为基础,因此得名块设备。
网络设备不同于上面两种,应用程序和网络设备驱动之间的通信由库和内核提供的一套数据包传输函数替代了open()、read()、write()等函数。


Linux应用程序和驱动程序的关系

(1)应用程序调用库函数提供的open()函数打开某个设备文件,该设备文件是在驱动加载成功之后在目录/dev中生成的,是应用程序调用相应硬件的入口。
(2)库根据open()函数的输入参数引起CPU异常进入内核,系统调用处于内核空间,应用程序无法直接访问,因此需要陷入到内核,方法就是软中断,陷入内核后还要指定系统调用号;
(3)内核的异常处理函数根据输入参数找到相应的驱动程序,返回文件句柄给库,库函数再返回给应用程序;
(4)应用程序再使用得到的文件句柄调用write()、read()等函数发出控制指令;
(5)库根据write()、read()等函数的输入参数引起CPU异常,进入内核;
(6)内核的异常处理函数根据输入参数调用相应的驱动程序执行相应的操作。
Linux应用程序调用驱动程序的步骤如下图所示。
在这里插入图片描述
应用程序中涉及到的open()、read()、write()等是由库提供的系统调用,通过执行某条指令引发异常进入内核,是应用程序操作硬件的途径。应用程序执行系统调用后进入内核,然后会使用驱动程序中对应的函数,驱动程序中的open()、read()、write()等函数是需要驱动开发人员实现的。应用程序运行于用户空间,驱动程序运行于内核空间,Linux系统可以通过MMU限制应用程序运行在某个内存块中,以避免这个应用程序出错导致整个系统崩溃,运行于内核空间的驱动程序是系统的一部分,驱动程序出错有可能牵连整个系统。


简单的测试驱动程序

在进行LED驱动开发之前,先使用下面的代码简单测试一下。

#include <linux/module.h>static int __init chardev_init(void)
{printk("Hello!\n");return 0;
}static void __exit chardev_exit(void)
{printk("GoodBye!\n");
}module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");

如果使用Linux内核编译上面的C文件,需要自己写Makefile,然后编译出驱动文件到7020开发板上验证,验证的结果如下图所示。
在这里插入图片描述
提示这个驱动是无效的模块格式,可能ZYNQ开发板就需要使用petalinux这样特定的工具进行开发,下面来看具体流程。
首先在petalinux的安装路径下设置环境变量。

source /opt/pkg/petalinux/settings.sh

进入到定制系统的根目录下,使用下面的命令添加字符设备驱动。

petalinux-create -t modules -n chardev

在当前路径下的/project-spec/meta-user/recipes-modules/下生成了一个名为chardev的文件夹,该文件夹下有以下三个文件,其中.c文件就是需要写入驱动代码的文件。
在这里插入图片描述
Makefile在创建工程的时候已经创建好了,里面的内容如下图所示,也不需要修改。
在这里插入图片描述
在C文件中写入驱动代码后,返回到自定义的/zynq7020目录下,使用下面的命令进行编译。

petalinux-build -c chardev

编译完成后的信息打印如下图所示。
在这里插入图片描述
由于编译成的驱动文件存放路径比较难找,因此直接在搜索栏中直接搜索驱动的名称就会出现,但是文件夹必须打开到自定义的工程的这一层才能搜索到。
在这里插入图片描述
可以右键该文件打开文件的具体存在位置为/opt/pkg/petalinux/zynq7020/build/tmp/sysroots-components/plnx_zynq7/chardev/lib /modules/4.14.0-xilinx-v2018.3/extra。
接下来就可以在开发板上验证该驱动了,验证的结果如下图所示。
在这里插入图片描述


在petalinux中添加LED驱动

同上面的示例,先在petalinux的安装路径下设置环境变量。

source /opt/pkg/petalinux/settings.sh

然后进入到定制系统的根目录下,使用下面的命令添加驱动。

petalinux-create -t modules -n psled1-driver

需要注意的是,驱动文件命名不能使用下划线,而要使用"-"代替。
在这里插入图片描述
创建成功以后,打印的消息提示创建的模块在当前路径下的/project-spec/meta-user/recipes-modules/psled1-driver中,进到这个目录下。在这里插入图片描述
一步步进到最终的目录下,在files文件夹下的.c文件就是要写入驱动代码的地方,在里面键入下面的代码。

//该代码来自ZYNQ教程,教程在文末给出
#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/init.h>  
#include <linux/ide.h>  
#include <linux/types.h>  /* 驱动名称 */  
#define DEVICE_NAME       "ps_led1"  
/* 驱动主设备号 */  
#define GPIO_LED_MAJOR    200  /* gpio寄存器虚拟地址 */  
static unsigned int gpio_add_minor;  
/* gpio寄存器物理基地址 */  
#define GPIO_BASE         0xE000A000  
/* gpio寄存器所占空间大小 */  
#define GPIO_SIZE         0x1000  
/* gpio方向寄存器 */  
#define GPIO_DIRM_0       (unsigned int *)(0xE000A204 - GPIO_BASE + gpio_add_minor)  
/* gpio使能寄存器 */   
#define GPIO_OEN_0        (unsigned int *)(0xE000A208 - GPIO_BASE + gpio_add_minor)  
/* gpio控制寄存器 */  
#define GPIO_DATA_0       (unsigned int *)(0xE000A040 - GPIO_BASE + gpio_add_minor)  /* 时钟使能寄存器虚拟地址 */  
static unsigned int clk_add_minor;  
/* 时钟使能寄存器物理基地址 */  
#define CLK_BASE          0xF8000000  
/* 时钟使能寄存器所占空间大小 */  
#define CLK_SIZE          0x1000  
/* AMBA外设时钟使能寄存器 */  
#define APER_CLK_CTRL     (unsigned int *)(0xF800012C - CLK_BASE + clk_add_minor)        /* open函数实现, 对应到Linux系统调用函数的open函数 */  
static int gpio_leds_open(struct inode *inode_p, struct file *file_p)  
{  /* 把需要修改的物理地址映射到虚拟地址 */gpio_add_minor = (unsigned int)ioremap(GPIO_BASE, GPIO_SIZE);  clk_add_minor = (unsigned int)ioremap(CLK_BASE, CLK_SIZE);/* MIO_0时钟使能 */  *APER_CLK_CTRL |= 0x00400000;  /* MIO_0设置成输出 */  *GPIO_DIRM_0 |= 0x00000001;  /* MIO_0使能 */  *GPIO_OEN_0 |= 0x00000001;  printk("gpio_test module open\n");   return 0;  
}  /* write函数实现, 对应到Linux系统调用函数的write函数 */  
static ssize_t gpio_leds_write(struct file *file_p, const char __user *buf, size_t len, loff_t *loff_t_p)  
{  int rst;  char writeBuf[5] = {0};  printk("gpio_test module write\n");  rst = copy_from_user(writeBuf, buf, len);  if(0 != rst)  {  return -1;    }  if(1 != len)  {  printk("gpio_test len err\n");  return -2;  }  if(1 == writeBuf[0])  {  *GPIO_DATA_0 &= 0xFFFFFFFE;  printk("gpio_test ON\n");  }  else if(0 == writeBuf[0])  {  *GPIO_DATA_0 |= 0x00000001;  printk("gpio_test OFF\n");  }  else  {  printk("gpio_test para err\n");  return -3;  }    return 0;  
}  /* release函数实现, 对应到Linux系统调用函数的close函数 */  
static int gpio_leds_release(struct inode *inode_p, struct file *file_p)  
{     	printk("gpio_test module release\n");  return 0;  
}  /* file_operations结构体声明, 是上面open、write实现函数与系统调用函数对应的关键 */  
static struct file_operations gpio_leds_fops = {  .owner   = THIS_MODULE,  .open    = gpio_leds_open,  .write   = gpio_leds_write,     .release = gpio_leds_release,   
};  /* 模块加载时会调用的函数 */  
static int __init gpio_led_init(void)  
{  int ret;  /* 通过模块主设备号、名称、模块带有的功能函数(及file_operations结构体)来注册模块 */  ret = register_chrdev(GPIO_LED_MAJOR, DEVICE_NAME, &gpio_leds_fops);  if (ret < 0)   {  printk("gpio_led_dev_init_error\n");  return ret;  }  else  {  /* 注册成功 */ printk("gpio_led_dev_init_ok\n");  }  return 0;  
}  /* 卸载模块 */  
static void __exit gpio_led_exit(void)  
{  /* 释放对虚拟地址的占用 */  iounmap((unsigned int *)gpio_add_minor);  iounmap((unsigned int *)clk_add_minor); /* 注销模块, 释放模块对这个设备号和名称的占用 */  unregister_chrdev(GPIO_LED_MAJOR, DEVICE_NAME);printk("gpio_led_dev_exit_ok\n");  
}  /* 标记加载、卸载函数 */  
module_init(gpio_led_init);  
module_exit(gpio_led_exit);  /* 驱动描述信息 */  
MODULE_AUTHOR("Alinx");  
MODULE_ALIAS("gpio_led");  
MODULE_DESCRIPTION("GPIO LED driver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL");  

上面介绍的是直接编译驱动,也可以以图形化的形式进行编译,返回到自定义的/zynq7020目录下,输入下面的命令配置根文件系统。

petalinux-config -c rootfs

在弹出的图形化配置窗口中选择modules进入子菜单中。
在这里插入图片描述
按Y键将该驱动包括进来,然后保存退出。
在这里插入图片描述
根文件系统就配置成功了,然后使用petalinux-build命令编译该工程。
在这里插入图片描述
编译成功后打印下面的信息。
在这里插入图片描述
在/zynq7020目录下搜索驱动文件,如下图所示。
在这里插入图片描述
右键该文件选择打开文件存放位置,其存放在/zynq7020/build/tmp/sysroots-components/plnx_zynq7/psled1-driver/lib/modules/4.14.0-xilinx-v2018.3/extra,还是比较难找的,所以以后直接在工程目录下搜索即可。
在开发板上加载驱动,可以看到相应的设备号已经出现了。
在这里插入图片描述
使用下面的命令创建字符设备文件,指定主设备号和次设备号,设备文件名称为psled1,之后写应用程序的时候要使用该名称来操作字符设备。

mknod /dev/psled1 c 200 0

创建设备文件成功之后在/dev目录下就可以看到新添加的设备,如下图所示。
在这里插入图片描述
接下来写一个应用端的测试程序,用来传入数据点亮或者熄灭LED,程序如下。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd;int status;fd = open("/dev/psled1", O_RDWR);if(fd < 0){perror("open /dev/psled1 error!\n"); return fd; }status = atoi(argv[1]);write(fd, &status, 1);close(fd);  return 0;
}

将上面的程序通过交叉编译工具编译出适合在ARM平台运行的文件,将其发送到开发板验证,结果如下图所示。
在这里插入图片描述
如果采用下面的应用程序进行测试,LED将每隔一秒改变一下状态。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd;int status;fd = open("/dev/psled1", O_RDWR);if(fd < 0){perror("open /dev/psled1 error!\n"); return fd; }while(1){status = 1;write(fd, &status, 1);sleep(1);status = 0;write(fd, &status, 1);sleep(1);}close(fd);  return 0;
}

在开发板上执行后的结果如下图所示。
在这里插入图片描述
开发板上PS LED1的状态开始循环亮灭,如下动图所示。
请添加图片描述
终端里也是每隔一秒打印一次LED关闭或打开的状态。请添加图片描述
卸载驱动程序后打印下面的信息。
在这里插入图片描述

新字符设备驱动

上面驱动代码中将设备号写死了,这样做有很多不便之处,因为编译驱动代码前需要查看目标系统中设备号的占用情况,驱动注册函数中仅有主设备号没有次设备号,这意味着一个设备会占用所有的次设备号,十分浪费资源。针对这些问题,Linux内核提出了新的字符设备注册方法,并由内核来管理设备号。
注册字符设备号的函数原型如下。

int register_chrdev_region(dev_t from,unsigned count,const char* name);

from :需要申请的起始设备号,取代了原有的主设备号和次设备号,在需要指定主次设备号的情况下,可以通过方法from = MKDEV(major,minor); 来实现。
count :需要申请的设备号个数。
name :设备名称。
在不需要指定主次设备号的情况下,设备号由内核来分配,传入指针来获取设备号,注册、注销设备号的函数原型如下。

int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char* name);
void unregister_chrdev_region(dev_t from,unsigned count);

dev :设备号指针,注册成功之后,主次设备号可以通过 major = MAJOR(dev); minor = MINOR(dev); 来获取。
baseminor :次设备号的起始地址。
新的注册方法使用cdev结构体来定义一个字符设备,cdev结构体如下。

struct cdev
{struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};

cdev结构体的初始化函数原型如下。

void cdev_init(struct cdev *cdev,const struct file_operations *fops);

注册、注销字符设备的函数原型如下。

int cdev_add(struct cdev *cdev,dev_t dev,unsigned count);
void cdev_del(struct cdev *cdev);

类的创建和删除函数原型如下。

struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key);
void class_destroy(struct class *cls);

owner指定为THIS_MODULE,name是类的名称,第三个参数可以省略。
设备节点的创建和删除函数原型如下。

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void * drvdata, const char *fmt, ...);
void device_destroy(struct class *class, dev_t devt);

class是通过class_create创建的类,parent是父设备,无则填NULL,devt是设备号,drvdata是设备可能用到的数据,没有则填NULL,fmt是设备名,创建成功后在/dev下生成。
下面代码使用的是新字符设备方法。

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/init.h>  
#include <linux/ide.h>  
#include <linux/types.h>  
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>#define DEVICE_NAME       "psled1"
#define DEVID_COUNT       1   //设备号个数 
#define DRIVE_COUNT       1   //驱动个数/* gpio寄存器虚拟地址 */  
static unsigned int gpio_add_minor;  
/* gpio寄存器物理基地址 */  
#define GPIO_BASE         0xE000A000  
/* gpio寄存器所占空间大小 */  
#define GPIO_SIZE         0x1000  
/* gpio方向寄存器 */  
#define GPIO_DIRM_0       (unsigned int *)(0xE000A204 - GPIO_BASE + gpio_add_minor)  
/* gpio使能寄存器 */   
#define GPIO_OEN_0        (unsigned int *)(0xE000A208 - GPIO_BASE + gpio_add_minor)  
/* gpio控制寄存器 */  
#define GPIO_DATA_0       (unsigned int *)(0xE000A040 - GPIO_BASE + gpio_add_minor)  /* 时钟使能寄存器虚拟地址 */  
static unsigned int clk_add_minor;  
/* 时钟使能寄存器物理基地址 */  
#define CLK_BASE          0xF8000000  
/* 时钟使能寄存器所占空间大小 */  
#define CLK_SIZE          0x1000  
/* AMBA外设时钟使能寄存器 */  
#define APER_CLK_CTRL     (unsigned int *)(0xF800012C - CLK_BASE + clk_add_minor)        #if 0
struct chardev
{dev_t            devid;      //设备号struct cdev      cdev;       //字符设备struct class     *class;     //类struct device    *device;    //设备节点
};static struct chardev alinx_char = {.cdev = {.owner = THIS_MODULE,},
};
#endifdev_t            devid;      //设备号
struct cdev      cdev;       //字符设备
struct class     *class;     //类
struct device    *device;    //设备节点static int gpio_leds_open(struct inode *inode_p, struct file *file_p)  
{  gpio_add_minor = (unsigned int)ioremap(GPIO_BASE, GPIO_SIZE);  clk_add_minor = (unsigned int)ioremap(CLK_BASE, CLK_SIZE); /* MIO_0时钟使能 */  *APER_CLK_CTRL |= 0x00400000;  /* MIO_0设置成输出 */  *GPIO_DIRM_0 |= 0x00000001;  /* MIO_0使能 */  *GPIO_OEN_0 |= 0x00000001;    printk("gpio_test module open\n");   return 0;  
}  static ssize_t gpio_leds_write(struct file *file_p, const char __user *buf, size_t len, loff_t *loff_t_p)  
{  int rst;  char writeBuf[5] = {0};   printk("gpio_test module write\n");  rst = copy_from_user(writeBuf, buf, len);  if(0 != rst)  {  return -1;    }  if(1 != len)  {  printk("gpio_test len err\n");  return -2;  }  if(1 == writeBuf[0])  {  *GPIO_DATA_0 &= 0xFFFFFFFE;  printk("gpio_test ON\n");  }  else if(0 == writeBuf[0])  {  *GPIO_DATA_0 |= 0x00000001;  printk("gpio_test OFF\n");  }  else  {  printk("gpio_test para err\n");  return -3;  }  return 0;  
}  static int gpio_leds_release(struct inode *inode_p, struct file *file_p)  
{  printk("gpio_test module release\n");  return 0;  
}  static struct file_operations chardev_fops = {  .owner   = THIS_MODULE,  .open    = gpio_leds_open,  .write   = gpio_leds_write,     .release = gpio_leds_release,   
};  static int __init gpio_led_init(void)  
{  #if 0alloc_chrdev_region(&alinx_char.devid, 0, DEVID_COUNT, DEVICE_NAME);  //注册设备号cdev_init(&alinx_char.cdev, &chardev_fops);  //初始化字符设备结构体cdev_add(&alinx_char.cdev, alinx_char.devid, DRIVE_COUNT);  //注册字符设备 alinx_char.class = class_create(THIS_MODULE, DEVICE_NAME);  //创建类if(IS_ERR(alinx_char.class)) {return PTR_ERR(alinx_char.class);}alinx_char.device = device_create(alinx_char.class, NULL, alinx_char.devid, NULL, DEVICE_NAME);  //创建设备节点printk("alloc success, major = %d minor = %d\n",MAJOR(alinx_char.devid),MINOR(alinx_char.devid));if (IS_ERR(alinx_char.device)) {return PTR_ERR(alinx_char.device);}#endifalloc_chrdev_region(&devid, 0, DEVID_COUNT, DEVICE_NAME);  //注册设备号cdev.owner = THIS_MODULE;cdev_init(&cdev, &chardev_fops);  //初始化字符设备结构体cdev_add(&cdev, devid, DRIVE_COUNT);  //注册字符设备 class = class_create(THIS_MODULE, DEVICE_NAME);  //创建类if(IS_ERR(class)) {return PTR_ERR(class);}device = device_create(class, NULL, devid, NULL, DEVICE_NAME);  //创建设备节点printk("alloc success, major = %d minor = %d\n",MAJOR(devid),MINOR(devid));if (IS_ERR(device)) {return PTR_ERR(device);}return 0;  
}static void __exit gpio_led_exit(void)  
{  iounmap((unsigned int *)gpio_add_minor);  iounmap((unsigned int *)clk_add_minor); #if 0cdev_del(&alinx_char.cdev);   //注销字符设备unregister_chrdev_region(alinx_char.devid, DEVID_COUNT);  //注销设备号device_destroy(alinx_char.class, alinx_char.devid);  //删除设备节点class_destroy(alinx_char.class);  //删除类#endifcdev_del(&cdev);   //注销字符设备unregister_chrdev_region(devid, DEVID_COUNT);  //注销设备号device_destroy(class, devid);  //删除设备节点class_destroy(class);  //删除类printk("gpio_led_dev_exit_ok\n");  
}  module_init(gpio_led_init);  
module_exit(gpio_led_exit);  
MODULE_LICENSE("GPL");  

加载驱动之后,内核就会为设备指定主设备号和次设备号,不用再使用命中自己指定了。
在这里插入图片描述


参考文档:course_s6_ZYNQ那些事儿-Linux驱动篇V1.05

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机的自动浇花器电路

一、系统概述 自动浇水灌溉系统设计方案&#xff0c;以AT89C51单片机为控制核心&#xff0c;采用模块化的设计方法。 组成部分为&#xff1a;5V供电模块、土壤湿度传感器模块、ADC0832模数转换模块、水泵控制模块、按键输入模块、LCD显示模块和声光报警模块&#xff0c;结构如…

基于SSM的婚恋网站的设计与实现(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的婚恋网站的设计与实现&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spri…

信创电脑|暴雨新增兆芯KX-7000处理器版本

IT世界 5 月 15 日消息&#xff0c;暴雨公司信创家族新上架了一款搭载兆芯KX-7000系列处理器、摩尔线程8GB 显卡、16G DDR5 内存以及 512G SSD 的新配置台式电脑主机。 兆芯 KX-7000 处理器采用开先的 8 核 Chiplet互联架构&#xff0c;最高频率3.7 GHz&#xff0c;拥有 32MB 的…

TINA 使用教程

常用功能 分析-电气规则检查&#xff1a;短路&#xff0c;断路等分析- 直流分析 交流分析 瞬态分析 视图-分离曲线 由于输出的容性负载导致的振荡 增加5欧电阻后OK 横扫参数 添加横扫曲线的电阻&#xff0c;选择R3&#xff1a;8K-20K PWL和WAV文件的支持 示例一&#xff1a;…

计算机Java项目|Springboot高校心理教育辅导设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

Blender雕刻建模_笔刷纹理和顶点绘制

笔刷纹理 主要用于皮肤&#xff0c;纹理的雕刻。 可以修改映射方式来实现不同绘制效果。 用一张纹理来定义笔刷各个点的强度。其中白色为1&#xff0c;黑色为0。 设置笔刷纹理步骤&#xff1a; -新建一套笔刷 -强度&#xff0c;设为0.15&#xff08;可以根据需求修改&#x…

MYSQL-9.问题排查

问题排查的思路与方向 问题排查思路 分析问题&#xff1a;根据理论知识经验分析问题&#xff0c;判断问题可能出现的位置或可能引起问题的原因&#xff0c;将目标缩小到一定范围&#xff1b;排查问题&#xff1a;基于上一步的结果&#xff0c;从引发问题的“可疑性”角度出发…

字符串_字符函数和字符串函数

C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串通常放在常量字符串中或者字符数组中。 字符串常量适用于那些对它不做修改的字符串函数。 目录 1.函数介绍 1.1strlen 1.1.1strlen函数的模拟实现 1.2strcpy 1.2.1st…

威纶通触摸屏下载项目文件后,文本都变成了框框的解决办法

威纶通触摸屏下载项目文件后,文本都变成了框框的解决办法 我们在用Easy builder pro编辑某些项目的情况下,编译没问题,为什么下载到触摸屏之后,文本都变成了框框了呢? 分析:: 不能正常显示文本的原因是字体文件缺失。 解决办法: 如下图所示,在Easy builder pro软件中,…

自定义注解

例如写一个注解PrintTime 如下&#xff1a; import java.lang.annotation.*;//下面的注解属于元注解 Target({ElementType.PARAMETER,ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Inherited Documented public interface PrintTime {/*** 注解的属性*/public S…

数据可视化的艺术:使用Matplotlib和Seaborn揭示数据故事

引言 数据可视化是数据分析中的关键一环&#xff0c;它帮助我们理解数据模式、趋势和异常。在Python中&#xff0c;Matplotlib和Seaborn是两个流行的数据可视化库&#xff0c;它们提供了丰富的图表和图形选项&#xff0c;使数据的可视化变得简单而强大。 Matplotlib&#xff…

三分钟快速上手SpringSecurity框架

导入依赖框架 web 框架(spring-boot-starter-web) <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> springSecurity 框架(spring-boot-starter-security) <de…