呼吸灯是灯从渐亮到渐灭周而复始形成的一个效果。由于51没有PWM所以需要定时器模拟PWM才能实现呼吸灯的效果,但是stm32的通用定时器是有PWM模式的,所以不需要再用软件模拟,精准度也高。
本实验用的基于stm32f103C8t6。在PB8引脚上接了一个led, led的另一端接到vcc上。
PB8除了是一个GPIO功能,还有一个复用功能即定时器4的channel 3功能。可以通过参考手册知晓。
具体配置就不细说了,这里将TIM4的关键配置标了出来
记得选中PWM 的模式1 和使能比较输出,CH Polarity设置Low 和 High 在呼吸灯这里无影响。
时钟的溢出配置公式如下:
这里将定时器设置为500ms,即Tout = 500ms,同时PSC = 71,ARR = 499, Tclk = 72MHZ。根据公式计算出Tout = (71 + 1) * (499 + 1) / 72000000 = 500ms。
int main(void)
{
uint16_t pwmVal = 0;
uint8_t dir = 1;
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM4_Init();
/* USER CODE BEGIN 2 */
// 开启定时器4
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3);
while (1)
{
HAL_Delay(1);
if(dir) {
pwmVal ++;
} else {
pwmVal--;
}
if(pwmVal > 500) {
dir = 0;
} else if(pwmVal <= 0) {
dir = 1;
}
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
/*
// 常亮
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 0);
// 常灭
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 500);
*/
}
}
下图是捕获/比较的输出阶段:
根据上图可以推出四种结果分别是
PWM模式1
PWM模式2
上图中绿框部分是有效的电平。这里有有效电平是OCxREF 参考电平。
OCx有效电平
手册中还有另外一个描述就是:
The output stage generates an intermediate waveform which is then used for reference:OCxRef (active high). The polarity acts at the end of the chain.
翻译一下就是:
输出部分产生一个中间波形OCxRef(高有效)作为基准,链的末端决定最终输出信号的极性。
CC1通道作为输出模式
在参考文档中有这样一个表格:
CCxE = 0时,禁止OCx输出,CCxE = 1时,OCx = OCxREF + Polarity
这里的OCxREF + Polarity是什么意思。这里先说明下这里是xor(异或)的意思。
我们可以从以下分析出:
在参考文档中的TIM1定时器章节有这样一个表格:
红色框中圈住的部分写出了OCx = OCxREF xor CCxP,当然这个表格是在TIM1和TIM8里出现的,像表格中的MOE,OSSI,OSSR,CCxNE,都是在TIM1和TIM8寄存器中存在的,在通用定时器里是没有的。
MOE,OSSI,OSSR存在于TIM1和TIM8寄存器中的BDTR,CCxNP和CCxNE也只存在于TIM1和TIM8定时器中的CCER寄存器。
通用定时器中的CCER (reserved部分要保持为0,即保持reset时的值)
在通用定时器里面,OSSR 无效, CCxNE = 0, OSSI无效,MOE 无效,所以异或操作还是适用的。
由
和 OCx = OCxREF xor CCxP
得出以下最终结果(绿色部分为有效输出):
总结:
PWM的主要流程大致如下:
代码主要是根据 定时器4的channel 3 + 向上计数模式 + 500ms 定时周期 这个为中心产生的。定时器涉及的寄存器比较多,定时总共有20种寄存器,在PWM输出模式下,用到的其实并不多。涉及的寄存器如下:
CR1 (control register)
CR2 (control register)
SMCR (slave mode control register)
EGR (event generation register)
CCMR (capture/compare mode register 2 )
CCER (capture/compare enable register )
CCR3 (capture/compare register 3 )
函数比较长,大致将功能分了下类,具体函数如下:
void MX_TIM4_Init(void)
{
// 这里主要是根据功能将寄存器分成几个模块进行配置
// 时钟相关配置
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
// 主从模式配置
TIM_MasterConfigTypeDef sMasterConfig = {0};
// 定时器输出捕获常规配置
TIM_OC_InitTypeDef sConfigOC = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 71;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 499;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
// 1、定时器常规初始化
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
// 2、定时器时钟配置
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
// 3、定时器PWM初始化
if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
// 4、定时器主从模式配置
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// 5、定时器channel配置
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
// 6、定时器主栈地址初始化
HAL_TIM_MspPostInit(&htim4);
}
上面主要做了下面几件事
下面主要针对上面的过程进行描述
TIM_TypeDef
typedef struct
{
__IO uint32_t CR1; /*!< TIM control register 1, Address offset: 0x00 */
__IO uint32_t CR2; /*!< TIM control register 2, Address offset: 0x04 */
__IO uint32_t SMCR; /*!< TIM slave Mode Control register, Address offset: 0x08 */
__IO uint32_t DIER; /*!< TIM DMA/interrupt enable register, Address offset: 0x0C */
__IO uint32_t SR; /*!< TIM status register, Address offset: 0x10 */
__IO uint32_t EGR; /*!< TIM event generation register, Address offset: 0x14 */
__IO uint32_t CCMR1; /*!< TIM capture/compare mode register 1, Address offset: 0x18 */
__IO uint32_t CCMR2; /*!< TIM capture/compare mode register 2, Address offset: 0x1C */
__IO uint32_t CCER; /*!< TIM capture/compare enable register, Address offset: 0x20 */
__IO uint32_t CNT; /*!< TIM counter register, Address offset: 0x24 */
__IO uint32_t PSC; /*!< TIM prescaler register, Address offset: 0x28 */
__IO uint32_t ARR; /*!< TIM auto-reload register, Address offset: 0x2C */
__IO uint32_t RCR; /*!< TIM repetition counter register, Address offset: 0x30 */
__IO uint32_t CCR1; /*!< TIM capture/compare register 1, Address offset: 0x34 */
__IO uint32_t CCR2; /*!< TIM capture/compare register 2, Address offset: 0x38 */
__IO uint32_t CCR3; /*!< TIM capture/compare register 3, Address offset: 0x3C */
__IO uint32_t CCR4; /*!< TIM capture/compare register 4, Address offset: 0x40 */
__IO uint32_t BDTR; /*!< TIM break and dead-time register, Address offset: 0x44 */
__IO uint32_t DCR; /*!< TIM DMA control register, Address offset: 0x48 */
__IO uint32_t DMAR; /*!< TIM DMA address for full transfer register, Address offset: 0x4C */
__IO uint32_t OR; /*!< TIM option register, Address offset: 0x50 */
}TIM_TypeDef;
TIM_TypeDef 结构体包括了定时器所有的寄存器,通过操作结构体就可以操作寄存器。在初始化的时候有这样一句代码 htim4.Instance = TIM4 这里的TIM4就是定时器4在外设中的地址,TIM4也是一个宏,具体就不展开了,它的定义和 GPIO类似,可参考GPIO,或自行在代码中查看。
TIM_HandleTypeDef
typedef struct
{
uint32_t Prescaler; // 配置时基单元中的预分频器
uint32_t CounterMode; // 计数模式(向上/向下/中央对齐)
uint32_t Period; // 定时周期(period + 1)
uint32_t ClockDivision; // 时钟分频因子
uint32_t RepetitionCounter; // 重复定时器(高级定时器中用)
uint32_t AutoReloadPreload; // 是否自动重装初值
} TIM_Base_InitTypeDef;
HAL_TIM_ActiveChannel (选中的channel)
typedef enum
{
HAL_TIM_ACTIVE_CHANNEL_1 = 0x01U, /*!< The active channel is 1 */
HAL_TIM_ACTIVE_CHANNEL_2 = 0x02U, /*!< The active channel is 2 */
HAL_TIM_ACTIVE_CHANNEL_3 = 0x04U, /*!< The active channel is 3 */
HAL_TIM_ACTIVE_CHANNEL_4 = 0x08U, /*!< The active channel is 4 */
HAL_TIM_ACTIVE_CHANNEL_CLEARED = 0x00U /*!< All active channels cleared */
} HAL_TIM_ActiveChannel;
TIM_HandleTypeDef(保存定时器相关配置,状态和方法,下面进行了精简)
{
TIM_TypeDef *Instance; // 定时器寄存器集合
TIM_Base_InitTypeDef Init; // 定时器基本配置
HAL_TIM_ActiveChannel Channel; //使用的channel
DMA_HandleTypeDef *hdma[7];
HAL_LockTypeDef Lock; //是否进行锁定,配置完成之后都要进行锁定
__IO HAL_TIM_StateTypeDef State; //定时器状态
__IO HAL_TIM_ChannelStateTypeDef ChannelState[4]; // channel的状态,总共有四个channel
__IO HAL_TIM_ChannelStateTypeDef ChannelNState[4];
__IO HAL_TIM_DMABurstStateTypeDef DMABurstState;
// 函数指针就写了一个,其它的看源码哈
void (* Base_MspInitCallback)(struct __TIM_HandleTypeDef *htim);
} TIM_HandleTypeDef;
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
{
/* Check the TIM handle allocation */
if (htim == NULL)
{
return HAL_ERROR;
}
// 结构体初始化时未设置,默认是RESET状态
if (htim->State == HAL_TIM_STATE_RESET)
{
htim->Lock = HAL_UNLOCKED;
// 由于未注册定时器回调,这里把回调相关的方法删除了
HAL_TIM_Base_MspInit(htim);
}
// 设置busy状态,防止操作定时器
htim->State = HAL_TIM_STATE_BUSY;
// 将Init中的配置同步到Tim4的寄存器中
TIM_Base_SetConfig(htim->Instance, &htim->Init);
// 未涉及DMA
htim->DMABurstState = HAL_DMA_BURST_STATE_READY;
//将四个channel设置成ready
TIM_CHANNEL_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
//将四个互补channel设置成ready(暂时无用)
TIM_CHANNEL_N_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
// 设置就绪状态
htim->State = HAL_TIM_STATE_READY;
return HAL_OK;
}
初始化核心代码是TIM_Base_SetConfig这个函数,具体实现如下:
void TIM_Base_SetConfig(TIM_TypeDef *TIMx, const TIM_Base_InitTypeDef *Structure)
{
uint32_t tmpcr1;
tmpcr1 = TIMx->CR1;
// 只要是TIM1-4 就会成立(这是一个简单的宏)
if (IS_TIM_COUNTER_MODE_SELECT_INSTANCE(TIMx))
{
// 清除CR1寄存器中的DIR 和CMS位
// DIR是CR1中的第4位,CMS是5 6 位
// DIR = 10000b CMS = 1100000
// 下面的意思是将DIR和CMS清0
tmpcr1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
// 重新设置计数模式, 这里只设置了DIR,CMS保持00,00状态就是边沿对齐模式(向上或向下)
// 下面的意思就是设置了边沿对齐的向上计数模式
tmpcr1 |= Structure->CounterMode;
}
// 只要是TIM1-4 就会成立(这是一个简单的宏)
if (IS_TIM_CLOCK_DIVISION_INSTANCE(TIMx))
{
// 清除时钟分频因子TIM_CR1_CKD = 1100000000b,下面就是清除CKD
tmpcr1 &= ~TIM_CR1_CKD;
// 重新配置时钟分频因子,本安全中外部传入的是0
tmpcr1 |= (uint32_t)Structure->ClockDivision;
}
// 这里先清除CR1中ARPE位,然后根据AutoReloadPreload配置,就是是否使能自动重装初值
MODIFY_REG(tmpcr1, TIM_CR1_ARPE, Structure->AutoReloadPreload);
// 配置CR1 寄存器
TIMx->CR1 = tmpcr1;
// 配置ARR自动重装寄存器
TIMx->ARR = (uint32_t)Structure->Period ;
// 配置PSC寄存器
TIMx->PSC = Structure->Prescaler;
//TIM1 才有效
if (IS_TIM_REPETITION_COUNTER_INSTANCE(TIMx))
{
/* Set the Repetition Counter value */
TIMx->RCR = Structure->RepetitionCounter;
}
// 配置事件产生寄存器UG代码第0位,数值1代表定时器溢出时会产生更新事件
TIMx->EGR = TIM_EGR_UG;
}
上面的代码主要是设置CR1、ARR、PSC、EGR和RCR(TIM1才有效)寄存器。
项目中用到的是内部时钟,所以代码简化如下,这个函数主要处理SMCR寄存器的配置。
HAL_StatusTypeDef HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim, const TIM_ClockConfigTypeDef *sClockSourceConfig)
{
HAL_StatusTypeDef status = HAL_OK;
uint32_t tmpsmcr;
__HAL_LOCK(htim);
htim->State = HAL_TIM_STATE_BUSY;
tmpsmcr = htim->Instance->SMCR;
// 下面的意思重置从模式寄存器所有位除了MSM位,
tmpsmcr &= ~(TIM_SMCR_SMS | TIM_SMCR_TS);
tmpsmcr &= ~(TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP);
htim->Instance->SMCR = tmpsmcr;
htim->State = HAL_TIM_STATE_READY;
__HAL_UNLOCK(htim);
return status;
}
由于未开启PWM回调, 这里的操作和定时器常规初始化几乎一样
HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)
{
if (htim->State == HAL_TIM_STATE_RESET)
{
htim->Lock = HAL_UNLOCKED;
// 这里的条件不成立
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
/* Reset interrupt callbacks to legacy weak callbacks */
TIM_ResetCallback(htim);
if (htim->PWM_MspInitCallback == NULL)
{
htim->PWM_MspInitCallback = HAL_TIM_PWM_MspInit;
}
htim->PWM_MspInitCallback(htim);
#else
/* Init the low level hardware : GPIO, CLOCK, NVIC and DMA */
// 这里是一个空操作
HAL_TIM_PWM_MspInit(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* Set the TIM state */
htim->State = HAL_TIM_STATE_BUSY;
// 重新走了一下定时器的配置
TIM_Base_SetConfig(htim->Instance, &htim->Init);
htim->DMABurstState = HAL_DMA_BURST_STATE_READY;
TIM_CHANNEL_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
TIM_CHANNEL_N_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
htim->State = HAL_TIM_STATE_READY;
return HAL_OK;
}
代表也非常简单,大致如下:
HAL_StatusTypeDef HAL_TIMEx_MasterConfigSynchronization(TIM_HandleTypeDef *htim,
const TIM_MasterConfigTypeDef *sMasterConfig)
{
uint32_t tmpcr2;
uint32_t tmpsmcr;
__HAL_LOCK(htim);
htim->State = HAL_TIM_STATE_BUSY;
tmpcr2 = htim->Instance->CR2;
tmpsmcr = htim->Instance->SMCR;
// 清除CR2寄存器中的MMS位,即 4 5 6 都是0
tmpcr2 &= ~TIM_CR2_MMS;
// 设置新的MMS主模式选择
tmpcr2 |= sMasterConfig->MasterOutputTrigger;
// 将CR2配置到寄存器中
htim->Instance->CR2 = tmpcr2;
if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
{
// 清除SMCR中的msm(主从模式选择)
tmpsmcr &= ~TIM_SMCR_MSM;
// 外部传入的是DISABLE = 0,0代表无作用
tmpsmcr |= sMasterConfig->MasterSlaveMode;
// 设置回寄存器
htim->Instance->SMCR = tmpsmcr;
}
htim->State = HAL_TIM_STATE_READY;
__HAL_UNLOCK(htim);
return HAL_OK;
}
选调用 HAL_TIM_PWM_ConfigChannel,内部对channel3的处理如下
TIM_OC3_SetConfig(htim->Instance, sConfig);
/* Set the Preload enable bit for channel3 */
htim->Instance->CCMR2 |= TIM_CCMR2_OC3PE;
/* Configure the Output Fast mode */
htim->Instance->CCMR2 &= ~TIM_CCMR2_OC3FE;
htim->Instance->CCMR2 |= sConfig->OCFastMode;
break;
核心代码 是TIM_OC3_SetConfig函数
static void TIM_OC3_SetConfig(TIM_TypeDef *TIMx, const TIM_OC_InitTypeDef *OC_Config)
{
uint32_t tmpccmrx;
uint32_t tmpccer;
uint32_t tmpcr2;
tmpccer = TIMx->CCER;
// 清除CCE使能位
TIMx->CCER &= ~TIM_CCER_CC3E;
tmpcr2 = TIMx->CR2;
tmpccmrx = TIMx->CCMR2;
// 清除CCMR2(输入捕获寄存器)0C3M(输出比较3模式),CC3S(捕获比较3选择)
tmpccmrx &= ~TIM_CCMR2_OC3M;
tmpccmrx &= ~TIM_CCMR2_CC3S;
// 外部设置的 TIM_OCMODE_PWM1即110 0000(向上计数模式)
tmpccmrx |= OC_Config->OCMode;
// 清除CCER(输入捕获寄存器)极性位
tmpccer &= ~TIM_CCER_CC3P;
// 外部传入的HIGH = 0,CC3E = 0 禁止输出
tmpccer |= (OC_Config->OCPolarity << 8U);
/* Write to TIMx CR2 */
TIMx->CR2 = tmpcr2;
/* Write to TIMx CCMR2 */
TIMx->CCMR2 = tmpccmrx;
/* Set the Capture Compare Register value */
TIMx->CCR3 = OC_Config->Pulse;
/* Write to TIMx CCER */
TIMx->CCER = tmpccer;
}
3.1.7、主栈地址初始化
里面就是就是使能了一下timer定时器
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM4)
{
/* TIM4 clock enable */
__HAL_RCC_TIM4_CLK_ENABLE();
}
}
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
{
uint32_t tmpsmcr;
/* Check the parameters */
assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
/* Check the TIM channel state */
if (TIM_CHANNEL_STATE_GET(htim, Channel) != HAL_TIM_CHANNEL_STATE_READY)
{
return HAL_ERROR;
}
// 将channel设置成busy(这里传入的是channel3)
TIM_CHANNEL_STATE_SET(htim, Channel, HAL_TIM_CHANNEL_STATE_BUSY);
// 使能channel 3
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
// TIM1才会进
if (IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET)
{
/* Enable the main output */
__HAL_TIM_MOE_ENABLE(htim);
}
/* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
{
// 获取SMCR 中0-2位(SMS) ,外部SMS是关闭的即0
tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
// 这个比较不成功 000 != 110
if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
{
// 使能CR1的第0位CEN开启计数
__HAL_TIM_ENABLE(htim);
}
}
else
{
__HAL_TIM_ENABLE(htim);
}
/* Return function status */
return HAL_OK;
}
核心代码就是使能通道3
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState)
{
uint32_t tmp;
/* Check the parameters */
assert_param(IS_TIM_CC1_INSTANCE(TIMx));
assert_param(IS_TIM_CHANNELS(Channel));
// 外部是Channel3 = 1000 ,Channel & 0x1FU = 1000,
tmp = TIM_CCER_CC1E << (Channel & 0x1FU); /* 0x1FU = 31 bits max shift */
// 清除CC3E
TIMx->CCER &= ~tmp;
// 这里ChannelState = Enable = 1, 使能CC3E
TIMx->CCER |= (uint32_t)(ChannelState << (Channel & 0x1FU)); /* 0x1FU = 31 bits max shift */
}
更改CCR3,来设置占空比。
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
(((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\
((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\
((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\
((__HANDLE__)->Instance->CCR4 = (__COMPARE__)))
#define __HAL_TIM_SetCompare __HAL_TIM_SET_COMPARE
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
1、HAL 中每次在设置相应的位时都会先清除一下,清除时设置的宏对应位是1。
tmpcr1 &= ~TIM_CR1_CKD;
tmpcr1 |= (uint32_t)Structure->ClockDivision;
2、定时器单线程初始化时通常会加锁,完成之后解锁(别忘解锁)。
__HAL_LOCK(htim);
__HAL_UNLOCK(htim);
// 加锁时会判断有没有锁住,没有锁住再加锁,有锁就直接返回
#define __HAL_LOCK(__HANDLE__) \
do{ \
if((__HANDLE__)->Lock == HAL_LOCKED) \
{ \
return HAL_BUSY; \
} \
else \
{ \
(__HANDLE__)->Lock = HAL_LOCKED; \
} \
}while (0U)
// 返回时直接解锁
#define __HAL_UNLOCK(__HANDLE__) \
do{ \
(__HANDLE__)->Lock = HAL_UNLOCKED; \
}while (0U)
代码地址