目前共有6篇帖子。 内容转换:不转换▼
 
点击 回复
701 5
【程序】STM32定时器1的使用
一派护法 十九级
1楼 发表于:2016-12-23 22:40
#include <stm32f10x.h>
#define _BV(n) (1 << (n))
uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};
uint8_t num = 0;
void delay(void)
{
    uint32_t i;
    for (i = 0; i < 20000; i++);
}
void SerIn(uint8_t data)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        GPIOC->BRR = _BV(15);
        if (data & 0x80)
            GPIOC->BSRR = _BV(13);
        else
            GPIOC->BRR = _BV(13);
        GPIOC->BSRR = _BV(15);
        data <<= 1;
    }
}
void ParOut(void)
{
    GPIOC->BRR = _BV(14);
    GPIOC->BSRR = _BV(14);
}
int main(void)
{
    // CNF=00: push-pull, MODE=11: 50MHz
    RCC->APB2ENR = 0x810; // 开启PC和TIM1时钟
    GPIOC->CRH = 0x33300000; // PC13~15设为输出
    // 显示E
    SerIn(0x86);
    SerIn(_BV(7));
    ParOut();
   
    TIM1->ARR = 0xffff; // 计数量 (auto-reload register, 16位)
    TIM1->PSC = 3; // 4分频 (prescaler, 16位)
    TIM1->RCR = 255; // 定时器溢出指定次数后才触发中断 (repetition counter register, 8位)
    TIM1->DIER = 0x01; // 允许定时器1中断产生 (DMA/interrupt enable register, UIE: Update interrupt enable = 1)
    NVIC->ISER[0] = _BV(25); // 允许执行定时器1中断服务函数, 编号为25
    TIM1->CR1 = 0x01; // 开启定时器1 (control register 1, CEN: Counter enable = 1)
   
    while (1)
    {
        SerIn(seg8[num % 10]);
        SerIn(_BV(0));
        ParOut();
        delay();
       
        SerIn(seg8[num % 100 / 10]);
        SerIn(_BV(1));
        ParOut();
        delay();
    }
}
// 定时器中断函数
void TIM1_UP_IRQHandler(void)
{
    TIM1->SR &= ~_BV(0); // Status register (UIF: Update interrupt flag = 0)
    num++;
    if (num >= 100)
        num = 0;
}

一派护法 十九级
2楼 发表于:2016-12-23 22:43

可以只使用分频器PSC,不使用用于跳过溢出的计数器RCR:

TIM1->ARR = 0xffff; // 计数量 (auto-reload register, 16位)
TIM1->PSC = 999; // 1000分频 (prescaler, 16位)
TIM1->DIER = 0x01; // 允许定时器1中断产生 (DMA/interrupt enable register, UIE: Update interrupt enable = 1)
NVIC->ISER[0] = _BV(25); // 允许执行定时器1中断服务函数, 编号为25
TIM1->CR1 = 0x01; // 开启定时器1 (control register 1, CEN: Counter enable = 1)

一派护法 十九级
3楼 发表于:2016-12-23 23:35
上述程序存在一个问题:从上电到第一次产生中断的时间极短。解决这个问题的办法就是在开启定时器前先刷新一下定时器,应用设置。
// 刷新寄存器
TIM1->CR1 = 0x04; // 防止UIF置位并产生中断
TIM1->EGR = 0x01; // 产生一个寄存器更新事件
 
TIM1->CR1 = 0x01; // 开启定时器1 (control register 1, CEN: Counter enable = 1)
一派护法 十九级
4楼 发表于:2016-12-23 23:54
手册里有这样一句话:
The new prescaler ratio is taken into account at the next update event.
因此,设置了PSC寄存器后必须要触发一次更新事件才能生效。实际上RCR寄存器也是如此。
默认情况下ARR寄存器设置后是立即生效,只有当CR1寄存器中的ARPE=1时才是更新事件触发后生效。

因此上述代码在定时器启动前手动触发了更新事件使之前的寄存器设置生效,防止数码管由0变为1后设置才生效,导致开机时数码管就为1的错误。此外,为了防止手动触发更新事件时也产生中断,将CR1的第二位URS设为了1。

开启定时器时把URS设为1也是可以的:
TIM1->CR1 = 0x05; // 开启定时器1
因为当开启了URS之后,只有定时器溢出才能触发中断,手动产生更新事件不能触发中断。
一派护法 十九级
5楼 发表于:2017-1-14 13:18

在程序中如果直接使用十六进制数,会使程序的可读性变差。并且在编写程序时由于记不住哪个设置位在某个寄存器的哪个位置,需要频繁地翻阅寄存器表。不过在我们引入的头文件stm32f10x.h中已经帮我们定义好了这些设置位的名称及其相应的位置。
因此,下面这句话:
TIM1->CR1 = 0x05; // CEN=1, URS=1
可以改为
TIM1->CR1 |= TIM_CR1_URS | TIM_CR1_CEN;

注意TIM后面没有定时器编号1。


这样一来,我们就只需要记住这些设置位的名称(通过英文全称来记忆会更加方便。比如CEN就是Counter Enable的缩写,CR就是Control Register的缩写,这些在手册上都有),以及所在的寄存器的名称,然后在程序中写出对应的宏名就行了,无需再去记忆它们在寄存器中的位置(比如CEN是_BV(0),URS是_BV(2)等等)

一派护法 十九级
6楼 发表于:2017-1-14 13:20
【不使用自定义的_BV宏,而是直接写出各设置位名称的程序】
#include <stm32f10x.h>

uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};
uint8_t num = 0;

void delay(void)
{
    uint32_t i;
    for (i = 0; i < 20000; i++);
}

void SerIn(uint8_t data)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        GPIOC->BRR = GPIO_BRR_BR15;
        if (data & 0x80)
            GPIOA->BSRR = GPIO_BSRR_BS0;
        else
            GPIOA->BRR = GPIO_BRR_BR0;
        GPIOC->BSRR = GPIO_BSRR_BS15; // BS15=BitSet15是设为高电平, BR15=BitReset15是设为低电平, 注意区分!
        data <<= 1;
    }
}

void ParOut(void)
{
    GPIOC->BRR = GPIO_BRR_BR14;
    GPIOC->BSRR = GPIO_BSRR_BS14;
}

int main(void)
{
    // 开启PA、PC和TIM1时钟
    RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_TIM1EN;
   
    // 每一位十六进制数代表一个端口
    // 详情请参阅: https://zh.arslanbar.net/post.php?t=24435
    // CNF=00: push-pull, MODE=11: 50MHz
    GPIOA->CRL = 0x00000003; // PA0设为输出
    GPIOC->CRH = 0x33000000; // PC14~15设为输出

    TIM1->ARR = 65535; // 计数量 (auto-reload register, 16位)
    TIM1->PSC = 999; // 1000分频 (prescaler, 16位)
    TIM1->DIER = TIM_DIER_UIE; // 允许定时器1中断产生
    NVIC->ISER[0] = NVIC_ISER_SETENA_25; // 允许执行定时器1中断服务函数, 编号为25
   
    // 刷新寄存器
    TIM1->CR1 |= TIM_CR1_URS; // URS=1(防止UIF置位并产生中断)
    TIM1->EGR |= TIM_EGR_UG; // UG=1(产生一个寄存器更新事件)
    TIM1->CR1 &= ~TIM_CR1_URS; // URS=0
   
    TIM1->CR1 |= TIM_CR1_CEN; // CEN=1(开启定时器1)
   
    // 数码管借助74HC595芯片进行动态扫描
    while (1)
    {
        SerIn(seg8[num % 10]);
        SerIn(GPIO_BSRR_BS0);
        ParOut();
        delay();
       
        SerIn(seg8[num % 100 / 10]);
        SerIn(GPIO_BSRR_BS1);
        ParOut();
        delay();
    }
}

// 定时器中断函数
void TIM1_UP_IRQHandler(void)
{
    TIM1->SR &= ~TIM_SR_UIF; // UIF=0
    num++;
    if (num >= 100)
        num = 0;
}

回复帖子

内容:
用户名: 您目前是匿名发表
验证码:
(快捷键:Ctrl+Enter)
 

本帖信息

点击数:701 回复数:5
评论数: ?
作者:巨大八爪鱼
最后回复:巨大八爪鱼
最后回复时间:2017-1-14 13:20
 
©2010-2024 Arslanbar Ver2.0
除非另有声明,本站采用知识共享署名-相同方式共享 3.0 Unported许可协议进行许可。