热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Lab7:字符设备驱动程序

一、连接和器材器材列表:RaspberryPi(树莓派)一块、USB-TTL串口线一根(PL2303芯片)、以太网线一根、8G容量SD卡一张、带windows7操作系统的PC一台、MAX7219驱动的


一、连接和器材

器材列表:

RaspberryPi(树莓派)一块、USB-TTL串口线一根(PL2303芯片)、以太网线一根、8G容量SD卡一张、带windows7操作系统的PC一台、MAX7219驱动的8x8 LED矩阵一个。

 

MAX7219的VCC, GND, DIN, CS, CLK分别与树莓派左边一列的第1,第5,第4,第3,第2个引脚相连。(1,3,5,7,9)

使用Fritzing画出的外部连接示意图:


实物连接图:


二、实验步骤

a.编写C/C++程序,采用库或虚拟文件系统访问GPIO,实现在矩阵上显示文字或图案;

首先到官网下载最新的datasheet,

找到timingdiagram部分如下:


当要发送数据时,首先把CS拉低,随后每一次CLK上升沿到来时,DIN上的位就会被读入,这样总共进行16次(数据的高位会先读入)。此后必须把CS拉高,否则之前读入的数据会丢失。

读入的数据的前8位是地址,后8为才是真正的数据,格式如下所示:

 

在点阵上显示字符之前,需要进行初始化,包括选择译码方式,亮度,扫描界限,掉电模式以及显示测试。

 

 

我所使用的树莓派上的GPIO库是WiringPi,使用git clone命令下载如下:


下载完成后使用

cd wiringPi

./build

完成编译和安装。

 

随后写好如下的gpio.c程序:

#include

#include

//set gpio pin number,

//使用WiringPi Pin的编号

int DIN=7;

int CS=9;

int CLK=8;

 

unsigned char mydisp[36][8]={

     {0x1c,0x22,0x22,0x22,0x22,0x22,0x22,0x1c},//0

     {0x08,0x18,0x08,0x08,0x08,0x08,0x08,0x1c},//1

     {0x1c,0x22,0x22,0x04,0x08,0x10,0x20,0x3e},//2

     {0x1c,0x22,0x02,0x0c,0x02,0x02,0x22,0x1c},//3

     {0x04,0x0c,0x14,0x14,0x24,0x1e,0x04,0x04},//4

     {0x3e,0x20,0x20,0x3c,0x02,0x02,0x22,0x1c},//5

     {0x1c,0x22,0x20,0x3c,0x22,0x22,0x22,0x1c},//6

     {0x3e,0x24,0x04,0x08,0x08,0x08,0x08,0x08},//7

     {0x1c,0x22,0x22,0x1c,0x22,0x22,0x22,0x1c},//8

     {0x1c,0x22,0x22,0x22,0x1e,0x02,0x22,0x1c},//9

         {0x8,0x14,0x22,0x3E,0x22,0x22,0x22,0x22},//A

         {0x3C,0x22,0x22,0x3E,0x22,0x22,0x3C,0x0},//B

         {0x3C,0x40,0x40,0x40,0x40,0x40,0x3C,0x0},//C

         {0x7C,0x42,0x42,0x42,0x42,0x42,0x7C,0x0},//D

         {0x7C,0x40,0x40,0x7C,0x40,0x40,0x40,0x7C},//E

         {0x7C,0x40,0x40,0x7C,0x40,0x40,0x40,0x40},//F

         {0x3C,0x40,0x40,0x40,0x40,0x44,0x44,0x3C},//G

         {0x44,0x44,0x44,0x7C,0x44,0x44,0x44,0x44},//H

         {0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x7C},//I

         {0x3C,0x8,0x8,0x8,0x8,0x8,0x48,0x30},//J

         {0x0,0x24,0x28,0x30,0x20,0x30,0x28,0x24},//K

         {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7C},//L

         {0x81,0xC3,0xA5,0x99,0x81,0x81,0x81,0x81},//M

         {0x0,0x42,0x62,0x52,0x4A,0x46,0x42,0x0},//N

         {0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x3C},//O

         {0x3C,0x22,0x22,0x22,0x3C,0x20,0x20,0x20},//P

         {0x1C,0x22,0x22,0x22,0x22,0x26,0x22,0x1D},//Q

         {0x3C,0x22,0x22,0x22,0x3C,0x24,0x22,0x21},//R

         {0x0,0x1E,0x20,0x20,0x3E,0x2,0x2,0x3C},//S

         {0x0,0x3E,0x8,0x8,0x8,0x8,0x8,0x8},//T

         {0x42,0x42,0x42,0x42,0x42,0x42,0x22,0x1C},//U

         {0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18},//V

         {0x0,0x49,0x49,0x49,0x49,0x2A,0x1C,0x0},//W

         {0x0,0x41,0x22,0x14,0x8,0x14,0x22,0x41},//X

         {0x41,0x22,0x14,0x8,0x8,0x8,0x8,0x8},//Y

         {0x0,0x7F,0x2,0x4,0x8,0x10,0x20,0x7F},//Z

};

 

//写一个字节

void MyWriteByte(unsigned char mydata){

     unsigned char i;

     unsigned char mybit;

     for(i=8;i>=1;i--){ //每次写一位

                   digitalWrite(CLK,LOW); //上升沿写,故先拉低CLK

                   mybit=mydata&0x80; //先写高位

                   digitalWrite(DIN,mybit); //放上数据位

                   mydata=mydata<<1;

                   digitalWrite(CLK,HIGH);

     }

}

 

//向特定地址写入一个字节的数据

void MyWrite(unsigned char address, unsigned char data){

         digitalWrite(CS,LOW); //拉低CS

         MyWriteByte(address); //先写地址

         MyWriteByte(data);   //再写数据

         digitalWrite(CS,HIGH);

}

 

//初始化

void MyInit(){

         MyWrite(0x09,0x00); //译码方式:BCD码

         MyWrite(0x0a,0x03); //亮度

         MyWrite(0x0b,0x07); //扫描界限;8个数码管显示

         MyWrite(0x0c,0x01); //掉电模式:0,普通模式:1

         MyWrite(0x0f,0x00); //显示测试:1;测试结束,正常显示:0

}

 

//用于送出8*8点阵

void  MySend(unsigned char * mydata){

         int i;

         for(i=1;i<=8;i++){

                   //printf("sending %d, %d\n",i,mydata[i-1]);

                   MyWrite(i,mydata[i-1]);

         }

}

 

int main(void)

{

         wiringPiSetup();

    pinMode(DIN,OUTPUT);

        pinMode(CS,OUTPUT);

    pinMode(CLK,OUTPUT);

         MyInit();

         int i=0;

        

         //循环输出0-9,A-Z

    while(1){

               MySend(mydisp[i]);

               delay(500); 

               i=(i+1)%36;

         }

         return 0;  

 

 

然后编译:

 

运行:


随后看到8*8的点阵上不断循环显示0~9,A~Z。比如显示3时的照片如下:

 

 

b.编写字符设备驱动程序,直接访问GPIO控制寄存器,能将write()送来的单个字符在矩阵上显示出来。

使用Linux内核的linux/gpio.h提供的GPIO接口,主要使用一个混杂设备,提供一个write接口。并且把用户write进来的一个字符显示到8*8矩阵上。

 

编写的driver.c文件内容如下:

#include

#include

#include

#include

#include

#include

#include

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("mymatrix");

MODULE_DESCRIPTION("lab7");

 

//set gpio pin number

static int DIN=4;

static int CS=3;

static int CLK=2;

 

unsigned char mydisp[36][8]={

     {0x1c,0x22,0x22,0x22,0x22,0x22,0x22,0x1c},//0

     {0x08,0x18,0x08,0x08,0x08,0x08,0x08,0x1c},//1

     {0x1c,0x22,0x22,0x04,0x08,0x10,0x20,0x3e},//2

     {0x1c,0x22,0x02,0x0c,0x02,0x02,0x22,0x1c},//3

     {0x04,0x0c,0x14,0x14,0x24,0x1e,0x04,0x04},//4

     {0x3e,0x20,0x20,0x3c,0x02,0x02,0x22,0x1c},//5

     {0x1c,0x22,0x20,0x3c,0x22,0x22,0x22,0x1c},//6

     {0x3e,0x24,0x04,0x08,0x08,0x08,0x08,0x08},//7

     {0x1c,0x22,0x22,0x1c,0x22,0x22,0x22,0x1c},//8

     {0x1c,0x22,0x22,0x22,0x1e,0x02,0x22,0x1c},//9

         {0x8,0x14,0x22,0x3E,0x22,0x22,0x22,0x22},//A

         {0x3C,0x22,0x22,0x3E,0x22,0x22,0x3C,0x0},//B

         {0x3C,0x40,0x40,0x40,0x40,0x40,0x3C,0x0},//C

         {0x7C,0x42,0x42,0x42,0x42,0x42,0x7C,0x0},//D

         {0x7C,0x40,0x40,0x7C,0x40,0x40,0x40,0x7C},//E

         {0x7C,0x40,0x40,0x7C,0x40,0x40,0x40,0x40},//F

         {0x3C,0x40,0x40,0x40,0x40,0x44,0x44,0x3C},//G

         {0x44,0x44,0x44,0x7C,0x44,0x44,0x44,0x44},//H

         {0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x7C},//I

         {0x3C,0x8,0x8,0x8,0x8,0x8,0x48,0x30},//J

         {0x0,0x24,0x28,0x30,0x20,0x30,0x28,0x24},//K

         {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7C},//L

         {0x81,0xC3,0xA5,0x99,0x81,0x81,0x81,0x81},//M

         {0x0,0x42,0x62,0x52,0x4A,0x46,0x42,0x0},//N

         {0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x3C},//O

         {0x3C,0x22,0x22,0x22,0x3C,0x20,0x20,0x20},//P

         {0x1C,0x22,0x22,0x22,0x22,0x26,0x22,0x1D},//Q

         {0x3C,0x22,0x22,0x22,0x3C,0x24,0x22,0x21},//R

         {0x0,0x1E,0x20,0x20,0x3E,0x2,0x2,0x3C},//S

         {0x0,0x3E,0x8,0x8,0x8,0x8,0x8,0x8},//T

         {0x42,0x42,0x42,0x42,0x42,0x42,0x22,0x1C},//U

         {0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18},//V

         {0x0,0x49,0x49,0x49,0x49,0x2A,0x1C,0x0},//W

         {0x0,0x41,0x22,0x14,0x8,0x14,0x22,0x41},//X

         {0x41,0x22,0x14,0x8,0x8,0x8,0x8,0x8},//Y

         {0x0,0x7F,0x2,0x4,0x8,0x10,0x20,0x7F},//Z

};

 

//写一个字节

void MyWriteByte(unsigned char mydata){

     unsigned char i;

     unsigned char mybit;

     for(i=8;i>=1;i--){ //每次写一位

                   gpio_set_value(CLK,0);   //上升沿写,故先拉低CLK

                   mybit=mydata&0x80; //先写高位

                   gpio_set_value(DIN,mybit);    //放上数据位

                   mydata=mydata<<1;

                   gpio_set_value(CLK,1);  

     }

}

 

//向特定地址写入一个字节的数据

void MyWrite(unsigned char address, unsigned char data){

         gpio_set_value(CS,0);      //拉低CS

         MyWriteByte(address);   //先写地址

         MyWriteByte(data);      //再写数据

         gpio_set_value(CS,1);

}

 

//初始化

void MyInit(void){

         MyWrite(0x09,0x00);  //译码方式:BCD码

         MyWrite(0x0a,0x03);  //亮度

         MyWrite(0x0b,0x07);  //扫描界限;8个数码管显示

         MyWrite(0x0c,0x01);  //掉电模式:0,普通模式:1

         MyWrite(0x0f,0x00);  //显示测试:1;测试结束,正常显示:0

}

 

//送出输入的一个字符

void MySend(unsigned char * mydata){

         int i;

    int which;

         if(*mydata>='0'&&*mydata<='9') //数字

                   which=*mydata-'0';

         else if(*mydata>='A' && *mydata<='Z') //字母

                   which=*mydata-'A'+10;

         else which=0;

         for(i=1;i<=8;i++) //每次送出点阵的一行

         {

                   MyWrite(i,mydisp[which][i-1]);

         }

}

 

//字符设备提供给应用程序的write接口,将用户传入的一个字符显示到8*8点阵上

static int mywrite_char(struct file *file, const char __user *mychar,

    size_t count, loff_t *ppos){

         MySend(mychar);

     return count;

}

 

static struct file_operations  mywrite = {

         .owner = THIS_MODULE,

         .write = mywrite_char,

         .llseek = noop_llseek //do nothing

};

 

//使用混杂设备

static struct miscdevice mymatrix = {

    .minor = MISC_DYNAMIC_MINOR, //number

    .name = "mymatrix",  //name

    .fops = &mywrite //file operation

};

 

//模块被载入时

static __init int mydriver_start(void){

         char a='0'; //默认显示

 

         //检测GPIO号是否合法

         if (gpio_is_valid(DIN)==0|| gpio_is_valid(CLK)==0|| gpio_is_valid(CS)==0){

                 printk( "Invalid pin number.\n");

                 return -ENODEV;

         }

        

         //注册混杂设备

         misc_register(&mymatrix);

        

         gpio_request(DIN, "sysfs");

    gpio_direction_output(DIN, 0);

    gpio_request(CS,"sysfs");

     gpio_direction_output(CS,  1);

     gpio_request(CLK,"sysfs");

    gpio_direction_output(CLK, 0);

  

   //初始化

         MyInit();

         MySend(&a);

 

         printk("My driver started.\n");

         return 0;

}

 

//模块被卸载时

static __exit void  mydriver_end(void){

        misc_deregister(&mymatrix);

         printk("<0>My driver ended.\n");

}

 

module_init(mydriver_start);

module_exit(mydriver_end);

 

Makefile文件内容如下:

TARGET = driver

KVER ?= $(shell uname -r)

KDIR = /home/xuanzhuanecy/modules/lib/modules/4.4.11+/build/

PWD = $(shell pwd)

obj-m += $(TARGET).o

default:

         make -C $(KDIR) M=$(PWD) modules

 

在ubuntu下编译:

 

随后scp生成的driver.ko到树莓派下。

 

安装模块:

 

此后看到了8*8矩阵上显示初始化数字0:

 

 

然后我们进入树莓派的/dev,输入如下命令;


最后使用sudo echo Z > mymatrix 向字符设备写入单个字符:

 

可以看到8*8的矩阵显示了字母Z:

 

 

还可以直接编写C程序调用字符设备mymatrix的write接口,

如下程序以1s为周期,循环显示0~9,A~Z

/*File name: lab7_test.c*/

#include

#include  

#include  

#include  

#include  

#include  

#include  

#include

 

int main(){

         int fd;

         int write_try;

         fd=open("/dev/mymatrix",O_RDWR); //打开字符设备

         if(fd == 1){

                   printf("Open failed.\n");

                   exit(-1);

         }

         int index=0;

         char dis_number[36]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //循环显示的字符队列

         while(1){

                   write_try=write(fd,&dis_number[index],1); //写字符设备

                   if(write_try == -1){

                            printf("write failed.\n");

                            exit(-1);

                   }

                   index=(index+1)%36;

                   sleep(1);

         }

        

         return 0;

 

}

 

在树莓派下编译执行:

 

随后看到矩阵以1s为间隔,循环显示0~9,A~Z。


推荐阅读
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了最长上升子序列问题的一个变种解法,通过记录拐点的位置,将问题拆分为左右两个LIS问题。详细讲解了算法的实现过程,并给出了相应的代码。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
author-avatar
虚幻星际
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有