打印
[应用相关]

STM32进阶--常用寄存器篇

[复制链接]
1562|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
energy1|  楼主 | 2015-2-27 21:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
这一次我们就来探讨一下如何操作底部的寄存器来实现我们灯闪烁的效果。
1.Memory mapped
在CM3中,使用了一种叫做 memory mapped的技术。这个技术让我们在访问芯片内部的寄存器的时候可以像访问内存地址那样方便。比如说我们有一个寄存器叫做REG,这个寄存器的地址是0X4000_0000。我们如何来访问这个寄存器呢?光靠喊REG的名字是远远不够的,因为芯片听不懂我们叫他的这个REG这个名字。刚刚我们也说道,访问寄存器可以像访问内存地址那样。于是我们可以编写下面的代码:
[cpp] view plaincopy


  • #define Reg(addr)       (*((unsigned int *)addr))  
  •   
  • #define Register1       Reg(0x40000000)  
  • #define Register2       Reg(0x40000004)  
  • //Register1         (*((unsigned int *)0x40000000))  


这个代码中,先来分解一下看看:(unsigned int *)addr,是将一个32位地址转换成为一个指针,然后对这个指针进行取值(*运算符),这样当操作Register1的时候,其实就是操作了0X40000000处的寄存器。当我们需要使用Register1的时候,直接写Register1,这样编译器会根据Define来给我们进行相应的替换。
2.Clock
在第二节中我们说到,Stm32有很多模块,每一个模块都有自己独立的时钟。要想操作这个模块,必须先打开他的时钟。那具体是如何来实现打开相应模块的时钟呢?第二节中使用STM的库函数来打开时钟。这次我们就从最底层的寄存器来一探究竟,先去《STM32数据手册》中找找端倪。我们在第70页看到了RCC_APB2ENR这个寄存器。

这里看到这么多的控制位看看第2位(我们是从0开始计数的),是一个IOPAEN,也就是GPIOA_ENABLE,只有这一个使能了,我们才能操作GPIOA,同样的,也看到了很多模块,比如GPIOC,GPIOG,USART1等等很多的模块时钟控制位。在看看上面有一个偏移地址0X18,这个0X18是什么意思呢?上面我们说到每一个寄存器都会有一个地址,这里只是给出了一个偏移地址,我们怎样来计算这个真实的地址呢?在从数据手册中找找看看还有什么发现。终于在数据手册的第28页看到了一张表:如下图

这个表中有一项是复位和时钟控制RCC,这是RCC的一个起始地址,这个地址加上刚才我们说的偏移地址0X18就是RCC的真实地址。也就是0X4002_1018.
我们在表示RCC_APB2ENR的地址的时候可以这样来表示,基地址加上偏移地址,如下的代码所示:
[cpp] view plaincopy


  • #define RCC_BASE_ADDR                   ((unsigned long)0x40021000)  
  • #define RCC_APB2ENR_OFFSET              ((unsigned char)0x18)  


沙发
energy1|  楼主 | 2015-2-27 21:31 | 只看该作者
RCC_APB2ENR_CONTENTS和上面第一小节Memory mapped中说的是一样的,通过指针来寻址。那么我们如何来控制每一个具体的位呢?也就是说如何打开对应位对应的时钟呢?我们知道,只要将RCC_APB2_ENR中每一位置1,就能够打开相应的时钟。于是我们可以这样写代码:
[cpp] view plaincopy


  • //clock in APB2  
  • #define OpenAFIOClock()                 RCC_APB2ENR_CONTENTS|=(1<<0)  
  • #define OpenPortAClock()                RCC_APB2ENR_CONTENTS|=(1<<2)        
  • #define OpenPortBClock()                RCC_APB2ENR_CONTENTS|=(1<<3)  
  • #define OpenPortCClock()                RCC_APB2ENR_CONTENTS|=(1<<4)  
  • #define OpenPortDClock()                RCC_APB2ENR_CONTENTS|=(1<<5)  
  • #define OpenPortEClock()                RCC_APB2ENR_CONTENTS|=(1<<6)  
  • #define OpenAllPortClock()              RCC_APB2ENR_CONTENTS|=0x01FC  
  • #define OpenADC1Clock()                 RCC_APB2ENR_CONTENTS|=(1<<9)  
  • #define OpenADC2Clock()                 RCC_APB2ENR_CONTENTS|=(1<<10)  
  • #define OpenSPI1Clock()                 RCC_APB2ENR_CONTENTS|=(1<<12)  
  • #define OpenUSART1Clock()               RCC_APB2ENR_CONTENTS|=(1<<14)  
  • #define OpenADC3Clock()                 RCC_APB2ENR_CONTENTS|=(1<<15)  


或者这个1<<x位可以这样写:
[cpp] view plaincopy


  • #define bit(b)                          (1<<b)  
  • #define OpenAFIOClock()                 RCC_APB2ENR_CONTENTS|=bit(0)  
  • #define OpenPortAClock()                RCC_APB2ENR_CONTENTS|=bit(2)         
  • #define OpenPortBClock()                RCC_APB2ENR_CONTENTS|=bit(3)  
  • #define OpenPortCClock()                RCC_APB2ENR_CONTENTS|=bit(4)  
  • //...  


关于bit这个宏定义在之前的Arduino部分库函数分析中已经说明了,不懂的话可以看看这个bit的使用技巧。
同样的道理,在STM32数据手册的第71页也说明了RCC_APB1ENR的每一位的作用。这里就不啰嗦了。

使用特权

评论回复
板凳
energy1|  楼主 | 2015-2-27 21:31 | 只看该作者
3.Systick
代码架构篇1中我们简单的说了一下如何使用Systick来实现一个延时的函数。这个Systick是基于CM3内核的所有芯片都具有的。正是因为有了这个CM3的强制支持,才使得嵌入式系统在所有的CM3内核的微控制器上得到更好地可移植性。这一小节我们就来谈谈这个Systick。在《STM32数据手册》中是找不到这个Systick的,要找这个Systick需要从《Cortex M3权威指南》(宋岩译)中寻找(在第133页)。

我们主要使用上面中的前两个寄存器,第一个是一开始在初始化的时候设置的,只要是设置时钟源(我们这里使用内核时钟源,72MHz),计数到0的时候是否产生中断,开启定时器。第二个寄存器是重装寄存器。
CM3内核的Systick是从高往低每隔一个技术周期减1,也就是说从重装数寄存器里面的数值开始递减,每隔一个定时周期(定时周期有控制及状态寄存器中的时钟源决定的)减小1,当减小到0的时候如果控制及状态
寄存器的TICKINT位位1的话产生一个中断请求,如果是0的话不产生中断(无动作)。下面我们就来谈谈如何设置Systick来产生我们需要的时基(时间基准,比如说1ms)。
看过上面的寄存器的说明大家可能很清楚了:1.设置控制及状态寄存器的时钟源,开启中断模式。2.根据你的时基配置重装数寄存器。3.Enable Systick。下面看看代码部分;
在V3.50的库的STM32F10x_StdPeriph_Driver文件夹里面是找不到Systick有关的函数的。因为这个Systick是由ARM公司设计的,所以这个关于Systick的代码是由ARM公司编写的。当我们打开关于Systick的文件时看见下面的注释:
[cpp] view plaincopy


  • /**************************************************************************//**
  • * @file     core_cm3.h
  • * @brief    CMSIS Cortex-M3 Core Peripheral Access Layer Header File
  • * @version  V1.30
  • * @date     30. October 2009
  • *
  • * @NOTE
  • * Copyright (C) 2009 ARM Limited. All rights reserved.
  • *
  • * @par
  • * ARM Limited (ARM) is supplying this software for use with Cortex-M  
  • * processor based microcontrollers.  This file can be freely distributed  
  • * within development tools that are supporting such ARM based processors.  
  • *
  • * @par
  • * THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED
  • * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
  • * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.
  • * ARM SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR
  • * CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
  • *
  • ******************************************************************************/  


可以看见这个Systick部分的文件是由ARM公司保留所有。这个文件路径是CMSIS--->CM3--->Core_Cm3.h,Core_Cm3.c。在里面我们看到了这样的代码(core_cm3.h):
[cpp] view plaincopy


  • typedef struct  
  • {  
  •   __IO uint32_t CTRL;


使用特权

评论回复
地板
energy1|  楼主 | 2015-2-27 21:35 | 只看该作者
第一个寄存器的名字叫做CTRL,第二个是LOAD,第三个是VAL,第四个不常用,上面的图片中没贴。这个SysTick_Type是一个结构体,而不是一个变量,我们如何来操作CTRL,LOAD呢?接着往下看:
[cpp] view plaincopy


  • #define SysTick             ((SysTick_Type *)       SysTick_BASE)     /*!< SysTick configuration struct ,Line 724     */  


而这个SysTick_BASE呢?
[cpp] view plaincopy


  • #define SCS_BASE            (0xE000E000)                              /*!< System Control Space Base Address */        
  • #define SysTick_BASE        (SCS_BASE +  0x0010)                      /*!< SysTick Base Address              */  


这样加起来SysTick_BASE也就是0XE000E010,而这个地址呢?看上面贴的图正是控制及状态寄存器(CTRL)的地址。这样看起来也就是SysTick是一个SysTick_Type 类型的指针,这个指针指向了地址在0XE000E010。由于struct的类型的地址是自动偏移的,这里简单的来计算一下一个寄存器的地址,就看重载数寄存器的地址吧:由于struct的地址在内存中是连续排列的,看下面的那一张图:


由于每个字节是32bit,也就是4Byte,因此RELOAD的地址是0XE000_E010+4,其余的寄存器也是同样的方法。这和上图贴出的地址是吻合的。另外STM的寄存器定义都是根据这种struct在内存中是连续存放的这个原理实现的,大家可以看看V3.50库的GPIO_TypeDef。以及GPIOA,GPIOB,GPIOC。。。的定义,和这个Systick都是类似的。说了这么多,下面我们来贴代码:

[cpp] view plaincopy


  • #include"core_cm3.h"  
  •   
  • //Function: config Systick   
  • //1.clock is core clock (72MHz)  
  • //2.enable interrupt  
  • //3.1ms  
  • //4.enable systick  
  • void SystickConfig(void)  
  • {  
  •     SysTick->CTRL|=(bit(2)|bit(1));//Function 1 and 2  
  •     SysTick->LOAD=72000;//Function 3  
  •     SysTick->CTRL|=bit(0);//Function 4  
  • }  


这样我们的Systick就可以配置成为1ms产生一次中断请求了,配合我们在《代码架构篇1》 中讲的,就能够产生一个精确的定时(延时)了。

使用特权

评论回复
5
energy1|  楼主 | 2015-2-27 21:37 | 只看该作者
注意:STM32F10X系列的有一些产品的内核时钟不是72MHz,这样上面函数中的那个72000就需要重新计算了。因为我们的时钟是72Mhz,一个周期是1/72us,当计数到72000的时候正好是时间1/72us*72000=1000us=1ms。看下面的代码:
File:system_stm32f10x.c

[cpp] view plaincopy


  • #if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)  
  • /* #define SYSCLK_FREQ_HSE    HSE_VALUE */  
  • #define SYSCLK_FREQ_24MHz  24000000  
  • #else  
  • /* #define SYSCLK_FREQ_HSE    HSE_VALUE */  
  • /* #define SYSCLK_FREQ_24MHz  24000000 */   
  • /* #define SYSCLK_FREQ_36MHz  36000000 */  
  • /* #define SYSCLK_FREQ_48MHz  48000000 */  
  • /* #define SYSCLK_FREQ_56MHz  56000000 */  
  • #define SYSCLK_FREQ_72MHz  72000000  
  • #endif  


如果你的芯片是LD_VL,MD_VL,HD_VL,那么你的时钟只能是24MHz,如果是其他的芯片,默认的是72MHz,但是也可以选择成为其他的频率。如果不了解LD_VL,MD,HD,请看《流水灯(初级篇)》
想让定时器一开始就工作,可以在main函数中添加代码:
[cpp] view plaincopy


  • int main()  
  • {  
  •     SystickConfig();  
  •     //add your code threr...  
  • }  


注意:SysTick的中断服务函数(ISR)在STM32F10X_it.c中,当你Enable了Systick之后,一定要添加STM32F10X_it.c到你的工程中取,否则,进入Systick的中断之后一直是一个死循环。

使用特权

评论回复
6
energy1|  楼主 | 2015-2-27 21:40 | 只看该作者
4.程序一直是从main执行的吗?
一般情况下我们认为的是程序是一直从main函数开始执行的,这主要是我们习惯了window或者基于其他操作系统下的编程。在那些系统上面,比如说千帆使用的盗版的win7系统,程序从main函数开始执行是确定无疑的,但是在我们的STM32平台上面就不是这样了。我们在《流水灯(初级篇)》 中说了要想使你的芯片能够正常工作,一开始要添加的一个启动文件到你的工程中,这个启动文件要根据你的芯片实际选择。下面就简单的看一下这个startup_stm32f10x_md.s文件的部分内容吧:
[plain] view plaincopy


  • ; Reset handler  
  • Reset_Handler    PROC  
  •                  EXPORT  Reset_Handler             [WEAK]  
  •      IMPORT  __main  
  •      IMPORT  SystemInit  
  •                  LDR     R0, =SystemInit  
  •                  BLX     R0  
  •                  LDR     R0, =__main  
  •                  BX      R0  
  •                  ENDP<u>  
  • </u>  


这是一个复位中断的ISR,不了解复位中断的话看看《Cortex M3权威指南》的第45页,里面有解释:(下面是截取的一部分)
请注意,这与传统的ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的
ARM 架构总是从0 地址开始执行第一条指令。它们的0 地址处总是一条跳转指令。在CM3
中,0 地址处提供MSP 的初始值,然后就是向量表(向量表在以后还可以被移至其它位置)。
向量表中的数值是32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行
的第一条指令。(摘抄自《Cortex M3权威指南》中文版,宋岩译

简单地说,0x0000_0000地址处存放的是MSP(主堆栈指针)的初始值,也就是栈顶。0x0000_0004地址地址处存放的是复位中断的ISR,也就是上面看到汇编代码。这个复位中断可以上上复位引起的中断,也可以是你手动按下复位键之后的中断。
总之,这个复位中断处理程序是你需要首先执行的。看看这个复位中断的处理程序干了些什么。首先它调用了一个其他文件(由IMPORT可以看出)的函数SystemInit,然后调用了__main(也就是我们编写的main函数)。而这个SystemInit是在文件
system_stm32f10x.c文件中实现的,下面是它的代码:
[cpp] view plaincopy


  • /**
  •   * @brief  Setup the microcontroller system
  •   *         Initialize the Embedded Flash Interface, the PLL and update the  
  •   *         SystemCoreClock variable.
  •   * @note   This function should be used only after reset.
  •   * @param  None
  •   * @retval None
  •   */  
  • 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;  
  •   
  • #ifdef STM32F10X_CL  
  •   /* 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;  
  • #elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)  
  •   /* Disable all interrupts and clear pending bits  */  
  •   RCC->CIR = 0x009F0000;  
  •   
  •   /* Reset CFGR2 register */  
  •   RCC->CFGR2 = 0x00000000;        
  • #else  
  •   /* Disable all interrupts and clear pending bits  */  
  •   RCC->CIR = 0x009F0000;  
  • #endif /* STM32F10X_CL */  
  •       
  • #if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)  
  •   #ifdef DATA_IN_ExtSRAM  
  •     SystemInit_ExtMemCtl();   
  •   #endif /* DATA_IN_ExtSRAM */  
  • #endif   
  •   
  •   /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */  
  •   /* Configure the Flash Latency cycles and enable prefetch buffer */  
  •   SetSysClock();  
  •   
  • #ifdef VECT_TAB_SRAM  
  •   SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */  
  • #else  
  •   SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */  
  • #endif   
  • }  


使用特权

评论回复
7
energy1|  楼主 | 2015-2-27 21:41 | 只看该作者
看上面的代码,主要是配置一些系统的时钟,如果有嵌入的Flash器件,则初始化它。对这些代码我们不用动它,保持原有的即可。另外,看他的注释,有一个需要注意的事项:
Note:This function should be used only after reset
也就是说这个代码你需要你主动调用,这个代码是给复位中断的ISR使用的,并且只能给复位中断的ISR使用。
从这个例子中,我们看出我们写的程序并不是从main开始执行的,而是从这个systemInit开始执行的。在看看上面我们配置的Systick,我们把它放在main的开始出主要是想让他一开始就会得到执行,并且每一次建立一个工程我们都需要先在main函数中写一遍,是挺麻烦的,现在看来有一个更好的方法了,直接Systick的配置写在SystemInit中就行了。于是我们将SystemInit函数做如下改写,上面的代码都没有改变,只是在函数的结尾处添加了一段代码:
[cpp] view plaincopy


  • //。。。  
  • #ifdef VECT_TAB_SRAM  
  •   SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */  
  • #else  
  •   SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */  
  • #endif   
  •     /************User Add,Set and open systick**************/  
  •     SysTick->LOAD=72000;//1ms  
  •     SysTick->CTRL|=0x07;//enable,open handler,core clock  
  •     /*******************end user add************************/  
  • }  



使用特权

评论回复
8
energy1|  楼主 | 2015-2-27 21:43 | 只看该作者
5.GPIO
说了这么多,如何操作引脚我们还没有说清楚,下面就来看看操作引脚的主角,GPIO。这个GPIO翻译过来也就是通用输入输出口,和51上面的引脚是差不多的。只不过传统51的端口只有输入输出两种方式,而这个STM32的GPIO有八种方式。这八种方式我们在《流水灯初级篇》中说过,不记得的可以去翻翻前面的。
接下来,看看STM的软件工程师是如何来定义GPIO的:
[cpp] view plaincopy


  • typedef struct  
  • {  
  •   __IO uint32_t CRL;  
  •   __IO uint32_t CRH;  
  •   __IO uint32_t IDR;  
  •   __IO uint32_t ODR;  
  •   __IO uint32_t BSRR;  
  •   __IO uint32_t BRR;  
  •   __IO uint32_t LCKR;  
  • } GPIO_TypeDef;//FILE:stm32f10x.h Line:1001  


[cpp] view plaincopy


  • #define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)  
  • #define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)  
  • #define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)  
  • #define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)  
  • #define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)  
  • #define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)  
  • #define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)  //FILE:stm32f10x.h Line:1408  


这个和上面我们分析SysTick的时候是一样的,都是通过struct在内存真那个连续存放这个特点来实现寄存器地址的增加。这样看来GPIOA->BRR的地址就是GPIOA_BASE+5*4。这个5*4Byte(0X14)是偏移地址。得到的GPIOA+0X14也就是真是的地址。同样的道理,我们也可以分析出GPIOB->BRR,GPIOC->BRR等等的地址。对于这个运算符->不熟悉的同学可要好好的看看C语言中结构体指针这一块了。在这里就不啰嗦了。
看看官方的《STM32数据手册》是如何对这些寄存器解释的。先看几个简单的寄存器吧,GPIOx->ODR,GPIOx->BRR和GPIOx->BSRR。在《STM32数据手册》的第115页。



可以看到,这个ODR就类似于51中的P1,只是管着输出的,比如说想让GPIOA口输出1010_1010_1010_1010这个二进制数,那么只需要将这个0XAAAA放到ODR中即可。只需要简单的代码:

[cpp] view plaincopy


  • GPIOA->ODR=0XAAAA;  


但是这样也有一个难题摆在面前,假如说我只将PA0输出一个0,并且不改变其他的位,这样该怎么办呢?直接将GPIOA->ODR=0X0000;这样是不不行的。因为这样也吧其他的位都清零了,那么应该这样做:先读,在改,最后在回写。

[cpp] view plaincopy


  • Word tmp;  
  • tmp=GPIOA->ODR;  
  • tmp&=0xFFFE;//1111_1111_1111_1110  
  • GPIOA->ODR=tmp;  


使用特权

评论回复
9
energy1|  楼主 | 2015-2-27 21:43 | 只看该作者
这样写虽然比较麻烦,但是能够实现效果。STM公司早就想到了这点,刚刚还看到了GPIOx->BRR这个寄存器,来看看这个寄存器的含义:

往这个寄存器的某一个位写1可以将对应的引脚清零,写0对对应的引脚不产生作用。看看上一个我们将PA0清零的例子,现在我们可以这样写:

[cpp] view plaincopy


  • #define bit(b)              (1ul<<b)  
  •   
  • GPIOA->BRR|=bit(0);  


如果是将PA13清零呢?只需要

[cpp] view plaincopy


  • GPIOA->BRR|=bit(13);  


这样是不是很简单呢?这里是将某一位清零,如果是将某一位置1呢?除了读-改-写三步**,我们还可以使用BSRR这个特殊的寄存器:

使用特权

评论回复
10
energy1|  楼主 | 2015-2-27 21:44 | 只看该作者
更正:
上面说的GPIOA->BRR|=bit(0);可以将PA0清零,这个代码虽然能够产生正确的结果,但是这不是最好的结果。看看上面对寄存器的描述,如果对BRR的某一位写1可以清零这一位,如果写0的话没有影响,这也就是说,如果我们想清零PA0的话,完全可以这样写:GPIOA->BRR=bit(0);这样是更好的方法。为什么呢?我们看看第一种使用|的方式展开之后的样子:
[cpp] view plaincopy


  • int tmp=GPIOA->BRR;  
  • tmp|=bit(0);  
  • GPIOA->BRR=tmp;  


这样使用|的方式依然是那种三步走,读,改,写。而使用=赋值的方式只是使用了其中的写这一步。这样的话执行的效率是更高的。这样看来,ST的工程师对BRR写0不产生影响的定义看来是另有意义的。
同样,BSRR也是这样的道理。


这个BSRR可以单独的将某一位置1.比如说将PA13置1,可以使用这样的代码:

[cpp] view plaincopy


  • GPIOA->BSRR|=bit(13);  


另外,可能淘气的人会这样使用代码:

[cpp] view plaincopy


  • GPIOA->BSRR|=bit(16);  


而这个代码会产生什么样的效果呢?将PA0清零。这个一定要记住啦。

使用特权

评论回复
11
energy1|  楼主 | 2015-2-27 21:44 | 只看该作者
说了这么多,该如何实现我们在《代码架构篇1》中提出的DigtalWrite呢?下面来看一种可行的实施方法:

[cpp] view plaincopy


  • #include"Stm32f10x_rcc.h"  
  • #include"Stm32f10x_gpio.h"  
  •   
  • typedef unsigned char Pin;  
  • typedef unsigned char Byte;  
  • #define bit(b)              (1ul<<b)  
  • #define PC13                ((Pin)0X2D)  
  • #define PinPort(pin)        ((Byte)pin>>4)  
  • #define PinNum(pin)         ((Byte)pin&0x0f)  
  • #define PortA               ((Byte)0x0)  
  • #define PortB               ((Byte)0x1)  
  • #define PortC               ((Byte)0x2)  
  •   
  • void DigtalWrite(const Pin pin,const Byte toWrite);  
  •   
  • int main()  
  • {  
  •     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//open gpioa clock  
  •       
  •     //config all pins  
  •     GPIO_InitTypeDef init;  
  •     init.GPIO_Mode=GPIO_Mode_Out_PP;  
  •     init.GPIO_Speed=GPIO_Speed_10MHz;  
  •     init.GPIO_Pin=GPIO_Pin_13;  
  •       
  •     GPIO_Init(GPIOC,&init);  
  •       
  •     while(1)  
  •     {  
  •         DigtalWrite(PC13,1);  
  •         for(int i=0;i<7200000;i++) ;  
  •         DigtalWrite(PC13,0);  
  •         for(int i=0;i<7200000;i++) ;  
  •     }  
  •       
  • }  
  •   
  • void DigtalWrite(const Pin pin,const Byte toWrite)  
  • {  
  •     Byte write=(toWrite==0?0:1);  
  •     Byte num=PinNum(pin);  
  •       
  •     switch(PinPort(pin))  
  •     {  
  •         case PortA:  
  •             //...  
  •             break;  
  •         case PortB:  
  •             //...  
  •             break;  
  •         case PortC:  
  •             if(write){  
  •                 GPIOC->BSRR|=bit(num);  
  •             }  
  •             else{  
  •                 GPIOC->BRR|=bit(num);  
  •             }  
  •             break;  
  •     }  
  • }  


上面是千帆的一些测试代码。其中//...的部分是千帆省略了,需要你自己补充的。因为千帆使用的板子上面PC13口上面有一个LED,所以用这个代码测试起来比较方便。如果你看不懂这部分的代码,没关系,去千帆之前的一篇《代码架构篇1》中去寻找答案吧,如果你对bit的使用不懂,请看这一篇《Arduino部分库函数分析》

使用特权

评论回复
12
energy1|  楼主 | 2015-2-27 21:45 | 只看该作者
看完了GPIO简单的寄存器,下面来看一个复杂的寄存器:GPIOx->CRL和GPIOx->CRH。为什么说这两个寄存器比较复杂呢,因为如果是自己配置寄存器的话会比较复杂。下面看看这两个寄存器器的介绍:

CRH和CRL每一位的功能是相同的,只不过CRL控制的是每一个端口的引脚0~引脚7共8个引脚,CRH控制的是每一个端口的引脚8~引脚15共8个引脚。这样CRL,CRH才能控制一个端口的16个引脚。
从图中也可以看出,每一个引脚的模式的配置是需要4bit来配置的。这4bit中的高两位代表哪一种方式(输入还是输出),当时输出模式的时候,低两位代表输出的速度,当时输入模式的时候,低两位的作用不大,
并且只能设置成为00。这样看来,假设我们需要将某一个引脚设置成为浮空输入,那么着四位从高到底依次是0100 (这也正是上电复位后的状态)。相同的,如果是设置成为推挽输出呢?很显然,从高到低四位依次是00XX(XX是除了00的任何值).
这后面的XX代表的是三种不同的输出速度。
千帆初学的时候,使用V3.50的库文件来编程,当把一个引脚配置成为输入模式的时候,看见别人没有给他配置速度,很是不解。当看到了这个寄存器功能的时候,才恍然大悟。可是这样看着挺简单的,根据在之前代码架构的时候提出的函数设想,该如何实现带代码呢?在之前我们是这样来架构我们的代码的:

[cpp] view plaincopy


  • typedef enum   
  • {  
  •     AnalogIn=0x00,InFloating=0x04,IPD=0x28,IPU=0x48,  
  •     OutOD=0x14,OutPP=0x10,AFOD=0x1C,AFPP=0x18  
  • }PinMode;  
  • typedef enum{Speed_10M=1,Speed_2M,Speed_50M} PinSpeed;  


[cpp] view plaincopy


  • void SetPinMode(const Pin pin,const PinMode mode,const PinSpeed speed=Speed_50M);//函数声明  


这个SetPinMode函数的声明看起来怪怪的,前两个参数比较简单,一个是引脚,一个是模式,可是第三个后面有个=,这是什么意思呢?在刚开始的时候,千帆就和大家说过,在写代码的时候,后缀改成.cpp,以C++的方式编程。后面的第三个参数叫做默认参数,也就是说,当调用函数的时候,第三个参数可以写,也可以不写。如果写了一个新的参数,那么新的参数将覆盖这个Speed_50M ,如果不写的话,第三个参数默认就是Speed_50M。
下面来想一想这个函数应该怎样写:
1.根据引脚来判断是需要CRL还是需要使用CRH。如果PinNum(pin)>7,那么操作的就是CRH,否则是CRL;
2.将之前控制引脚的位擦出(清零)。比如说第二个引脚吧,那就将CRL的4~7位擦除;
3.根据Mode和Speed算出需要在引脚控制位的地方写入的4bit(比如说第二引脚需要设置成为推挽输出,50MHz,那么就将4~7位写入0011)。
下面是函数的具体实现,千帆写的稍微有点有点复杂。如果你有好的算法,希望能够告诉我,谢过啦;

[cpp] view plaincopy


  • #define CNF_MODE_AIN            0X0 /*Andlog In*/  
  • #define CNF_MODE_INFLOATING     0X4 /*In Floating*/  
  • #define CNF_MODE_IP             0x8 /*IPD and IPU */  
  •   
  • void SetPinMode(const Pin pin,const PinMode mode,const PinSpeed speed)  
  • {  
  •     OpenPinClock(pin);  
  •     //add your code in there...  
  •     Byte CNFandMODE=0x0;  
  •     switch(mode)  
  •     {  
  •         case AnalogIn:  
  •             SetPinMode_1(pin,CNF_MODE_AIN);  
  •         return ;  
  •          
  •         case InFloating:  
  •             SetPinMode_1(pin,CNF_MODE_INFLOATING);  
  •         return ;  
  •          
  •         case IPD:  
  •             SetPinMode_1(pin,CNF_MODE_IP);  
  •             DigtalWrite(pin,0);  
  •         return ;  
  •          
  •         case IPU:  
  •             SetPinMode_1(pin,CNF_MODE_IP);  
  •             DigtalWrite(pin,1);  
  •         return ;  
  •          
  •         case OutPP:  
  •             CNFandMODE=(0x0|speed);/* 00,speed*/  
  •             SetPinMode_1(pin,CNFandMODE);  
  •         return ;  
  •          
  •         case OutOD:  
  •             CNFandMODE=(0x4|speed);/*01,speed*/  
  •             SetPinMode_1(pin,CNFandMODE);  
  •         return ;  
  •          
  •         case AFPP:  
  •             CNFandMODE=(0x8|speed);/*10,speed*/  
  •             SetPinMode_1(pin,CNFandMODE);  
  •         return ;  
  •          
  •         case AFOD:  
  •             CNFandMODE=(0xC|speed);/*11,speed*/  
  •             SetPinMode_1(pin,CNFandMODE);  
  •         return ;  
  •               
  •          
  •     }  
  •       
  •       
  • }  




[cpp] view plaincopy


  • void SetPinMode_1(const Pin pin,const Byte toWrite)  
  • {  
  •     Byte num=PinNum(pin);  
  •     Byte pos=(num<8?num:num-8)*4;//result:0,4,8,12,16,20,24,28  
  •     DWord clearBits= ~(0xF<<pos);  
  •       
  •     /**
  •     ***First:首先将CRL或者是CRH寄存器中影响pin的位清除
  •     **Second: 将toWrite指定的四个二进制位填充进刚刚清零的地方
  •     **/  
  •     switch(PinPort(pin))  
  •     {  
  •         case PortA:  
  •             if(num<8){//Op CRL  
  •                 GPIOA->CRL&=clearBits;  
  •                 GPIOA->CRL|=(toWrite<<pos);  
  •             }  
  •             else{//Op CRH  
  •                 GPIOA->CRH&=clearBits;  
  •                 GPIOA->CRH|=(toWrite<<pos);  
  •             }  
  •             break;  
  •         case PortB:  
  •             if(num<8){  
  •                 GPIOB->CRL&=clearBits;  
  •                 GPIOB->CRL|=(toWrite<<pos);  
  •             }  
  •             else{  
  •                 GPIOB->CRH&=clearBits;  
  •                 GPIOB->CRH|=(toWrite<<pos);  
  •             }  
  •             break;  
  •         case PortC:  
  •             if(num<8){  
  •                 GPIOC->CRL&=clearBits;  
  •                 GPIOC->CRL|=(toWrite<<pos);  
  •             }  
  •             else{  
  •                 GPIOC->CRH&=clearBits;  
  •                 GPIOC->CRH|=(toWrite<<pos);  
  •             }  
  •             break;  
  •         case PortD:  
  •             if(num<8){  
  •                 GPIOD->CRL&=clearBits;  
  •                 GPIOD->CRL|=(toWrite<<pos);  
  •             }  
  •             else{  
  •                 GPIOD->CRH&=clearBits;  
  •                 GPIOD->CRH|=(toWrite<<pos);  
  •             }  
  •             break;  
  •         case PortE:  
  •             if(num<8){  
  •                 GPIOE->CRL&=clearBits;  
  •                 GPIOE->CRL|=(toWrite<<pos);  
  •             }  
  •             else{  
  •                 GPIOE->CRH&=clearBits;  
  •                 GPIOE->CRH|=(toWrite<<pos);  
  •             }  
  •             break;  
  •     }  
  • }  


对这两段代码就不做过多的解释啦

使用特权

评论回复
13
baita| | 2015-2-27 22:12 | 只看该作者
写的不错我都是这么做的,看库太麻烦还是寄存器来的直接。

使用特权

评论回复
14
mmuuss586| | 2015-2-28 12:22 | 只看该作者
谢谢楼主分享;

使用特权

评论回复
15
zh113214| | 2015-2-28 14:22 | 只看该作者
这些资料直接下载datasheet PDF看就ok啊

使用特权

评论回复
16
powerful1| | 2015-2-28 14:23 | 只看该作者
谢谢分享

使用特权

评论回复
17
vigous1| | 2015-2-28 16:11 | 只看该作者
操作底部的寄存器来实现我们灯闪烁的效果

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

94

主题

422

帖子

10

粉丝