stm32学习(一)

公司开发需要用到stm32,nxp,gd32等系列的单片机,大学几乎没接触过单片机的学习,所以只能自己下来慢慢学,如有错误,欢迎指出。

ARM(公司,处理器,架构)

ARM处理器家族

  • 经典:ARM7,ARM9,ARM11
  • Cortex-M:单片机驱动(控制)
  • Cortex-A:开发式系统的高性能处理器(智能手机 ,数字电视)
  • Cortex-R:实时系统,实时的控制需求(汽车制动,动力系统)

想要操作stm32的外设通常可以使用寄存器或者stm32库,在当今stm32库已经十分成熟,并且操作十分简单。

寄存器:寄存器则是内置于各个 IP 外设中,是一种用于配置外设功能的存储器,就是一种内存,并且有相对应的地址。

GPIO:通用输入输出接口

按键:通过输入电平的高低感知来得到按键的按下与否。

led灯:通过输出高低电平控制led的亮与灭。

GPIO也可以作为串口数据的收发管脚或AD接口等复用功能,根据数据手册配置相应的功能寄存器。

GPIO寄存器MODER

32位两位为一组,分为了16个,分别对应每一组GPIO的16个 管脚。如PA0-PA15。

点灯:学习嵌入式开发最开始的操作就是点灯,以下使用寄存器操作和库函数操作来点灯。

寄存器:从技术参考手册中可以找到ODR 寄存器的地址偏移是: 0CH,这个偏移地址是基于端口的起始地址而言的。在 STM32 中,每个外设都有一个起始地址,叫做外设基址,外设的寄存器就以这个基地址为标准按照顺序排列,跟结构体里面的成员差不多。

假设我们需要点亮的灯是由GPIOB-0这个管脚去控制的,找到GPIOB对应的基地址,再加上ODR的偏移地址就可以去实现输出功能。

 

其中 GPIOB 的起始地址是: 0X4001 0C00,这样就可以算出 GPIOB_ODR 寄存器的地址是: 0X4001 0C00 + 0X0C = 0X4001 0C0C。现在我们就可以定义 GPIOB_ODR 这个寄存
器了:
 
#define     GPIOB_ODR     *(volatile unsigned long *)0x40010C0C
有了这个寄存器定义,我们就可以直接操作 GPIOB_ODR 了。虽然配置了 ODR 寄存器,但是这个时候还不能点亮 LED ,因为 STM32 的 IO 口还要配置方向,这个由端口配置寄存器来控制。端口配置寄存器分为高低两个,每 4bit 控制一个 IO 口,所以端口配置低寄存器: CRL 控制这 IO 口的低 8 位,端口配置高寄存器: CRH控制这 IO 口的高 8位。在 4 位一组的控制位中, CNFy[1:0] 用来控制端口的输入输出,MODEy[1:0] 用来控制输出模式的速率,即输出时, IO 电平翻转的速度。
输入有三种模式,输出有 4 中模式,我们在控制 LED 的时候选择通用推挽输出。
输出速率有三种模式: 2M、10M 、50M,这里我们选择 2M。 
 
同 GPIOB_ODR 一样,我们也可以算出 GPIO_CRL 的地址为: 0x40010C00。那么设置PB0 为通用推挽输出,输出速率为 2M 的代码则如下所示:
#define GPIOB_CRL *(volatile unsigned long *)0x40010C00
// 配置 PB0 为通用推挽输出,输出速率为 2M
GPIOB_CRL = (2<<0) | (0<<2); 
当我们设置了 IO 口的方向,并在相应的输出寄存器里面输入了值的时候,以为现在总算可以点亮 LED 了吧,其实还差最后一步。
STM32 外设很多,为了降低功耗,每个外设都对应着一个时钟,在系统复位的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。
STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC(reset and clockcontrol ), RCC 在 STM32 中文参考手册的第六章。 
 
STM32 的外设因为速率的不同,分别挂载到三条总系上: AHB 、APB2 、APB1 ,APB为高速总线, APB2 次之, APB1 再次之。
所有的 IO 口都挂载到 APB2 总线上,属于高速外设。时钟由 APB2 外设时钟使能寄存器 (RCC_APB2ENR) 来控制,其中 PB 端口的时钟由该寄存器的位 3 写 1 使能。
 
同 ODR 和 CRL,我们可以算出 RCC_APB2ENR 的地址为: 0x40021018。那么使能PB 口的时钟代码则如下所示:
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018
// 开启端口 B 时钟
RCC_APB2ENR |= 1<<3;
我们虽然开了端口时钟,那这个时钟到底是多大?时钟到底是从哪里来的?
如果我们用的是库,那么有个库函数 SystemInit ,会帮我们把系统时钟设置成 72M 。现在我们没有使用库,那现在时钟是多少?
答案是 8M,当外部 HSE 没有开启或者出现故障的时候,系统时钟由内部低速时钟 LSI 提供,现在我们是没有开启 HSE,所以系统默认的时钟是 LSI=8M 。
如果你想自己先尝鲜,那么看 RCC 外设中的:时钟控制寄存器 (RCC_CR) 和时钟配置寄存器(RCC_CFGR) 这两个寄存器即可。
 
控制了电平,配置了方向,开启了时钟,经过这三步,我们总算可以控制一个 LED了。
现在我们完整组织下用 STM32 控制一个 LED 的代码:
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018
#define GPIOB_CRL *(volatile unsigned long *)0x40010C00
#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main( void )
{ // 开启端口 B 的时钟
RCC_APB2ENR |= 1<<3;

// 配置 PB0 为通用推挽输出模式,速率为 2M
GPIOB_CRL = (2<<0) | (0<<2);

// PB0 输出低电平,点亮 LED
GPIOB_ODR = 0<<0;
}

void SystemInit( void )
{

}

这种就是比较愚蠢的配置方式,每次配置一个外设就去从参考手册中去找,浪费时间且容易出错,但新手入门是需要理解并且操作学习一下的。

如果要以寄存器配置的话,会将所有的寄存器位置都先定义好,这样就不用一个个去找。实际开发过程中很少使用寄存器操作,一般都是库函数的操作,下面介绍库的操作。

keilstm32环境自行去百度配置,篇幅太长就不写出了。

固件库分析:看看每个文件的作用是什么,这对我们能否清晰的调用库函数编程非常重要。

STM32 Cortex-M3 内核和内核之外的各种外设组成,库在编写的时候也遵循这中组成结构,把代码分成两大部分,一种是操作内核外设的,

另外一种是内核之外的外设,为了听起来不那么绕,下面我们把内核之外的外设用处理器外设来代替。

  • startup_stm32f10x_hd.s:
    这个是由汇编编写的启动文件,是 STM32 上电启动的第一个程序,启动文件主要实现了:1、初始化堆栈指针SP;2、设置 PC 指针 =Reset_Handler ;3、设置向量表的地址,并初始化向量表,向量表里面放的是 STM32 所有中断函数的入口地址4、调用库函数SystemInit ,把系统时钟配置成 72M ,SystemInit 在库文件 stytem_stm32f10x.c 中定义;5、跳转到标号 _main,最终去到 C 的世界。
  • system_stm32f10x.c:
    这个文件的作用是里面实现了各种常用的系统时钟设置函数,有 72M,56M, 48,36,24,8M,我们使用的是把系统时钟设置成 72M。
  • stm32f10x.h:
    这个头文件非常重要,可以说是上帝之手。这个头文件实现了: 1、处理器外设寄存器的结构体定义 2、处理器外设的内存映射 3、处理器外设寄存器的位定义。
  • stm32f10x_xxx.h:
    外设 xxx 应用函数库头文件,这里面主要定义了实现外设某一功能的结构体,比如通用定时器有很多功能,有定时功能,有输出比较功能,有输入捕捉功能,而通用定时器有非常多的寄存器要实现某一个功能,比如定时功能,我们根本不知道具体要操作哪些寄存器,这个头文件就为我们打包好了要实现某一个功能的寄存器,是以结构体的形式定义的,比如通用定时器要实现一个定时的功能,我们只需要初始化TIM_TimeBaseInitTypeDef 这个结构体里面的成员即可,里面的成员就是定时所需要操作的寄存器。 有了这个头文件,我们就知道要实现某个功能需要操作哪些寄存器,然后再回手册中精度这些寄存器的说明即可。
  • stm32f10x_xxx.c :
    外设 xxx 应用函数库,这里面写好了操作 xxx 外设的所有常用的函数,我们使用库编程的时候,使用的最多的就是这里的函数。
  • cor_cm3.h:
    这个头文件实现了: 1、内核结构体寄存器定义 2、内核寄存器内存映射 3、内存寄存器位定义。跟处理器相关的头文件 stm32f10x.h 实现的功能一样,一个是针对内核的寄存器,一个是针对内核之外,即处理器的寄存器。
  • misc.c:
    内核应用函数库文件,对应 stm32f10x_xxx.c 。在 CM3 这个内核里面还有一些功能组件,如 NVIC、SCB、ITM 、MPU、CoreDebug ,CM3 带有非常丰富的功能组件,但是芯片厂商在设计 MCU 的时候有一些并不是非要不可的,是可裁剪的,比如 MPU、ITM 等在STM32 里面就没有。其中 NVIC 在每一个 CM3 内核的单片机中都会有,但都会被裁剪,只能是 CM3 NVIC 的一个子集。在 NVIC 里面还有一个 SysTick,是一个系统定时器,可以提供时基,一般为操作系统定时器所用。
  • misc.h:
    内核应用函数库头文件,对应 stm32f10x_xxx.h 。

直接写库函数操作了:

void LED_GPIO_Config(void)
{        
    /*定义一个GPIO_InitTypeDef类型的结构体*/
    GPIO_InitTypeDef GPIO_InitStructure;

    /*开启GPIOC的外设时钟*/
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); 

    /*选择要控制的GPIOB引脚*/                                                               
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;    

    /*设置引脚模式为通用推挽输出*/
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

    /*设置引脚速率为50MHz */   
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 

    /*调用库函数,初始化GPIOB*/
      GPIO_Init(GPIOB, &GPIO_InitStructure);          

    /* 关闭led灯    */
    GPIO_SetBits(GPIOB, GPIO_Pin_0);     

    /* 开启led灯    */
  GPIO_ResetBits(GPIOB, GPIO_Pin_0);
}
typedef struct
{
uint16_t GPIO_Pin; 
/*指定将要进行配置的 GPIO 引脚*

GPIOSpeed_TypeDef GPIO_Speed;    
/*指定 GPIO 引脚可输出的最高频率*/

GPIOMode_TypeDef GPIO_Mode;    
/*指定 GPIO 引脚将要配置成的工作状态*/

}GPIO_InitTypeDef;

Mode和Speed相关结构体也是被设定好的,可以自行查找这里就不写出来了,直接赋值就可以了,十分方便并且相对于寄存器操作便于理解。

GPIO_Init(GPIO_TypeDef *, GPIO_InitTypeDef *);第一个参数,说明它将要对GPIOB 端口进行初始化。初始化的配置以第二个参数 GPIO_InitStructure 结构 体的成员值为准。这个结构体的成员,我们在调用GPIO_Init()前,已对它们 赋予了控制参数。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
原文地址:https://www.cnblogs.com/pw-fan/p/11254974.html