8个定时器中,Timer1 和Timer8是由APB2(输出最高频率为72MHZ)预分频后,再通过一个倍频器得到时钟频率,最高为72MHz。Timer2~Timer7则是由APB1(输出最高频率为36MHZ)预分频后,再通过一个倍频器得到时钟频率,最高为36MHz。
1、如何进行程序编写
这里我通过定时器来控制一个LED亮0.5s 灭0.5s ,交替闪烁。当然要让定时器正常工作起来,还要配置中断NVIC。定时器计数到某个数,产生中断,从而进入中断服务程序,点亮LED灯。
main函数分析:
#include "stm32f10x.h"
void GPIO_Config(void)//GPIO配置
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//使能gpioc的时
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //选择管脚PC.13作LED灯
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //管脚速度为50M
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置输出模式为推挽输出
GPIO_Init(GPIOC, &GPIO_InitStructure); //将上述设置写入到GPIOC里去
}
void NVIC_Config(void) //中断控制器的配置
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); //优先组设置
NVIC_InitStructure.NVIC_IRQChannel =TIM2_IRQn ; //TIM2中断选通
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断控制
NVIC_Init(&NVIC_InitStructure);
}
void Timer_Config(void) //定时器的配置
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE); //Timer2 时钟使能
TIM_DeInit(TIM2); //复位TIM2定时器
TIM_TimeBaseStructure.TIM_Period=1000; //定时器周期
TIM_TimeBaseStructure.TIM_Prescaler=36000-1; //预分频数
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //TIM2时钟分频,为1表示不分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//定时器计数为向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器2的溢出标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //使能定时器2溢出中断
TIM_Cmd(TIM2, ENABLE); //定时器2使能
}
int main(void)
{
SystemInit();//初始化时钟,配置为72MHz,我试过将这句注释掉,好像不影响结果。查
了一下,在配置
//main函数之前的启动代码有这样一句 LDR R0, =SystemInit,我疑惑的是难
道启动的时候就配成72Mhz?
GPIO_Config();
NVIC_Config();
Timer_Config();
while(1)
{
;
}
}
中断服务函数
void TIM2_IRQHandler(void)
{
static int flag_bit=0;//定义一个标志位
if ( TIM_GetITStatus(TIM2 , TIM_IT_Update) != RESET ) //判断中断溢出标志为是否为1
{
TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update); //清除溢出中断标志位
flag_bit = !flag_bit;
if(flag_bit == 1)
GPIO_SetBits(GPIOC, GPIO_Pin_13); //熄灭LED
if(flag_bit == 0)
GPIO_ResetBits(GPIOC, GPIO_Pin_13); //点亮LED
}
这个函数是写在stm32f10x_it.c 里面的,我对TIM2_IRQHandler()函数的理解应该是这样的:
首先由定时器定时,定时好了产生中断溢出标志位,发送中断
然后进入中断服务函数TIM2_IRQHandler(),进入函数之后要做的就是清除中断溢出
标志位。
最后再执行函数里的其他内容。
定时器定时时间计算是这两句:
TIM_TimeBaseStructure.TIM_Period=1000; //定时器周期
TIM_TimeBaseStructure.TIM_Prescaler=36000-1; //预分频数
Prescaler可以理解为定时器的基数是72M / Prescaler+1 = 2000k,也就是500us ,Period 可以理解为要计数多少次,这里是1000次。 所以就是每500us记一次,计数1000次,就是500ms。
公式为:
Period / (72M / (Prescaler+1) )=____ 秒
1000 / (72 M/ (35999+1) ) = 0.5 秒
我有的一些疑问:1、进入中断函数之后,定时器要干些什么,是不是就停止计数了?
2、计数记到1000发生中断,计数值是不是有自动清零
问题先放到这儿,边学习,边解决。
2、仿真结果观察
前面第三章已经过如何仿真波形的步骤,可以参看前面。点击setup 按钮 会弹出一个窗口,在窗口的右上边,有个new的按钮,点击后输入 PORTC.13

仿真运行结果如下:

可以从仿真结果中观察到,方波的周期为一秒。占空比为0.5 ,跟预期一致。
3、对第四章串口的补充
第四章介绍了串口的打印函数printf 是如何调用实现的。但要使用keil自带的微库microLIB ,那能不能不使用这个微库呢。我参照野火的教程,修改了程序,自己编写usart_printf()函数来实现打印的功能。
USRT1的配置不改变,主要的就是添加打印函数实现串口输出功能。代码感觉可能很长,但无非就是一些判断,看看字符串最后一位是不是\0 ,不是的话,遇到转义字符,/n /r 怎么做,以及将数字转换成字符这些。
这些很多时候我都没注意:
1、 这一句while ( *Data != 0) // 判断是否到达字符串结束符
我们平时不是都用 \0 吗? 用0开始我还没反应过来。 其实ASCII的十六进制的0 就是\0
如果要使用\0,while ( *Data !='\0') ,主要要加单引号表字符串,上面没有加就是十六进制,后面的也就能明白了。
2、stdarg.h这个函数,以前都没见过,但学习就是要学习新知识。 知识改变命运,我一直都相信这句!
3、char *itoa(int value, char *string, int radix) 是指针函数,返回值是一个地址
4、其他的都是涉及指针的操作,所以C指针一定要学好,没学好,不要紧,趁这个机会把它弄明白,当我看懂了下面这些,也就明白了。
#ifndef __usart_debug_H
#define __usart_debug_H
#include "stm32f10x.h"
#include //stdarg.h是C语言中C标准函数库的头文件,目的为让函数能够接收可变参数。
extern void usart_debug_config(void); //提供给外部函数调用usart_debug_config()函数。
//
// 函数名:itoa
// 描述 :将整形数据转换成字符串
//输入 :-radix =10 表示10进制,其他结果为0
// -value 要转换的整形数
// -buf 转换后的字符串
// -radix = 10
* 输出 :无
* 返回 :无
* 调用 :被USART1_printf()调用
*
static char *itoa(int value, char *string, int radix)
{
int i, d;
int flag = 0;
char *ptr = string;
//This implementation only works for decimal numbers.
if (radix != 10)
{
*ptr = 0;
return string;
}
if (!value)
{
*ptr++ = 0x30;
*ptr = 0;
return string;
}
//if this is a negative value insert the minus sign.
if (value < 0)
{
*ptr++ = '-';
// Make the value positive.
value *= -1;
}
for (i = 10000; i > 0; i /= 10)
{
d = value / i;
if (d || flag)
{
*ptr++ = (char)(d + 0x30);
value -= (d * i);
flag = 1;
}
}
// Null terminate the string.
*ptr = 0;
return string;
} //NCL_Itoa
// 函数名:USART1_printf
//描述 :格式化输出,类似于C库中的printf,但这里没有用到C库
//输入 :-USARTx 串口通道,这里只用到了串口1,即USART1
// -Data 要发送到串口的内容的指针
// -... 其他参数
//输出 :无
// 返回 :无
//调用 :外部调用
// 典型应用USART1_printf( USART1, "\r\n this is a demo \r\n" );
//USART1_printf( USART1, "\r\n %d \r\n", i );
//USART1_printf( USART1, "\r\n %s \r\n", j );
//
static void USART1_printf(USART_TypeDef* USARTx, uint8_t *Data,...)
{
const char *s;
int d;
char buf[16];
va_list ap; // va_list ap 和 va_start(ap, Data)以及后面的va_arg() 都来自 stdarg.h
va_start(ap, Data);//具体用法请参照相关资料。
while ( *Data != 0) // 判断是否到达字符串结束符
{
if ( *Data == 0x5c ) // '\' ASCII表 0x5c就是转义字符'\'
{
switch ( *++Data )
{
case 'r': //回车符
USART_ClearFlag(USART2,USART_FLAG_TC);
USART_SendData(USARTx, 0x0d);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
Data ++;
break;
case 'n': //换行符
USART_ClearFlag(USART2,USART_FLAG_TC);
USART_SendData(USARTx, 0x0a);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
Data ++;
break;
default:
Data ++;
break;
}
}
else if ( *Data == '%')
{ //
switch ( *++Data )
{
case 's': //字符串
s = va_arg(ap, const char *);
for ( ; *s; s++)
{
USART_ClearFlag(USART2,USART_FLAG_TC);
USART_SendData(USARTx,*s);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
}
Data++;
break;
case 'd': //十进制
d = va_arg(ap, int);
itoa(d, buf, 10);
for (s = buf; *s; s++)
{
USART_ClearFlag(USART2,USART_FLAG_TC);
USART_SendData(USARTx,*s);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
}
Data++;
break;
default:
Data++;
break;
}
} //end of else if
else {
USART_ClearFlag(USART2,USART_FLAG_TC);
USART_SendData(USARTx, *Data++);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
}
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET );
}
}
#endif // __USART1_H
软件仿真中的效果图:
波形图 IO口状态 串口输出 都在下面图里了
