一、分文件编程案例:
好处:分模块编程思想
网络a
超声波b
电机c
功能责任划分
方便调试
include的时候后面有时候跟<>
有时候跟""
<>表示系统自动去usr/include底下寻找头文件
""表示自己生成的,系统先在当前目录寻找.h文件找不到再在include下寻找
ls /usr/include/ |grep stdio
#include
int main()
{
while(1)
{
tips();//用来提示用户输入
Getinput();//用来获取用户输入以便选择算法开始计算
getchar();//用于吸收每次输完data2之后的回车键
}
return 0;
}
#include
void tips()
{
printf("************************\n");
printf("请输入你想选择的算法+-*/\n");
printf("************************\n");
}
int add()
{
int data1;
int data2;
int y;
printf("你选择的是加法\n");
printf("请输入第一个加数:\n");
scanf("%d",&data1);
printf("请输入第二个加数:\n");
scanf("%d",&data2);
printf("两数之和是:%d\n",(y=data1+data2));
return 0;
}
int sub()
{
int data1;
int data2;
int y;
printf("你选择的是减法\n");
printf("请输入减数:\n");
scanf("%d",&data1);
printf("请输入被减数:\n");
scanf("%d",&data2);
printf("两数之差是:%d\n",(y=data1-data2));
return 0;
}
int mul()
{
int data1;
int data2;
int y;
printf("你选择的是乘法\n");
printf("请输入第一个乘数:\n");
scanf("%d",&data1);
printf("请输入第二个乘数:\n");
scanf("%d",&data2);
printf("两数之积是:%d\n",(y=data1*data2));
return 0;
}
int div()
{
int data1;
int data2;
float y;
printf("你选择的是除法\n");
printf("请输入除数:\n");
scanf("%d",&data1);
printf("请输入被除数:\n");
scanf("%d",&data2);
printf("两数之商是:%f\n",(y=(float)data1/data2));
return 0;
}
void Getinput()
{
char A;
scanf("%c",&A);
getchar();//用来吸收回车键
switch(A)
{
case '+':
add();
break;
case '-':
sub();
break;
case '*':
mul();
break;
case '/':
div();
break;
default:
printf("你输入的无法识别\n");
break;
}
}
#include “func.h”
#include
void tips();
int add();
int sub();
int mul();
int div();
void Getinput();
头文件中,<>默认是从include下查找到,""默认是从当前路径下查找的
以计算器为例示范分文件编程分三个文件:
1、主函数一个文件
2、其余函数一个文件
3、复制一下其余函数,令其生成.h文件,去除其主要部分
会出现粘贴后自动换行问题:先ESC,接着直接gg=G就自动对其
二、linux库引入之动态库静态库
静态数据库:程序执行之前就已经加载到程序中了
优点:运行快
缺点:程序大
动态数据库:程序执行过程中,动态的加载这个库
优缺点正好相反
静态库的制作声明和使用
静态库的格式:xxx.a
静态库的制作://这里要给功能函数生成一个库
1、gcc testfunc.c -c//给功能函数生成.o文件
2、ar rcs libtestfunc.a testfunc.o//把.o文件生成.a文件
接着只用保留主函数.c文件,及功能函数的.h .a文件
假定目前在test文件夹下:
mv testfunc.* ~//把所有testfunc的文件移到工作目录下
cp ~/testfunc.h .//把工作目录下testfunc.h文件复制到当前路径(前面复制多了)
cd .. 返回工作目录
ls ~//查看工作目录下的文件
编译时:gcc testmain.c -ltestfunc -L ./
//掐头去尾去掉lib .a
-L ./ 指定从当前文件去找库
-l非标准库都要-l进行链接起来
编译时:保留.h文件,main.c文件
动态库的声明和使用
动态库的制作://把testfunc.c变成动态库
1、gcc -shared -fpic testfunc.c -o libtest.so
-shared指定生成动态库
-fpic标准
lib//代表共享库 .so固定后缀 test要生成库名字
动态库的使用:
2、gcc testmain.c -ltest -L ./ -o test
./test;//这样运行是不行的,因为动态库是使用中去找,
3、 解决方法一:sudo cp libtest.so /usr/lib/ 把库拷贝到lib文件夹里面(默认去/usr/lib/去找)
解决方法二:指定从当前位置找动态库,linux指定动态库位置(修改环境变量)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./(把当前路径也添加到环境变量里边,临时)
解决方法三:创建个脚本vi start.sh,在脚本中把当前位置设置为环境变量,第二行./a.out。
接着chmod +x start.sh。./start.sh运行脚本,看到程序已经运行
chmod +x Add 给Add加一个可执行权限
du a.out查看文件大小。
chmod +x Add 给Add加一个可执行权限
三、树莓派外设开发综述
1、IO口:input output对于芯片而言
input:人体、烟雾、振动
output:继电器、蜂鸣器
mkdir:建立文件夹
偶尔可能会出现vi里面异常的情况可以尝试重新打开终端
gpio readall //查看树莓派的端口
左右对称
知识点:
继电器主要使非可编程器件变得智能
用树莓派端口看wPi,用驱动端口看BCM。
wiringPiSetup();//硬件接口初始化
digitalWrite(7,LOW);//给树莓派某个接口低电平或者高电平
//树莓派操作继电器默认要用3.3V
#include
#include
int main()
{
int cmd;
if(wiringPiSetup() == -1)
{
printf("硬件接口初始化失败\n");
return -1;
}
pinMode(12,OUTPUT);
digitalWrite(7,HIGH);//默认继电器是不通的
while(1)
{
printf("请输入0/1,0:断开开关,1:打开开关\n");
scanf("%d",&cmd);
if(cmd == 0)
{
digitalWrite(7,LOW);
}
else if(cmd == 1)
{
digitalWrite(7,HIGH);
}
else
{
printf("输入错误\n");
}
}
}
//树莓派控制继电器组件:
#include
#include
int main()
{
char cmd[8];
if(wiringPiSetup() == -1)
{
printf("硬件接口初始化失败\n");
return -1;
}
pinMode(8,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
digitalWrite(8,HIGH);//默认继电器是不通的
digitalWrite(9,HIGH);
digitalWrite(10,HIGH);
digitalWrite(11,HIGH);
while(1)
{
printf("请输入例如:1 on,1 off\n");
scanf("%s",cmd);
getchar();
if(strcmp(cmd,"1 on") == 0)
{
digitalWrite(8,LOW);
}
else if(strcmp(cmd,"1 off") == 0)
{
digitalWrite(8,HIGH);
}
else if(strcmp(cmd,"2 on") == 0)
{
digitalWrite(9,HIGH);
}
else if(strcmp(cmd,"2 off") == 0)
{
digitalWrite(9,HIGH);
}
else
{
printf("输入错误\n");
}
}
}
//树莓派超声波测距
#include
#include
#include
#define Trig 0
#define Echo 2
void SR04INIT()
{
pinMode(Trig,OUTPUT);
pinMode(Echo,INPUT);
}
float getdis()
{
struct timeval va1;
struct timeval va2;
float Start;
float Stop;
float dis;
digitalWrite(Trig,LOW);
delayMicroseconds(2);//初始化完毕
digitalWrite(Trig,HIGH);
delayMicroseconds(10);
digitalWrite(Trig,LOW);//测距信号发送完毕
while(!(digitalRead(Echo) == 1));//当Echo为1时不卡顿,开始计时
gettimeofday(&va1,NULL);
while(!(digitalRead(Echo) == 0));//当Echo为1时不卡顿,开停止计时
gettimeofday(&va2,NULL);
Start = va1.tv_sec*1000000 + va1.tv_usec;
Stop = va2.tv_sec*1000000 + va2.tv_usec;
dis = (Stop-Start)/1000000 * (34000/2);
return dis;
}
int main()
{
float dis;
if(wiringPiSetup() == -1)
{
printf("硬件接口初始化失败\n");
return -1;
}
SR04INIT();
while(1)
{
dis = getdis();
printf("%f cm\n",dis);
delay(1000);
}
}
串口通信协议:
数据格式(要用串口看得懂的语言)、波特率
树莓派和电脑的串口通信
初次使用树莓派串口编程,需要配置
cd /boot/
sudo vim cmdline.txt
删除【】之间的部分
dwc_otg.lpm_enable=0 【console=ttyAMA0,115200】 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline
树莓派和电脑串口通信时,要加接地线
1、树莓派给电脑串口发送数据
2、电脑给树莓派发送数据
#include
#include
#include
int main()
{
int fd;
wiringPiSetup();
fd = serialOpen ("/dev/ttyAMA0", 9600);
while(1)
{
serialPutchar(fd,'c');
delayMicroseconds(1000000);
}
return 0;
}
//串口接收并且读取消息
while(serialGetchar (fd) != -1)//如果串口有数据发送过来就读取
{
cmd = serialDataAvail (fd);//读到cmd里面
}
交叉编译:
在一个平台上生成另一个平台上的可执行代码
例如:在windows编写的C51代码,生成.hex文件,在51上面执行
在Ubuntu上面编写可执行的a.out可执行文件在树莓派上面运行
宿主机(host) : 编辑和编译程序的平台,一 般是基于X86的PC机通常也被称为主机。
目标机(target) : 用户开发的系统,通常都是非X86平台。host编译得到的可执行代码在target上运行。
交叉编译需要用到工具:交叉编译器
配置环境变量步骤:
1、拷贝到工作目录
2、解压 unzip tools-master.zip:解压交叉编译器的资源包
依次进入:
1、cd tools-master
2、cd arm-bcm2708
3、gcc-linaro-arm-linux-gnueabihf-raspbian-x64//我们要用到工具
echo $PATH//查看环境变量
4、把它配置为环境变量
一、临时有效
1、yx@yx-virtual-machine:~/桌面/raspberryPI/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin$//进入到bin下面
2、export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games://环境变量位置
3、/home/yx/桌面/raspberryPI/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin//PWD查看到当前工作位置
二者拼在一起
二、永久有效
修改工作目录下.bashrc文件(隐藏文件)
1、vi .bashrc(在工作目录下修改)
2、文件最后一行加入:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/yx/桌面/raspberryPI/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
//home后面的是编译链的位置
3、source /home/yx/.bashrc/让这个文件生效
gcc -v 查看gcc版本
一、
vi test.c
gcc test.c -o test_1
file test_1//file查看文件属性的
//这样直接编译出来的test_1只能运行在x86系统中
二、
1、arm-linux-gnueabihf-gcc test.c -I ./ -o test_1
2、file test_1
test_1: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked,interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.26, BuildID[sha1]=e0b49c0b2a1c7e65d0532dc7cc2e5ec40321f7a7, not stripped
这样编译出来的test_1才是运行在ARM系统中的可执行文件
3、把交叉编译后的文件放在树莓派上面运行:scp test_1 pi@192.168.233.235:/home/pi
这样test_1文件就传输过去了。
!!!虚拟机为何要编译wiringPi呢?因为在虚拟机上给树莓派交叉编译无wiringPi库
4、把windows下的wiringPi拿到虚拟机里面,并且./build
然后ls /usr/local/lib
history | grep demo2.c//把有关demo2.c的相关指令全过滤出来
这里的库只适用于X86平台,不适用于树莓派的ARM平台,编译会出错,所以只能把树莓派raspberry库拿出来用
5、进入树莓派下lib
cd /usr/lib
ls -l |grep wiringPi//查看所有的wiring库文件,带->都是软连接(快捷方式),真正的在箭头后面
scp -v libwiringPi.so.2.50 book@192.168.43.133:/home/book/LessonPI//把真正的ARM下的库放到虚拟机底下
这里会运行卡住,原因:ubuntu未安装ssh服务:(或许也因为没有桥接网络)
(1)sudo apt-get install openssh-server
(2)sudo /etc/init.d/ssh start
树莓派创建软连接:(生成一个快捷方式)
ln -s libwiringPi.so.2.50 libwiringPi.so
要链接的文件 软链接名字
树莓派创建硬连接:(在选定位置生成一个文件)
ln libwiringPi.so.2.50 libwiringPi.so
好的方式是在上位机直接装wiringpi,而不是树莓派拿过来
ubuntu设置共享文件夹为开发内核做准备:
设置里面启用共享文件夹
mkdir SYSTEM
cp -r /mnt/hgfs/share/linux-rpi-4.14.y.zip /home/book/SYSTEM //把压缩文件放入虚拟机共享文件下
unzip /linux-rpi-4.14.y.zip
X86 Windows启动过程:
电源->BIOS->windows内核->C、D盘->程序启动
树莓派等产品的启动过程:
电源->Bootloader->linux内核->文件系统(根据功能性组织文件夹,带访问权限)
C51、STM32都是C直接操控底层寄存器
安装tree来帮助查看内核完整性
drivers:设备驱动相关代码
net:网络相关代码
ipc:进程间通信代码
kernel:内核最核心代码
我们只是调用了API让内核帮我们干活,而不是我们自己写的进程代码等。
树莓派Linux源码配置:
驱动代码的编写
驱动代码的编译需要一个提前编译好的内核
编译内核就需要配置
配置的最终目标是生成.config文件,该文件指导makefile把有用的文件组织成内核
第一种方式:
厂家配linux内核源码,比如买了树莓派,厂家会配置树莓派config
第二种方式:
通常 make menuconfig一项项配置,通常基于厂家的config来配置
第三种方式:
完全自己来
配置树莓派内核:
使用树莓派自带的config:
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make bcm2709_defconfig(一定要在解压后的文件夹内进行)
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make menuconfig(进入修改页面)
linux内核的编译:
1、sudo ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j2 zImage modules dtbs
编译生成zImage文件在linux-rpi-4.14.y/arch/arm/boot文件夹下
2、./scripts/mkknlimg arch/arm/boot/zImage ./kernel_new.img打包zImage成树莓派可用的xxx.img
编译成功后看到源码树目录多了一个Vmlinux
把img镜像下载到读卡器,读卡器连接到Ubuntu
dmesg://在虚拟机下面查看连接到设备
3、sudo mount /dev/sdb1 data1把树莓派的sdb1分区挂载到data1里面
sudo mount /dev/sdb2 data2把树莓派的sdb2分区挂载到data2里面
4、安装modules:(设备驱动文件,如wifi,IO口,网卡,HDMI)
sudo ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make INSTALL_MOD_PATH=/home/book/data2 modules_install
data1下面镜像文件为kernel7.img文件,先复制一下kernel7.img否则刷机失败后树莓派无法启动
cp kernel7.img kernel7OLD.img
cp kernel_new.img /home/book/data1/kernel7.img//把之前编译后的新内核拷贝到挂载的data1目录下
du kernel_new.img -h//查看拷贝后的内核大小
5、md5sum kernel_new.img//查看之前的内核MD5的值,
md5sum /home/book/data1/kernel7.img//查看拷贝的内核MD5的值,
因为每个内核都有自己的md5的值,如果拷贝过程中,拷贝出错,MD5的值就会出错
6、复制其他相关文件:
cp arch/arm/boot/dts/.*dtb* /home/book/data1//把.开头中间有dtb的文件全部拷贝过去,.d中间有什么不管
cp arch/arm/boot/dts/overlays/.*dtb* /home/book/data1/overlays/
cp arch/arm/boot/dts/overlays/README /home/book/data1/overlays/
7、USB先与虚拟机断开连接
主机修改boot里面的cmdline文件,在root前面加上console=serial0,115200//目的是去观察串口启动过程
uname -r//串口登录查看树莓派内核版本
那为什么要编译内核呢,因为驱动编写需要一个提前编译好的内核,编译内核必须配置
文件系统
1、什么是文件系统?
常规认知:根目录
文件管理系统(程序)简称文件系统
2、文件系统有哪些?
FAT NTFS EXT 1/2/3/4 VFAT HFS
树莓派查看文件系统命令:df -T
temfs:临时文件系统(基于内存)
3、什么是分区
windows:系统一般装在C盘,也可以装其他文件(目录即分区)
linux:按照功能来分区,每个分区严格存放文件,嵌入式系统可以分为四个分区(这四个分区内存连续):(目录不是分区,与windows不同)
bootloader :启动代码
param:启动代码向内核传递参数的位置
kernel:内核分区
根分区:文件系统
对于windows,C、D盘都是连续的一大块内存,对于linux下文件系统则不一定连续,为何呢,因为
linux下肉眼可见的文件夹可能来自不同的分区;而windows下看见的文件夹一定是同一分区的。
以.开头的是隐藏文件
Linux文件系统目录结构:cd ~就代表进入工作目录(类似电脑D盘)
文件系统认知:
bin:存放一些指令 ls cd等
boot:存放dtb内核文件
lost+found:开机找回的一些东西
sudo passwd root:修改root用户密码,
su - root:进入root目录;rm / -rf:删库跑路,删除所有根目录
什么是虚拟文件系统(VFS在内核中)?
vfs就是对文件系统的一个抽象,它为各种文件系统提供一个通用的接口;是一个内核软件层,在具体的文件系统之上抽象的一层,用来处理与Posix文件系统相关的所有调用,表现为能够给各种文件系统提供一个通用的接口,使上层的应用程序能够使用通用的接口访问不同文件系统
虚拟文件系统有什么用?
(1)简化应用程序员的开发
(2)不管是什么文件类型,不管文件是磁盘还是设备,都统一用read、write、open操作
比如open("test.config")、open("serial")这俩open都是用户空间的,当调用时,
都会先经过vfs系统,vfs决定打开串口还是磁盘。vfs属于内核空间
用户态一般包括:C程序和C库,一般我们编写的read等 先到内核里面sys_read()
C库提供应用程序支配内核干活的接口
C库是linux标准库,WiringPi是树莓派GPIO库不一定有,得自己制作。
内核态:内核可以理解为超级裸机,开发者已经为我们编写好了各种硬件程序,全开放硬件平台,高度自由化。
硬件态:
驱动认知:
为什么我们open()他就知道打开鼠标、磁盘,他怎么知道的,这些设备需不需要驱动?驱动又在哪个位置
那如何找到我们所需要的驱动呢?
(1)文件名
(2)设备号
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。
应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。
主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。
主设备例如HUAWEI手机,次设备例如MATA50 PRO。
驱动链表:管理所有设备的驱动,驱动插入链表的顺序由设备号检索。添加、查找。
驱动开发:编写完驱动程序,加载到内核。先添加驱动,再调用驱动。
例如:当调用open时,用户空间会发生软中断,终端号0x80,由用户空间进入内核空间,调用sys_call()。
sys_call调用sys_open()。然后找到设备号对寄存器进行操作。
基于驱动框架编写驱动
给结构体里面单独元素赋值
static限定该结构体只在当前程序内起作用
struct test
{
.b = 2;
.c = 3;
}
基于驱动框架编写字符设备驱动代码
操作驱动的上层代码:
#include
#include
#include
#include
void main()
{
int fd,data;
fd = open("/dev/pin4",O_RDWR);
if(fd<0){
printf("open fail\n");
perror("reson:");
}
else{
printf("open successful\n");
}
fd=write(fd,'1',1);
}
//假设dev下已经有了一个引脚设备驱动
字符设备驱动框架:(源码下drivers/char)
#include //file_operations声明
#include //module_init module_exit声明
#include //__init __exit 宏定义声明
#include //class devise声明
#include //copy_from_user 的头文件
#include //设备号 dev_t 类型声明
#include //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"; //模块名
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_write\n"); //内核的打印函数和printf类似
return 0;
}
//file_operations结构体类型
//file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,//避免正在操作时被卸载,一般为初始化为THIS_MODULES
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
//static限定这个结构体的作用,仅仅只在这个文件。
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); //创建设备文件
//这两句干的是:创建一个类,类下创建个设备
return 0;
}
void __exit pin4_drv_exit(void)
{
device_destroy(pin4_class,devno);//先销毁设备
class_destroy(pin4_class);//再销毁类
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //入口,内核加载驱动的时候,这个宏会被调用,去调用pin4_drv_init这个函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
char文件夹里面makefile里面加入:
加入下面这句代码
obj-m += pin4driver2.o//以模块化编译
源码目录树下进行,非char下。
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
//指定架构是arm架构
//交叉编译工具是arm-linux-gnueabihf-
//KERNEL=kernel7 内核的版本
//生成modules
把编译好得pin4drivers2.ko传到树莓派
scp drivers/char/pin4drive2.ko [email protected]:/home/pi
交叉编译让驱动运行的文件传给树莓派:
#include
#include
#include
#include
void main()
{
int fd,data;
fd = open("/dev/pin4",O_RDWR);
if(fd<0){
printf("open fail\n");
perror("reson:");
}
else{
printf("open successful\n");
}
fd=write(fd,'1',1);
}
把编译好得pin4test.ko传到树莓派
scp pin4test [email protected]:/home/pi
sudo insmod pin4drive2.ko;//树莓派下加载这个编译好得驱动
lsmod:查看树莓派下驱动
此时 ./dev/pin4还没有启动权限
sudo chomd 666 /dev/pin4 给pin4加权限,所有用户均可读写
pin4_class=class_create(THIS_MODULE,"myfirstdemo");//让代码在dev下自动生成设备
手动生成设备
sudo mknod 设备名 设备类型 主设备号 次设备号
sudo mknod feng c 8 1
//c是字符型设备类型
编写驱动步骤,在源码树下得相关驱动目录编写驱动程序,编译的话在相关目录下修改Makefile,
回到树目录下ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
编译生成.ko文件,交叉编译测试驱动的文件,把.ko 以及交叉编译后的文件都传给树莓派。
1.总线地址
地址总线(Address Bus ;又称:位址总线)属于种电脑总线(- 部份),是由CPU或有DMA能力的单元,用来沟通这些单元想要存取 (读取/写入)电脑内存元件/地方的实体位址。
cpu能够访问内存的范围
现象:装了32位的win7系统 明明内存条8G,可是系统只识别了3.8G , 装了64位,才能识别到8g32位能表示/访问4, 294,967,296bit
2.物理地址
硬件实际地址或绝对地址程序在磁盘超过1g,1g
3.虚拟地址
逻辑(基于算法的地址(软件层面的地址,假) )地址称为虚拟地址
unix设计与实现,内核的设计方法、思想
虚拟地址通过页表(MMU单元)映射成物理地址,32无MMU单元
驱动:电路图和芯片手册
DMA:快速拷贝单元
#include //file_operations声明
#include //module_init module_exit声明
#include //__init __exit 宏定义声明
#include //class devise声明
#include //copy_from_user 的头文件
#include //设备号 dev_t 类型声明
#include //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"; //模块名
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)//目的:配置pin4为输出引脚
{
printk("pin4_open\n"); //内核的打印函数和printf类似
*GPFSEL0 &=~(0x6<<12);//把bit14,bit13配置成0
*GPFSEL0 |=(0x1<<12);//把bit12置成1
//配置pin4引脚为输出引脚,bit 14~12配置成001
return 0;
}
//read函数
static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos)
{
printk("pin4_read\n"); //内核的打印函数和printf类似
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int usercmd;
printk("pin4_write\n"); //内核的打印函数和printf类似
copy_from_user(&usercmd,buf,count);//获取上层write函数的值
//根据值来操作io口,高电平或者低电平
if (usercmd==1)
{
printk("set 1\n");
*GPSET0 |=(0x1 <<4);
}
else if(usercmd==0){
printk("set 0\n");
* GPCLR0 |=(0x1<<4);
}
else{
printk("do nothing\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
//static限定这个结构体的作用,仅仅只在这个文件。
int __init pin4_drv_init(void) //真实的驱动入口
{
int ret;
printk("module success\n");
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);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
//这三行是设置寄存器的地址,volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.ioremap函数将物理地址转换为虚拟地址,IO口寄存器映射成普通内存单元进行访问。ioremap函数第一个参数输物理地址,第二个参数是
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); //入口,内核加载驱动的时候,这个宏会被调用,去调用pin4_drv_init这个函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
sudo ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
#include
#include
#include
#include
void main()
{
int fd,data;
fd = open("/dev/pin4",O_RDWR);
printf("plese input 1 or 0 //1:high 0:low\n ");
scanf("%d",&data);
if(fd<0){
printf("open fail\n");
perror("reson:");
}
else{
printf("open successful\n");
}
fd=write(fd,&data,1);//命令传给底层
}
arm-linux-gnueabihf-gcc pint4test.c -o pin4test
sudo chmod 666 /home/pi
sudo insmod pin4drive2.ko;//树莓派下加载这个编译好得驱动(加载完了dev下才有pin4驱动)
sudo chomd 666 /dev/pin4
./pin4test