由于licheepi4a边上16个gpio中没有提供pwm引脚,所以只能用定时器来模拟,先来考虑一下可行性,拿一块24兆的51单片机来举例,因为24M单片机,每秒的时钟周期为24000000,pwm宽度为4096的话,那么其频率为24000000/4096=5859HZ,那么,想用定时器来模拟pwm的话,宽度与频率只能降低,那么频率应该只要在100hz左右就能满足需求,因为我们使用的交流电也才50hz,假如我使用1个1ms定时器,就是每秒执行1000次回调函数,即然要降低要求的话,肯定不能像单片机那样有4096的宽度下还能达到5859hz的频率,那么可以降低到10的话,正好可以调制出100hz的频率,如果用此pwm驱动电机转的话,有10种不同的速度可以选择,当然,我这里使用0.5ms的定时器,效果会更好,呵呵。。。
下面上驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/irq.h>
// GPIO号,GPIO1_5
#define GPIO1_5 453
/*------------------ 字符设备内容 ----------------------*/
#define GPIO_NAME1_5 "gpio1_5"
#define GPIO_CNT (1)
/*------------------ 设备数据结构体 ----------------------*/
struct gpio_dev_t
{
dev_t devid; // 设备号
struct cdev cdev; // cdev
struct class *class; // 类
struct device *device; // 设备
struct device_node *nd; // 设备节点
int irq_num; // 中断号
int gpio; // 数据接收引脚
};
struct gpio_dev_t gpio_dev; // 设备数据结构体
// 按键初始化
static int gpio_init(void)
{
int res;
/* 申请 GPIO 资源 */
gpio_dev.gpio = GPIO1_5;
res = gpio_request(gpio_dev.gpio, GPIO_NAME1_5);
if (res)
{
pr_err("key dev: Failed to request gpio\n");
return res;
}
/* 将 GPIO 设置为输出模式 */
gpio_direction_output(gpio_dev.gpio, 1);
return 0;
}
// 打开设备
static int gpio_open(struct inode *inode, struct file *filp)
{
/* 将设备数据设置为私有数据 */
filp->private_data = &gpio_dev;
printk("gpio_open\n");
return 0;
}
static ssize_t gpio_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt)
{
int ret = 0;
// struct gpio_dev_t *gpio_dev = filp->private_data;
char dat;
ret = copy_from_user(&dat, buf, sizeof(dat));
if(ret != 0) {
return -1;
}
//printk("write data %d,%d\n",dat, (int)count);
if(dat=='1'){
gpio_set_value(gpio_dev.gpio, 1);
}else{
gpio_set_value(gpio_dev.gpio, 0);
}
return 0;
}
// 关闭/释放设备
static int gpio_release(struct inode *inode, struct file *filp)
{
int res = 0;
printk("gpio_release\n");
return res;
}
/* 设备操作函数结构体 */
static struct file_operations gpio_ops = {
.owner = THIS_MODULE,
.open = gpio_open,
.write = gpio_write,
.release = gpio_release,
};
// 注册字符设备驱动
static int gpio_register(void)
{
int ret = -1; // 保存错误状态码
/* GPIO 中断初始化 */
ret = gpio_init();
/* 1、创建设备号 */
/* 采用动态分配的方式,获取设备编号,次设备号为0 */
/* 设备名称为 GPIO_NAME1_5,可通过命令 cat /proc/devices 查看 */
/* GPIO_CNT 为1,只申请一个设备编号 */
ret = alloc_chrdev_region(&gpio_dev.devid, 0, GPIO_CNT, GPIO_NAME1_5);
if (ret < 0)
{
pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", GPIO_NAME1_5, ret);
goto fail_region;
}
/* 2、初始化 cdev */
/* 关联字符设备结构体 cdev 与文件操作结构体 file_operations */
gpio_dev.cdev.owner = THIS_MODULE;
cdev_init(&gpio_dev.cdev, &gpio_ops);
/* 3、添加一个 cdev */
/* 添加设备至cdev_map散列表中 */
ret = cdev_add(&gpio_dev.cdev, gpio_dev.devid, GPIO_CNT);
if (ret < 0)
{
pr_err("fail to add cdev \r\n");
goto del_unregister;
}
/* 4、创建类 */
gpio_dev.class = class_create(THIS_MODULE, GPIO_NAME1_5);
if (IS_ERR(gpio_dev.class))
{
pr_err("Failed to create device class \r\n");
goto del_cdev;
}
/* 5、创建设备,设备名是 GPIO_NAME1_5 */
/*创建设备 GPIO_NAME1_5 指定设备名,*/
gpio_dev.device = device_create(gpio_dev.class, NULL, gpio_dev.devid, NULL, GPIO_NAME1_5);
if (IS_ERR(gpio_dev.device))
{
goto destroy_class;
}
return 0;
destroy_class:
device_destroy(gpio_dev.class, gpio_dev.devid);
del_cdev:
cdev_del(&gpio_dev.cdev);
del_unregister:
unregister_chrdev_region(gpio_dev.devid, GPIO_CNT);
fail_region:
gpio_free(gpio_dev.gpio);
return -EIO;
}
// 注销字符设备驱动
static void gpio_unregister(void)
{
/* 1、删除 cdev */
cdev_del(&gpio_dev.cdev);
/* 2、注销设备号 */
unregister_chrdev_region(gpio_dev.devid, GPIO_CNT);
/* 3、注销设备 */
device_destroy(gpio_dev.class, gpio_dev.devid);
/* 4、注销类 */
class_destroy(gpio_dev.class);
/* 释放 IO */
gpio_free(gpio_dev.gpio);
}
// 驱动入口函数
static int __init gpio_driver_init(void)
{
pr_info("gpio_driver_init\n");
return gpio_register();
}
// 驱动出口函数
static void __exit gpio_driver_exit(void)
{
pr_info("gpio_driver_exit\n");
gpio_unregister();
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
/* LICENSE 和作者信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("乘简");
应用代码pwm.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
timer_t timerid;
int fd;//文件指针
int pwm_width=0;//pwm宽度
int pwm_low=0;//低电平时间
int pwm_count=0;//计数器
int pwm_start=0;//1为开启,0为不开启,不开启时为高电平
char pwm_value=0;//当前gpio的值
long get_system_time_microsecond()
{
struct timeval timestamp = {};
if (0 == gettimeofday(×tamp, NULL))
return timestamp.tv_sec * 1000000 + timestamp.tv_usec;
else
return 0;
}
//设为低电平
void setLow(){
if(pwm_value != '0'){
pwm_value = '0';
write(fd, &pwm_value, 1);//设置电平
}
}
//设为高电平
void setHigh(){
if(pwm_value != '1'){
pwm_value = '1';
write(fd, &pwm_value, 1);//设置电平
}
}
//开启pwm
void setStart(){
setHigh();//设置高电平
pwm_start = 1;//开启pwm
}
//关闭pwm
void setClose(){
pwm_start = 0;
}
void timer_handler(int signo, siginfo_t *info, void *v)
{
if(pwm_start){
if(pwm_count < pwm_low){//计算器小于所设定的低电平时间
setLow();
}else{
setHigh();
}
pwm_count = ++pwm_count % pwm_width;//计算器+1
}else{//没开启
if(pwm_value != 0){//不为高电平
pwm_count=0;//计算器
setHigh();
pwm_value=0;
}
}
}
int main(int argc, char *argv[])
{
struct sigevent sev;
struct itimerspec its;
struct sigaction sa;
if(argc!=3){
printf("./pwm 5 3\n");
return -1;
}
pwm_width = atoi(argv[1]);
pwm_low = atoi(argv[2]);
if(pwm_low > pwm_width)pwm_low = pwm_width;
fd=open("/dev/gpio1_5", O_RDWR);
if(fd<0){
printf("can't open file %s",argv[1]);
return -1;
}
// 设置信号处理函数
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
printf("sigaction() failed\n");
return 1;
}
// 创建定时器
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGRTMIN;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
printf("timer_create() failed\n");
return 1;
}
// 设置定时器
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 500000;//500us
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 500000;
if (timer_settime(timerid, 0, &its, NULL) == -1) {
printf("timer_settime() failed\n");
return 1;
}
setStart();
// 等待定时器事件
while (1) {
sleep(1);
}
close(fd);
return 0;
}
执行效果测试,输入
sudo ./app 10 0
此时量GPIO1_5的电压为1.8V
sudo ./app 10 2
此时量电压为1.44V
sudo ./app 10 5
电压为0.9V
sudo ./app 10 9
电压为0.17V
sudo ./app 10 10
此时为0V
但这种办法,会占用18%左右的CPU,不知道有没有更好的办法实现
本帖最后由 乘简 于 2023-11-23 17:07 编辑
引用: Jacktang 发表于 2023-11-24 07:26 用0.5ms的定时器,效果会更好,这个是真的
就是直接写个定时器程序,回调函数中什么语句都不写,CPU占用也是出奇的高,难道是linux的定时器冗余代码太多了吗?这么吃CPU,真是无语。。。