一 按键驱动程序的简单实现

下面是基于中断和消息的按键驱动程序,其工作原理是:当应用程序读取键值时,会调用按键驱动程序的read函数,而我们实现的read函数检测完读取长度后没有直接读取键值而是等待按键消息,如果没有按键,程序会进入休眠状态,这样可以节省大量的CPU,而当我们按键时硬件会产生中断,程序自动进入中断处理函数,在中断处理函数中,驱动程序读取键值存入全局变量并激活read函数中等待的消息,应用程序被迅速唤醒并通过read函数读取键值,如此,完成了获取键值的工作。下面是源码,比较简单,也就不多说了。
#include <linux/types.h>  
#include <linux/module.h>  
#include <linux/cdev.h>  
#include <linux/fs.h>  
#include <linux/device.h>  
#include <linux/gpio.h>  
#include <linux/irq.h>  
#include <linux/interrupt.h>  
#include <linux/sched.h>   
#include <linux/wait.h>  
#include <linux/uaccess.h>  
 
static dev_t devno;
static struct cdev cdev;
static struct class* buttons_class;
static struct device* buttons_device;
 
static wait_queue_head_t button_waitq;
 
static volatile int pressed = 0;
static unsigned char key_val;
 
struct key_desc{
    unsigned int  pin;
    unsigned char value;
};
 
static struct key_desc key_descs[8] = {
    [0] = {
        .pin = S5PV210_GPH0(0),
        .value = 0x00,
    },
 
    [1] = {
        .pin = S5PV210_GPH0(1),
        .value = 0x01,
    },
 
    [2] = {
        .pin = S5PV210_GPH0(2),
        .value = 0x02,
    },
 
    [3] = {
        .pin = S5PV210_GPH0(3),
        .value = 0x03,
    },
 
    [4] = {
        .pin = S5PV210_GPH0(4),
        .value = 0x04,
    },
 
    [5] = {
        .pin = S5PV210_GPH0(5),
        .value = 0x05,
    },
 
    [6] = {
        .pin = S5PV210_GPH2(6),
        .value = 0x06,
    },
 
    [7] = {
        .pin = S5PV210_GPH2(7),
        .value = 0x07,
    },
};
 
static irqreturn_t buttons_irq(int irq, void *dev_id){
    volatile struct key_desc *key = (volatile struct key_desc *)dev_id;
 
    if(gpio_get_value(key->pin)){
        key_val = key->value|0x80;
    }
    else{
        key_val = key->value;
    }
 
    pressed = 1;
    wake_up_interruptible(&button_waitq);
 
    return IRQ_RETVAL(IRQ_HANDLED);
}
 
static int buttons_open(struct inode *inode, struct file *file){
    int ret;
 
    ret = request_irq(IRQ_EINT(0),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key1", &key_descs[0]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(1),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key2", &key_descs[1]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(2),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key3", &key_descs[2]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(3),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key4", &key_descs[3]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(4),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key5", &key_descs[4]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(5),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key6", &key_descs[5]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(22),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key7", &key_descs[6]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(23),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key8", &key_descs[7]);
    if(ret)
        return ret;
    return 0;
}
 
static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
    if(count != 1){
        printk(KERN_ERR "The driver can only give one key value once!\n");
        return -ENOMEM;
    }
 
    wait_event_interruptible(button_waitq, pressed);
    pressed = 0;
 
    if(copy_to_user(data, &key_val, 1)){
        printk(KERN_ERR "The driver can not copy the data to user area!\n");
        return -ENOMEM;
    }
     
    return 0;
}
 
static int buttons_close(struct inode *inode, struct file *file){
    free_irq(IRQ_EINT(0),  &key_descs[0]);
    free_irq(IRQ_EINT(1),  &key_descs[1]);   
    free_irq(IRQ_EINT(2),  &key_descs[2]);
    free_irq(IRQ_EINT(3),  &key_descs[3]);
    free_irq(IRQ_EINT(4),  &key_descs[4]);
    free_irq(IRQ_EINT(5),  &key_descs[5]);
    free_irq(IRQ_EINT(22), &key_descs[6]);
    free_irq(IRQ_EINT(23), &key_descs[7]);
    return 0;
}
 
struct file_operations buttons_ops = {
    .open    = buttons_open,
    .read    = buttons_read,
    .release = buttons_close,
};
 
int buttons_init(void){
    int ret;
 
    cdev_init(&cdev, &buttons_ops);
    cdev.owner = THIS_MODULE;
 
    ret = alloc_chrdev_region(&devno, 0, 1, "buttons");
    if(ret){
        printk(KERN_ERR "alloc char device region faild!\n");
        return ret;
    }
 
    ret = cdev_add(&cdev, devno, 1);
    if(ret){
        printk(KERN_ERR "add char device faild!\n");
        goto add_error;
    }
 
    buttons_class = class_create(THIS_MODULE, "buttonsdrv");
    if(IS_ERR(buttons_class)){
        printk(KERN_ERR "create class error!\n");
        goto class_error;
    }
 
    buttons_device = device_create(buttons_class, NULL, devno, NULL, "buttons");
    if(IS_ERR(buttons_device)){
        printk(KERN_ERR "create buttons device error!\n");
        goto device_error;
    }
 
    init_waitqueue_head(&button_waitq);
 
    return 0;
 
device_error:
    class_destroy(buttons_class);
class_error:
    cdev_del(&cdev);
add_error:
    unregister_chrdev_region(devno,1);
 
    return -ENODEV;
}
 
void buttons_exit(void){
    device_destroy(buttons_class, devno);
    class_destroy(buttons_class);
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
}
 
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

#include <linux/types.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uaccess.h>

static dev_t devno;
static struct cdev cdev;
static struct class* buttons_class;
static struct device* buttons_device;

static wait_queue_head_t button_waitq;

static volatile int pressed = 0;
static unsigned char key_val;

struct key_desc{
 unsigned int  pin;
 unsigned char value;
};

static struct key_desc key_descs[8] = {
 [0] = {
  .pin = S5PV210_GPH0(0),
  .value = 0x00,
 },

[1] = {
  .pin = S5PV210_GPH0(1),
  .value = 0x01,
 },

[2] = {
  .pin = S5PV210_GPH0(2),
  .value = 0x02,
 },

[3] = {
  .pin = S5PV210_GPH0(3),
  .value = 0x03,
 },

[4] = {
  .pin = S5PV210_GPH0(4),
  .value = 0x04,
 },

[5] = {
  .pin = S5PV210_GPH0(5),
  .value = 0x05,
 },

[6] = {
  .pin = S5PV210_GPH2(6),
  .value = 0x06,
 },

[7] = {
  .pin = S5PV210_GPH2(7),
  .value = 0x07,
 },
};

static irqreturn_t buttons_irq(int irq, void *dev_id){
 volatile struct key_desc *key = (volatile struct key_desc *)dev_id;

if(gpio_get_value(key->pin)){
  key_val = key->value|0x80;
 }
 else{
  key_val = key->value;
 }

pressed = 1;
 wake_up_interruptible(&button_waitq);

return IRQ_RETVAL(IRQ_HANDLED);
}

static int buttons_open(struct inode *inode, struct file *file){
 int ret;

ret = request_irq(IRQ_EINT(0),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key1", &key_descs[0]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(1),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key2", &key_descs[1]);
 if(ret)
  return ret;
  ret = request_irq(IRQ_EINT(2),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key3", &key_descs[2]);
 if(ret)
  return ret;
  ret = request_irq(IRQ_EINT(3),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key4", &key_descs[3]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(4),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key5", &key_descs[4]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(5),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key6", &key_descs[5]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(22),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key7", &key_descs[6]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(23),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key8", &key_descs[7]);
 if(ret)
  return ret;
 return 0;
}

static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
 if(count != 1){
  printk(KERN_ERR "The driver can only give one key value once!\n");
  return -ENOMEM;
 }

wait_event_interruptible(button_waitq, pressed);
 pressed = 0;

if(copy_to_user(data, &key_val, 1)){
  printk(KERN_ERR "The driver can not copy the data to user area!\n");
  return -ENOMEM;
 }
 
 return 0;
}

static int buttons_close(struct inode *inode, struct file *file){
 free_irq(IRQ_EINT(0),  &key_descs[0]);
 free_irq(IRQ_EINT(1),  &key_descs[1]);
 free_irq(IRQ_EINT(2),  &key_descs[2]);
 free_irq(IRQ_EINT(3),  &key_descs[3]);
 free_irq(IRQ_EINT(4),  &key_descs[4]);
 free_irq(IRQ_EINT(5),  &key_descs[5]);
 free_irq(IRQ_EINT(22), &key_descs[6]);
 free_irq(IRQ_EINT(23), &key_descs[7]);
 return 0;
}

struct file_operations buttons_ops = {
 .open    = buttons_open,
 .read    = buttons_read,
 .release = buttons_close,
};

int buttons_init(void){
 int ret;

cdev_init(&cdev, &buttons_ops);
 cdev.owner = THIS_MODULE;

ret = alloc_chrdev_region(&devno, 0, 1, "buttons");
 if(ret){
  printk(KERN_ERR "alloc char device region faild!\n");
  return ret;
 }

ret = cdev_add(&cdev, devno, 1);
 if(ret){
  printk(KERN_ERR "add char device faild!\n");
  goto add_error;
 }

buttons_class = class_create(THIS_MODULE, "buttonsdrv");
 if(IS_ERR(buttons_class)){
  printk(KERN_ERR "create class error!\n");
  goto class_error;
 }

buttons_device = device_create(buttons_class, NULL, devno, NULL, "buttons");
 if(IS_ERR(buttons_device)){
  printk(KERN_ERR "create buttons device error!\n");
  goto device_error;
 }

init_waitqueue_head(&button_waitq);

return 0;

device_error:
 class_destroy(buttons_class);
class_error:
 cdev_del(&cdev);
add_error:
 unregister_chrdev_region(devno,1);

return -ENODEV;
}

void buttons_exit(void){
 device_destroy(buttons_class, devno);
 class_destroy(buttons_class);
 cdev_del(&cdev);
 unregister_chrdev_region(devno, 1);
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
测试程序代码:

[cpp]
#include <stdio.h>  
#include <fcntl.h>  
 
int main(){
    int fd = open("/dev/buttons", O_RDWR);
    if(fd < 0){
        printf("open error");;
        return 0;
    }
 
    unsigned char key;
    while(1){
        read(fd, &key, 1);
        printf("The key = %x\n", key);
    }
 
    close(fd);
}

#include <stdio.h>
#include <fcntl.h>

int main(){
 int fd = open("/dev/buttons", O_RDWR);
 if(fd < 0){
  printf("open error");;
  return 0;
 }

unsigned char key;
 while(1){
  read(fd, &key, 1);
  printf("The key = %x\n", key);
 }

close(fd);
}相比轮询方式的按键驱动程序,中断方式编写的按键驱动程序可以很大程度上节省CPU资源,因此,推荐使用中断方式。

二 支持POLL机制

上面这种方式实现的按键驱动程序有个弊端,如果我们不按键,应用程序将会永远阻塞在这里,幸运的是,linux内核提供了poll机制,可以设置超时等待时间,如果在这个时间内读取到键值则正常返回,反之则超时退出。使内核支持poll非常简单,为file_operations的poll成员提供poll处理函数即可。

使内核支持poll还需要以下几步:

添加poll头文件

[cpp]
#include <linux/poll.h>

#include <linux/poll.h>

编写poll处理函数:

[cpp]
static unsigned buttons_poll(struct file *file, poll_table *wait){
    unsigned int mask = 0;
    poll_wait(file, &button_waitq, wait);
 
    if (pressed)
        mask |= POLLIN | POLLRDNORM;
 
    return mask;
}

static unsigned buttons_poll(struct file *file, poll_table *wait){
 unsigned int mask = 0;
 poll_wait(file, &button_waitq, wait);

if (pressed)
  mask |= POLLIN | POLLRDNORM;

return mask;
}将poll处理函数添加给file_operations:

[cpp]
.poll    = buttons_poll,

.poll    = buttons_poll,这样,驱动程序就支持poll机制了。下面是poll方式的测试程序:

[cpp]
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <poll.h>  
 
int main(int argc, char **argv){
    int fd;
    unsigned char key_val;
    int ret;
 
    struct pollfd fds[1];
     
    fd = open("/dev/buttons", O_RDWR);
    if (fd < 0){
        printf("can't open!\n");
    }
 
    fds[0].fd     = fd;
    fds[0].events = POLLIN;
    while (1){
        ret = poll(fds, 1, 5000);
        if (ret == 0){
            printf("time out\n");
        }
        else{
            read(fd, &key_val, 1);
            printf("key_val = 0x%x\n", key_val);
        }
    }
     
    return 0;
}

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>

int main(int argc, char **argv){
 int fd;
 unsigned char key_val;
 int ret;

struct pollfd fds[1];
 
 fd = open("/dev/buttons", O_RDWR);
 if (fd < 0){
  printf("can't open!\n");
 }

fds[0].fd     = fd;
 fds[0].events = POLLIN;
 while (1){
  ret = poll(fds, 1, 5000);
  if (ret == 0){
   printf("time out\n");
  }
  else{
   read(fd, &key_val, 1);
   printf("key_val = 0x%x\n", key_val);
  }
 }
 
 return 0;
}这样,应用程序可以限制时间,如果在一定时间内读取不到键值就可以做特殊处理,这种思想在网络通信中应用广泛。

三 支持异步机制

很多情况下,我们的程序在等待按键期间需要处理其它任务而不是在这里空等,这时,就需要采用异步模式了。所谓异步模式,实际上是采用消息机制(以本文的按键程序为例),即当驱动程序检测到按键后发送消息给应用程序,应用程序接收到消息后再去读取键值。与前面的两种模式相比,最大的不同在于异步方式是驱动告诉应用程序来读而不是应用程序主动去读。添加异步支持更加简单,首先是为file_operations注册fasync函数,函数内容如下:

[cpp]
static int buttons_fasync(int fd, struct file * file, int on){
    return fasync_helper(fd, file, on,  &button_async);
}

static int buttons_fasync(int fd, struct file * file, int on){
    return fasync_helper(fd, file, on,  &button_async);
}然后再buttons_read函数中添加一行代码,修改后的代码如下:[cpp] view plaincopyprint?static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
    if(count != 1){
        printk(KERN_ERR "The driver can only give one key value once!\n");
        return -ENOMEM;
    }
 
    wait_event_interruptible(button_waitq, pressed);
    pressed = 0;
 
    if(copy_to_user(data, &key_val, 1)){
        printk(KERN_ERR "The driver can not copy the data to user area!\n");
        return -ENOMEM;
    }
 
    return 0;
}

static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
    if(count != 1){
        printk(KERN_ERR "The driver can only give one key value once!\n");
        return -ENOMEM;
    }

wait_event_interruptible(button_waitq, pressed);
    pressed = 0;

if(copy_to_user(data, &key_val, 1)){
        printk(KERN_ERR "The driver can not copy the data to user area!\n");
        return -ENOMEM;
    }

return 0;
}这样,驱动程序就支持异步获取键值了,为了测试效果,测试程序也需要修改,代码如下:[cpp] view plaincopyprint?#include <fcntl.h>  
#include <stdio.h>  
#include <poll.h>  
#include <signal.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <fcntl.h>  
 
 
/* sixthdrvtest
 */
int fd;  
 
void my_signal_fun(int signum)
{
    unsigned char key_val;
    read(fd, &key_val, 1);  
    printf("key_val: 0x%x\n", key_val);
}
 
int main(int argc, char **argv)
{
    unsigned char key_val;
    int ret;
    int Oflags;
 
    signal(SIGIO, my_signal_fun);
 
    fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
    if (fd < 0){  
        printf("can't open!\n");
        return -1;  
    }    
 
    fcntl(fd, F_SETOWN, getpid());
    Oflags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, Oflags | FASYNC);
 
 
    int rest;
    while (1){
        printf("Hello\n");
        while(rest = sleep(50)){
            sleep(rest);
        }
    }
 
    return 0;
}

#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

/* sixthdrvtest
 */
int fd;

void my_signal_fun(int signum)
{
    unsigned char key_val;
    read(fd, &key_val, 1);
    printf("key_val: 0x%x\n", key_val);
}

int main(int argc, char **argv)
{
    unsigned char key_val;
    int ret;
    int Oflags;

signal(SIGIO, my_signal_fun);

fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
    if (fd < 0){
        printf("can't open!\n");
        return -1;
    }

fcntl(fd, F_SETOWN, getpid());
    Oflags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, Oflags | FASYNC);

int rest;
    while (1){
        printf("Hello\n");
        while(rest = sleep(50)){
            sleep(rest);
        }
    }

return 0;
}这里需要注意的是,应用程序接收到消息会打断sleep,比如执行sleep(5)之后程序接收到了一个消息,这时,应用程序就被唤醒了,虽然是去执行的消息处理函数。如果程序接收到消息时仅睡眠了2秒,那么sleep被中断时会返回5-2=3,所以代码中采用while循环方式进行sleep,这样,即使接收到了消息也能完整的休眠5秒,当然,sleep函数本身是不够精确的,不过相差无几。

到这里,这个驱动程序基本上就算可以了,当然,还有对阻塞和非阻塞的支持,同步与互斥的支持,而阻塞与非阻塞无非是加上个逻辑判断,同步与互斥根应用程序的同步控制也差不多,无非就是信号量或者原子操作,这里就不多说了,如果有朋友需要这些内容可以留言讨论。

keyboard的更多相关文章

  1. Fedora 22中的Locale and Keyboard Configuration

    Introduction The system locale specifies the language settings of system services and user interface ...

  2. android:configChanges="keyboard|keyboardHidden|orientation|screenSize"

    <activity android:name="xxxActivity" android:configChanges="keyboard|keyboardHidde ...

  3. USB Keyboard Recorder

    catalogue . 引言 . Device Class Definition for Human Interface Devices (HID) . USB HID Report Descript ...

  4. imx6 matrix keyboard

    imx6需要添加4x4的矩阵键盘.本文记录添加方法. 参考链接 http://processors.wiki.ti.com/index.php/TI-Android-JB-PortingGuide h ...

  5. Codeforces Round #389 Div.2 B. Santa Claus and Keyboard Check

    time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...

  6. UVa 11998 Broken Keyboard (数组模拟链表问题)

    题目链接: 传送门 Broken Keyboard #include<bits/stdc++.h> using namespace std; char str[100010]; int m ...

  7. vimium Keyboard Bindings

    Modifier keys are specified as `<c-x>`, `<m-x>`, and `<a-x>` for ctrl+x, meta+x, a ...

  8. UVa 11988 Broken Keyboard(链表->数组实现)

    /*数组形式描述链表:链表不一定要用指针. 题目链接:UVa 11988 Broken Keyboard 题目大意: 小明没有开屏幕输入一个字符串,电脑键盘出现了问题会不定时的录入 home end ...

  9. 6754 Keyboard of a Mobile Telephone

    /*实践再次说明ch=getchar()的速度非常慢*/ /*大水题,不解释*/ #include<stdio.h> #include<string.h> int main() ...

  10. uGUI练习(三) KeyBoard Navigation

    练习目标 练习通过键盘在按钮或其它Selectable类型组件上导航 步骤 创建一排的Button,及一个右边的Button 2.查看Button的属性里有一栏下拉列表Navigation,默认选择的 ...

随机推荐

  1. 《UNIX环境高级编程》学习心得 二

    窝萌来看我们看到这本书里的第一个程序 #include "apue.h" #include <dirent.h> int main(int argc, char *ar ...

  2. [转]Android 应用的自动升级、更新模块的实现

    本文转自:http://www.oschina.net/question/163910_28462 我们看到很多Android应用都具有自动更新功能,用户一键就可以完成软件的升级更新.得益于Andro ...

  3. CF Preparing Olympiad (DFS)

    Preparing Olympiad time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  4. 2013 长沙网络赛J题

    思路:这题对于其他能退出所有值的情况比较好像,唯一不能确定的是XXOXXOXXOXX这个形式的序列,其中XX表示未知,O表示已知. 我们令num[1]=0,那么num[4]=sum[3]-sum[2] ...

  5. 【转】唱吧CEO陈华:创业四年,我积累的7点管理经验

    现象级产品“唱吧”至今拥有令人羡慕的用户数量,3亿.而这一切,却用了短短不到四年时间.唱吧团队如何应对越来越复杂的市场变化:怎样用人,才能不断激励新老员工做出更棒的业绩:CEO陈华又如何用“下大雪”模 ...

  6. c#中文件上传(1)

    * * ;//3M picPath = Server.MapPath("........."); HttpFileCollection postfile = Context.Req ...

  7. Ionic Android开发环境搭建 上

    首先,需要下载并安装Node.js. 什么是Node.js?百科上说:Node.js是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快.易于扩展的网络应用.Nod ...

  8. Android支付宝SDK开发笔记

    一.准备工作 〉1.下载开发包 https://b.alipay.com/order/productDetail.htm?productId=2014110308141993&tabId=4# ...

  9. Part 17 Temporary tables in SQL Server

    Temporary tables in SQL Server

  10. VHDL MOD和REM(转)

    mod(取模)and rem(取余) VHDL has mod and rem. They return the same value if both arguments are positive. ...