banner

Friday, February 17, 2012

LINUX DEVICE DRIVER FOR A 16x2 LCD MODULE CONNECTED AT PARALLEL PORT

This is a small parallel port char driver for printing text on a 16x2 lcd module connected at parallel port of a PC. I did this as a part of learning linux kernel-module programming. May be this could be considered as a hello world device driver.

INTRODUCTION:

 As we know, everything in Linux is treated as a file, even hardware devices like serial ports, hard disks, and scanners. In order to access these devices, a special file called a device node has to be present. All device nodes are stored in the /dev directory. Here, my 16x2 lcd connected to parallel port is also treated as a file and is accessed via a device node....
    Now, after connecting the 16x2 lcd to parallel port (as in my circuit diagram), and then inserting the driver module, a message "DRIVER INSERTED" is displayed on the 16x2 LCD. Later, if we make a character special file (node) with major number as 61, and if we write any string to it (echo HELLO > file), then it will be displayed on the 16x2 LCD. Now, if we write a long string to it, then it is displayed on the lcd by scrolling it from bottom to top until the string is displayed completely ...





CIRCUIT DIAGRAM:
(i forgot to draw a connection between the ground of lcd and parallel port, don't miss it if any one is trying to do this. pin 25 to 18 is the ground, can select any one or all together)

DRIVER CODE - lcd.c : ('tested ok' in linux kernel 3.0):
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/fcntl.h>
#include <linux/ioport.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/delay.h>

MODULE_LICENSE("Dual BSD/GPL");

#define LCD(x) outb((c = ((0x0f & x) | (c & 0xf0) )), 0x378)
#define RS(x) outb((c = (((~(1 << 4)) & c) | (x << 4))), 0x378)
#define EN(x) outb((c = (((~(1 << 5)) & c) | (x << 5))), 0x378)
#define en 0b00010000
#define rs 0b00100000
#define OUT(x) outb(x, 0x378)
#define MAXSIZE 17

static char lcd_buffer1[17];
static unsigned char c;
static char d_buf[MAXSIZE];
static int port;
static char lcd_space = ' ';
static int major = 61;

static int pport_open(struct inode *inode, struct file *filp);
static int pport_close(struct inode *inode, struct file *filp);
static ssize_t pport_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
static ssize_t pport_write(struct file *filp, char *buf, size_t count, loff_t *f_pos);
static void lcd_strobe(void);
static void data(unsigned char);
static void cmd(unsigned char);
static void clear(void);
static void lcd_init(void);
static void printlcd(char *);

static struct file_operations fops = {
    open: pport_open,
    read: pport_read,
    write: pport_write,
    release: pport_close
};

static int pport_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int pport_close(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t pport_write(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
    if(count < MAXSIZE) {
        copy_from_user(d_buf, buf, count);
        d_buf[count] = 0;
        printlcd(d_buf);
        *f_pos += count;
        return count;
        } else {
        copy_from_user(d_buf, buf, MAXSIZE - 1);
        d_buf[MAXSIZE - 1] = 0;
        printlcd(d_buf);
        *f_pos += MAXSIZE - 1;
        return MAXSIZE - 1;
    }
}

static ssize_t pport_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
    return 0;
}


int init_module(void)
{
    int a;
    a = register_chrdev(major, "registered_pport_61", &fops);
    if(a < 0) {
        printk(KERN_ALERT "error: can't register major number %d\n", major);
        return a;
    }
    port = check_region(0x378, 1);
    if(port) printk(KERN_ALERT "pport cannot reserve 0x378\n");
    request_region(0x378, 1, "registered_pport_61");
    //lcd setup//
    outb(0, 0x378);
    udelay(10000);
    lcd_init();
    udelay(10000);
    printlcd("DRIVER INSERTED ");
    return 0;
}

void cleanup_module(void)
{
    printk(KERN_ALERT "pport module is going to terminate\n");
    printlcd("DRIVER REMOVED  ");
    
    unregister_chrdev(major, "registered_pport_61");
    clear();
}

static void lcd_strobe(void)
{
    EN(1);
    udelay(1);
    EN(0);
    udelay(1);
}

static void data(unsigned char data)
{
    RS(1);
    udelay(40);
    LCD(data >> 4);
    lcd_strobe();
    LCD(data);
    lcd_strobe();
    udelay(10);
    RS(0);
    udelay(10);
}

static void cmd(unsigned char command)
{
    RS(0);
    udelay(40);
    LCD(command >> 4);
    lcd_strobe();
    LCD(command);
    lcd_strobe();
}

static void clear(void)
{
    cmd(1);
    udelay(2000);
}

static void lcd_init(void)
{
    cmd(0x30);
    cmd(0x30);
    cmd(0x28);
    cmd(0x0c);
    clear();
    cmd(0x6);
}

static void printlcd(char *p)
{
    static int count = 0;
    count = 0;
    clear();
    cmd(0x80);
    while(lcd_buffer1[count])
    data(lcd_buffer1[count++]);
    count = 0;
    cmd(0xc0);
    while(*p) {
        if((*p != '\n') && (*p != '\t')) {
            lcd_buffer1[count++] = *p;
            data(*p);
            } else {
            data(lcd_space);
            lcd_buffer1[count++] = lcd_space;
        }
        p++;
    }
    lcd_buffer1[16] = 0;
    msleep(2000);
}


Makefile:
obj-m += lcd.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
PROCEDURE:

1> save lcd.c to a new folder
2>create Makefile in the same folder
3>type "make" to build the driver module led.ko
4>Setup the 16x2 lcd as in circuit diagram and power on the 5v supply to lcd.
5>Insert the module using "sudo insmod lcd.ko"
5>To see the kernel message, type dmesg or use tty3 or 4 to see it directly on screen
6>Now if every thing is all right, then we could see a text "DRIVER INSERTED" on the second row of 16x2 lcd.
7>Now create a node any where, with major number 61.
 eg: "sudo mknod /dev/my_lcd c 61 0"
TESTING:
  • Type "echo HELLO WORLD > /dev/my_lcd" . This should print HELLO WORLD on the lcd.
  • type "date > /dev/my_lcd" and observe  the date on lcd.
  • Type "echo THIS IS A LARGE STRING CONTAINING MORE THAN 32 CHARACTERS. I WANT TO SEE HOW IT IS DISPLAYED ON THE LCD > /dev/my_lcd" and see the long string on the lcd.
  • Type "ls > /dev/my_lcd" as root and observe contents on the folder on the lcd.
  • Type "story.txt > /dev/my_lcd" as root and read the story on LCD. :-)
  • Now, we can also try to display some thing using a user program...
Hope this much test is enough...:-)

Screenshot of test and result:
----------------------------------------------------------------------------------------

 ---------------------------------------------------------------------------------------


 --------------------------------------------------------------------------------------

A good tutorial about kernel module programming is available at
http://tldp.org/LDP/lkmpg/2.6/html/index.html



7 comments:

Gilmar said...

Good tutorial, very informative.

How about USB driver? It would be more fun.

thanks
Ilms

gSalo... said...

Good job. I have learnt a lot with this tutorial. Well done !!

Naveen Karuthedath said...

Nice tutorial.. :)
thanks

omar ezzat said...

can u make me a video of the test please

Osegueda said...

No pude lograr la compilaciĆ³n con make.
que puede ser?

Winda said...

hi, thanks for the tutorial.
I try it with pic16f877a, and i used usb serial port in my laptop.
But i failed in step 3 (3>type "make" to build the driver module led.ko )
could you please show me the details / step by step to do the third step? *i'm beginner with this
Thanks

Sushant Bhangale said...

Thanx for Help nice blogg

Post a Comment

SHARE IT on FB