采用移位寄存器(SN74HC164DR)驱动多个LED<应用案例>

系统

linux4.14

实现目的

多个led灯通过移位寄存器(SN74HC164DR)移位驱动,达到两个引脚控制多个灯的目的。应用层通过写入参数,在驱动层通过定时器的方式定时驱动SN74HC164,实现灯闪烁。此方案中有共20个LED灯的控制。

原理概述

  • 硬件原理图

主控通过MCU_LED_DA和MCU_LED_CLK两个引脚控制移位寄存器74HC164,clk上升沿时将数据右移,将多个串起来会自动右移。例如如上图所示,驱动中给20个时钟,实现将LG3_L/A点亮。

for(i = 0;i < dev->gpio_cnt;i++){//由于硬件中实际控制的灯只有20个,所以gpio_cnt为20个。
    gpio_direction_output(dev->gpio_da, s[i]);
    gpio_direction_output(dev->gpio_clk, 0);
    gpio_direction_output(dev->gpio_clk, 1);
}

通过赋予da不同的值达到控制灯亮的目的。例如s[0]为1时,当此for循环执行完成后LG3_L/A引脚会置位为高电平。执行过程为GE0_LINK、GE0_ACT、GE1_LINK..........LG2_L/A、LG3_L/A依次为高电平,理想情况是由于每个周期为个位数的微秒级,所以人眼并看不到led灯的亮。实际还是总能看到余光闪烁。

驱动程序说明

驱动程序解析设备树,获取led灯的默认个数和状态,根据应用层设置的参数信息通过定时器实现灯的自动闪烁及熄灭控制。

  • 设备树

gpio74hc164{
    compatible = "xxx,gpio_74hc164";
    gpio-led0{
        label = "netled0";
        status = "okay";
        default-state = "off";
        gpio_da = <&port4c 15 GPIO_ACTIVE_LOW> ;   //da引脚
        gpio_clk = <&port4c 9 GPIO_ACTIVE_LOW> ;    //clk引脚
    };
​
gpio-led1{
        label = "netled1";
        status = "okay";
        default-state = "off";
        gpio_da = <&port4c 13 GPIO_ACTIVE_LOW> ; //da引脚
        gpio_clk = <&port4d 23 GPIO_ACTIVE_LOW> ;//clk引脚
    };
};
  • 驱动源码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/timer.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/property.h>
​
​
#define TIME_PERIOD     500     /*默认灯闪烁的频率*/
#define DEV_CNT     2       /* 设备号个数    */
​
#define LED_ON          0       /* 开灯 */
#define LED_OFF             1       /* 关灯 */
​
int dev_nports;                 /*设备树中实际配置的需要控制的设备数量*/
​
struct class *myled_class;  /* 类,在/sys/class目录的中会产生真实的目录*/
 
dev_t parent_devid;         /* 设备号*/
​
/* timer设备结构体 */
struct hc164_dev{
    dev_t devid;                    /* 设备号   */
    struct cdev cdev;               /* cdev     */
    struct device *device;          /* 设备    */
    int major;                      /* 主设备号   */
    int minor;                      /* 次设备号   */
​
    int   enable;                   /* 设备树使能标志 */
    struct fwnode_handle * fwnode;  
    struct device_node  *nd;        /* 设备节点 */
    int timeperiod;                 /* 定时周期,单位为ms */
    
    struct timer_list timer;        /* 定义一个定时器*/
​
    unsigned int gpio_da;           /* 74hc164 data引脚 */
    unsigned int gpio_clk;          /* 74hc164 clk引脚 */
​
    unsigned int gpio_state;        //每个bit代表一个状态 0-灭 1-闪
    unsigned int gpio_cnt;          //gpio个数
    unsigned int gpio_cur_state;    //记录当前状态
    char * name;                    //记录设备树的label
    spinlock_t lock;                /* 定义自旋锁 */
};
​
struct hc164_dev hc164dev[DEV_CNT]; /* timer设备 */
​
​
void hc164_gpio_set_value(struct hc164_dev *dev)
{
    int i;
    char s[32];
​
    for(i = 0;i < dev->gpio_cnt;i++){
        if(dev->gpio_cur_state & (0x01<<i))
            s[i] = 1;
        else
            s[i] = 0;
    }
​
    local_irq_disable();//关闭中断
    preempt_disable();  //关闭抢占
​
    /*此处操作实际clk的每个上升沿之间的时间差在几十个微秒之间,导致即使我不操作那个引脚
    由于移位寄存器的运行原理导致led灯有肉眼可见的闪烁。
    通过单片机中验证时间在个位数的微秒之间就可以看不到闪烁了,
    此处做了关闭中断和抢占的尝试,也没解决掉,待高手指点了,
    否则下一步要改板子,通过spi来控制了*/
​
    for(i = 0;i < dev->gpio_cnt;i++){
        gpio_direction_output(dev->gpio_da, s[i]);
        gpio_direction_output(dev->gpio_clk, 0);
        gpio_direction_output(dev->gpio_clk, 1);
    }
​
    local_irq_enable();
    preempt_enable();
​
    for(i = 0;i < dev->gpio_cnt;i++){
        if(dev->gpio_state & (0x01 << i)){
            if(dev->gpio_cur_state & (0x01<<i)){
                dev->gpio_cur_state &= (~(0x01<<i));
            }
            else{
                dev->gpio_cur_state |= (0x01<<i);
            }   
        }
        else{
            dev->gpio_cur_state |= (0x01<<i);
        }
    }
​
}
​
/* 定时器回调函数 */
void timer_function(unsigned long arg)
{
    struct hc164_dev *dev = (struct hc164_dev *)arg;
    int timerperiod;
    unsigned long flags;
    spin_lock_irqsave(&dev->lock, flags);
​
    hc164_gpio_set_value(dev);
    timerperiod = dev->timeperiod;
​
    spin_unlock_irqrestore(&dev->lock, flags);
    
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
}
​
/*
 * @description     : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp    : 设备文件,file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * @return          : 0 成功;其他 失败
 */
static int hc164_open(struct inode *inode, struct file *filp)
{
    //filp->private_data = &timerdev;   /* 设置私有数据 这个只适合一个设备*/
    //如何判断当前是打开/dev/myled1、/dev/myled2、/dev/myled3、/dev/myled4
 
    //获取用户打开的设备对应的字符设备结构体
 
    //container_of:根据结构体中的某个成员的地址,从而得到整个结构体的地址
 
    //现在必须得到led_dev_tbl[0]或led_dev_tbl[1]的首地址
    struct hc164_dev *dev = container_of(inode->i_cdev, struct hc164_dev, cdev);
    printk("hc164_open\n");
​
    if(dev){
        printk("dev:%s\r\n",dev->name);
    }
    if(!dev->enable){
        printk("dev 未使能\n");
        return 0;
    }
    filp->private_data = dev;
    dev->timer.function = timer_function;
    dev->timer.data = (unsigned long)dev;
    dev->timeperiod = TIME_PERIOD;      /* 设置定时时间,自动进行闪烁,而不用每次应用来操作了 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(TIME_PERIOD));
    return 0;
}
​
/*
 * @description     : ioctl函数,
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - cmd     : 应用程序发送过来的命令
 * @param - arg     : 参数
 * @return          : 0 成功;其他 失败
 */
static long hc164_ioctl(struct file *filp, unsigned int count, unsigned long arg)
{
    unsigned long flags;
    struct hc164_dev *dev =  (struct hc164_dev *)filp->private_data;
​
    spin_lock_irqsave(&dev->lock, flags);
​
    dev->gpio_state = arg;//gpio状态每位bit代表一位移位寄存器的一个引脚,1-闪烁 0-熄灭
    dev->gpio_cnt = count;    //代表引脚的个数,即需要产生多少次上升沿
​
    spin_unlock_irqrestore(&dev->lock, flags);
    return 0;
}
​
/* 设备操作函数 */
static struct file_operations hc164_fops = {
    .owner = THIS_MODULE,
    .open = hc164_open,
    .unlocked_ioctl = hc164_ioctl,
};
​
bool smc_74hc164_get_pdate(struct hc164_dev *dev)
{
    const char * str;
    int err;
    u32 val;
    int i;
    struct fwnode_handle * fwnode = dev->fwnode;
    
    dev->gpio_da = of_get_named_gpio(to_of_node(fwnode), "gpio_da", 0);
    if (dev->gpio_da < 0) {
        printk("can't get gpio_da\r\n");
        return false;
    }
​
    dev->gpio_clk = of_get_named_gpio(to_of_node(fwnode), "gpio_clk", 0);
    if (dev->gpio_clk < 0) {
        printk("can't get gpio_clk\r\n");
        return false;
    }
​
    printk("da:%d clk:%d\n",dev->gpio_da,dev->gpio_clk);
    
    if(fwnode_property_read_u32(fwnode, "led-count", &val) < 0){
        printk("fwnode_property_read_u32 led-count failed!default 20\r\n");
        dev->gpio_cnt = 20;
    }
    else{
        dev->gpio_cnt = val;
    }
​
    if(fwnode_property_read_string(fwnode, "label", &str) < 0){
        printk("fwnode_property_read_string label failed! \r\n");
        fwnode_handle_put(fwnode);
        return ERR_PTR(-EINVAL);
    }
    
    dev->name = str;
​
    if(fwnode_property_read_string(fwnode, "default-state", &str)){
        printk("can't get default-state\r\n");
        err = 1;
    }
    printk("default-state is %s \r\n", str);            /* 调试用 ps_led1 怎么不亮 ?原因设备树 属性名字有问题 0219lc */
    
    if(!err) {
        if (!strcmp(str, "on"))
            val = LED_ON;
        else
            val = LED_OFF;
    } else
        val = LED_OFF;
​
    err = gpio_request(dev->gpio_da, "gpio_da");
    if (err < 0) {
        printk("%s: gpio_request(%d) for gpio_da failed\n",
         __FUNCTION__, dev->gpio_da);
    }
  
​
    err = gpio_request(dev->gpio_clk, "gpio_clk");
    if (err < 0) {
        printk("%s: gpio_request(%d) for gpio_clk failed\n",
         __FUNCTION__, dev->gpio_clk);
    }
​
     for(i = 0;i < dev->gpio_cnt;i++){
        gpio_direction_output(dev->gpio_da, val);
        gpio_direction_output(dev->gpio_clk, 0);
        gpio_direction_output(dev->gpio_clk, 1);
    }
​
​
​
​
    return true;
}
​
static int smc_74hc164_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int i;
    int ret;
    int nports;
    struct fwnode_handle *fwnode;
​
   
    nports = device_get_child_node_count(dev);//获取子节点个数
    if (nports == 0)
        return -ENODEV;
​
    printk("nports:%d\n",nports);
    
    if(nports > DEV_CNT){
        printk("error:%s:%d %s\n",__FILE__,__LINE__,__func__);
        return -ENODEV;
    }
    dev_nports = nports;
    
    myled_class = class_create(THIS_MODULE, "net_leds");
    if (IS_ERR(myled_class)) {
        printk("smc_74hc164_init class_create error\n");
        ret = PTR_ERR(myled_class);
        goto out3;
    }
    printk("class_create\n");
    
    i = 0;
    device_for_each_child_node(dev, fwnode) {
        hc164dev[i].fwnode = fwnode;
        if(smc_74hc164_get_pdate(&hc164dev[i])){
            /* 初始化自旋锁 */
            spin_lock_init(&hc164dev[i].lock);
            hc164dev[i].enable = 1;
        }
        else hc164dev[i].enable = 0;
        i++;
    }
      
    ret = alloc_chrdev_region(&parent_devid, 0, nports, "net_leds");    /* 该名字在 cat /proc/devices 下面可看到设备号 申请设备号 */
    if (ret){
        printk("alloc_chrdev_region error\n");
        goto out1;
    }
​
​
    for(i = 0;i < nports;i++){
        if(hc164dev[i].enable){
            
            hc164dev[i].devid = parent_devid+i;
            hc164dev[i].major = MAJOR(hc164dev[i].devid);   /* 获取分配号的主设备号 */
            hc164dev[i].minor = MINOR(hc164dev[i].devid);   /* 获取分配号的次设备号 */
​
            printk("enable,major:%d,%d\n",hc164dev[i].major, hc164dev[i].minor);
​
            hc164dev[i].cdev.owner = THIS_MODULE;
​
             /* 2、初始化cdev */
            cdev_init(&hc164dev[i].cdev, &hc164_fops);
​
                /* 3、添加一个cdev */
            ret = cdev_add(&hc164dev[i].cdev, hc164dev[i].devid, 1);
            if (ret){
                 printk("cdev_add error\n");
                goto out2;
            }
        }
    }
​
​
   
    for(i = 0;i < nports;i++){
        if(hc164dev[i].enable){
             /* 5、创建设备 */
            hc164dev[i].device = device_create(myled_class, NULL, hc164dev[i].devid, NULL, "%s",hc164dev[i].name);
            if (IS_ERR(hc164dev[i].device)) {
                printk("smc_74hc164_probe device_create error\n");
                ret = PTR_ERR(hc164dev[i].device);
                goto out4;
            }
            init_timer(&hc164dev[i].timer);
        }
    }
​
    printk("probe success\n");
​
    return 0;
out4:
    class_destroy(myled_class);
    
out3:
    for ( i = 0; i < nports; i++){
        if(hc164dev[i].enable){
            cdev_del(&hc164dev[i].cdev);
        }
    }
        
​
out2:
    unregister_chrdev_region(parent_devid, nports);
   
out1:
    for ( i = 0; i < nports; i++){
        if(hc164dev[i].enable){
            gpio_free(hc164dev[i].gpio_da); 
            gpio_free(hc164dev[i].gpio_clk);
        }
    }
    
    return 0;
}
​
static int smc_74hc164_remove(struct platform_device *pdev)
{
    int i;
    for ( i = 0; i < dev_nports; i++){
        if(hc164dev[i].enable){
            gpio_direction_output(hc164dev[i].gpio_da, LED_OFF);
            gpio_direction_output(hc164dev[i].gpio_clk, LED_OFF);
            gpio_free(hc164dev[i].gpio_da); 
            gpio_free(hc164dev[i].gpio_clk);
            del_timer_sync(&hc164dev[i].timer);             /* 删除timer */
            cdev_del(&hc164dev[i].cdev);
        }
    }
​
    unregister_chrdev_region(parent_devid, dev_nports);
​
    for ( i = 0; i < dev_nports; i++){
        if(hc164dev[i].enable){
            device_destroy(myled_class, hc164dev[i].devid);//注销设备
        }
    }
​
    class_destroy(myled_class);//注销类
​
    return 0;
}
​
static const struct of_device_id smc_74hc164_of_match[] = {
    { .compatible = "xxx,gpio_74hc164",},
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, smc_74hc164_of_match);
​
static struct platform_driver smchip_gpio_driver = {
    .driver     = {
        .name   = "74hc164",
        .of_match_table = of_match_ptr(smc_74hc164_of_match),
    },
    .probe      = smc_74hc164_probe,
    .remove     = smc_74hc164_remove,
};
​
static int __init smc_74hc164_init(void)
{
    return platform_driver_register(&smchip_gpio_driver);
}
​
static void __exit smc_74hc164_exit(void)
{
    platform_driver_unregister(&smchip_gpio_driver);
}
​
module_init(smc_74hc164_init);
module_exit(smc_74hc164_exit);
​
MODULE_AUTHOR("xxx");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("xxx GPIO 74hc164 driver");

应用层调用demo

1.源码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	char *filename;
	unsigned int cmd;
	unsigned int arg;
	unsigned char str[100];

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s,%d\r\n", filename,fd);
		return -1;
	}

	while (1) {
		printf("Input gpio count:");
		ret = scanf("%d", &cmd);
		if (ret != 1) {				/* 参数输入错误 */
			gets(str);				/* 防止卡死 */
		}
		printf("Input state:");
		ret = scanf("%d", &arg);
		if (ret != 1) {			/* 参数输入错误 */
			gets(str);			/* 防止卡死 */
		}
		ioctl(fd, cmd, arg);		/* 控制定时器的打开和关闭 */	
	}
	close(fd);
}

2.应用示例

./a.out /dev/netled0
Input gpio count:20
Input state:48

实现控制两个灯的自动闪烁,其他灯保持熄灭状态。

问题解决

led的闪烁问题这个在单片机侧验证发现在个位数的微秒切换时肉眼看不到闪烁,在linux侧直接控制gpio周期为几十个微秒,甚至上百微秒,人眼总是可以看到灯的闪,通过直接操控gpio的寄存器,可以将gpio的两个上升沿的时延控制到10us左右,不很仔细看,看不出闪烁,勉强算解决吧。如果还没有更好的解决方法,只能下一版硬件通过spi来控制这两个gpio。

参考资料

1.正点原子阿尔法Linux开发板的定时器的例程

2.linux字符设备驱动创建多个设备(一主设备号多个次设备号)完整示例,开发板领航者7010_linux简单字符设备驱动驱动2个设备_流川枫2333的博客-CSDN博客

74hc164芯片手册

SN54HC164 data sheet, product information and support | TI.com

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值