一、连接和器材
器材列表:
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。