树莓派高级开发之树莓派博通BCM2835芯片手册导读与及“相关IO口驱动代码的编写”

news/2024/5/21 8:39:48/文章来源:https://blog.csdn.net/weixin_51976284/article/details/126892237

首先我们要知道,驱动的两大利器:电路图(通过电路图去寻找寄存器)和芯片手册

一、寄存器的介绍

芯片手册第六章的89页,GPIO有41个寄存器,所有访问都是32位的。Description是寄存器的功能描述。GPFSEL0(寄存器名) GPIO Function Select 0(功能选择:输入或输出);GPSET0 (寄存器名) GPIO Pin Output Set 0(将IO口置0);GPSET1(寄存器名) GPIO Pin Output Set 1(将IO口置1);GPCLR0(寄存器名) GPIO Pin Output Clear 0 (清0)下图的地址是:总线地址(并不是真正的物理地址)
在这里插入图片描述

GPFSEL0是pin0 ~ pin9的配置寄存器,GPFSEL1是pin10 ~ pin19的配置寄存器,以此类推,GPFSEL5就是pin50~pin53的配置寄存器。

字段名描述用法
GPFSEL0GPIO Function select 0,功能选择输出/输入以引脚9举例:000 = GPIO Pin 9 is an input,001 = GPIO Pin 9 is an output
GPSET0GPIO Pin output Set 0,输出00 = No effect ,1 = Set GPIO pin n
GPSET1GPIO Pin output set 1,输出10 = No effect ,1 = Set GPIO pin n
GPCLR0GPIO Pin output clear 0,清00 = No effect ,1 = Clear GPIO pin n

在上面的文档里已经说的很清楚了,000是引脚输入,而001则是引脚输出,在这里要注意每个寄存器都是32位的

  • FSELn表示GPIOn,下图给出第九个引脚的功能选择示例,对寄存器的29-27进行配置,进而设置相应的功能。根据图片下方的register
    0表示0~9使用的是register 0(即GPFSEL0)这个寄存器。

在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/28a4ca08cba847d68c3e4c11b5f7615

  • 输出集寄存器用于设置GPIO管脚。SET{n}字段定义,分别对GPIO引脚进行设置,将“0”写入字段没有作用。如果GPIO管脚为在输入(默认情况下)中使用,那么SET{n}字段中的值将被忽略。然而,如果引脚随后被定义为输出,那么位将被设置根据上次的设置/清除操作。分离集和明确功能取消对读-修改-写操作的需要。GPSETn寄存器为了使IO口设置为1,set4位设置第四个引脚,也就是寄存器的第四位。
    在这里插入图片描述
  • 输出清除寄存器用于清除GPIO管脚。CLR{n}字段定义要清除各自的GPIO引脚,向字段写入“0”没有作用。如果在输入(默认),然后在CLR{n}字段的值是忽略了。然而,如果引脚随后被定义为输出,那么位将被定义为输出根据上次的设置/清除操作进行设置。分隔集与清函数消除了读-修改-写操作的需要。GPCLRn是清零功能寄存器。
    在这里插入图片描述
    在这里插入图片描述

把pin4引脚配置为输出引脚:
FSEL4 14-12 001 我们把4引脚的14-12配置成001 GPIO Pin 4 is an output
详细操作:
只需要将GPFSL0这个寄存器的14~12位设置为001就可以了。只需要将0x6(对应的2进制是110)左移12位·然后取反再与上GPFSL0就可以将13、14这两位配置为0,然后再将0x6(对应2进制110)左移12位,然后或上GPFSL0即可将12位置1。

特别提示:进行取反后再进行按位与操作是为了不影响其他引脚

配置pin4引脚为输出引脚 bit 12-14 配置成001

31 30 ······14 13 12 11 10 9 8 7 6 5 4 3 2 1 
0  0  ······0  0  1  0  0  0 0 0 0 0 0 0 0 0 //配置pin4引脚为输出引脚      bit 12-14  配置成001  *GPFSEL0 &= ~(0x6 <<12); // 把bit13 、bit14置为0  //0x6是110  <<12左移12位 ~取反 &按位与*GPFSEL0 |= (0x1 <<12); //把12置为1   |按位或

忘了按位与和按位或的点这里

代码实现:

  *GPFSEL0 &=~(0x6 <<12); // 把13 、14置为0*GPFSEL0 |= (0x1 <<12); //把12置为1
  • 注意:我们配置的底层引脚对应得是BCM 寄存器第0组位FESL0–9, 这个就是在寄存器GPFSEL0里,寄存器已经分好组了
    寄存器第1组位FSEL10–19,这个在寄存器GPFSEL1里

在这里插入图片描述

更多的引脚对应的寄存器可以去树莓派官网进行查看
树莓派引脚查看官网
在这里插入图片描述
在上图中我们可以点击对应的引脚编号,就可以查看到对应的引脚的相关的信息

二、寄存器的地址问题

我们在编写驱动程序的时候,IO口空间的起始地址是0x3f00 0000,加上GPIO的偏移量0x200 0000,所以GPIO的物理地址应该是0x3f20 0000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。
在这里插入图片描述
上图的尾部偏移是对的,根据GPIO的物理地址0x3f20 0000可以知道:

GPFSEL0 0x3f20 0000 //IO口的初始的物理地址,而并不是手册里面的那个总线地址
GPSET0 0x3f20 001c  //地址通过查找芯片手册里面的对应的GPSET0 的总线地址的后两位决定是1c
GPCLR0 0x3f20 0028 //地址是查找GPCLR0在芯片手册里的总线地址确定的28,所以地址后两位是28
  • 在原来框架的基础上,添加寄存器的定义
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;

完成以上代码需要搞清楚的几点

  1. 弄清楚寄存器的分组
    GPFSEL0是pin0 ~ pin9的配置寄存器,GPFSEL1是pin10 ~ pin19的配置寄存器,以此类推,GPFSEL5就是pin50~pin53的配置寄存器。这个由查阅芯片手册可以得知

  2. volatile关键字的使用(笔试可能会考)

  • 在此处的作用:防止编译器优化(可能是省略,也可能是更改)这些寄存器地址变量,常见于在内核中对IO口进行操作

  • 作用:确保指令不会因编译器的优化而省略,且要求每次直接读值,在这里的意思就是确保地址不会被编译器更换

  1. 如何配置寄存器的地址
    首先是在1.的基础上,在pin4_drv_init这个函数里面添加寄存器地址的配置
GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);

写出以上的代码,要搞清楚以下几点
分别找到几个IO寄存器的物理地址(非常易错),弄清楚GPIO的物理地址(真实地址)
记住并不是用下面这张图的地址来对应GPIO功能选择寄存器0的地址,否则编译后运行会有段错误。
在这里插入图片描述
IO口的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的实际物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上,编程都是操作虚拟地址。

然后我们可以根据这个偏移值来确定寄存器的物理地址(真实的地址
可以看到寄存器GPSET0相对于GPIO物理地址的偏移值为1C。即0x3f20001C
在这里插入图片描述
同样的方法,寄存器GPCLR0的偏移值为28,即0x3f200028
寄存器GPFSEL0的偏移值为0,即0x3f200000

  1. 如何让引脚拉高或拉低电平
    代码实现:
if(userCmd == 1){printk("set 1\n");*GPSET0 |= (0x1 << 4); //这里的1左移4位的目的就是促使寄存器将电平拉高,即变为HIGH}else if(userCmd == 0){printk("set 0\n");*GPCLR0 |= (0x1 << 4); //这里的1左移4位也是一样只是为了让寄存器将电平拉低,即变为LOW}else{printk("nothing undo\n"); }

引脚输出高电平:
*GPSET0 |= (0x1 << 4);
左移4位, 这里无论什么寄存器都是写1,写1并不是为某个io口去写1,而是1是驱动(SET)设置寄存器工作将bit4的电平拉高即变为高电平,为什么要进行或操作,是因为为了不影响其他引脚的状态

引脚输出低电平:
*GPCLR0 |= (0x1 << 4);
同样道理,左移4位,这里的1也并不是为了某个io口去写1,而是1是驱动(CLR)清零寄存器将电平拉低,即变为低电平,进行或操作也一样是为了不影响其他引脚的电平状态

  1. 在Linux内核的io.h头文件中声明了ioremap()函数,用来将IO内存资源映射到核心虚拟地址空间(3Gb~4GB)中,当然不用了可以将其取消映射iounmap()。这两个函数在mm/ioremap.c文件中:
开始映射:void* ioremap(unsigned long phys_addr , unsigned long size , unsigned long flags)
//用map映射一个设备意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或写入,实际上就是对设备的访问。
第一个参数是映射的起始地址
第二个参数是映射的长度
第二个参数怎么定啊?
====================
这个由你的硬件特性决定。
比如,你只是映射一个32位寄存器,那么长度为4就足够了。
(这里树莓派IO口功能设置寄存器、IO口设置寄存器都是32位寄存器,所以分配四个字节就够了)比如:GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
这三行是设置寄存器的地址,volatile的作用是作为指令关键字
确保本条指令不会因编译器的优化而省略,且要求每次直接读值
ioremap函数将物理地址转换为虚拟地址,IO口寄存器映射成普通内存单元进行访问。解除映射:void iounmap(void* addr)//取消ioremap所映射的IO地址
比如:iounmap(GPFSEL0);iounmap(GPSET0);iounmap(GPCLR0); //卸载驱动时释放地址映射
  1. 浅谈一下copy_from_usercopy_to_user 函数的用法
    copy_from_usercopy_to_user这两个函数相信做内核开发的人都非常熟悉,分别是将用户空间的数据拷贝到内核空间以及将内核空间中的数据拷贝到用户空间
 函数copy_from_user原型:copy_from_user(void *to, const void __user *from, unsigned long n)返回值:失败返回没有被拷贝成功的字节数,成功返回0
参数详解:
1. to 将数据拷贝到内核的地址,即内核空间的数据目标地址指针
2. from 需要拷贝数据的地址,即用户空间的数据源地址指针
3. n 拷贝数据的长度(字节)
也就是将@from地址中的数据拷贝到@to地址中去,拷贝长度是n

详细了解copy_from_user和copy_to_user

三、驱动代码与应用测试代码

3.1 相关代码
底层驱动代码:

#include <linux/fs.h>		 //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>	 //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件static struct class *pin4_class;  
static struct device *pin4_class_dev;static dev_t devno;                //设备号
static int major =231;  		   //主设备号
static int minor =0;			   //次设备号
static char *module_name="pin4";   //模块名--这个模块名到时候是在树莓派的/dev底下显示相关驱动模块的名字volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;//volatile关键字的作用:确保指令不会因编译器的优化而省略,且要求每次直接读值,在这里的意思就是确保地址不会被编译器更换//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n");  //内核的打印函数和printf类似    //由于pin4在 14-12位,所以将14-12位分别置为001即为输出引脚,所以下面的那两个步骤分别就是将14,13置为0,12置为1*GPFSEL0 &= ~(0x6 << 12); //把13,14位 置为0*GPFSEL0 |=  (0x1 << 12); //把12位 置为1 return 0;
}//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{int userCmd;int copy_cmd;printk("pin4_write\\n");//copy_from_user(void *to, const void __user *from, unsigned long n)copy_cmd = copy_from_user(&userCmd,buf,count); //函数的返回值是,如果成功的话返回0,失败的话就是返回用户空间的字节数if(copy_cmd != 0){printk("fail to copy from user\n");}if(userCmd == 1){printk("set 1\n");*GPSET0 |= (0x1 << 4); //这里的1左移4位的目的就是促使寄存器将电平拉高,即变为HIGH}else if(userCmd == 0){printk("set 0\n");*GPCLR0 |= (0x1 << 4); //这里的1左移4位也是一样只是为了让寄存器将电平拉低,即变为LOW}else{printk("nothing undo\n"); }return 0;
}static ssize_t pin4_read(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{printk("pin4_read\n");return 0;	
}static struct file_operations pin4_fops = {.owner = THIS_MODULE,.open  = pin4_open,.write = pin4_write,.read  = pin4_read,
};int __init pin4_drv_init(void)   //设备驱动初始化函数(真实的驱动入口)
{int ret;devno = MKDEV(major,minor);  //创建设备号ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //这个是让代码在/dev目录底下自动生成设备,自己手动生成也是可以的pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件//由于以下的地址全是物理地址,所以我们要将物理地址转换成虚拟地址 GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4); //由于寄存器是32位的,所以是映射4个字节,一个字节为8位GPSET0  = (volatile unsigned int *)ioremap(0x3f20001c,4);GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);return 0;
}void __exit pin4_drv_exit(void)  //卸载驱动,即将驱动从驱动链表中删除掉 
{iounmap(GPFSEL0);iounmap(GPSET0);iounmap(GPCLR0);device_destroy(pin4_class,devno);class_destroy(pin4_class);unregister_chrdev(major, module_name);  //卸载驱动
}module_init(pin4_drv_init);  //真正的入口
module_exit(pin4_drv_exit);  //卸载驱动
MODULE_LICENSE("GPL v2");	

上层应用测试代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd;int userCmd;fd = open("/dev/pin4",O_RDWR);if(fd < 0){printf("fail to open the pin4\n");perror("the reason:");}else{printf("success to open the pin4\n");}printf("please Input 1-HIGH,0-LOW \n");scanf("%d",&userCmd);write(fd,&userCmd,4); //这里userCmd是一个整型数,所以写的是4个字节return 0;
}

至于怎么在虚拟机中编译驱动文件以及如何将编译好的文件发送至树莓派,敬请关注以下博文
如何将编译好的文件发送至树莓派底下

3.2 在树莓派底下进行代码的测试与验证
相关的驱动的装载与卸载也查看驱动装载与卸载

  • 先来查看一下树莓派4号引脚的初始状态是什么
    在这里插入图片描述
    输入1,将引脚电平变为高电平
    在这里插入图片描述
    输入0,将引脚电平变为低电平
    在这里插入图片描述
    到目前为止,我们经过那么多节的对驱动的学习的博文,现在终于自己终于实现了类似于wiringPi这样的一个驱动文件,我们在这里做的是引脚4的驱动,那么我们就可以按着模样来写引脚5,引脚6,甚至其他引脚的驱动,在这里想说一句,驱动代码的编写,都是基于linux内核源码来进行编写的,linux内核源码这个文件在前面的博文有,如有需要自行去下载。

学习笔记,仅供参考

树莓派高级开发之IO口驱动代码编写 优秀博文参考一
树莓派高级开发之IO口驱动代码编写 优秀博文参考二

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

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

相关文章

2022年最新《Java八股文面试宝典》全网独一份!(效率最高、知识最新、包含各个技术栈)

今天在脉脉刷到了这么一条消息&#xff0c;现在这个大环境&#xff0c;都后悔学Java了&#xff0c;想转行学前端&#xff0c; 看完很是震惊&#xff0c;据大数据统计&#xff0c;Java的待遇是要好过前端的。小伙伴竟然被卷到想要转行......但是行情这个东西&#xff0c;也不是我…

vue3.x之isRef toRefs isRef readonly 公共数据配置 axios配置 路由配置

isRef toRefs toRef 参数&#xff1a; (源对象 , 源对象属性) 可以用来为源响应式对象上的某个 property 新创建一个 ref。然后&#xff0c;ref 可以被传递&#xff0c;它会保持对其源 property 的响应式连接。 也就是说源响应式对象(toRef的第一个参数) 上的某个 property…

【3D视觉】PointNet解析

您好&#xff0c;各位&#xff01;今天就基于3D点云数据的分类以及分割模型 : PointNet与PointNet做一个简单的解析&#xff0c;解析部分将结合论文与代码&#xff0c;加上一些我个人微不足道&#xff08;也不一定对&#xff09;的见解在里面。 在看PointNet与PointNet之前&am…

第三章实验

实例一print("今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问几何?\n") number = int(input("请输入您认为符合条件的数:")) if number%3 == 2 and number%5 == 3 and number%7 == 2:print(number,"符合条件:三三数之剩二,五五数…

GBase 8s是如何实现库中数据安全保障的

随着计算机网络的广泛应用&#xff0c;网上信息的开放性与共享性日益增强&#xff0c;但随之而来的是信息安全问 题愈发严重。数据库是这些数据信息存储的主要场所&#xff0c;因此确保数据库中存储以及存取信息的安 全是确保网络安全的首要问题。为此&#xff0c;需要在通用的…

Nginx在Linux下的安装

✨ Nginx在Linux下的安装安装pcre安装其他的依赖安装Nginx(把压缩包放到opt目录)&#x1f4c3;个人主页:不断前进的皮卡丘&#x1f31e;博客描述:梦想也许遥不可及&#xff0c;但重要的是追梦的过程&#xff0c;用博客记录自己的成长&#xff0c;记录自己一步一步向上攀登的印记…

软件测试 git和gitee集成Pycharm 基于Flask的Mock Server服务器

文章目录1 Git1.1 作用1.2 工具1.3 名称解释2 安装git和注册Gitee3 使用Git(1)clone克隆命令(2)初始化(3)查看文件状态(4)文件提交暂存区(5)提交到本地版本库(6)修改文件(7)查看日志(8)跳转到提交的时间截点4 git和gitee集成Pycharm4.1 在Pycharm安装git和连接gitee(1)新建项目…

交互与前端3 前端需求简单梳理

说明 技术的终点是前端 我是从模型/算法作为起点的,顺着工作的需要和自己的兴趣&#xff0c;慢慢的逐步走到了前端。我想现在也是时候把前端搞好了&#xff0c;前端有几个作用&#xff1a; 1 对外可以作为广告。技术最终还是要考虑变现的。2 与外部协同。有很多工作是需要外部…

关于穿越机FPV视频果冻效应的讨论

关于穿越机FPV视频果冻效应的讨论1. 名词定义2. 摄像原理2.1 快门分类2.2 卷帘拍摄3. 产生原因4. 解决方法4.1 振动出处4.2 软件方法(辅助作用)4.3 硬件方法(直接办法)5. F450试验机遇到的问题5.1 现象5.2 测试5.3 减震改善5.4 其他改善5.5 初步结论5.6 改进方向6. 总结7. 参考…

基于ssm的远程家庭健康监测管理系统设计与实现-计算机毕业设计源码+LW文档

开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;…

常用的荧光染料示踪剂 Me-tetrazine-ICG,甲基-四嗪-吲哚菁绿 有哪些特点?

甲基-四嗪-吲哚菁绿是一种荧光染料化合物&#xff0c;四嗪可通过TCO点击化学标记到其它大分子上。吲哚菁绿是生物学中常用的荧光染料示踪剂&#xff0c;波长更长。 西安凯新生物科技有限公司常规修饰性聚乙二醇常备现 货&#xff0c;非常规基团修饰性聚乙二醇&#xff08;PEG&a…

h5(1)

H5 一、canvas标签 canvas是绘图标签&#xff0c;可以使用该标签在网页上生成一块画布&#xff0c;然后就可以在这块画布中随意的绘图。 canvas标签基本使用&#xff1a; <canvas width"500" height"500" id"cvs"></canvas> //w…

AWS聚焦数字经济与可持续发展

2022年中国国际服务贸易交易会于9月5日圆满闭幕&#xff0c;AWS在此间展示了多项领先的云计算技术和行业解决方案。围绕着本届服贸会“服务合作促发展绿色创新迎未来”的主题&#xff0c;AWS也在此次论坛中诠释和传递着其在助力数字经济及企业可持续发展的价值愿景。 9月3日与…

springboot 上传文件/图片到本地文件夹,利用nginx可以采用地址打开该文件

springboot 上传文件/图片到本地文件夹&#xff0c;利用nginx可以采用地址打开该文件 步骤&#xff1a; 一、下载nginx 打开nginx.conf 文件&#xff0c;配置nginx 启动nginx在nginx.exe文件所在的文件夹路径上直接cmd&#xff0c;输入nginx.exe即可启动nginx 注意&#xff…

企业运营管理 | 如何用「内容」取胜营销战?

全媒体时代&#xff0c;舆论生态、媒体格局、传播方式日新月异&#xff0c;但「内容为王」始终是品牌营销增长的公认规则。 除了投放渠道多、内容更新频次快、跨地域运营等挑战&#xff0c;如何规模化地输出统一的、优质的内容&#xff0c;以此驱动品牌增长&#xff0c;成为品牌…

u盘部分文件无故消失该怎么办?

u盘属于移动存储设备&#xff0c;用于备份数据&#xff0c;方便携带。可以存放各种格式的数据、文档、音频、视频、图片&#xff0c;即插即用&#xff0c;随时拔下。这给了我们极大地便利。但是我们在使用u盘的时候偶尔会出现一些意外&#xff0c;比如u盘文件没删除却消失了&am…

数据分析案例-基于sklearn随机森林算法探究影响预期寿命的因素

目录 项目目标 导入数据 查看数据基本信息 数据预处理 数据可视化 特征工程 建模 项目目标 **探索影响预期寿命的因素** 世卫组织建立了一段时间内所有国家健康状况的数据集&#xff0c;其中包括预期寿命&#xff0c;成人死亡率等方面的统计数据。使用此数据集&#xff…

Panama-FFI实现原理与移植

移植FFI 在说明如何对FFI进行移植之前需要先说明FFI的实现原理。JEP424是外部函数访问+本地内存,但是实际上需要移植的内容只有外部函数访问,对于本地内存的操作并不需要修改。 从java中调用native方法叫做downcall,而从native方法中调用java方法叫做upcall,下面通过downca…

泰克示波器知识分享-波的类型

提到泰克示波器&#xff0c;相信大多数人都知道&#xff0c;那大家对示波器基础知识了解多少呢?今天安泰测试就给大家分享一波干活——波的类型介绍&#xff1a; 您可以把大多数波分成下面几类&#xff1a; 周期信号和非周期信号 重复的信号称为周期信号,一直变化的信号则称为…

基于Android studio有声听书系统 java音乐播放器系统

1&#xff1a;注册登录&#xff1a;未注册用户首先进行账号注册&#xff0c;注册成功后进行登录&#xff0c;已注册用户直接输入账号密码进行登录&#xff0c;登录成功后进入主页面。 2&#xff1a;主页面&#xff1a;通过左右滑动可以实现对推荐界面、订阅界面、历史界面的切换…