热门标签 | 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。


推荐阅读
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 加密世界下一个主流叙事领域:L2、跨链桥、GameFi等
    本文介绍了加密世界下一个主流叙事的七个潜力领域,包括L2、跨链桥、GameFi等。L2作为以太坊的二层解决方案,在过去一年取得了巨大成功,跨链桥和互操作性是多链Web3中最重要的因素。去中心化的数据存储领域也具有巨大潜力,未来云存储市场有望达到1500亿美元。DAO和社交代币将成为购买和控制现实世界资产的重要方式,而GameFi作为数字资产在高收入游戏中的应用有望推动数字资产走向主流。衍生品市场也在不断发展壮大。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
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社区 版权所有