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命令 拷贝到开发板