基于前面所学知识,写了一个完整的小程序,功能如下:
App可控制led的亮灭,采用定时器模拟pwm输出,key1增大占空比,led亮度增高,key2减少占空比。
gpio中断控制key,按键按一次,输出此时占空比,每次±10%,范围在0%-100%
两个定时器,定时器1负责输出PWM,定时器2用于按键延迟确认。
窗口输入,可直接设置占空比
可设置输出,显示目前亮度占空比
目录标题
一、设备树
1.iomuxc设置
定义的gpio在imx6ul-pinfunc.h找。
pinctrl_key1:key1zch{
fsl,pins=<
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0xF080
>;
};
pinctrl_key2:key2zch{
fsl,pins=<
MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0xF080
>;
};
pinctrl_led: ledzch{
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000010B0
>;
};
2.节点设置
写完记得查重,屏蔽重复端口
gpio_key1{
#address-cells = <1>;
#size-cells = <1>;
compatible = "zch-key1";
pinctrl-names = "default";
pinctrl-0 = "&pinctrl_key1";
key1-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio5>;
interrupts = <1 IRQ_TYPE_EDGE_FALLING>;
status = "okay";
};
gpio_key2{
#address-cells = <1>;
#size-cells = <1>;
compatible = "zch-key2";
pinctrl-names = "default";
pinctrl-0 = "&pinctrl_key2";
key2-gpio = <&gpio4 14 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio4>;
interrupts = <14 IRQ_TYPE_EDGE_FALLING>;
status = "okay";
};
gpio_led{
#address-cells = <1>;
#size-cells = <1>;
compatible = "zch-led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>;
status = "okey";
};
二、驱动程序
按照编写程序思路,而非程序结构思路
1.两个结构体
CMD命令参照下面连接解释_IO等宏的使用,和ioctl的命令cmd构造详解
#define SET_CMD (_IO(0XEF, 0x01))
#define OPEN_CMD (_IO(0XEF, 0x02))
#define CLOSE_CMD (_IO(0XEF, 0x03))
struct zchirq_desc { //外部中断结构体
int gpio; //gpio编号
int irqnum; //中断号
int flag; //按键标志
char name[10]; //按键名字
irqreturn_t (*handler) (int, void *); //服务函数
};
struct irq_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd; //节点
int led_gpio; //led的gpio
atomic_t keyvalue; //原子变量,敏感,在gpio中断中更改
spinlock_t spinlock; //自旋锁
struct timer_list timer[2]; //两个定时器
int period; //10自减至1
struct zchirq_desc keydesc[2]; //两个按键
};
struct irq_dev irqled;
2.入口函数
static int __init irq_initt(void)
{
int ret = 0;
spin_lock_init(&irqled.spinlock); //初始化自旋锁
/*设备号*/
if (irqled.major)
{
irqled.devid = MKDEV(irqled.major, 0);
register_chrdev_region(irqled.devid, IRQ_CNT, IRQ_NAME);
}
else
{
alloc_chrdev_region(&irqled.devid, 0, IRQ_CNT, IRQ_NAME);
irqled.major = MAJOR(irqled.devid);
irqled.minor = MINOR(irqled.devid);
}
irqled.cdev.owner = THIS_MODULE;
cdev_init(&irqled.cdev, &irq_fops);
cdev_add(&irqled.cdev, irqled.devid, IRQ_CNT);
irqled.class = class_create(THIS_MODULE, IRQ_NAME);
irqled.device = device_create(irqled.class, NULL, irqled.devid, NULL, IRQ_NAME);
//初始化,占空比5
atomic_set(&irqled.keyvalue, 5);
ret = gpio_init();
if(ret < 0){
printk("gpioinit error");
return -1;
}
//两个定时器初始化,还未设置周期,不会激活
init_timer(&irqled.timer[0]);
init_timer(&irqled.timer[1]);
irqled.timer[0].function = timer1_func;
irqled.timer[1].function = timer2_func;
irqled.timer[0].data = (unsigned long) &irqled;
irqled.timer[1].data = (unsigned long) &irqled;
printk("gpioinit success\r\n");
return 0;
}
3.gpio初始化
int ret = 0;
unsigned char i = 0;
//先设置ledgpio
irqled.nd = of_find_node_by_path("/gpio_led"); //找gpio节点
if(irqled.nd == NULL){
printk("ledgpio node not find");
return -1;
}
irqled.led_gpio = of_get_named_gpio(irqled.nd, "led-gpio", 0); //找gpio编号
if(irqled.led_gpio < 0){
printk("ledgpio not get");
return -1;
}
gpio_request(irqled.led_gpio, "led"); //申请gpio 使用之前要申请
ret = gpio_direction_output(irqled.led_gpio, 1); //设置ledgpio输出模式,初始值1
if(ret < 0)
printk("not set ledgpio");
else
printk("gpioled set success");
//再设置keygpio1
irqled.nd = of_find_node_by_path("/gpio_key1"); //找key1节点
if(irqled.nd == NULL){
printk("keygpio1 node not find");
return -1;
}
irqled.keydesc[0].gpio = of_get_named_gpio(irqled.nd, "key1-gpio", 0); //编号
if(irqled.keydesc[0].gpio < 0){
printk("key1 not get");
return -1;
}
sprintf(irqled.keydesc[0].name, "key1");//设置名字label
irqled.keydesc[0].handler = key1_handler; //中断函数句柄
//再设置keygpio2
irqled.nd = of_find_node_by_path("/gpio_key2"); //找key2节点
if(irqled.nd == NULL){
printk("keygpio2 node not find");
return -1;
}
irqled.keydesc[1].gpio = of_get_named_gpio(irqled.nd, "key1-gpio", 0); //编号
if(irqled.keydesc[1].gpio < 0){
printk("key2 not get");
return -1;
}
sprintf(irqled.keydesc[1].name, "key2");//设置名字label
irqled.keydesc[1].handler = key2_handler; //中断函数句柄
//key设置中断,两个一起
for(i=0; i < 2; ++i){
irqled.keydesc[i].flag = 0; //标志位清零
gpio_request(irqled.keydesc[i].gpio, irqled.keydesc[i].name); //申请
gpio_direction_input(irqled.keydesc[i].gpio); //设置方向
irqled.keydesc[i].irqnum = gpio_to_irq(irqled.keydesc[i].gpio); //获取中断号
//申请中断
ret = request_irq(irqled.keydesc[i].irqnum, //中断号
irqled.keydesc[i].handler, //中断函数句柄
IRQF_TRIGGER_FALLING, //中断触发标志
irqled.keydesc[i].name, //中断名字
&irqled); //设备结构体地址
if(ret < 0){
printk("irq %d request faild ", irqled.keydesc[i].irqnum);
return -1;
}
}
return 0;
}
4.两个外部中断函数
//减少占空比,keyvalue
static irqreturn_t key1_handler (int irqnum, void *dev_id)
{
struct irq_dev *dev = (struct irq_dev *)dev_id;
irqled.keydesc[0].flag = 1; //key1按下
printk("key1 push\r\n");
mod_timer(&dev->timer[1], jiffies + msecs_to_jiffies(10)); //10ms,启动定时器2
return IRQ_RETVAL(IRQ_HANDLED);
}
//增大占空比,
static irqreturn_t key2_handler (int irqnum, void *dev_id)
{
struct irq_dev *dev = (struct irq_dev *)dev_id;
irqled.keydesc[1].flag = 1; //key2按下
printk("key2 push\r\n");
mod_timer(&dev->timer[1], jiffies + msecs_to_jiffies(10)); //10ms,启动定时器2
return IRQ_RETVAL(IRQ_HANDLED);
}
4.两个定时器服务函数
//pwm输出定时器
//定义period,从10循环自减1。小于value输出1,大于value输出0,
static void timer1_func (unsigned long dev_num)
{
struct irq_dev *dev = (struct irq_dev *)dev_num;
int value = atomic_read(&dev->keyvalue);
if(dev->period > value)
gpio_set_value(dev->led_gpio, 0);
else
gpio_set_value(dev->led_gpio, 1);
spin_lock(&dev->spinlock); //加锁
dev->period--; //保护
if(dev->period < 1){
dev->period = 10; //范围1-10
}
spin_unlock(&dev->spinlock); //解锁
//重启定时器
mod_timer(&dev->timer[0], jiffies + msecs_to_jiffies(100));
}
//按键延时定时器10ms,keyvalue的值:0-10
static void timer2_func (unsigned long dev_num)
{
struct irq_dev *dev = (struct irq_dev *)dev_num;
//key1
if(dev->keydesc[0].flag == 1) //判断哪个按键
if(gpio_get_value(dev->keydesc[0].gpio) == 0)
if(atomic_inc_return(&dev->keyvalue) > 10)//value自加1,并返回判断
atomic_set(&dev->keyvalue, 10); //最大为10
dev->keydesc[0].flag = 0; //标志清零
//key2
if(dev->keydesc[1].flag == 1)
if(gpio_get_value(dev->keydesc[1].gpio) == 0)
if(atomic_dec_return(&dev->keyvalue) < 0) //value自减1,并返回判断
atomic_set(&dev->keyvalue, 0); //最小为0
dev->keydesc[1].flag = 0;
}
5.开、读、写函数
static int irq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &irqled;
//初始化period,从10开始减,激活定时器
mod_timer(&irqled.timer[0], jiffies + msecs_to_jiffies(100));
irqled.period = 10;
return 0;
}
static ssize_t irq_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret, value;
struct irq_dev *dev = filp->private_data;
//获取原子变量,并输出到用户空间
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf, &value, sizeof(value));
return ret;
}
static ssize_t irq_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
unsigned char databuf[1];
struct irq_dev *dev = filp->private_data;
//取值
ret = copy_from_user(databuf, buf, cnt);
dev->timer[0].expires = jiffies + msecs_to_jiffies(100);
if(ret < 0){
printk("Error");
}
return 0;
}
6.ioctl函数
static long irq_unlocked_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
struct irq_dev *dev = filp->private_data;
switch(cmd){
case CLOSE_CMD: //关
del_timer_sync(&dev->timer[0]); //关定时器,并保存状态
gpio_set_value(dev->led_gpio, 0); //关灯
break;
case OPEN_CMD: //开 10ms
mod_timer(&dev->timer[0], jiffies + msecs_to_jiffies(100));
break;
case SET_CMD:
if(arg > 10) //最大10
atomic_set(&dev->keyvalue, 10);
else if(arg < 0) //最小0
atomic_set(&dev->keyvalue, 0);
else //中间数 直接赋值
atomic_set(&dev->keyvalue, arg);
break;
default:
break;
}
printk("ioctl success\r\n");
return 0;
}
7.出口函数
static void __exit irq_exitt(void)
{
del_timer_sync(&irqled.timer[0]);
del_timer_sync(&irqled.timer[1]);
free_irq(irqled.keydesc[0].irqnum, &irqled);
free_irq(irqled.keydesc[1].irqnum, &irqled);
cdev_del(&irqled.cdev);
unregister_chrdev_region(irqled.devid, IRQ_CNT);
device_destroy(irqled.class, irqled.devid);
class_destroy(irqled.class);
}
三、测试
1.测试app
int main (int argc, char *argv[])
{
int fd, ret, value;
char *filename;
unsigned char databuf[1];
unsigned int cmd, arg;
if(argc != 3){
printf("error\r\n");
return (-1);
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("error file");
return -1;
}
databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0){
printf("error write");
close(fd);
return -1;
}
while(1) {
printf("Input CMD:\r\n");
printf("1 is set; 2 is open, 3 is close, 4 is printf\r\n");
ret = scanf("%d", &cmd);
if(cmd == 3) //关闭
cmd = CLOSE_CMD;
else if(cmd == 2) //打开
cmd = OPEN_CMD;
else if(cmd == 1){//直接设置占空比
cmd = SET_CMD;
printf("input pwm 0-10\r\n");
ret = scanf("%d",&arg);
}
else if(cmd == 4){ //打印占空比
read(fd, &value, sizeof(value));
printf("pwm is %d \r\n", value);
}
ioctl(fd, cmd, arg); //控制
}
close(fd);
return 0;
}
2.编译测试
和前面一样