NanoPi PWM驱动(Nanopi-S2)
- pwm相关手册说明
These timers can be used to generate internal interrupts to the ARM subsystem. In addition, Timers 0, 1, 2, and 3include a PWM function (Pulse Width Modulation) which can drive an external I/O signal. The PWM for timer 0 has a optional dead-zone generator capability, which can be utilized to support a large current device. Timer 4 is only an internal timer with no output pins.
这些定时器在ARM子系统中产生一个内部中断。另外,定时器0、1、2、3包含一个PWM功能,来驱动一个外部I/O信号。PWM定时器0有一个支持死区发生器能力,支持大电流设备,定时器4是一个内部定时器,没有引脚输出。
The Timers are normally clocked off of a divided version of the APB-PCLK. Timers 0 and 1 share a programmable 8-bit prescaler that provides the first level of division for the PCLK. Timer 2, 3, and 4 share a different 8-bit prescaler. Each timer has its own, private clock-divider that provides a second level of clock division (prescaler divided by 2, 4, 8, or 16). Alternatively, the Timers can also select a clock source from an external pin, except Timer 4. Timers 0 and 1 can select the external clock TCLK0. Timers 2 and 3 can select the external clock TCLK1. Timer 4 operates with APB-PCLK only.
定时器通常由APB-PCLK的分频提供时钟。定时器0和1共享一个可编程的8位预分频器,为PCLK提供第一级分频。 定时器2、3和4共享一个不同的8位预分频器。每个定时器都有自己的专用时钟分频器,提供第二级时钟分频(预分频器除以2,4,8或16)。除定时器4外,定时器还可从外部引脚选择一个时钟源。定时器0和1可选择外部时钟TCLK0。 定时器2和3可以选择外部时钟TCLK1。 定时器4仅使用APB-PCLK进行操作。
Each timer has its own 32-bit down-counter which is driven by the timer clock. The down-counter is initially loadedfrom the Timer Count Buffer register (TCNTBn). When the down-counter reaches zero, the timer interrupt requestis generated to inform the CPU that the timer operation is completed. When the timer down-counter reaches zero,the value of corresponding TCNTBn can be automatically reloaded into the down-counter to start the next cycle.However, if the timer stops, for example, by clearing the timer enable bit of TCONn during the timer running mode, the value of TCNTBn is not reloaded into the counter.
每个定时器都有自己的32位递减计数器,由定时器时钟驱动。 递减计数器最初从定时器计数缓冲寄存器(TCNTBn)加载。 当减计数器达到零时,产生定时器中断请求以通知CPU定时器操作完成。 当定时器减计数器达到零时,对应的TCNTBn的值可以自动重新加载到减计数器中以开始下一个循环。然而,如果定时器停止,例如:在定时器运行模式下,清除定时器的使能位TCONn,TCNTBn的值是不能重加载到计数器的。
The Pulse Width Modulation function (PWM) uses the value of the TCMPBn register. The timer control logic changes the output level when the down-counter value matches the value of the compare register in the timer control logic. Therefore, the compare register determines the turn-on time (or turn-off time) of a PWM output.
脉宽调制功能(PWM)使用TCMPBn寄存器的值。 当递减计数器的值与定时器控制逻辑中的比较寄存器的值相匹配时,定时器控制逻辑改变输出电平。 因此,比较寄存器决定PWM输出的导通时间(或关断时间)。
The TCNTBn and TCMPBn registers are double buffered to allow the timer parameters to be updated in the middle of a cycle. The new values will not take effect until the current timer cycle completes.
TCNTBn和TCMPBn寄存器是双缓冲寄存器,允许在一个周期中更新定时器参数。 新值在当前计时器周期完成之后才会生效。
-
示例
- 初始化 TCNTBn = 160 ,TCMPBn = 110
- 通过设置定时器开始位开启定时器,160被装载到TCNTBn中,输出低电平
- 当减计数器计数向下计数的值从TCNTBn减到TCMPBn寄存器值为110时,输出变化从低到高
- 当减计数器计数向下计数的值为0时,产生中断请求
- 同时,减计数器自动重加载TCNTBn,重新开始该循环
-
mmap函数
作用:将物理内存映射到虚拟内存上,通过对虚拟内存的读写,实现对物理地址的读写
头文件:
<sys/mman.h> <unistd.h>
函数原型:void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offsize);
函数返回:成功则返回映射区起始地址, 失败则返回MAP_FAILED(-1)参数说明:
- addr:指定映射的起始地址,通常设为NULL,由系统指定
- length:将文件的多大长度映射到内存
- prot:映射区的保护方式,可以是:PROT_EXEC PROT_READ PROT_WRITE PROT_NONE
- flags:映射区的特性
- MAP_SHARED:对映射区域的写入数据会复制回文件,且允许其他映射该文件的进程共享
- MAP_PRIVATE:对映射区域的写入操作会产生一个映射的复制(copy-on-write),对此区域所做的修改不会写回原文件
- 其他
- fd:由open返回的文件描述符,代表要映射的文件
- offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射
-
实际操作
以GPIOD1为例,根据手册GPIOD1为PWM0,需要使能GPIO的 Alternate Function 1功能
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define GPIOD_BASE_ADDRESS (0xC001D000)
#define GPIODOUT (*(volatile unsigned int *)GPIOD_BASE_ADDRESS)
#define GPIODOUTENB (*(volatile unsigned int *)(GPIOD_BASE_ADDRESS + 0x04))
#define GPIODALTFN0 (*(volatile unsigned int *)(GPIOD_BASE_ADDRESS + 0x20))
#define GPIODALTFN1 (*(volatile unsigned int *)(GPIOD_BASE_ADDRESS + 0x24))
#define PWM_MAP_SIZE (0x44 + 0x04)
#define GPIO_MAP_SIZE (0x24 + 0x04)
//PWM与TIMER的基址是不一样的
#define TIMER_BASE_ADDRESS (0xC0017000)
#define PWM_BASE_ADDRESS (0xC0018000)
#define TIMER_TCFG0 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x00))
#define TIMER_TCFG1 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x04))
#define TIMER_TCON (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x08))
/*Timer0*/
#define TIMER_TCNTB0 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x0C))
#define TIMER_TCMPB0 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x10))
#define TIMER_TCNTO0 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x14))
/*Timer1*/
#define TIMER_TCNTB1 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x18))
#define TIMER_TCMPB1 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x1C))
#define TIMER_TCNTO1 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x20))
/*Timer2*/
#define TIMER_TCNTB2 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x24))
#define TIMER_TCMPB2 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x28))
#define TIMER_TCNTO2 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x2C))
/*Timer3*/
#define TIMER_TCNTB3 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x30))
#define TIMER_TCMPB3 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x34))
#define TIMER_TCNTO3 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x38))
/*Timer4*/
#define TIMER_TCNTB4 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x3C))
#define TIMER_TCNTO4 (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x40))
/*Timer interrupt and status*/
#define TIMER_TINT_CSTAT (*(volatile unsigned int *)(PWM_BASE_ADDRESS + 0x44))
#define PWMTIMERCLKENB (*(volatile unsigned int *)(0xC00C0000 + 0xD000))
#define PWMTIMERCLKGEN0L (*(volatile unsigned int *)(0xC00C0000 + 0xD004))
static int dev_fd = -1;
//GPIOD1 PWM0 Alternate Function 1
int main(int argc, char **argv)
{
dev_fd = open("/dev/mem", O_RDWR | O_NDELAY);
if (dev_fd < 0)
{
printf("open(/dev/mem) failed.");
return 0;
}
unsigned int PWMBaseAddr = (unsigned int)mmap(NULL, PWM_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, PWM_BASE_ADDRESS );
unsigned int GPIOBaseAddr = (unsigned int)mmap(NULL, GPIO_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, GPIOD_BASE_ADDRESS );
PWMTIMERCLKENB |= 1<<2;
/*设置定时器时钟*/
TIMER_TCFG0 &= ~(0xff<<8);
TIMER_TCFG0 |= (20<<8);//变成10MHz的时钟输入Timer234
TIMER_TCFG1 &= ~(0xf<<16);
TIMER_TCFG1 |= (0x4<<16);//16分频为625KHz,0.0000016s每次
TIMER_TCNTB4 = 625;//装填值625,为1ms中断一次
TIMER_TINT_CSTAT |= (1<<4);//开timer4中断
TIMER_TCON &= ~(0x7<<20);//清位timer4
TIMER_TCON |= (0x7<<20);//开启timer4,自动重装模式
//page 591
GPIODOUT |= 1<<1;
GPIODOUTENB |= 1<<1;
GPIODALTFN0 |= 1<<2;
GPIODALTFN0 &= ~(1<<3);
if(dev_fd)
close(dev_fd);
munmap((unsigned int *)PWMBaseAddr,PWM_MAP_SIZE);
munmap((unsigned int *)GPIOBaseAddr,GPIO_MAP_SIZE);
return 0;
}
备注:时间关系以上代码未测试,可能时钟树的配置有问题
-
其他方案
通过高精度定时器实现pwm,参考文章,实测发现interval小于5k后,内核会重启,精度不太适合产生pwm