1. 用法:
将开发板DE10-Nano 的串口与Windows PC 连接。
我使用了一个putty串口小工具:
串口驱动和putty小工具下载地址:
打开设备管理器,查看串口端口号(我的电脑上是com5):
点击Open。
设置DE10-Nano开发板的MSEL 为01010(关于MSEL 的设置问题参考:),给DE10-Nano开发板插上电源上电。
先下发下面的命令让kernel信息打印到串口:
echo 7 4 1 7 > /proc/sys/kernel/printk
然后增加程序可执行性:
chmod 777 pwmdriver_app
然后加载驱动:
insmod pwmdriver.ko
如果insmod命令不支持请参考:linux命令找不到的万能办法 -bash: insmod: command not found
然后执行app, 传递pwm频率值400000 和 占空比200000:
./pwmdriver_app /dev/pwm 400000 200000
先执行 ./pwmdriver_app /dev/pwm 400000 200 然后执行./pwm_driver_app /dev/pwm 400000 200, 可以发现LED[1]明显变暗。
2. 底层硬件设计
module av_pwm( input clk, input reset_n, input as_read, input as_address, input as_write, output reg [31:0] as_readdata, input [31:0] as_writedata, output reg led); reg [31:0]cnt_freq; reg [31:0]cnt_duty; reg [31:0] cnt0; wire end_cnt0; //写pwm频率寄存器 always@(posedge clk or negedge reset_n) if(!reset_n) begin cnt_freq <= 32'd0; end else if(as_write && (as_address == 0))begin cnt_freq <= as_writedata; end else begin cnt_freq <= cnt_freq; end //写pwm占空比寄存器 always@(posedge clk or negedge reset_n) if(!reset_n) begin cnt_duty <= 32'd0; end else if(as_write && (as_address == 1)) begin cnt_duty <= as_writedata; end else begin cnt_duty <= cnt_duty; end //读寄存器 always@(posedge clk or negedge reset_n) if(!reset_n) as_readdata <= 32'd0; else if(as_read)begin case(as_address) 0:as_readdata <= cnt_freq; 1:as_readdata <= cnt_duty; default:as_readdata <= 32'd0; endcase end always @(posedge clk or negedge reset_n)begin if(!reset_n)begin cnt0 <= 0; end else begin if(end_cnt0)begin cnt0 <= 0;end else begin cnt0 <= cnt0 + 1; end end end assign end_cnt0 = (cnt0==cnt_freq -1); always @(posedge clk or negedge reset_n)begin if(reset_n==1'b0)begin led <= 1; end else if(cnt0==cnt_duty-1)begin led <= 0; end else if(end_cnt0)begin led <= 1; end end endmodule
3. 底层Linux驱动设计
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/printk.h> #include <linux/list.h> #include <linux/uaccess.h> //#include <linux/version.h> //#include <linux/slab.h> #include <asm/io.h> #include "hps_0.h" /*************************************************************** 文件名: pwmdriver.c 作者: Doreen 版本: V1.0 2024/01/04 描述: pwm (led)驱动文件 博客:https://blog.csdn.net/weixin_47841246?spm=1000.2115.3001.5343 知乎: https://www.zhihu.com/people/fen-dou-de-nian-qing-ren-1 论坛: http://www.myfpga.org ***************************************************************/ /* 寄存器物理地址 */ #define H2F_LW_BASE_ADDR (0xFF200000) //#define PWM_BASE 0x6000 #define MAP_PWM_ADDR (H2F_LW_BASE_ADDR + PWM_BASE) #define PWM_SPAN 16 /* 映射后的寄存器虚拟地址指针 */ static void __iomem *LWH2F_bridge_addr; /* pwm设备结构体 */ struct pwm_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ //unsigned int cnt_freq; /*pwm频率寄存器*/ //unsigned int cnt_duty; /*pwm占空比寄存器*/ }; struct pwm_dev pwm; /* pwm设备 */ /*定义设备名称*/ #define DEVICE_NAME "pwm" static int pwm_open(struct inode *inode, struct file *filp ) { //try_module_get(THIS_MODULE); filp->private_data = &pwm; /* 设置私有数据 */ // printk(KERN_WARNING "pwm_open()\r\n"); return 0; } static int pwm_release(struct inode *inode, struct file *filp ) { // module_put(THIS_MODULE); filp->private_data = NULL; //printk("pwm_close()\n"); return 0; } ssize_t pwm_read(struct file *filp, char *buf, size_t count,loff_t *f_pos) { int ret,register_data_1,register_data_2; char char_data[32]; //printk("kernel pwm_read\n"); register_data_1 = ioread32(LWH2F_bridge_addr + 0);/*读 PWM 寄存器0,即 cnt_freq 寄存器*/ register_data_2 = ioread32(LWH2F_bridge_addr + 4);/*读 PWM 寄存器1,即 cnt_duty 寄存器*/ /*将读取到的数据拷贝到用户空间*/ sprintf(char_data,"%d %d",register_data_1,register_data_2); ret = copy_to_user(buf, char_data, sizeof(char_data)); printk("register_data_1 = %d; copy_to_user = %s; \n",register_data_1,char_data); if (ret !=0) { return -EFAULT; } //printk("read done\n"); return count; } ssize_t pwm_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { int ret; int data_1; int data_2; char char_data[32]=""; //printk("kernel pwm_write\n"); /*从用户空间将数据拷贝到 char_data 数组中*/ ret=copy_from_user(char_data, buf, 32); printk("copy_from_user=%s \n",char_data); if (ret !=0) { return -EFAULT; } sscanf(char_data,"%d %d",&data_1,&data_2); iowrite32(data_1, LWH2F_bridge_addr + 0);/*写 PWM 寄存器 0,即 cnt_freq 寄存器*/ iowrite32(data_2, LWH2F_bridge_addr + 4);/*写 PWM 寄存器 1,即 cnt_duty 寄存器*/ printk("kernel set pwm_freq:%d; pwm_duty:%d\n",data_1, data_2); //printk("write done\n"); return count; } /*定义驱动程序操作函数*/ struct file_operations pwm_fops = { .owner = THIS_MODULE, .open = pwm_open, .write = pwm_write, .read = pwm_read, .release = pwm_release, }; /* 驱动入口函数 */ static int __init pwm_init(void) { // u32 val = 0; int ret; LWH2F_bridge_addr = ioremap_nocache(MAP_PWM_ADDR, PWM_SPAN); /* 注册字符设备驱动 */ /* 1、创建设备号 */ if (pwm.major) { /* 定义了设备号 */ pwm.devid = MKDEV(pwm.major, 0); ret = register_chrdev_region(pwm.devid, 1, DEVICE_NAME); if(ret < 0) { pr_err("cannot register %s char driver [ret=%d]\n",DEVICE_NAME, 1); goto fail_map; } } else { /* 没有定义设备号 */ ret = alloc_chrdev_region(&pwm.devid, 0, 1, DEVICE_NAME); /* 申请设备号 */ if(ret < 0) { pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DEVICE_NAME, ret); goto fail_map; } pwm.major = MAJOR(pwm.devid); /* 获取分配号的主设备号 */ pwm.minor = MINOR(pwm.devid); /* 获取分配号的次设备号 */ } printk(KERN_ERR "pwm major=%d,minor=%d\r\n",pwm.major, pwm.minor); /* 2、初始化cdev */ pwm.cdev.owner = THIS_MODULE; cdev_init(&pwm.cdev, &pwm_fops); /* 3、添加一个cdev */ ret = cdev_add(&pwm.cdev, pwm.devid, 1); if(ret < 0) goto del_unregister; /* 4、创建类 */ pwm.class = class_create(THIS_MODULE, "pwm"); if (IS_ERR(pwm.class)) { goto del_cdev; } /* 5、创建设备 */ pwm.device = device_create(pwm.class, NULL, pwm.devid, NULL, DEVICE_NAME); if (IS_ERR(pwm.device)) { goto destroy_class; } return 0; /* 注销字符设备 */ del_cdev: cdev_del(&pwm.cdev); /* 销毁类 */ destroy_class: class_destroy(pwm.class); /* 注销设备号 */ del_unregister: unregister_chrdev_region(pwm.devid, 1); /* 释放内存映射 */ fail_map: iounmap(LWH2F_bridge_addr);/*取消虚拟地址映射*/ return -EIO; } /*驱动出口函数*/ static void __exit pwm_exit(void) { /* 取消映射 */ iounmap(LWH2F_bridge_addr); /* 注销字符设备 */ cdev_del(&pwm.cdev); /* 注销设备号 */ unregister_chrdev_region(pwm.devid, 1); /* 销毁设备 */ device_destroy(pwm.class, pwm.devid); /* 销毁类 */ class_destroy(pwm.class); } module_init(pwm_init); module_exit(pwm_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Doreen");
4. 应用程序设计:
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" /*************************************************************** 文件名 : pwmdriver_app.c 作者 : Doreen 版本 : V1.0 描述 : pwm 测试 应用程序。 使用方法 :./pwmdriver_pp /dev/pwm 400000 200000 (第三个400000是pwm波频率 第四个参数200000 是pwm占空比 50%) 博客:https://blog.csdn.net/weixin_47841246?spm=1000.2115.3001.5343 知乎: https://www.zhihu.com/people/fen-dou-de-nian-qing-ren-1 论坛: http://www.myfpga.org ***************************************************************/ /* * @description : main 主程序 * @param - argc : argv 数组元素个数 * @param - argv : 具体参数 * @return : 0 成功;其他 失败 */ int main(int argc, char *argv[]) { int ret = 0; int fd = 0; char *filename; char data_w[32]=""; //char *data_w[2]; char data_r[32]; if(argc != 4){ printf("Error Usage!\r\n"); return -1; } filename = argv[1]; //data_w[0] = argv[2]; //data_w[1] = argv[3]; /* 打开 pwm 驱动 */ fd = open(filename, O_RDWR); if(fd < 0){ printf("file %s open failed!\r\n", argv[1]); return -1; } /*调用read方法读取/dev/pwm设备的寄存器数据到用户空间的data数组中*/ /* 向/dev/pwm 文件写入数据 */ strcat(data_w,argv[2]); strcat(data_w," "); strcat(data_w,argv[3]); printf("parameters from command are = %s\r\n",data_w); ret = write(fd, data_w, sizeof(data_w)); if(ret < 0){ printf("User write Failed!\r\n"); close(fd); return -1; } else {/*打印你写入的数据*/ printf("write successfully\r\n"); } usleep( 100*1000 ); /* 从/dev/pwm 文件读出数据 */ ret = read(fd,data_r,32); if(ret < 0){ printf("read error \n"); return -1;} else {/*打印读取到的数据*/ //printf("read_byte_number:%d; \r\n",ret); printf("user read %s;\r\n",data_r); //printf("Read pwm_freq:%s; pwm_duty:%s;\r\n",data_r[0],data_r[1]); } /* 关闭文件 */ ret = close(fd); if(ret < 0){ printf("file %s close failed!\r\n", argv[1]); return -1; } return 0; }
5. 编译生成驱动文件.ko 和 编译应用程序app
(1)先生成hps_0.h文件,参考:如何在linux系统下生成hps_0.h头文件
(2)写Makefile文件(注意内核文件路径要替换成您的实际路径):
KDIR := /home/doreen/DE10-Nano_v.1.3.8_HWrevC_SystemCD/DE10_NANO_SoC_FB/software/linux-socfpga-4.16 PWD := $(shell pwd) default: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean obj-m += pwmdriver.o
(3)编译生成驱动文件.ko 和 编译应用程序app
export ARCH=arm export CROSS_COMPILE=/home/doreen/DE10_NANO_SoC_FB/software/gcc-linaro-6.5.0-2018.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- make arm-linux-gnueabihf-gcc -o pwmdriver_app pwmdriver_app.c
于是得到pwmdrive.ko文件和可执行文件pwmdriver_app :
在root权限下拷贝两个文件到root路径下:
如果文件在Windows上,可以参考如何将文件从Windows电脑传递到开发板DE10-Nano 的ubuntu系统?scp命令 拷贝到开发板。
无