EKE 发表于 2024-7-7 12:34:51

linux开发板驱动使用timer

linux开发板驱动使用timer输出800khz,周期1.25us,占空比66%的方波。失败,用示波器测量的结果是周期为125us,占空比为50%。不论怎么改变high_time的值,占空比一直是50%不变,不知道是那有问题,还请小伙伴们指点,谢谢!








main.cpp:

#include "gpio.h"
#include <iostream>
#include <unistd.h>

void generate_pwm(GPIO &gpio, Timer &timer); // 声明 generate_pwm 函数
int main() {
    GPIO gpio;
    gpio.Init();
    Timer timer;
    timer.Init();
    gpio.ConfigPin(PORT::PE, 22, PIN_MODE::OUT);
    gpio.ConfigPin(PORT::PE, 21, PIN_MODE::OUT);

    int a = 0, b = -1;
    for (int i = 0; i < 2; i++) {

      generate_pwm(gpio, timer); // 生成PWM信号
      

      sleep(1);
    }

    gpio.Free();
    return 0;
}



gpio.cpp:


#include "gpio.h"
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <thread>
#include <chrono>
#include <time.h>// 添加这个头文件来使用 clock_nanosleep

#define TIMER_BASE 0x01C20C00// 替换为你的定时器基地址
#define TIMER_CTRL_OFFSET 0x00
#define TIMER_INTV_VALUE_OFFSET 0x04
#define TIMER_CUR_VALUE_OFFSET 0x08

#define GPIO_BASE 0x01C20800// 替换为你的GPIO基地址
#define GPIO_SET_OFFSET 0x1C
#define GPIO_CLR_OFFSET 0x28

#define PE22 (1 << 22)// PE22引脚






// GPIO 类的构造函数和析构函数
GPIO::GPIO() : fd(-1), gpio_map(nullptr), PIO(nullptr) {}
// GPIO 类的析构函数
GPIO::~GPIO() {
    Free();// 调用 Free 方法释放资源
}

// 初始化 GPIO
void GPIO::Init() {
    // 打开 /dev/mem 设备文件,以读写方式打开
    if ((fd = open("/dev/mem", O_RDWR)) == -1) {
      std::cerr << "open error /dev/mem" << std::endl;
      return;
    }

    PageSize = sysconf(_SC_PAGESIZE);// 获取系统页面大小
    PageMask = (~(PageSize - 1));// 计算页掩码

    addr_start = PIO_BASE_ADDRESS & PageMask;// 计算映射起始地址
    addr_offset = PIO_BASE_ADDRESS & ~PageMask;// 计算地址偏移量

// 使用 mmap 将设备内存映射到用户空间
    gpio_map = (unsigned int *)mmap(NULL, PageSize * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr_start);
    if (gpio_map == MAP_FAILED) {
      std::cerr << "mmap error" << std::endl;
      close(fd);// 映射失败,关闭文件描述符
      return;
    }
//计算GPIO寄存器地址..
    PIO = (PIO_Map *)((unsigned int)gpio_map + addr_offset);
    close(fd);
}

// 配置指定引脚的模式
void GPIO::ConfigPin(PORT port, unsigned int pin, PIN_MODE mode) {
    if (gpio_map == nullptr) return; // 若 gpio_map 为 nullptr,直接返回

    PIO->Pn.CFG &= ~((unsigned int)0x07 << (pin % 8) * 4);
    PIO->Pn.CFG |= ((unsigned int)mode << (pin % 8) * 4);
}

// 设置指定引脚的电平
void GPIO::SetPin(PORT port, unsigned int pin, unsigned int level) {
    if (gpio_map == nullptr) return;

    if (level)
      PIO->Pn.DAT |= (1 << pin);// 设置引脚输出高电平
    else
      PIO->Pn.DAT &= ~(1 << pin);// 设置引脚输出低电平
}

// 释放 GPIO 资源
int GPIO::Free() {
    if (munmap(gpio_map, PageSize * 2) == 0) {
      std::cout << "unmap success!" << std::endl;
    } else {
      std::cerr << "unmap failed!" << std::endl;
    }
    return 0;
}





// 设置定时器
// Timer 类的构造函数和析构函数
Timer::Timer() : fd(-1), timer_map(nullptr), timer(nullptr) {}
Timer::~Timer() {
    Free(); // 调用 Free 方法释放资源
}

// 初始化定时器
void Timer::Init() {
    // 打开 /dev/mem 设备文件,以读写方式打开
    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) {
      std::cerr << "open error /dev/mem" << std::endl;
      return;
    }

    PageSize = sysconf(_SC_PAGESIZE); // 获取系统页面大小
    PageMask = (~(PageSize - 1)); // 计算页掩码

    addr_start = TIMER_BASE & PageMask; // 计算映射起始地址
    addr_offset = TIMER_BASE & ~PageMask; // 计算地址偏移量

    // 使用 mmap 将设备内存映射到用户空间
    timer_map = (unsigned int *)mmap(NULL, PageSize * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr_start);
    if (timer_map == MAP_FAILED) {
      std::cerr << "mmap error" << std::endl;
      close(fd); // 映射失败,关闭文件描述符
      return;
    }

    // 计算定时器寄存器地址
    timer = (volatile unsigned int *)((unsigned int)timer_map + addr_offset);
    close(fd);
}





// 设置时钟源为OSC24M
void Timer::SetClockSource() {
    if (timer) {
      timer &= ~(0x3 << 2); // 清除时钟源位
      timer |= (0x1 << 2);// 设置时钟源为OSC24M
    }
}






void Timer::SetPeriod(unsigned int period_ns) {
    if (timer) {
      // 计算计数值,每个计数单位大约为41.67纳秒
      unsigned int timer_counts = static_cast<unsigned int>(period_ns / 41.67/100);

      // 设置定时器的间隔值寄存器和当前值寄存器
      timer = timer_counts;
      timer = timer_counts;
    }
}

// 启动定时器
void Timer::Start() {
    if (timer) {
      timer = 0x3; // 启用定时器,自动重载模式
    }
}

// 停止定时器
void Timer::Stop() {
    if (timer) {
      timer = 0x0; // 停止定时器
    }
}

// 释放 Timer 资源
int Timer::Free() {
    if (timer_map && munmap((void *)timer_map, PageSize * 2) == 0) { // 确保 timer_map 有效
      std::cout << "unmap success!" << std::endl;
    } else {
      std::cerr << "unmap failed!" << std::endl;
    }
    return 0;
}





void generate_pwm(GPIO &gpio, Timer &timer) {
    const int period_ns = 1250; // 设置周期为1.25µs
    const int high_time = 825; // 66% of 1250ns => 825ns
    const int low_time = period_ns - high_time; // 余下的时间为低电平

    gpio.ConfigPin(PORT::PE, 22, PIN_MODE::OUT);
    gpio.ConfigPin(PORT::PE, 21, PIN_MODE::OUT);

    while (true) {
      gpio.SetPin(PORT::PE, 21, 1);
      timer.SetPeriod(82); // 设置高电平时间
      timer.Start();
      std::this_thread::sleep_for(std::chrono::nanoseconds(high_time))
      timer.Stop();


      gpio.SetPin(PORT::PE, 21, 0);
      timer.SetPeriod(43); // 设置低电平时间
      timer.Start();
      std::this_thread::sleep_for(std::chrono::nanoseconds(low_time))
      timer.Stop();


    }
}




gpio.h:


#ifndef GPIO_H
#define GPIO_H




#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstdint> // 添加这个头文件来定义 uint32_t
#include <fcntl.h>

// 定义 PIO_BASE_ADDRESS 和页面大小
#define PIO_BASE_ADDRESS 0x01C20800

// 定义 PIO_Struct 结构体,描述每个 PIO 控制器的寄存器布局
typedef struct {
    unsigned int CFG; // 配置寄存器数组
    unsigned int DAT;    // 数据寄存器
    unsigned int DRV0;   // 驱动能力寄存器0
    unsigned int DRV1;   // 驱动能力寄存器1
    unsigned int PUL0;   // 上拉寄存器0
    unsigned int PUL1;   // 上拉寄存器1
} PIO_Struct;

// 定义 PIO_Map 结构体,描述所有 PIO 控制器的映射
typedef struct {
    PIO_Struct Pn; // PIO_Struct 数组,对应7个 PIO 控制器
} PIO_Map;

typedef enum {
    PA = 0,
    PB = 1,
    PC = 2,
    PD = 3,
    PE = 4,
    PF = 5,
    PG = 6,
} PORT;

typedef enum {
    IN = 0x00,
    OUT = 0x01,
    AUX = 0x02,
    INT = 0x06,
    DISABLE = 0x07,
} PIN_MODE;

// GPIO 类定义
class GPIO {
public:
    GPIO();   // 构造函数
    ~GPIO();// 析构函数

    void Init();                  // 初始化 GPIO
    void ConfigPin(PORT port, unsigned int pin, PIN_MODE mode);// 配置指定引脚的模式
    void SetPin(PORT port, unsigned int pin, unsigned int level);   // 设置指定引脚的电平
    int Free();                  // 释放 GPIO 资源

private:
    int fd;                         // 文件描述符
    unsigned int *gpio_map;         // 指向内存映射的 GPIO 控制器寄存器
    PIO_Map *PIO;                   // 指向 PIO_Map 的指针,用于访问 PIO 控制器的映射
    unsigned int addr_start;      // 映射起始地址
    unsigned int addr_offset;       // 地址偏移量
    unsigned int PageSize;          // 页面大小
    unsigned int PageMask;          // 页掩码
};














class Timer {
public:
    Timer();
    ~Timer();
    void Init();
    void SetPeriod(unsigned int period_ns);
    void Start();
    void Stop();
    void SetClockSource();
    bool CheckInterrupt();

private:
    int fd;
    unsigned int PageSize;
    unsigned int PageMask;
    unsigned int addr_start;
    unsigned int addr_offset;
    volatile unsigned int *timer_map;
    volatile unsigned int *timer;
    int Free();
};



void delay_ns(uint32_t ns);

void setup_timer();
void init_timer();
//void generate_pwm(gpio, timer);
void generate_pwm(GPIO &gpio, Timer &timer);
void delay2_ns(uint32_t ns);// 声明纳秒级延时函数
void delay3_ns(long nanoseconds);// 声明纳秒级延时函数
#endif // GPIO_H








FishC 发表于 2024-7-7 12:35:05

您好,小鱼油!这问题有点像在调戏硬件,但别急,咱们慢慢分析。生成800kHz、周期1.25us,占空比66%的方波,有点难度。但咱不怕,仔细看看代码和逻辑,找到问题的症结。

首先,咱需要了解你是如何设置和操作定时器的。你的代码展示了部分内容,但还不太完整。我们主要看的是 `generate_pwm` 函数如何生成PWM信号。目前你给出了一部分代码,我们需要更多信息。但是,我可以先给你一些调试建议:

1. **Timer 的配置**:
   - 确认你使用的定时器计数设置是否正确。如果周期是1.25us,那么计数值应该相应地缩小。
   - 例如,如果你的时钟频率是 24MHz,那么1.25us就对应30个时钟周期。
   
2. **占空比控制**:
   - 占空比66%意味着高电平时间约为0.825us(大约10个时钟周期),低电平时间约为0.425us(大约20个时钟周期)。
   - 确保你配置计数器的高低时间正确,并且都要在循环中设置。

3. **时序问题**:
   - 如果硬件配置和软件逻辑都有bug,可能导致输出波形不稳定。用示波器仔细检查实际输出,确认是否按你的预期变化。

4. **代码示例**:
   - 由于你提供的代码片段不完整,我无法准确判断。
   - 但是我能够给一个简化的示例来说明如何用定时器生成PWM。

假设我们有以下简化的 `generate_pwm` 函数,来用定时器实现简单的PWM:

#include <unistd.h>
#include <time.h>

// 简单的纳秒级精确延时函数
void delay_ns(long ns) {
    struct timespec req;
    req.tv_sec = 0;
    req.tv_nsec = ns;
    nanosleep(&req, NULL);
}

// generate_pwm 函数
void generate_pwm(GPIO &gpio, Timer &timer) {
    int high_time_ns = 825; // 高电平时间,单位纳秒
    int low_time_ns = 425;// 低电平时间,单位纳秒

    while (true) {
      gpio.WritePin(PORT::PE, 22, true);// 设置高电平
      delay_ns(high_time_ns);             // 高电平持续时间
      gpio.WritePin(PORT::PE, 22, false); // 设置低电平
      delay_ns(low_time_ns);            // 低电平持续时间
    }
}

- 注意,这只是一个非常简化的例子,仅适用于CPS不高且不需要其他复杂任务的情况。
- 实际工程中应使用硬件定时器或更低级别的寄存器操作,以减少上下文切换和提高实时性。

总的来说,要想实现高精度的PWM输出,你需要确保定时器配置正确,并且明确如何设置高低电平时间。尝试一步步验证,直到找到问题所在。

加油,小鱼油!希望这能帮到你,如果还有进一步的疑问,随时欢迎继续提问哦~
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
页: [1]
查看完整版本: linux开发板驱动使用timer