• 树莓派——9、IO操控代码编程


    编写操控io的代码

    关于树莓派的cpu型号:

    使用pinout
    在这里插入图片描述
    使用cat /proc/cpuinfo
    在这里插入图片描述
    查阅资料:

    查阅资料发现树莓派3b的CPU型号就是BCM2709,也就是pinout中的BCM2827
    在这里插入图片描述

    要先确定IO空间的起始地址(物理地址

    ps:树莓派3B的是cpu型号为bcm2709.
    在这里插入图片描述

    0x3F00 0000就是IO外围设备的起始物理地址

    我们需要确定GPFSEL0的物理地址,就要找出偏移量
    在这里插入图片描述
    在这里插入图片描述
    可以得到物理地址:0x3F00 0000+(0x7E20 0000- 0x7E00 0000)=0x3F20 0000

    GPFSEL0的地址为:0x3F20 0000;
    GPSET0的地址为: 0x3F20 001C;
    GPCLR0的地址为: 0x3F20 0028;

    我们得到的是物理地址是不可以直接操作的,我们需要转化成虚拟地址,通过调用函数:__ioremap

    1. void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
    2. ioremap宏定义在asm/io.h内:
    3. #define ioremap(cookie,size) __ioremap(cookie,size,0)

    参数:
    phys_addr:要映射的起始的IO地址

    size:要映射的空间的大小

    flags:要映射的IO空间和权限有关的标志

    驱动代码编写

    基于驱动框架的代码进行修改

    php 从数据库提取二进制图片的处理代码

    pdf正在上传…重新上传取消0星超过10%的资源28KB

    下载

    sed -ir ‘s/hello/pin4/g’ pin4_drv.c (把pin4_drv.c里面所有的hello替换成pin4)

    sed -ir ‘s/关键字/目标关键字/g’ 文件在这里插入图片描述

    先定义寄存器的地址

    错误示范:
    在这里插入图片描述
    这样直接定义是不对的,linux中代码访问的是虚拟地址,所以我们需要在入口函数把物理地址转化为虚拟地址

    寄存器地址先初始化为NULL
    在这里插入图片描述

    volatile的作用:防止编译器对代码优化,变量值是直接从变量地址中读取和存储的

    驱动加载的时候在入口函数对寄存器地址赋值

    在这里插入图片描述
    在open的时候配置pin4为输出引脚

    在这里插入图片描述

    在write函数获取用户层的数据,根据这个数据来操作io口输出高电平或者低电平

    在这里插入图片描述

    1. copy_from_user(void *to, const void __user *from, unsigned long n)
    2. //从用户空间拷贝数据到内核空间
    3. *to :将数据拷贝到内核的地址
    4. *from :需要拷贝数据的地址
    5. n :拷贝数据的长度(字节)
    6. 也就是将form地址中的数据拷贝到to地址中去,拷贝长度是n

    data-channel:将数据通道转换为流

    zip正在上传…重新上传取消0星超过10%的资源3KB

    下载

    并且在出口函数解除寄存器的地址映射(防止风险)
    在这里插入图片描述

    完整代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. static struct class *pin4_class;
    9. static struct device *pin4_class_dev;
    10. static dev_t devno; //设备号
    11. static int major = 231; //主设备号
    12. static int minor = 0; //次设备号
    13. static char *module_name = "pin4"; //模块名
    14. volatile unsigned int *GPFSEL0 = NULL;
    15. //volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
    16. volatile unsigned int *GPSET0 = NULL;
    17. volatile unsigned int *GPCLR0 = NULL;
    18. //led open 函数
    19. static int pin4_open(struct inode *inode,struct file *file)
    20. {
    21. printk("pin4_open\n");
    22. *GPFSEL0 &= ~(0x6 << 12);//0x6用二进制表示是110 想左移12位在取反就可以保证 14-12位为001在用&(与运算)运算我们就可以保证 14,13位一定为0
    23. *GPFSEL0 |= (0x1 <<12);//0x1 用二进制表示是1 想左移12位在用|(或运算)就可以保证第12为一定为1
    24. return 0;
    25. }
    26. static int pin4_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
    27. {
    28. int userCmd;
    29. printk("pin4_write\n");
    30. copy_form_user(&userCmd,buf,size);//这个函数是用于接收上层代码的,在内核源码中可以找到这个函数用法
    31. if (userCmd==1){
    32. printk("set 1\n");
    33. *GPSET0 |= (0x1 << 4);//0x1 左移四位就可以将第4引脚置1并且不影响其他位,并且将pin4设置为高电平
    34. }else if(userCmd==0){
    35. printk("set 0\n");
    36. *GPCLR0 |= (0x1 << 4);//原理上,但这边设置的是低电平
    37. }else{
    38. printk("undo\n");
    39. }
    40. return 0;
    41. }
    42. static int pin4_read(struct file *file1, char __user *buf, size_t size, loff_t *ppos)
    43. {
    44. printk("pin4_read\n");
    45. return 0;
    46. }
    47. static struct file_operations led_fops = {
    48. .owner = THIS_MODULE,
    49. .open = pin4_open,
    50. .write = pin4_write,
    51. .read = pin4_read,
    52. };
    53. int __init pin4_drv_init(void)
    54. {
    55. int ret;
    56. printk("pin4 insmod success\n");
    57. devno = MKDEV(major,minor);
    58. ret = register_chrdev(major,module_name, &led_fops);//注册驱动 告诉内核
    59. pin4_class = class_create( THIS_MODULE, "myfirstdemo");
    60. pin4_class_dev = device_create(pin4_class,NULL,devno,NULL,module_name);
    61. GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);//ioremap():物理地址转换成虚拟地址,io口寄存器映射成普通内存单元进行访问,0x3f200000 GPFSL0的物理地址,在下面为大家介绍为什么这个
    62. //第二个参数表示映射的大小
    63. GPSET0 = (volatile unsigned int *)ioremap(0x3f20001c,4);//物理地址的映射要在创建之后
    64. GPCLR0 = (volatile unsigned int *)ioremap(0x3f200028,4);
    65. return 0;
    66. }
    67. void __exit pin4_drv_exit(void)
    68. {
    69. iounmap(GPFSEL0 );//取消ioremap所映射的IO地址
    70. iounmap(GPSET0 );//取消隐射要在摧毁前
    71. iounmap(GPSET0);
    72. device_destroy(pin4_class,devno);
    73. class_destroy(pin4_class);
    74. unregister_chrdev(major,module_name);//卸载驱动
    75. printk("pin4 destroyed\n");
    76. }
    77. module_init(pin4_drv_init);
    78. module_exit(pin4_drv_exit);
    79. MODULE_LICENSE("GPL v2");

    编译阶段

    1. 把写好的驱动文件拷贝到源码树目录的 /drivers/char 目录下
      在这里插入图片描述

    2. 修改Makefile(为了在编译的时候生成.ko文件)
      在这里插入图片描述

    3. 编译驱动代码(切回到源码树目录进行编译)
      ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

    在这里插入图片描述
    在这里插入图片描述

    测试阶段

    1. 把驱动文件拷贝到树莓派,并且安装驱动
      在这里插入图片描述
      安装驱动
      在这里插入图片描述
      驱动加载成功
      在这里插入图片描述

    2. 编写测试程序拷贝到树莓派
      测试程序:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(){
    7. int fd;
    8. char cmd;
    9. fd=open("/dev/pin4_drv",O_RDWR);
    10. if(fd==-1){
    11. printf("open failed\n");
    12. perror("reason");
    13. }else{
    14. printf("open success\n");
    15. }
    16. printf("imput 1 or 0\n1:pin4 set 1\n0:pin4 set 0\n");
    17. scanf("%c",&cmd);
    18. if(cmd=='1'){
    19. write(fd,"1",1);
    20. }
    21. else if (cmd=='0'){
    22. write(fd,"0",1);
    23. }else{
    24. printf("imput error\n");
    25. }
    26. return 0;
    27. }

    在lUbuntu交叉编译后把程序拷贝到树莓派,然后在树莓派运行

    在这里插入图片描述
    测试结果:用dmesg查看内核打印信息
    在这里插入图片描述
    查看io口状态:
    在这里插入图片描述

  • 相关阅读:
    如何在 Ubuntu VPS 实例上安装 Chef 服务器、工作站和客户端
    Linux 权限
    .NET通过源码深究依赖注入原理
    P问题、NP问题、NPC问题和NP-Hard问题,相关概念与题目
    是否在业务中使用大语言模型?
    计算机主机名与用户名区别
    树莓派CM4安装系统
    述职报告写作总结
    【C++】你想要的——印刷模板儿
    MR案例 - TopNScore [成绩分组排行榜]
  • 原文地址:https://blog.csdn.net/qq_53889131/article/details/126653514