Lab 7:字符设备驱动程序

l  这个实验的目的是掌握在嵌入式Linux中如何使用已有的函数库编写应用程序操纵GPIO,如何编写字符设备驱动程 序在内核程序中使用GPIO,并学习用面包板搭简单的外部设备电路的方法。

l  实验要求在Linux板卡上连接两个级联的74HC595,由595的输出端控制一个8x8LED矩阵。通过两个GPIO引脚 分别控制595的时钟和数据输入端,可以实现对LED矩阵的控制,在矩阵上显示文字和图案。

实验目的

1.       学习嵌入式LinuxGPIO的使用方式;

2.       学习嵌入式LinuxArduino接口库;

3.       学习使用面包板搭简单的外部电路;

4.       学习Linux设备驱动程序的开发过程。

5.       学习在内核中访问外设寄存器,操纵外设的方法。

实验器材

硬件

·         Linux实验板卡一块;

·         5V/1A电源一个;

·         microUSB线一根;

·         面包板一块;

·         MAX7219驱动的8x8 LED矩阵一个;

·         面包线若干。

以下为自备(可选)器材:

·         PCWindows/Mac OS/Linux)一台;

·         USB-TTL串口线一根(FT232RL芯片或PL2303芯片);

·         以太网线一根(可能还需要路由器等)。

软件

·         编译软件;

·         Fritzing

实验步骤

1.       设计方案,画连线示意图。

2.       在面包板上连线,完成外部电路。

本次实验使用wiringPi库函数,下载链接为https://github.com/WiringPi/WiringPi设计树莓派与LED连接。接线方案如下:

树莓派

LED

VCC

VCC

GND

GND

GPIO0

DIN

GPIO2

CS

GPIO3

CLK

具体连线拍照效果可见后文。

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

使用wiringPi库函数来控制树莓派的GPIO。下载到本地需要./build编译后就可以使用,编程中引入头文件即可。当然编译的时候,需要加上“–lwiringPi”链接入库。

整个写入流程是首先需要把CS引脚置0,表示允许写入。而后从高位顺序写入16bit。每个bit的写入方式为首先DIN置为要写入的bit值,而后CLK产生一个下降沿(图中为上升沿,不知道为何有差别)即被读入。最后CS引脚置1表示写入结束。而除了数据传输流程之外,接下来是如何在点阵上显示图案了。在运行之前,需要进行一次初始化,其行为是向某几个特定的地址写入特定的值。至少需要写入两个地址,第一个是0x0b,写入0x07表示扫描显示所有行。第二个是0x0c,写入1表示进入工作模式。而后点阵上每一行都有其地址,如第一行是0x01到第八行是0x08,每次向固定行的地址写入一个8位二进制数即可在指定行上显示图案。

代码如下:

#include <wiringPi.h>

#define uchar unsigned char

#define uint  unsigned int

 

int CLK = 0;

int CS = 2;

int DIN = 3;

uchar disp1[38][8]={

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

{0x10,0x18,0x14,0x10,0x10,0x10,0x10,0x10},//1

{0x7E,0x2,0x2,0x7E,0x40,0x40,0x40,0x7E},//2

{0x3E,0x2,0x2,0x3E,0x2,0x2,0x3E,0x0},//3

{0x8,0x18,0x28,0x48,0xFE,0x8,0x8,0x8},//4

{0x3C,0x20,0x20,0x3C,0x4,0x4,0x3C,0x0},//5

{0x3C,0x20,0x20,0x3C,0x24,0x24,0x3C,0x0},//6

{0x3E,0x22,0x4,0x8,0x8,0x8,0x8,0x8},//7

{0x0,0x3E,0x22,0x22,0x3E,0x22,0x22,0x3E},//8

{0x3E,0x22,0x22,0x3E,0x2,0x2,0x2,0x3E},//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

{0x8,0x7F,0x49,0x49,0x7F,0x8,0x8,0x8},//

{0xFE,0xBA,0x92,0xBA,0x92,0x9A,0xBA,0xFE},//

};

 

//--------------------------------------------

//功能:向MAX7219(U3)写入字节

//入口参数:DATA

//出口参数:无

//说明:

void Write_Max7219_byte(uchar DATA)        

{

  uchar i;   

  digitalWrite(CS,LOW); 

  for(i = 8; i >= 1; i--)

  {    

    digitalWrite(CLK, LOW);

    //Max7219_pinDIN = DATA & 0x80;

    if(DATA & 0x80)

      digitalWrite(DIN,HIGH);

    else

      digitalWrite(DIN,LOW);

    DATA = DATA << 1;

    digitalWrite(CLK, HIGH);

  }                                

}

//-------------------------------------------

//功能:向MAX7219写入数据

//入口参数:addressdat

//出口参数:无

//说明:

void Write_Max7219(uchar address,uchar dat)

{

  digitalWrite(CS, LOW);

  Write_Max7219_byte(address);           //写入地址,即数码管编号

  Write_Max7219_byte(dat);               //写入数据,即数码管显示数字

  digitalWrite(CS, HIGH);

}

 

void Init_MAX7219(void)

{

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

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

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

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

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

}

int mian()

{

       wiringPiSetup(); //wiringPi初始化

    pinMode(CLK, OUTPUT);

    pinMode(CS, OUTPUT);

    pinMode(DIN, OUTPUT);

    uchar i, j;

    delay(50);

    Init_MAX7219();

    while (1) {

      for(j=0;j<38;j++)

      {

        for(i=1;i<9;i++)

          Write_Max7219(i, disp1[j][i-1]);

        delay(1000);

      } 

    }

}

之后使用gcc test.cpp –lwiringPi命令编译即可。运行文件时需要root,否则会运行失败。结果图:

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

和上一个实验一样需要首先编写内核模块。之后编译内核装载即可。装载之后可以在dev目录下看到新的模块。这里有个坑就是,内核中的GPIO脚编号和之前实验中的GPIO脚编号不一致。这里需要修改一下。写入的时候出发模块中的matrix_write函数,这和之前操作系统中文件操作的方法类似。代码如下:

#include <linux/init.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/fs.h>

#include <linux/miscdevice.h>

#include <linux/string.h>

#include <linux/delay.h>

#include <linux/gpio.h>

#include <linux/workqueue.h>

MODULE_LICENSE("GPL");

#define DIN 17 //原来的0

#define CS 27 //原来的GPIO.2

#define CLK 55 //原来的GPIO.3

#define BUFFERSIZE 128

#define DELAYTIME 1

unsigned char disp[BUFFERSIZE];

int head = 0, tail = 0;

static struct timer_list timer;

 

void write_byte(unsigned char b){

    unsigned char i, tmp;

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

        tmp = (b & 0x80) > 0;

        b <<= 1;

        gpio_set_value(DIN, tmp);

        gpio_set_value(CLK, 1);

        gpio_set_value(CLK, 0);

    }

}

 

void write_word(unsigned char addr, unsigned char num){

    gpio_set_value(CLK, 0);

    gpio_set_value(CS, 1);

    gpio_set_value(CS, 0);

    write_byte(addr);

    write_byte(num);

    gpio_set_value(CS, 1);

}

 

void Matrix_render(unsigned char* tmp){

    int i;

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

        write_word(i+1, tmp[i]);

    }

}

 

unsigned char digits[][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

};

 

unsigned char LM[]={

    0x80, 0x80, 0x80, 0x91, 0x9b, 0xf5, 0x11, 0x11

};

 

void Matrix_clear(void){

    Matrix_render(LM);

}

 

void Matrix_init(void){

    write_word(0x09, 0x00);

    write_word(0x0a, 0x03);

    write_word(0x0b, 0x07);

    write_word(0x0c, 0x01);

    Matrix_clear();

}

 

void Matrix_next_display(unsigned long);

 

void ptr_inc(int *ptr){

    *ptr = (*ptr + 1) % BUFFERSIZE;

}

 

static void timer_register(struct timer_list* ptimer){

    init_timer(ptimer);

    ptimer->data = DELAYTIME;

    ptimer->expires = jiffies + (DELAYTIME * HZ);

    ptimer->function = Matrix_next_display;

    add_timer(ptimer);

}

 

void disp_start(void){

    timer_register(&timer);

}

 

void Matrix_next_display(unsigned long data){

    if (head != tail){

        unsigned char *ptr = LM;

        unsigned char c = disp[head];

        if ('0' <= c && c <= '9'){

            ptr = digits[c - '0'];

        }

        Matrix_render(ptr);

        ptr_inc(&head);

        disp_start();

    }else{

        Matrix_clear();

    }

}

 

static int matrix_write(struct file *file, const char __user *buffer,

        size_t count, loff_t *ppos){

    int i;

    if (head == tail && count > 0){

        disp_start();

    }

    for (i=0; i<count; i++){

        ptr_inc(&tail);

        if (tail == head)

            ptr_inc(&head);

        disp[tail] = buffer[i];

    }

    return count;

}

 

static struct file_operations matrix_fops = {

    .owner = THIS_MODULE,

    .write = matrix_write,

    .llseek = noop_llseek

};

 

static struct miscdevice matrix_misc_device = {

    .minor = MISC_DYNAMIC_MINOR,

    .name = "matrix",

    .fops = &matrix_fops

};

 

static int __init matrix_init(void){

 

    misc_register(&matrix_misc_device);

    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);

    Matrix_init(); 

    return 0;

}

 

static void __exit matrix_exit(void){

    misc_deregister(&matrix_misc_device);

    del_timer(&timer);

}

 

module_init(matrix_init);

module_exit(matrix_exit);

之后make编译内核,makefile和上个实验一致。生成内核模块,就可以安装内核了。

/dev/matrix中输入数字,就可以看到结果了:

可以看到试验成功。