工程师STM32单片机学习基础手记(2):从勉强看懂一行程序到IO口研究
扫描二维码
随时随地手机看文章
勉勉强强看懂一行程序
继续学习中,先把开发板自带一个例子做了些精简,以免看得吓人。。。。
就是这个,让PORTD上接的4个LED分别点亮。
开始研究代码
int main(void)
{
Init_All_Periph();
。。.。。.
看到这一行,开始跟踪,于是又看到了下面的内容
void Init_All_Periph(void)
{
RCC_Configuration();
。。.。。.
继续跟踪
void RCC_Configuration(void)
{
SystemInit();
。。.。。.
这行代码在system_stm32f10x.c中找到了。
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC-》CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC-》CFGR &= (uint32_t)0xF8FF0000;
#else
RCC-》CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC-》CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC-》CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC-》CFGR &= (uint32_t)0xFF80FFFF;
#ifndef STM32F10X_CL
/* Disable all interrupts and clear pending bits */
RCC-》CIR = 0x009F0000;
#else
/* Reset PLL2ON and PLL3ON bits */
RCC-》CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC-》CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC-》CFGR2 = 0x00000000;
#endif /* STM32F10X_CL */
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
}
这一长串的又是什么,如何来用呢?看来,偷懒是不成的了,只能回过头去研究STM32的时钟构成了。
相当的复杂。
系统的时钟可以有3个来源:内部时钟HSI,外部时钟HSE,或者PLL(锁相环模块)的输出。它们由RCC_CFGR寄存器中的SW来选择。
SW(1:0):系统时钟切换
由软件置’1’或清’0’来选择系统时钟源。 在从停止或待机模式中返回时或直接或间接作为系统时钟的HSE出现故障时,由硬件强制选择HSI作为系统时钟(如果时钟安全系统已经启动)
00:HSI作为系统时钟;
01:HSE作为系统时钟;
10:PLL输出作为系统时钟;
11:不可用。
////////////////////////////////////////////////////////////////////
PLL的输出直接送到USB模块,经过适当的分频后得到48M的频率供USB模块使用。
系统时钟的一路被直接送到I2S模块;另一路经过AHB分频后送出,送往各个系统,其中直接送往SDI,FMSC,AHB总线;8分频后作为系统定时器时钟;经过APB1分频分别控制PLK1、定时器TIM2~TIM7;经过APB2分频分别控制PLK2、定时器TIM1~TIM8、再经分频控制ADC;
由此可知,STM32F10x芯片的时钟比之于51、AVR、PIC等8位机要复杂复多,因此,我们立足于对着芯片手册来解读程序,力求知道这些程序代码如何使用,为何这么样使用,如果自己要改,可以修改哪些部分,以便自己使用时可以得心应手。
单步执行,看一看哪些代码被执行了。
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC-》CR |= (uint32_t)0x00000001;
这是RCC_CR寄存器,由图可见,HSION是其bit 0位。
HSION:内部高速时钟使能
由软件置’1’或清零。
当从待机和停止模式返回或用作系统时钟的外部4-25MHz时钟发生故障时,该位由硬件置’1’来启动内部8MHz的RC振荡器。当内部8MHz时钟被直接或间接地用作或被选择将要作为系统时钟时,该位不能被清零。
0:内部8MHz时钟关闭;
1:内部8MHz时钟开启。
///////////////////////////////////////////////////////////////////////
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC-》CFGR &= (uint32_t)0xF8FF0000;
这是RCC_CFGR寄存器
该行程序清零了MC0[2:0]这三位,和ADCPRE[1:0],ppre2[2:0],PPRE1[2:0],HPRE[3:0],SWS[1:0]和SW[1:0]这16位。
/*
MCO: 微控制器时钟输出,由软件置’1’或清零。
0xx:没有时钟输出;
100:系统时钟(SYSCLK)输出;
101:内部8MHz的RC振荡器时钟输出;
110:外部4-25MHz振荡器时钟输出;
111:PLL时钟2分频后输出。
*/
/* Reset HSEON, CSSON and PLLON bits */
RCC-》CR &= (uint32_t)0xFEF6FFFF;
清零了PLLON,HSEBYP,HSERDY这3位。
/* Reset HSEBYP bit */
RCC-》CR &= (uint32_t)0xFFFBFFFF;
清零了HSEBYP位 ///???为什么不一次写??
HSEBYP:外部高速时钟旁路,在调试模式下由软件置’1’或清零来旁路外部晶体振荡器。只有在外部4-25MHz振荡器关闭的情况下,才能写入该位。
0:外部4-25MHz振荡器没有旁路;
1:外部4-25MHz外部晶体振荡器被旁路。
所以要先清HSEON位,再清该位。
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC-》CFGR &= (uint32_t)0xFF80FFFF;
清零了:USBPRE,PLLMUL,PLLXTPR,PLLSRC共7位
/* Disable all interrupts and clear pending bits */
RCC-》CIR = 0x009F0000;
////这个暂不解读
SetSysClock();
跟踪进入该函数,可见一连串的条件编译:
单步运行,执行的是:
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
为何执行该行呢,找到SYSCLK_PREQ_**的相关定义,如下图所示。
这样就得到了我们所要的一个结论:如果要更改系统工作频率,只需要在这里更改就可以了。
可以继续跟踪进入这个函数来观察如何将工作频率设定为72MHz的。
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE */
RCC-》CR |= ((uint32_t)RCC_CR_HSEON);
//开启HSE
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC-》CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut));
//等待HSE确实可用,这有个标志,即RCC_CR寄存器中的HSERDY位(bit 17),这个等待不会无限长,有个超时策略,即每循环一次计数器加1,如果计数的次数超过HSEStartUp_TimeOut,就退出循环,而这个HSEStartUp_TimeOut在stm32f10x.h中定义,
#define HSEStartUp_TimeOut ((uint16_t)0x0500) /*!《 Time out for HSE start up */
///////////////////////////////////////////////////////////////////////////////////////////////
if ((RCC-》CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
///再次判断HSERDY标志位,并据此给HSEStatus变量赋值。
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
FLASH-》ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH-》ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH-》ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
RCC-》CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
//找到定义: #define RCC_CFGR_HPRE_DIV1 ((uint32_t)0x00000000) /*!《 SYSCLK not divided */
/* PCLK2 = HCLK */
RCC-》CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
//找到定义:#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)0x00000000) /*!《 HCLK not divided */
/* PCLK1 = HCLK */
RCC-》CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
//找到定义:#define RCC_CFGR_PPRE1_DIV2 ((uint32_t)0x00000400) /*!《 HCLK divided by 2 */
#ifdef STM32F10X_CL
……
#else
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC-》CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC-》CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
//以上是设定PLL的倍频系数为9,也就是说,这个72M是在外部晶振为8M时得到的。
/* Enable PLL */
RCC-》CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC-》CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC-》CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC-》CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC-》CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
/* Go to infinite loop */
while (1)
{
}
}
}
至此,我们可以归纳几条:
(1) 时钟源有3个
(2) 开机时默认是HSI起作用,可以配置为所要求的任意一个时钟
(3) 配置时必须按一定的顺序来打开或都关闭一些位,并且各时钟起作用有一定的时间,因此要利用芯片内部的标志位来判断是否可以执行下一步。
(4) 如果外部时钟、PLL输出失效,系统可以自动回复到HSI(开启时钟安全系统)
(5) HSI的频率准确度可以达到+/- 1%,如果有必要时,还可以用程序来调整这个频率,可调的范围大致在200KHz左右。
最后让我们来感受一下劳动的果实吧--试着改改频率看有何反应。
为查看更改后的效果,先记录更改前的数据。将调试切换到仿真,在第一条:
Delay(0xAFFFF);
指令执行前后,分别记录下Status和Sec
Status:2507 3606995
Sec:0.00022749 0.05028982
将振荡频率更改为36MHz,即
。。.
#define SYSCLK_FREQ_36MHz 36000000 //去掉该行的注释
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
/*#define SYSCLK_FREQ_72MHz 72000000*/ //将该行加上注释
再次运行,结果如下:
Status:2506 3606994
Sec:0.00008478 0.10036276
基本上是延时时间长了一倍。改成硬件仿真,将代码写入板子,可以看到LED闪烁的频率明显变慢了。
IO研究
前面的例子研究了时钟,接下来就来了解一下引脚的情况
Main.c中,有关I/O口的配置代码如下:
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure IO connected to LD1, LD2, LD3 and LD4 leds *********************/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
这几行代码是将GPIOD的第8,9,10和11引脚配置成输出,并且还可以设定输出引脚的速度(驱动能力?),这里设定为 50MHz,这应该是常用的,还有可以设置为2MHz的。那么如何将引脚设置成输入呢?查看电路原理图,GPIOD.0~GPIO.4是接一个摇杆的5个按钮的,因此,下面尝试着将它们设置成为输入端。
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOD, &GPIO_InitStructure);
第1行和第3行完全是照抄,第2行那个GPIO_Mode_IN_FLOATING是在stm32f10x_gpio.h中找到的。
当然是因为这里还有GPIO_Mode_Out_PP,所以猜测应该是它了。至于还有其他那么多的符号就不管了。
定义完成,编译完全通过,那就接下来准备完成下面的代码了。
int main(void)
{
Init_All_Periph();
while(1)
{ if( GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_0)) //1
{ GPIO_ResetBits(GPIOD, GPIO_Pin_8);
}
else
{ /* Turn on LD1 */
GPIO_SetBits(GPIOD, GPIO_Pin_8);
/* Insert delay */
}
。。.。。.
标号为1的行显然其作用是判断GPIOD.0引脚是0还是1。这个函数是在stm32f10x_gpio.c中找到的。
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t bitstatus = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
if ((GPIOx-》IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
{
bitstatus = (uint8_t)Bit_SET;
}
else
{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
虽然程序还有很多符号看不懂(没有去查),但凭感觉它应该是对某一个引脚的状态进行判断,因为这个函数的类型是uint8_t,估计stm32没有bit型函数(需要验证),所以就用了uint8_t型了),如果是读的端口的值,应该用uint16_t型。这一点在下面也可以得到部分的验证:
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
这些函数是读引脚及输出寄存器的数据的。
再次编译,也是顺利通过,依法炮制,将其他三个引脚输入控制LED的代码也写上,为保险起见,先用软件仿真,免得反复擦写FLASH(顺便说一句,目前还没有搞定将代码写入RAM及从RAM中执行)
进入仿真后打开外围部件接口,单步执行,果然如同设想那样运作了,单击Pins 0后面的勾,再次运行,果然PIN8后面的勾没了。做到这里,就感觉到用keil的好处了,这块熟啊,几乎没有花时间在上面,一用就成了。
至此,按我的习惯,要翻开STM32F的数据手册,研究一下其IO端口了。下面是数据手册中的一段话:
-------------------------------------
每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR,GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。
根据数据手册中列出的每个I/O端口的特定硬件特征, GPIO端口的每个位可以由软件分别配置成多种模式。
─ 输入浮空
─ 输入上拉
─ 输入下拉
─ 模拟输入
─ 开漏输出
─ 推挽式输出
─ 推挽式复用功能
─ 开漏复用功能
----------------------------------------------------
当然,数据手册上关于IO端口的描述是很多很多的,我也只是大概地了解了一下,真正要设计产品时,肯定还要细看。但至少,知道了IO端口复位后处于浮空状态,也就是其电平状态由外围电路决定,这很重要,如果设计工业品的话,这是必须要确定的;知道了IO引脚可以兼容5V电源;知道了在什么地方可以找到这些引脚在库中的定义而不必看着数据手册去控制那些位;也知道了这些引脚的一些基本操作函数(连猜带蒙带测试应该可以搞定大部分功能),那么我心里基本就有底啦。
最后用一段电子荧火虫(也就是呼吸灯)的简单程序作为结束。
int main(void)
{ uint8_t Count;
uint32_t DelayTim=“0x1000”; //基数
uint32_t ChangTim=“0x2000”; //每次变化的量
Init_All_Periph();
while(1)
{
for(Count=0;Count《16;Count++) //渐亮
{
GPIO_SetBits(GPIOD, GPIO_Pin_8); //点亮灯
Delay(DelayTim+Count*ChangTim);
GPIO_ResetBits(GPIOD, GPIO_Pin_8); //熄灭灯
Delay(DelayTim+(16-Count)*ChangTim);
}
for(Count=16;Count》0;Count--) //渐暗
{
GPIO_SetBits(GPIOD, GPIO_Pin_8); //点亮灯
Delay(DelayTim+Count*ChangTim);
GPIO_ResetBits(GPIOD, GPIO_Pin_8); //熄灭灯
Delay(DelayTim+(16-Count)*ChangTim);
}
}
}
定时器初步
接下来研究定时器。为什么开始研究定时器了呢?那个I/O还是刚刚开了头啊,还有很多很多知识没有掌握!不怕,方法论告诉我们:对事物的认识是螺旋式上升的,所以不要一杆子打到底,想着把I/O口的所有情况都掌握了再学下面的,那会是很困难的
STM32的定时器是什么样子的,心里一点底也没有,还是找个现成的例子来吧。在ST提供的库里,有很多的例子
第一个就是它了。
把整个文件夹复制一份到自己的实验文件夹中
在Source文件夹中再建立名为APP的文件夹,将上图中所有源程序文件全部复制到APP文件夹中。然后将库所提供的CMSIS文件夹和 STM32F10x_StdPeriph_Driver文件夹复制到Source文件夹中。然后按照前面的方法建立项目。
这是建好的项目的结构。
下面开始研究,首先看附带的readme.txt文件,了解到该例子的大体用途是验证Tim2的Output Compare Timing mode的。于是打开PDF文件,直接翻到下面的位置:
-----------------------------------------------------------------------------------------
13.3.8 输出比较模式
此项功能是用来控制一个输出波形或者指示何时一段给定的的时间已经到时。
当计数器与捕获/比较寄存器的内容相同时,输出比较功能做如下操作:
● 将输出比较模式(TIMx_CCMRx寄存器中的OCxM位)和输出极性(TIMx_CCER寄存器中的CCxP位)定义的值输出到对应的管脚上。在比较匹配时,输出管脚可以保持它的电平
(OCxM=000)、被设置成有效电平(OCxM=001)、被设置成无有效电平(OCxM=010)或进行翻转(OCxM=011)。
● 设置中断状态寄存器中的标志位(TIMx_SR寄存器中的CCxIF位)。
● 若设置了相应的中断屏蔽(TIMx_DIER寄存器中的CCXIE位),则产生一个中断。
● 若设置了相应的使能位(TIMx_DIER寄存器中的CCxDE位,TIMx_CR2寄存器中的CCDS位选择DMA请求功能),则产生一个DMA请求。
TIMx_CCMRx中的OCxPE位选择TIMx_CCRx寄存器是否需要使用预装载寄存器。
在输出比较模式下,更新事件UEV对OCxREF和OCx输出没有影响。
同步的精度可以达到计数器的一个计数周期。输出比较模式(在单脉冲模式下)也能用来输出一个单脉冲。
输出比较模式的配置步骤:
1. 选择计数器时钟(内部,外部,预分频器)
2. 将相应的数据写入TIMx_ARR和TIMx_CCRx寄存器中
3. 如果要产生一个中断请求和/或一个DMA请求,设置CCxIE位和/或CCxDE位。
4. 选择输出模式,例如:必须设置OCxM=’011’、OCxPE=’0’、CCxP=’0’和CCxE=’1’,当计数器CNT与CCRx匹配时翻转OCx的输出管脚,CCRx预装载未用,开启OCx输出且高电平有效。
5. 设置TIMx_CR1寄存器的CEN位启动计数器--------------------------------------------------------------------------------
说得并不复杂,但是要弄清楚也绝非易事,况且main.c中一系列的符号究竟是什么意思也不是那么容易搞清楚的。这些东西搞不清,将来自己编程时,就算想要依葫芦画瓢都难。怎么办呢?可能并没有多少偷巧的办法,只能是一步一步地摸索吧。
进入调试,打开Peripherals-》Timers-》Tim2,出现下面的窗口。
然后单步执行程序,并观察界面的变化,并将这些符号作为线索,在PDF文件,stmf10x_tim.c等文件中搜索,了解相关符号的含义。
如下图是执行到:while(1)前,也就是所有设置完成后的图。
沿着这些线索一路追踪,边看源程序,边找数据手册,边找相关的头文件,其他源程序,总算大体有了个明白,下面将main.c中的部分代码作为注释,也算是做点记录。
/* Includes ------------------------------------------------------------------*/
#include “stm32f10x.h”
//这个头文件需要根据所选择的芯片进行更改
/* Private variables ---------------------------------------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure; //定义两个结构型变量
__IO uint16_t CCR1_Val = 49152;
__IO uint16_t CCR2_Val = 32768;
__IO uint16_t CCR3_Val = 16384;
__IO uint16_t CCR4_Val = 8192; //
ErrorStatus HSEStartUpStatus;
/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
int main(void)
{
/* System Clocks Configuration */
RCC_Configuration();
/* NVIC Configuration */
NVIC_Configuration();
/* GPIO Configuration */
GPIO_Configuration();
/* ---------------------------------------------------------------
TIM2 Configuration: Output Compare Timing Mode:
TIM2CLK = 36 MHz, Prescaler = 4, TIM2 counter clock = 7.2 MHz
CC1 update rate = TIM2 counter clock / CCR1_Val = 146.48 Hz
CC2 update rate = TIM2 counter clock / CCR2_Val = 219.7 Hz
CC3 update rate = TIM2 counter clock / CCR3_Val = 439.4 Hz
CC4 update rate = TIM2 counter clock / CCR4_Val = 878.9 Hz
--------------------------------------------------------------- */
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 65535; //它对应TIM2_ARR
TIM_TimeBaseStructure.TIM_Prescaler = 0;
/* 它对应TIM2_PSC,相关代码如下(stm32f10x_tim.c中):
TIMx-》PSC = TIM_TimeBaseInitStruct-》TIM_Prescaler;
但很奇怪,这里令其为0,然后再在下面设置为4??为何??
*/
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0200;
//ClockDivision是对CKD位进行设置的,但是这个必须要自己给其赋正确的值
/*在stm32f10_tim.c文件中是这样操作的
TIMx-》CR1 |= (uint32_t)TIM_TimeBaseInitStruct-》TIM_ClockDivision |
TIM_TimeBaseInitStruct-》TIM_CounterMode;
*/
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*计数模式,st32f10x_tim.h中定义,其他可用的符号还有:
TIM_CounterMode_Up,TIM_CounterMode_Down,TIM_CounterMode_CenterAligned1
TIM_CounterMode_CenterAligned2,TIM_CounterMode_CenterAligned3
*/
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* Prescaler configuration */
TIM_PrescalerConfig(TIM2, 4, TIM_PSCReloadMode_Immediate);
/*stm32f10x_tim.c中相关代码:
TIMx-》PSC = Prescaler;
*/
/* Output Compare Timing Mode configuration: Channel1 *///输出比较模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val; //预置值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //对CCER中CCxP的操作
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
/*对捕获/比较使能寄存器(TIMx_CCER)进行操作,置CC1E为1 */
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* Output Compare Timing Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
//这个是将CCR2_Val的值送到TMI2_CCR2中
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* Output Compare Timing Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
/*
*/
/* Output Compare Timing Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* TIM IT enable */
TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE);
对DIER寄存器操作,中断允许配置,相关代码如下:
而DIER寄存器如下:
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
//开启定时器的运行
while (1);
}
解读:
(1) 时钟来源 CK_INT,设置的方法是让TIMx_SMCR中的SMS[2:0]=000,最后通过TIM_Cmd(TIM2, ENABLE);函数将TIMx_CR1中的CEN置1,开启定时器的运行;
(2) 接下来设置定时器的基本参数
(3) 然后是设置定时器的各个通道
(4) 最后开启定时器运行
最后,将这个例子稍加修改,令其运行在我的实验板上。
需要修改的仅是将其原来的输出从GPIOC的第6~第9脚变为GPIOD的第8~第11脚。为此,需要改的地方有:
将蓝色框内的GPIOC改为GPIOD。这个是最先写的,但实际上一开始根本没注意到这个地方,没有改成GPIOD,结果一仿真,不正确,再一细查,原来端口还要配置时钟,改过来就对了。瞧,这不验证了上面的说法“认识事物是螺旋式上升的”学了定时器,对于I/O口和时钟又有了更进一步的理解了。
将红色框内的GPIO分别改成8,9,10,11,将蓝色框内的GPIOC改为GPIOD。
最后,到stm32f10x_it.c中,修改相应的输出
参考上图红色框内,将GPIOC改为GPIOD,将6脚改为8脚,其他部分类推。
修改好后运行,所有灯亮了,看不出效果,于是又将预分频系数由4改为64,这样一来,LED开始闪烁了。