Lab 7:字符设备驱动程序
l 这个实验的目的是掌握在嵌入式Linux中如何使用已有的函数库编写应用程序操纵GPIO,如何编写字符设备驱动程 序在内核程序中使用GPIO,并学习用面包板搭简单的外部设备电路的方法。
l 实验要求在Linux板卡上连接两个级联的74HC595,由595的输出端控制一个8x8的LED矩阵。通过两个GPIO引脚 分别控制595的时钟和数据输入端,可以实现对LED矩阵的控制,在矩阵上显示文字和图案。
1. 学习嵌入式Linux的GPIO的使用方式;
2. 学习嵌入式Linux的Arduino接口库;
3. 学习使用面包板搭简单的外部电路;
4. 学习Linux设备驱动程序的开发过程。
5. 学习在内核中访问外设寄存器,操纵外设的方法。
· Linux实验板卡一块;
· 5V/1A电源一个;
· microUSB线一根;
· 面包板一块;
· MAX7219驱动的8x8 LED矩阵一个;
· 面包线若干。
以下为自备(可选)器材:
· PC(Windows/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,表示允许写入。而后从高位顺序写入16个bit。每个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写入数据 |
//入口参数:address、dat |
//出口参数:无 |
//说明: |
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中输入数字,就可以看到结果了:
可以看到试验成功。