树莓派与Ubuntu

一、分文件编程案例:

好处:分模块编程思想
	网络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 //查看树莓派的端口
左右对称

树莓派与Ubuntu_第1张图片

知识点:
继电器主要使非可编程器件变得智能
用树莓派端口看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盘)
树莓派与Ubuntu_第2张图片

文件系统认知:
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属于内核空间

内核结构框图:
树莓派与Ubuntu_第3张图片

用户态一般包括: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

你可能感兴趣的:(Linux,ubuntu,linux,运维)