宁波国际照明展
广州国际照明展览会(光亚展)
光影显示元宇宙展 2025年5月27 - 30日
2024大湾区国际车灯与车辆照明技术展览会(AUTO LAMP SHOW) 2024年12月4 - 6日
广州国际专业灯光、音响展会 2025年5月27 - 30日

LED驱动详解

#includ
#includ//头文件:module_initmodule_exit等宏定义。
#includ////structfile_operations
#includ
#includ//S3C2410GPIO寄存器定义
s3c2410_gpio_cfgpin等 #includ//s3c2410_gpio_setpin.
#includ//class_crdevice_cr注意。看一下这个头问题里边是哪一个)udev自动在/dev下创建设备节点有些2.6.27以前是可能是class_device_cr如果呈现implic错误时。
#includ//字符设备节点注册。cdev_add,函数有cdev_init.cdev_del等早期的方法是register_chrdev,unregister_chrdev这种方法应避免使用。
#definDEVICE_NA ME"leds"  
#definLED_MA JOR231    
#definIOCTL_LED_ON     1#definIOCTL_LED_OFF    0
staticunsignlongled_tabl[]={S3C2410_GPB5. S3C2410_GPB7, S3C2410_GPB6. S3C2410_GPB8,}
staticunsignintled_cfg_tabl[]={S3C2410_GPB5_OUTP,led_table数组相当于对应了GPB四个IO口的索引。 上面代码中。S3C2410_GPB6_OUTP,S3C2410_GPB7_OUTP,


S3C2410_GPB8_OUTP,};

structleds_type

{

structcdevcdev;

};

structleds_typ*my_leds_dev;


staticintEmbedSky_leds_openstructinod*inode,structfile*file

{

       inti;

        fori=0;i<4;i++

       { //设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能                s3c2410_gpio_cfgpinled_table[i],led_cfg_table[i];

       }

      return0;}


staticintEmbedSky_leds_ioctlstructinod*inode,structfile*file,unsignintcmd,unsignlongarg

{

ifarg>4

        {

               return-EINVA L;

        }

        switchcmd

        {

           caseIOCTL_LED_ON://设置指定引脚的输出电平为0                         s3c2410_gpio_setpinled_table[arg],0;

                         return0;

       caseIOCTL_LED_OFF://设置指定引脚的输出电平为1                         s3c2410_gpio_setpinled_table[arg],1;                         return0;

          default:return-EINVA L;

        }

}


staticstructfile_operEmbedSky_leds_fops={

.owner= THIS_MODULE,

.open  = EmbedSky_leds_open,

     .ioctl = EmbedSky_leds_ioctl,};

staticchar__initdatabanner[]="TQ2440/SKY2440LEDS,c2008,2009www.embedsky.net\n";statstructclass*led_class;


staticint__initEmbedSky_leds_initvoid{

intret;dev_tdevno=MKDELED_MA JOR,0;

       printk"initled\n";

        printkbanner;

       if!my_leds_dev

{

ret=-ENOMEM;

         gotofail_malloc;

       }

      memsetmy_leds_dev,0,sizeofstructleds_typ;

      cdev_init&my_leds_dev->cdev,&EmbedSky_leds_fop;

      ret=cdev_add&my_leds_dev->cdev,devno,1;  

      ifretprintkKERN_NOTICE"ERROR%d",ret;

            //注册一个类,使mdev可以在"/dev/"目录下面建立设备节点

       led_class=class_creatTHIS_MODULE,DEVICE_NA ME;

       ifIS_ERRled_class

      {

                printk"Err:failinEmbedSky-lclass.\n";                return-1;

     }//创建一个设备节点,节点名为DEVICE_NA ME

  device_crled_class,NULL,MKDEVLED_MA JOR,0,NULL,DEVICE_NA ME; 

printkDEVICE_NA ME"initialized\n";

    return0;

    fail_malloc:unregister_chrdev_regiondevno,1;

    returnret;}


staticvoid__exitEmbedSky_leds_exitvoid{                unregister_chrdevLED_MA JOR,DEVICE_NA ME;

        device_destroiled_class,MKDEVLED_MA JOR,0;//删掉设备节点        class_destroiled_class; //注销类}


module_initEmbedSky_leds_init;module_exitEmbedSky_leds_exit;


MODULE_A UTHOR"http://www.embedsky.net"; //驱动顺序的作者

MODULE_DESCRIPTION"TQ2440/SKY2440LEDDriver"; //一些描述信息

MODULE_LICENSE"GPL";          //遵循的协议

通过这四个值,对这四个IO口进行相关操作。例如:
       S3C2410_GPB5= S3C2410_GPIONOS3C2410_GPIO_BA NKB,5

                    =S3C2410_GPIO_BA NKB+5

                    =32*1+5

s3c2410_gpio_setpinS3C2410_GPB50中,该函数首先通过S3C2410_GPB5获得GPB虚拟地址和偏移地址再对GPB5GPBDA T寄存器进行操作,具体

voids3c2410_gpio_setpinunsignintpin,unsignintto{

   void__iomem*base=S3C2410_GPIO_BA SEpin;

   unsignlongoff=S3C2410_GPIO_OFFSETpin;

  unsignlongflags;unsignlongdat;local_irq_savflag;

   dat=__raw_readlbase+0x04;//读取GPIODA T数据到dat

   dat&=~1<<off;//先将要设置的IO口拉低

dat|=to<<offs; //再将形参的to值赋给dat  

__raw_writeldat,base+0x04;//最后将DA T值写进GPIODA T

  local_irq_restorflag;}

上面的函数调用了两个子函数,具体定义如下


#defineS3C2410_GPIO_BA SEpin  pin&~31>>1+S3C24XX_VA _GPIO

#defineS3C2410_GPIO_OFFSETpinpin&31

其中S3C24XX_VA _GPIO定义如下:


#defineS3C24XX_VA _GPIO   S3C2410_A DDR0x00E00000

#defineS3C2410_A DDRx  0xF0000000+x

这里S3C2410_A DDR基地址为0xF0000000??也即2440所有寄存器的虚拟地址的基地址。0x00E00000表示2440GPIO偏移地址,也就是说其GPIO虚拟地址首地址为0xF0E00000
再看看S3C2410_GPIO_BA SEpin定义。可以得到S3C2410_GPB5&~31=32其目的就是去掉GPB偏移值,不仿把S3C2410_GPB5值放进去计算。然后再右移一位,和GPIO虚拟地址首地址相加。因此,S3C2410_GPIO_BA SEpin只代表了对应GPIO组的虚拟地址,如GPB虚拟地址为10000B+0xF0E00000=0xF0E00010依此类推,可以得到所有GPIO偏移地址,具体如下表:
S3C2410_GPIO_OFFSET用于获得具体GPIO偏移地址。如GPB5则S3C2410_GPIO_OFFSETpin=pin&31=32*1+5&31=5有了*base和off就可以操作具体的寄存器了

函数s3c2410_gpio_cfgpin用于配置GPCON寄存器。具体代码

unsignintfunction{ voids3c2410_gpio_cfgpinunsignintpin.>
   void__iomem*base=S3C2410_GPIO_BA SEpin;
   unsignlongmask;
   unsignlongcon;
   unsignlongflags;
    ifpin<S3C2410_GPIO_BA NKB
    {
       mask=1<<S3C2410_GPIO_OFFSETpin;//GPA 寄存器只占一位   }
  else
   {    mask=3<<S3C2410_GPIO_OFFSETpin*2;//非GPA 寄存器占两位   }   local_irq_savflag;
  con =__raw_readlbase+0x00;//先保留GPCON值
   con&=~mask;  //再将要设置的管脚的CON值清零
con|=function;    //然后将形参传进来的配置赋给CON
base+0x00;//最后将CON值写进GPCON寄存器   __raw_writelcon.
    local_irq_restorflag;}
上面的LED驱动顺序中。调用上面的函数后即设置为Output led_cfg_tabl数组给出了GPB相应管脚的属性设置。
此为止。应该就一目了然了整个S3C2440IO口操作。
#defin__raw_writelv.*volatilunsignint__forc*a=v a__chk_io_ptra.
#defin__chk_io_ptrxvoid0
__chk_io_ptr编译器为了更细致地检查参数的属性。正常编译时没有作用。用于调试。
volatil为了防止Compil优化。
如果要对0x56000010物理地址进行访问(一般是外设寄存器),不能直接访问0x56000010物理地址。内核中,对所有的地址都是通过虚拟地址进行访问的.因此。那么需要把0x56000010物理地址映射为虚拟地址,然后对该虚拟地址进行访问就是对实际的物理地址进行访问了
需要注意的:进行映射时.否则就是加了volatil也是没有用的该虚拟地址需要disablcach和WriteBuffer.
这个IOREMA P实现过程中
offset=phys_addr&~PA GE_MA SK;
phys_addr&=PA GE_MA SK;
size=PA GE_A LIGNlast_addr-phys_addrs;
下面是通过vmlist中查找以size大小的空闲块.已经做过了页的对齐,所以从这里可以看出.只以映射的大小
杂项设备(miscdevic
杂项设备也是嵌入式系统中用得比较多的一种设备驱动。Linux内核的include\linux目录下有Miscdevice.h文件。所有这些设备采用主编号10一起归于miscdevic其实misc_regist就是用主标号10调用register_chrdev 要把自己定义的miscdevic从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴。
misc设备其实也就是特殊的字符设备。也就是说。
字符设备(chardevic
LED_MA JOR不能相同,否则几个设备都无法注册(已验证)如果模块使用该方式注册并且 LED_MA JOR为0自动分配主设备号 使用insmod命令加载模块时会在终端显示分配的主设备号和次设备号,/dev目录下建立该节点,比如设备led如果加载该模块时分配的主设备号和次设备号为253和0则建立节点:mknodledc2530使用register_chrdevLED_MA JOR,如果有多个设备使用该函数注册驱动顺序。使用register_chrdevLED_MA JOR,DEVICE_NA ME,&dev_fop注册字符设备驱动顺序时。DEVICE_NA ME,&dev_fop注册字符设备驱动顺序时都要手动建立节点,否则在应用顺序无法打开该设备。
__raw_readl和__raw_writel
Linux对I/O操作都定义在asm/io.h中。就在asm-arm/io.h中。相应的arm平台下。
#defin__raw_writebv.*volatilunsignchar__force *a=v a  __chk_io_ptra.
#defin__raw_writewv.*volatilunsignshort__forc*a=v a  __chk_io_ptra.
#defin__raw_writelv.*volatilunsignint__force  *a=vinclude\linux\compiler.h中: a  __chk_io_ptra.
#ifdef__CHECKER__
externvoid__chk_io_ptrvoid__iomem*;
#els
#defin__chk_io_ptrxvoid0
#endif
*volatilunsignint_forc*a就是返回地址为a处的值。voidxx做法有时候是有用的例如编译器打开了检查未使用的参数的时候需要将没有用到参数这么弄一下才干编译通过。 __raw_readla展开是void0,否则__chk_io_ptr什么也不做。*volatilunsignint_forc*a定义了__CHECKER__时候先调用__chk_io_ptr检查该地址。
CPU对I/O物理地址的编程方式有两种:一种是I/O映射。由此派生进去的操作方法有:inboutb_memcpy_fromioreadbwritebioread8iowrite8等。一种是内存映射。__raw_readl和__raw_writel等是原始的操作I/O方法。

本文来自网络。 授权转载请注明出处:http://www.ledjia.com/article/pid-2266.html

快速评论 发表新评论

您还未登录!登录后可以发表回复

文章评论 0人参与

联系我们

联系我们

137-9836-0047

在线咨询: QQ交谈

邮箱: admin@ledjia.com

工作时间:周一至周五,9:00-17:00,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部