Compiling and Testing PWM Capture Driver Based on NXP LS1028A Platform
In practical application scenarios, a PWM output is required to control the rotation of a stepper motor, and an IO pin controls the direction of rotation. However, one issue arises: how much does the stepper motor advance or retreat? The system cannot obtain the actual distance the motor has moved, so a PWM capture function is needed to achieve this.
Driver Compilation:
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/gpio/consumer.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/export.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/of_irq.h> #include <linux/slab.h> #include <asm/uaccess.h> #include <asm/io.h> #define DRIVER_NAME "pwm_in_driver" #define OK1028IRQ_CNT 1 /*device number and quantity*/ #define OK1028IRQ_NAME "pwm_in" #define DTS_A_GPIO "encoder,A_gpio" #define DTS_B_GPIO "encoder,B_gpio" #define DTS_OF_NAME "forlinx,pwm_in" #define DEBUG 0 /*debug information 1 on 0 off*/ #define FOR_INFO(fmt, arg...) /*debug information definition*/ \ ({ \ pr_info("FORLINX: (%s, %d): " fmt, __func__, __LINE__, ##arg); \ }) #define FOR_ERR(fmt, arg...) \ ({ \ pr_err("FORLINX: (%s, %d): " fmt, __func__, __LINE__, ##arg); \ }) \ struct irq_keydesc{ int gpio; /*gpio号*/ int irqnum; /*interrupt number*/ char name[10]; /* Name */ irqreturn_t (*handler)(int, void *); /*Interrupt service function*/ }; struct gpioirq_dev{ dev_t devid; /*device number*/ struct cdev cdev; /* character device */ struct class *class; /* class */ struct cdev cdev; /* device */ int major; /* main device number */ int minor; /* secondary device number */ struct device_node *nd; /* device node */ struct device *dev; /*device tree node*/ struct irq_keydesc gpio_pwm_A; /*phase A interrupt IO*/ struct irq_keydesc gpio_pwm_A; /*phase B interrupt IO*/ atomic_t value; /*passing data at the user level*/ long int number; /*count times*/ }; static struct gpioirq_dev gpioirq; /* irq device */ /* @description : interrupt service function. * @param - irq : interrupt signal * @param - dev_id : device structure * @return : interrupt execution results */ static irqreturn_t key_handler_A(int irq, void *dev_id) { int gpio_valueA=3,gpio_valueB=3; gpio_valueA = gpio_get_value(gpioirq.gpio_pwm_A.gpio); gpio_valueB = gpio_get_value(gpioirq.gpio_pwm_B.gpio); if ((gpio_valueA == 0 && gpio_valueB == 1) || (gpio_valueA == 1 && gpio_valueB == 0)) { gpioirq.number ++; } else if ((gpio_valueA == 0 && gpio_valueB == 0) || (gpio_valueA == 1 && gpio_valueB == 1)) { gpioirq.number --; } #if DEBUG FOR_INFO("key_handler_A gpio_valueA = %d gpio_valueB = %d number = %ld\r\n", gpio_valueA, gpio_valueB,gpioirq.number); #endif return IRQ_RETVAL(IRQ_HANDLED); } static irqreturn_t key_handler_B(int irq, void *dev_id) { int gpio_valueA=3,gpio_valueB=3; gpio_valueA = gpio_get_value(gpioirq.gpio_pwm_A.gpio); gpio_valueB = gpio_get_value(gpioirq.gpio_pwm_B.gpio); if ((gpio_valueB == 0 && gpio_valueA == 0) || (gpio_valueB == 1 && gpio_valueA == 1)) { gpioirq.number ++; } else if ((gpio_valueB == 0 && gpio_valueA == 1) || (gpio_valueB == 1 && gpio_valueA == 0)) { gpioirq.number --; } #if DEBUG FOR_INFO("key_handler_B gpio_valueA = %d gpio_valueB = %d number = %ld\r\n", gpio_valueA, gpio_valueB,gpioirq.number); #endif return IRQ_RETVAL(IRQ_HANDLED); } /* * @description : open device * @param – inode : the inode passed to the driver * @param - filp : for device files, the file structure has a member variable called private_data. * Normally private_data is pointed to the device structure when open. * @return : 0 success; other Failure */ static int ok1028irq_open(struct inode *inode, struct file *filp) { filp->private_data = &gpioirq; /* set private data */ #if DEBUG FOR_INFO("This is ok1028irq_open\r\n"); #endif return 0; } /* * @description : Read data from device * @param – filp : Device file to open (file descriptor) * @param – buf : Data buffer returned to user space * @param - cnt : Length of data to be read * @param – offt : Offset from the beginning of the file * @return : Number of bytes read. A negative value indicates a read failure */ static ssize_t ok1028irq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { static long int value = 0; struct gpioirq_dev *dev = (struct gpioirq_dev *) filp->private_data; atomic_set(&dev->value, dev->number); value = atomic_read(&dev->value); if(copy_to_user(buf, &value, sizeof(value)) != 0 ) { return -EFAULT; // copy failed, error code returned } #if DEBUG FOR_INFO("This is ok1028irq_read,value = %ld ,dev->number = %ld\r\n",value,dev->number); #endif return 0; // returns the number of bytes copied } /* * @description : Write data to the device * @param – filp : Device file to open (file descriptor) * @param – buf : User-space data buffer to be written * @param - count : Length of data to be written * @param – ppos : Offset from the beginning of the file * @return : Number of data bytes written. If non-zero, read failed */ static ssize_t ok1028irq_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { char kernel_buf[1] = {0}; int data=0; // Copy data from user space to kernel space if (copy_from_user(kernel_buf, buf, 1) != 0) { return -EFAULT; } // convert strings to long integers if (kstrtoint(kernel_buf, 10, &data) < 0) { return -EINVAL; } // printk("kernel_buf = %s, data = %d", kernel_buf, data); if (data == 1) { // clear the count of num gpioirq.number = 0; #if DEBUG FOR_INFO("This is ok1028irq_write,kernel_buf = %s, data = %d\r\n, dev->number = %ld",kernel_buf,data,gpioirq.number); #endif data=0; } return 0; // returns the number of bytes written } /* device operation function */ static struct file_operations pwm_in_fops = { .owner = THIS_MODULE, .open = ok1028irq_open, .read = ok1028irq_read, .write = ok1028irq_write, }; /* * @description : Drive entry function * @param : No * @return : No */ static int my_gpio_probe(struct platform_device *pdev){ int ret = 0; /*GPIO initialization function*/ struct device *dev = &pdev->dev; struct device_node *dev_node = dev->of_node; /* 1. Build device number */ if (gpioirq.major) { gpioirq.devid = MKDEV(gpioirq.major, 0); register_chrdev_region(gpioirq.devid, OK1028IRQ_CNT, OK1028IRQ_NAME); } else { alloc_chrdev_region(&gpioirq.devid, 0, OK1028IRQ_CNT, OK1028IRQ_NAME); gpioirq.major = MAJOR(gpioirq.devid); gpioirq.minor = MINOR(gpioirq.devid); } /* 2. register the character device */ cdev_init(&gpioirq.cdev, &pwm_in_fops); //operation function cdev_add(&gpioirq.cdev, gpioirq.devid, OK1028IRQ_CNT); /* 3. create a class */ gpioirq.class = class_create(THIS_MODULE, OK1028IRQ_NAME); if (IS_ERR(gpioirq.class)) { return PTR_ERR(gpioirq.class); } /* 4. create device */ gpioirq.device = device_create(gpioirq.class, NULL, gpioirq.devid, NULL, OK1028IRQ_NAME); if (IS_ERR(gpioirq.device)) { return PTR_ERR(gpioirq.device); } /*set gpio.name*/ memset(gpioirq.gpio_pwm_A.name, 0, sizeof(gpioirq.gpio_pwm_A.name)); sprintf(gpioirq.gpio_pwm_A.name, "PWM_A_INT"); memset(gpioirq.gpio_pwm_B.name, 0, sizeof(gpioirq.gpio_pwm_B.name)); sprintf(gpioirq.gpio_pwm_B.name, "PWM_B_INT"); /*acquire device tree node information*/ gpioirq.gpio_pwm_A.gpio = of_get_named_gpio(dev_node, DTS_A_GPIO, 0); gpioirq.gpio_pwm_B.gpio = of_get_named_gpio(dev_node, DTS_B_GPIO, 0); #if DEBUG FOR_INFO("gpio_pwm_A num : %d\n",gpioirq.gpio_pwm_A.gpio); FOR_INFO("gpio_pwm_B num : %d\n",gpioirq.gpio_pwm_B.gpio); #endif /*determine whether the GPIO is valid*/ if (!gpio_is_valid(gpioirq.gpio_pwm_A.gpio)) { FOR_ERR("Invalid INT gpio: %d\n", gpioirq.gpio_pwm_A.gpio); return -EBADR; } if (!gpio_is_valid (gpioirq.gpio_pwm_B.gpio)) { FOR_ERR("Invalid INT gpio: %d\n", gpioirq.gpio_pwm_B.gpio); return -EBADR; }
/*require GPIO*/ ret = gpio_request (gpioirq.gpio_pwm_A.gpio, gpioirq.gpio_pwm_A.name); if (ret < 0) { FOR_ERR("Request PWM_A_INT GPIO failed, ret = %d\n", ret); gpio_free(gpioirq.gpio_pwm_A.gpio); ret = gpio_request(gpioirq.gpio_pwm_A.gpio, gpioirq.gpio_pwm_A.name); if (ret < 0){ FOR_ERR("Retrying request PWM_A_INT GPIO still failed , ret = %d\n", ret); return ret; } } ret = gpio_request(gpioirq.gpio_pwm_B.gpio, gpioirq.gpio_pwm_B.name); if (ret < 0) { FOR_ERR("Request PWM_B_INT GPIO failed, ret = %d\n", ret); gpio_free(gpioirq.gpio_pwm_B.gpio); ret = gpio_request(gpioirq.gpio_pwm_B.gpio, gpioirq.gpio_pwm_B.name); if (ret < 0) { FOR_ERR("Retrying request PWM_B_INT GPIO still failed , ret = %d\n", ret); return ret; } } /*get GPIO interrupt number*/ gpioirq.gpio_pwm_A.irqnum = gpio_to_irq(gpioirq.gpio_pwm_A.gpio); gpioirq.gpio_pwm_B.irqnum = gpio_to_irq(gpioirq.gpio_pwm_B.gpio); #if DEBUG FOR_INFO("gpio_pwm_A.irqnum = %d\n", gpioirq.gpio_pwm_A.irqnum); FOR_INFO("gpio_pwm_B.irqnum = %d\n", gpioirq.gpio_pwm_B.irqnum); #endif /*set the GPIO to the input state*/ gpio_direction_input(gpioirq.gpio_pwm_A.gpio); gpio_direction_input(gpioirq.gpio_pwm_A.gpio); /* request an interrupt and register the interrupt function */ gpioirq.gpio_pwm_A.handler = key_handler_A; gpioirq.gpio_pwm_B.handler = key_handler_B; ret = request_irq(gpioirq.gpio_pwm_A.irqnum, gpioirq.gpio_pwm_A.handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, gpioirq.gpio_pwm_A.name, &gpioirq); if(ret < 0){ printk("irq %d request failed!\r\n", gpioirq.gpio_pwm_A.irqnum); return -EFAULT; } ret = request_irq(gpioirq.gpio_pwm_B.irqnum, gpioirq.gpio_pwm_B.handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, gpioirq.gpio_pwm_B.name, &gpioirq); if(ret < 0){ printk("irq %d request failed!\r\n", gpioirq.gpio_pwm_B.irqnum); return -EFAULT; } return 0; } /* * @description : driver export function * @param : No * @return : No */ static int ok1028irq_exit(struct platform_device *pdev){ /*release the interrupt*/ ret = request_irq(gpioirq.gpio_pwm_A.irqnum, gpioirq.gpio_pwm_A.handler, free_irq(gpioirq.gpio_pwm_B.irqnum, &gpioirq); /*release IO*/ gpio_free(gpioirq.gpio_pwm_A.gpio); gpio_free(gpioirq.gpio_pwm_B.gpio); /*delete character device*/ cdev_del(&gpioirq.cdev); /* unregistered character devices */ unregister_chrdev_region(gpioirq.devid, OK1028IRQ_CNT); /*Destruct device*/ device_destroy(gpioirq.class, gpioirq.devid); /*Destruct class*/ class_destroy(gpioirq.class); return 0; } static const struct of_device_id of_pwm_in_match[] = { { .compatible = DTS_OF_NAME }, {}, }; static struct platform_driver ok1028_encoder = { .probe = my_gpio_probe, .remove = ok1028irq_exit, .driver = { .name = DRIVER_NAME, .of_match_table = of_pwm_in_match, .owner = THIS_MODULE, }, }; module_platform_driver(ok1028_encoder); MODULE_LICENSE("GPL"); MODULE_AUTHOR("forlinx_wht"); MODULE_DESCRIPTION("GPIO ENCODER");
Device Tree:
pwm_in{ compatible = "forlinx,pwm_in"; encoder,A_gpio = <&gpio1 26 1>; encoder,B_gpio = <&gpio2 29 1>; };
Makefile Compilation:
I put the driver under drivers/input/, so just open the Makefile file in this directory and add the following:
obj-y += pwm_inter.o
Note: obj-y : compiled into kernel obj-m : compiled as ko module
Test APP
#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" #includeint main(int argc, char *argv[]) { int fd; int ret = 0; char *filename; long int data; if (argc != 3) { printf("Error Usage!\r\n"); printf("Usage: %s\n", argv[0]); printf(": Path to the file\n"); printf(": r for read, w for write\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if (fd < 0) { printf("Can't open file %s\r\n", filename); return -1; } if (argv[2][0] == 'r') { ret = read(fd, &data, sizeof(data)); if (ret < 0) { /* data read error or invalid */ printf("read err=%d\n",ret); } else { /* the data is read correctly */ if (data) /* read the data */ printf("key value = %ld\r\n", data/4); } } else if (argv[2][0] == 'w') { ret = write(fd, "1", 1); if (ret < 0){ printf("write err=%d\n",ret); } } else{ printf("Invalid action: %s\n", argv[2]); printf("Valid actions: r (read), w (write)\n"); ret = -1; } close(fd); return ret; }
Makefile Compilation:
export CC=/home/zyh/otheruser_home/compile_user/WHT/1028/forlinx_OpenIL-v1.9-202009/sources/tools/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc TARGET=app_pwmin OBJS=app_pwmin.o CFLAGS+=-c -Wall -g LDFLAGS+= -lpthread $(TARGET):$(OBJS) $(CC) $^ $(LDFLAGS) -o $@ %.o:%.c $(CC) $^ $(CFLAGS) -o $@ clean: $(RM) *.o $(TARGET) -r install: install -m 0755 $(TARGET) /usr/bin/
Drive loading test:
Copy the compiled pwm_inter.ko or kernel, along with the device tree and app to the development board. This time, we take the example of compiling into a ko module.
It is recommended that for the first test, the #define DEBUG in the driver is changed to 1 so that you can see the basic information when the driver is loaded and run.
e.g.
Driver loading
Note: My driver here is pwm_inter_copy.ko and pwm_inter.ko are the same, the names are different because of different options during testing.
Motor forward rotation printing information:
Click to reverse the print information:
How to use app:
App to read the data:
Clear drive count in the app:
After testing is complete, turn off the print information and compile the driver into the kernel for the final version test. How to compile the driver into the kernel will not be described here, as it is assumed this is already known.
An external motor from the client has been connected, which is the one seen at the beginning. The motor is driven by PWM, and an IO is used to control its forward and reverse rotation. The PWM drive script is relatively rough, so please feel free to take a look.
#!/bin/bash echo 1 > /sys/class/pwm/pwmchip0/export echo 1000000 > /sys/class/pwm/pwmchip0/pwm1/period echo 500000 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle echo 376 > /sys/class/gpio/export echo out > /sys/class/gpio/gpio376/direction if [ "$1" == "0" ] then echo 0 > /sys/class/gpio/gpio376/value #forward rotation else echo 1 > /sys/class/gpio/gpio376/value #reverse rotation fi echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable echo 0 > /sys/class/pwm/pwmchip0/pwm1/enable #cat /sys/class/pwm/pwmchip0/pwm1/{enable,period,duty_cycle}
Let's take a look at the complete test flow:
Start the motor to rotate forward, and then read the data:
[root@ LS1028 ARDB ~/pwm] # ./pwm_out.sh 0 [root@LS1028ARDB ~/pwm] # ./app_pwmin /dev/pwm_in r key value = 200
Drive the motor to reverse, and then read the data. In order to see the difference of the data more intuitively, I reverse it twice:
[root@LS1028ARDB ~/pwm] # ./pwm_out.sh 1 [root@LS1028ARDB ~/pwm] # ./pwm_out.sh 1 [root@LS1028ARDB ~/pwm] # ./app_pwmin /dev/pwm_in r key value = -200
It can be seen that the belt motor position is now -200, that is, 200 pulse positions have been retreated.