我的开发板是学习板,很多管脚没有引出。弄到定时器产生PWM这一块,想用下管脚重映射功能,毕竟库函数没有像操作寄存器那样容易理解。所以一开始着实糊涂了一会,最后还是给调出来了。结合很多别人的例程,但是发现都不够详细,所以就共享下,如果有同样问题的同学,希望能给你帮助。下面就共享下main.c。声明:以下是本人开发板的管道对应引脚
复用功能 | 没有重映射 | 部分重映射 | 完全重映射 |
TIM3_CH1 | PA6 | PB4 | PC6 |
CH2 | PA7 | PB5 | PC7 |
CH3 | PB0 | PB0 | PC8 |
CH4 | PB1 | PB1 | PC9 |
/**
重映射 tim3 使得PC7 PC8输出Pwm波
******************************************************************************
* @file Project/Template/main.c
* @author MCD Application Team
* @version V3.0.0
* @date 04/06/2009
* @brief Main program body
******************************************************************************
* @copy
*
* THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
* WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
* TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
* DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
* FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
* CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* <h2><center>© COPYRIGHT 2009 STMicroelectronics</center></h2>
*/
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "stm32f10x_tim.h"
#include "misc.h"
void GPIO_AFIODeInit(void
);//这个函数不能落下,否则不能成功
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//配置PWM需要使用的结构体声名
TIM_OCInitTypeDef TIM_OCInitStructure;
u16 CCR3_Val =250;
u16 CCR4_Val =250;
void GPIO_Config(void);
void Timer_Config(void);
/**
* @brief Delay program.
* @param None
* @retval : None
*/
void Delay (u32 nCount)
{
for(; nCount != 0; nCount--);
}
/**
* @brief Main program.
* @param None
* @retval : None
*/
int main(void)
{
/* Setup STM32 system (clock, PLL and Flash configuration) */
SystemInit();
GPIO_Config();
Timer_Config();
/* Infinite loop */
while (1) {
}
}
/**
* @brief GPIO_Config program.
* @param None
* @retval : None
*/
void GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC |RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE);
GPIO_AFIODeInit();
//刚开始没有这个语句,所以一直没有重映射成功
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3 , ENABLE);
//这个就是重映射功能函数
/*GPIOA Configuration: TIM3 channel 3 and 4 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//这里也要注意
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//这四句是为了校验究竟PB0,PB1是否还有PWM输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void Timer_Config(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_DeInit(TIM3);
TIM_TimeBaseStructure.TIM_Period = 999;
//在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = 0;
// 设置了用来作为 TIMx 时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//TIM_OCMode
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
//设置了待装入捕获比较寄存器的值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
//TIM_OCPolarity :TIM 输出比较极性高
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
//使能或者失能 TIM3 在 CCR3 上的预装载寄存器
/* PWM1 Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
/* TIM3 enable counter */
TIM_Cmd(TIM3, ENABLE);
//开启时钟,别落下
}
/**
* @brief EXTI_Config Program.
* @param None
* @retval : None
*/
/**
* @brief Configures the nested vectored interrupt controller.
* @param None
* @retval : None
*/
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval : None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
/**
* @}
*/
/******************* (C) COPYRIGHT 2009 STMicroelectronics *****END OF FILE****/
//=================华丽的分割线=================//
高级控制定时器(TIM1 和TIM8)
TIM1和TIM8定时器的功能包括:
● 16位向上、向下、向上/ 下自动装载计数器
● 16位可编程( 可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535 之间的任意数值
● 多达4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 死区时间可编程的互补输出
● 使用外部信号控制定时器和定时器互联的同步电路
● 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
● 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
● 如下事件发生时产生中断/DMA :
─ 更新:计数器向上溢出/ 向下溢出,计数器初始化(通过软件或者内部/ 外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/ 外部触发计数)
─ 输入捕获
─ 输出比较
─ 刹车信号输入
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
通用定时器(TIMx)
通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括:
● 16位向上、向下、向上/ 向下自动装载计数器
● 16位可编程( 可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536 之间的任意数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA :
─ 更新:计数器向上溢出/ 向下溢出,计数器初始化(通过软件或者内部/ 外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/ 外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
基本定时器(TIM6 和TIM7)
TIM6和TIM7定时器的主要功能包括:
● 16位自动重装载累加计数器
● 16位可编程( 可实时修改)预分频器,用于对输入的时钟按系数为1~65536 之间的任意数值
分频
● 触发DAC的同步电路
● 在更新事件(计数器溢出)时产生中断/DMA 请求
STM32的通用定时器是一个通过可编程预分频器(PSC)驱动的16 位自动装载计数器(CNT)构成。STM32的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32的每个通用定时器都是完全独立的,没有互相共享的任何资源。
注意单元模块时钟总线
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。STM32的定时器除了TIM6和7。其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,这样,STM32最多可以同时产生30路PWM输出!
要使STM32的通用定时器TIMx产生PWM输出,除了定时器介绍的寄存器外,我们还会用到3 个寄存器,来控制PWM 的。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。
TIM3_CH2默认是接在PA7面的,而我们的DS0接在PB5上面,如果普通MCU,可能就只能用飞线把PA7飞到PB5上来实现了,不过,我们用的是STM32,它比较高级,可以通过重映射功能,把TIM3_CH2映射到PB5上。 STM32的重映射控制是由复用重映射和调试IO 配置寄存器(AFIO_MAPR)控制的。
1)开启TIM3时钟以及复用功能时钟,配置PB5为复用输出。
要使用TIM3,我们必须先开启TIM3的时钟,还要配置PB5为复用输出,这是因为TIM3_CH2通道将重映射到PB5上,此时,PB5属于复用功能输出。
库函数使能TIM3时钟的方法是:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
库函数设置AFIO时钟的方法是:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //复用时钟使能
设置PB5为复用功能输出的方法:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
2)设置TIM3_CH2重映射到PB5上。
因为TIM3_CH2默认是接在PA7上的,所以我们需要设置TIM3_REMAP为部分重映射(通过AFIO_MAPR配置),让TIM3_CH2重映射到PB5上面。在库函数函数里面设置重映射的函数是:
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
STM32重映射只能重映射到特定的端口。第一个入口参数可以理解为设置重映射的类型,比如TIM3部分重映射入口参数为 GPIO_PartialRemap_TIM3,这点可以顾名思义了。所以TIM3部分重映射的库函数实现方法是: GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
3)初始化TIM3,设置TIM3的ARR和PSC。
在开启了TIM3的时钟之后,我们要设置ARR和PSC两个寄存器的值来控制输出PWM的周期。当PWM周期太慢(低于50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM周期在这里不宜设置的太小。这在库函数是通过TIM_TimeBaseInit函数实现的,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为:
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的
4)设置TIM3_CH2的PWM模式,使能TIM3的CH2输出。
接下来,我们要设置TIM3_CH2为PWM模式(默认是冻结的),因为我们的DS0是低电平亮,而我们希望当CCR2的值小的时候,DS0就暗,CCR2值大的时候,DS0就亮,所以我们要通过配置TIM3_CCMR1的相关位来控制TIM3_CH2的模式。在库函数中,PWM通道设置是通过函数TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道2,所以使用的函数是TIM_OC2Init()。
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
5)使能TIM3。
在完成以上设置了之后,我们需要使能TIM3。使能TIM3的方法前面已经讲解过: TIM_Cmd(TIM3, ENABLE); //使能TIM3
6)修改TIM3_CCR2来控制占空比。
最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改TIM3_CCR2则可以控制CH2的输出占空比。继而控制DS0的亮度。 在库函数中,修改TIM3_CCR2占空比的函数是:
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2); 理所当然,对于其他通道,分别有一个函数名字,函数格式为TIM_SetComparex(x=1,2,3,4)。
通过以上6个步骤,我们就可以控制TIM3的CH2输出PWM波了。
//=================华丽分割线=====================//
STM32 PWM输出以及端口重映射
2013年11月19日
⁄ 裸机开发 ⁄ 共 2252字 ⁄ 字号
小 中 大
⁄ 阅读 1,884 次
由于STM32没有专门的PWM引脚,所以使用IO口的复用模式,通用定时器TIM2-TIM5每个可以产生4路PWM(CH1-CH4)。
一:首先,配置GPIO为复用模式,如下图:
配置GPIO为复用模式
配置GPIO为复用模式
配置GPIO为复用模式
配置GPIO为复用模式
例如开启TIM3的CH2通道PWM,查表知该通道关联的是PA7口,所以,配置PA7为输出,并设置它为复用输出。
GPIOA->CRL&=0X0FFFFFFF;//PA7输出
GPIOA->CRL|=0XB0000000;//复用功能输出
如何选择开启每个定时器中的4路PWM呢?这就用到捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器总共有2个,TIMx _CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和2,而TIMx_CCMR1控制CH3和4。该寄存器如图(下排为输入模式):
TIMx_CCMR控制寄存器
这里要用到的是模式设置位(12-14、4-6位)OCxM,此部分由3位组成。总共可以配置成7种模式,若要使用PWM模式,则将这3位设置为110/111。这两种PWM模式的区别就是输出电平的极性相反,即高电平或低电平时间所占总周期的比例,110为高电平时间所占总周期比例;
还有使能位11位、3位,置1预装载使能CH2和CH1。
预装载使能打开并未真正打开PWM通道,还有两个开关,一个是单通道使能—TIMx_CCER寄存器,一个是TIMx开关TIMx_CRx。
TIMx_CCER寄存器控制CH1—CH4输入输出通道的开关,只需设置对应的CCxE位为1即可将PWM信号输出到对应的输出引脚。 该寄存器如下图:
TIMx_CCER寄存器
TIMx_CRx寄存器:需要打开该寄存器自动重装载预装载允许位ARPE (第7位)。注:个人认为不对该寄存器操作也可。
二:配置TIM3的ARR寄存器和PSC寄存器,确定PWM频率。
这里配置的这两个定时器确定了PWM的频率,我的理解是:PWM的周期(频率)就是ARR寄存器值与PSC寄存器值相乘得来,但不是简单意义上的相乘,例如要设置PWM的频率参考上次通用定时器中设置溢出时间的算法,例如输出100HZ频率的PWM,首先,确定TIMx的时钟,除非APB1的时钟分频数设置为1,否则通用定时器TIMx的时钟是APB1时钟的2倍,这时的TIMx时钟为72MHz,用这个TIMx时钟72MHz除以(PSC+1),得到定时器每隔多少秒涨一次,这里给PSC赋7199,计算得定时器每隔0.0001秒涨一次,即此时频率为10KHz,再把这个值乘以(ARR+1)得出PWM频率,假如ARR值为0,即0.0001*(0+1),则输出PWM频率为10KHz,再假如输出频率为100Hz的PWM,则将ARR寄存器设置为99即可。如果想调整PWM占空比精度,则只需降低PSC寄存器的值即可。
三:TIMx_CCRx寄存器,确定PWM的占空比。
TIMx_CCR1—TIMx_CCR4确定定时器的CH1—CH4四路PWM的占空比。直接给该寄存器赋0—65535值即可确定占空比。占空比计算方法:TIMx_CCRx的值除以ARR寄存器的值即为占空比,因为占空比在0—100%之间,所以一般TIMx_CCRx寄存器值不能超过ARR寄存器的值,否则可能会引起PWM的频率或占空比的准确性。
TIMx_CCRx寄存器如下图所示:
TIMx_CCRx寄存器
STM32 PWM输出函数(举例TIM3-CH2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
void
PWM_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<1;
GPIOA->CRL&=0X0FFFFFFF;
GPIOA->CRL|=0XB0000000;
TIM3->ARR=arr;
TIM3->PSC=psc;
TIM3->CCMR1|=6<<12;
TIM3->CCMR1|=1<<11;
TIM3->CCER|=1<<4;
TIM3->CR1=0x80;
TIM3->CR1|=0x01;
}
|