nRF51822使用Timer制作4路PWM波详解

Date:2015.5.8 Author:杨正  QQ:1209758756 <[email protected]>

一、            pwm简介

PWM英文名叫Pulse Width Modulation,中文名叫脉宽调制。那它到底是什么呢?其实它是由定时器产生的,比普通的定时器多了一个比较寄存器。PWM里面有一个词叫占空比,即一个周期内,高电平持续时间与周期的比值。如下图:

占空比(dutycycle) = t/T。

PWM用途:控制电机调速,控制蜂鸣器播放音乐,控制led灯亮度等

二、            Timer,PPI,GPIOTE之间的关系

由Timer产生一个事件,PPI捕获这个事件并把这个事件转化为任务传递给GPIOTE,由GPIOTE模块执行最终指定操作(该操作后面会讲到):

三、Timer定时器

要产生PWM波,需要将定时器产生的信号通过指定的引脚输出。定时器有两种模式,即计数器模式和定时器模式,要产生PWM波,自然要选择定时器模式,然而定时器模式里面也有一个计数器寄存器,即Counter。定时器模式还有一个捕获/比较寄存器,即CC寄存器。nRF51822的Timer中的Counter是递增的方式计数,当Counter的计数值与CC寄存器中的值相等时,就会产生一个事件。

nRF51822里面有三个定时器,因为TIMER0被CPU占用,所以只能使用TIMER1和TIMER2来做4路PWM波。使用TIMER1的CC[0]和CC[1]分别控制一路PWM波的频率和占空比,CC[2]和CC[3]分别控制第二路PWM波的频率和占空比。TIMER2类推,这样就可以做出4路PWM波。以下是第一路PWM波的timer init函数, 其他三路的timer init函数可以由此类推,eg:

static voidtimer1_cc01_init(void)  //第一路PWM波的timer
init函数

{

NRF_CLOCK->EVENTS_HFCLKSTARTED  = 0;

NRF_CLOCK->TASKS_HFCLKSTART     = 1;

while(NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)

{

}

NRF_TIMER1->MODE =TIMER_MODE_MODE_Timer;

NRF_TIMER1->BITMOD=TIMER_BITMODE_BITMODE_16Bit<<TIMER_BITMODE_BITMODE_Pos;  //counter与指定cc寄存器的16位进行比较

NRF_TIMER1->PRESCALER   = 9;  //9分频,每一个tick为32us

NRF_TIMER1->TASKS_CLEAR = 1;

NRF_TIMER1->CC[0]=256;    //控制周期,周期相当于256*32us

NRF_TIMER1->CC[1]= 1;    //控制占空比

NRF_TIMER1->SHORTS=(TIMER_SHORTS_COMPARE0_CLEAR_Enabled<<TIMER_SHORTS_COMPARE0_CLEAR_Pos);

NRF_TIMER1->INTENSET= (TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos);    //使能中断,中断发生时就会产生COMPARE事件,就会即把compare寄存器置1

NVIC_EnableIRQ(TIMER1_IRQn);

NVIC_SetPriority(TIMER1_IRQn,APP_IRQ_PRIORITY_HIGH);

}

void TIMER1_IRQHandler(void)   //定时器的中断处理函数;Timer2的中断处理函数类似,这里不再给出。

{

//第一路PWM

//cc0 controlperiod, cc1 controls the duty cycle

NRF_TIMER1->EVENTS_COMPARE[0]= 0; 
 //把Compare寄存器清零

NRF_TIMER1->CC[1] = duty_cycle1_01;       //每次timer中断都会重新获取CC[1]的值作为占空比

//第二路PWM

//cc2 control period, cc3 controls the duty cycle

NRF_TIMER1->EVENTS_COMPARE[2] = 0;

NRF_TIMER1->CC[3] = duty_cycle1_23;

}

四、ProgrammablePeripheral Interconnect (PPI)

nRF51822的寄存器分为三类:

Task寄存器:外设可以执行的task;

Event寄存器:外设带有的event;

普通寄存器;

Task寄存器和Event寄存器在PPI中的使用非常重要,例如,在PPI中,设置EEP寄存器为某个外设A的Event寄存器地址,TEP寄存器设为另外一个外设Task寄存器地址,那么当外设A的event发生时可以直接触发外设B的Task,而不经过CPU。

eg:

staticvoid ppi_init(void)   //初始化PPI模块,设置EEP寄存器和TEP寄存器

{

// Configure PPI channel 01 to togglePWM_OUTPUT_PIN on every TIMER1 COMPARE match.

NRF_PPI->CH[0].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];

NRF_PPI->CH[0].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[0];

NRF_PPI->CH[1].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[1];

NRF_PPI->CH[1].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[0];

// Configure PPI channel 23 to toggle PWM_OUTPUT_PIN on every TIMER1COMPARE match.

NRF_PPI->CH[2].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[2];

NRF_PPI->CH[2].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[1];

NRF_PPI->CH[3].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[3];

NRF_PPI->CH[3].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[1];

// Configure PPI channel 45 to toggle PWM_OUTPUT_PIN on every TIMER2COMPARE match.

NRF_PPI->CH[4].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[0];

NRF_PPI->CH[4].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[2];

NRF_PPI->CH[5].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[1];

NRF_PPI->CH[5].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[2];

// Configure PPI channel 67 to toggle PWM_OUTPUT_PIN on every TIMER2COMPARE match.

NRF_PPI->CH[6].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[2];

NRF_PPI->CH[6].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[3];

NRF_PPI->CH[7].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[3];

NRF_PPI->CH[7].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[3];

// Enable PPI channels 0-7.

NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled<< PPI_CHEN_CH0_Pos)

| (PPI_CHEN_CH1_Enabled<< PPI_CHEN_CH1_Pos)

| (PPI_CHEN_CH2_Enabled<< PPI_CHEN_CH2_Pos)

| (PPI_CHEN_CH3_Enabled<< PPI_CHEN_CH3_Pos)

|(PPI_CHEN_CH4_Enabled << PPI_CHEN_CH4_Pos)

|(PPI_CHEN_CH5_Enabled << PPI_CHEN_CH5_Pos)

|(PPI_CHEN_CH6_Enabled << PPI_CHEN_CH6_Pos)

|(PPI_CHEN_CH7_Enabled << PPI_CHEN_CH7_Pos);

}

注:这里NRF_PPI->CH[n]是指PPI通道。

PPI由两个端点寄存器组成,即Event End-Point (EEP),Task End-Point (TEP)。一个外设任务通过与任务相关的任务寄存器连接到TEP;同样的,一个外设事件通过与事件相关的事件寄存器连接到EEP。

NRF_PPI->CH[0].EEP对应NRF_PPI->CH[0].TEP,以此类推。

EVENT_COMPARE[0]对应CC[0],以此类推。

TASK_OUT[n]指定任务输出通道。

五、GPIOTasks and Events (gpiote)

GPIOTE模块也是设计成减少CPU占用的Task Event模式,使得事件不经过CPU直接得到响应。

Event引脚触发源:上升沿,下降沿等;

Task引脚操作方式:置位,清零,翻转;

Event和Task之间可以通过PPI连接在一起。

一旦把某个引脚分配给Task(OUT[n])或Event(IN[n]),那么该引脚只能被GPIOTE模块写操作,而普通的gpio写入无效。

当GPIOTE通道被配置用于操作一个任务引脚n,那么该引脚n的初值需要在CONFIG[n]寄存器的OUTINIT区域中设定。

eg:

staticvoid gpiote_init(void)    //初始化GPIOTE模块,设置4路PWM信号输出引脚

{

APP_GPIOTE_INIT(APP_GPIOTE_MAX_USERS);

// Configure PWM_OUTPUT_PIN_NUMBER as anoutput.

nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER0);

nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER1);

nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER2);

nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER3);

// Configure GPIOTE channel 0 to toggle thePWM pin state

// @note Only one GPIOTE task can beconnected to an output pin.

nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER0      , \

NRF_GPIOTE_POLARITY_TOGGLE,NRF_GPIOTE_INITIAL_VALUE_HIGH);

nrf_gpiote_task_config(1,PWM_OUTPUT_PIN_NUMBER1      , \

NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_HIGH);

nrf_gpiote_task_config(2,PWM_OUTPUT_PIN_NUMBER2      , \

NRF_GPIOTE_POLARITY_TOGGLE,NRF_GPIOTE_INITIAL_VALUE_HIGH);

nrf_gpiote_task_config(3,PWM_OUTPUT_PIN_NUMBER3      , \

NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_HIGH);

}

注:重要函数static__INLINE void nrf_gpiote_task_config(uint32_t channel_number, uint32_tpin_number, nrf_gpiote_polarity_t polarity, nrf_gpiote_outinit_t initial_value)

channel_number:指定GPIOTE的通道[0:3],并配置成输出通道,与PPI中的TASK_OUT[n]对应;

pin_number:指定pin管脚,在GPIOTE中使用,也就是最终被pwm控制的管脚;

polarity:指定在GPIOTE中的极性,当任务信号到达就会执行该动作;

initial_value:指定pin管脚的初始值。

六、            总结

 

问题一:通过pwm波控制灯的亮度范围是全灭到全亮,但是现在做出来的pwm波不能使灯全灭或者全亮。比如我的周期用了256,占空比的范围只能是1到255,这样的话通过占空比是不能控制灯全灭或者全亮。

解决方法:如果占空比为0,或者为256,就会在同一时间触发两个事件,如果占空比为0,即CC[1]=0,那么当计数器超过CC[0]的值(256)时,就会自动置零并从零开始重新计数,而且会产生一个事件,当计数器置零时,CC[1]的值也为零,所以CC[1]也会产生一个事件,所以同一时间会产生两个事件,分别由CC[0],CC[1]产生(这部分是个人理解)。

所以这样是不能控制灯全灭或者全亮。所以有以下办法能控制灯全亮或者全灭:

要设置该PWM引脚为低或者高,可以重新初始化这个引脚的GPIOTE:
nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER, NRF_GPIOTE_POLARITY_TOGGLE,
NRF_GPIOTE_INITIAL_VALUE_HIGH);设置为高或者低,然后把控制他的PPI disable掉。

或者还有一种方法,设置高配置为:

nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER,NRF_GPIOTE_POLARITY_LOTOHI,
NRF_GPIOTE_INITIAL_VALUE_HIGH
)

设置低配置为nrf_gpiote_task_config(0, PWM_OUTPUT_PIN_NUMBER,NRF_GPIOTE_POLARITY_HITOLO,NRF_GPIOTE_INITIAL_VALUE_LOW)

可以试试哪种方法比较好用,这里给出后面这种方法:

for (; ;)

{

power_manage();

if (duty_cycle1_01 == 255)

{

nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER0,  \

NRF_GPIOTE_POLARITY_LOTOHI,NRF_GPIOTE_INITIAL_VALUE_HIGH); //当占空比为255时,灯最暗(但是微亮),把引脚拉高,就会全灭

// nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER0,  \

NRF_GPIOTE_POLARITY_HITOLO,NRF_GPIOTE_INITIAL_VALUE_LOW);  
//当占空比为1时,灯最亮(但不是全亮),把引脚拉低,就会全亮

}

……

时间: 2024-10-08 06:36:29

nRF51822使用Timer制作4路PWM波详解的相关文章

linux学习之路之sudo详解

sudo详解 之前介绍过su的使用,su就是switch user,从一个用户切换到另一个用户 那么sudo是什么东东呢? sudo:就是让某个用户能够以另外任意一个用户的身份通过某些主机执行某些任务.记住了,是以另外 一个身份来执行命令,而不是切换到另一个用户上去哦! 但是要想让某个用户能够使用sudo来执行命令的话,必须要在sudo的配置文件定义才可以,只有在 /etc/sudoers中定义过的用户才可以执行相应的命令,这些命令也必须要在sudo的配置文件/etc/sudoers中定义才可以

xcode 制作静态库.a文件 详解

http://blog.csdn.net/kepoon/article/details/21516977 最近在做Apple的IOS开发,有开发静态库的需求,本身IOS的开发,只允许静态库或者Framework.在Xcode上没有找到允许编译,如同Android上的*.so和Win32上的dll这样的说法.不过Framework这样的框架,估计也是类似动态库的实现,不过没有具体研究过,后续继续深入研究. 我这个文档的静态库的开发是基于Xcode4.2和iOS SDK5.0编写的.Xcode4跟之

FPM打包工具制作Tengine为RPM包详解

目录 1.Tengine编译安装 2.FPM制作Tengine为RPM包 3.总结 1.Tengine编译安装 [[email protected] ~]# cat /etc/issue CentOS release 6.4 (Final) Kernel \r on an \m [[email protected] ~]# uname -r 2.6.32-358.el6.x86_64 请确保系统安装了"Development tools"和"Server Platform D

51单片机PWM程序详解

#include<reg51.h> //程序是基于KEIL-C51编写,引入8051头文件 sbit P10=P1^0; sbit P11=P1^1; unsigned int scale; //占空比控制变量 void main(void) { unsigned int n; //延时循环变量 TMOD=0x02; //定时器0,工作模式2(0000 0010),8位定时 TH0=0x06; //定时250us(12M晶振) TL0=0x06; //预置值 TR0=1; //启动定时器0 E

Python全栈之路--Django ORM详解

ORM:(在django中,根据代码中的类自动生成数据库的表也叫--code first) ORM:Object Relational Mapping(关系对象映射) 我们写的类表示数据库中的表 我们根据这个类创建的对象是数据库表里的一行数据 obj.id  obj.name.....就是数据库一行数据中的一部分数据 ORM--First: 我们在学习django中的orm的时候,我们可以把一对多,多对多,分为正向和反向查找两种方式. class UserType(models.Model):

后端程序员前端之路04--盒子模型详解

目录 图解 盒模型尺寸基准 使用浏览器的开发者工具,查看元素高(宽)度时,遇到的问题 一.图解 说明:由内而外依次是content.padding(内边距).border(边框).margin(外边距). 举个例子:一个快递车里放了许多快递包裹(盒子包装的),每个包裹里面又放了不同的东西. content:真正容纳其他东西的区域.比如快递车的content,就是那些包裹所处的那个空间: 而每个包裹的content,就是那些东西所处的空间.                     content里

C++学习之路: 构造函数详解与初始化列表

引言:这是C++对象内存分配的基础,为防止忘记. 看一个类包含其他类时是如何构造的. #include <iostream> using namespace std; class Object { public: Object() { cout << "Object.." << endl; } ~Object() { cout << "~Object.." << endl; } }; class Conta

Android之路-------Activity的详解

前言 由于接近放假,公司在赶项目所以前段LP比较忙,没什么时间总结和写博客,只是准备睡觉的时候看看书,每天看的不算多,大概10多页左右吧,不过每天坚持如此的话那也是一个庞大的数字. 今天LP的任务完成了,在领导还没分配任务之前再发布一篇关于Activity的博文.......... 上一篇跟大家分享了Android的发展史.系统框架.还有Android的四大组件,下面LP就为大家介绍下四大组件之一的Activity. 知道了Android的历史之后,有没有让Android那种打不死的精神打动了,

NRF51822自学笔记(二)PWM

PWM这个东西我在32上用来电机调速过……通过改变高低电平占空比来实现一些功能. keil的nrf51822目录下没有pwm.c..就在网上找了个pwm蜂鸣器的例程……看画风应该是官方的……吧…… 例程的define为NRF51 SETUPA BOARD_PCA10028..修改一下,设置如下. 然后通过两个灯来实现一下两路pwm波……pin为20和21的LED_2和LED_3 先看main.c(非例程) [cpp] view plain copy print? #include <stdboo