STM 32学习笔记 1


[success]这个笔记主要记下stm32使用的一些知识点,所以可能会很长,建议大家通过目录来查找自己需要的内容[/success]

STM 32基础

因为stm32已经算是很高级的芯片了,所以写程序是肯定没有51单片机那么简单的只需要引入头文件就可以了,所以这个时候目录结构有条理就显得非常重要,通常我们在写程序的时候都是直接在模板文件的基础上进行添加,这样就不需要每次添加一大堆文件了。

这个是主要的目录结构,USER目录主要放主函数,还有stm32的头文件,HARDWAE目录放自己写的一些函数,比如这里写的是LED的控制函数,里面主要用来设置IO口的,这样主函数就可以直接调用函数就不会显得很冗长。SYSTEM里面主要是放一些别人帮你写好的函数,比如演示函数,串口发送函数。CORE放的是stm32的核心代码,里面放的是stm32的内核文件以及芯片的启动文件(芯片的容量不同这里的文件是不一样的 )[collapse title="芯片容量的小知识"]

startup_stm32f10x_ld.s  FLASH≤32K
startup_stm32f10x_md.s  64K≤FLASH≤128K
startup_stm32f10x_hd.s  256K≤FLASH  

[/collapse]

FWLIB就是放各种库函数的地方,因为stm32管脚太多,所以我们是通过调用库函数来设置IO口的,库函数是和寄存器打交道的,它控制着最底层,我们是通过库函数提供的api来间接控制硬件的。stm32的所有功能都是通过寄存器来实现的,所以就响应的会有许多库函数,所以,我们需要用到什么功能就要调用相关的库函数。

STM32寄存器控制IO口

虽然我们使用的是库函数,但是我们还是需要了解一些非常底层的东西,这样才能更好的使用库函数。所以,我们来学一下如果自己控制stm32的寄存器。

因为stm32是低功耗芯片,所以我们在使用IO口时需要使能相应IO口的时钟寄存器这样才可以控制IO口。

我们先控制外设时钟使能寄存器APB2(这里就有IO口时钟的寄存器):

RCC->APB2ENR|=1<<3; 
RCC->APB2ENR|=1<<6; 

我们因为要使用端口B和E(因为LED的IO口在PE5和PB5),所以我们要把寄存器的第3位和第6位置为1,同时还不能影响其他的位,所以才有了或运算。

时钟使能完后我们还需要到相应的IO口配置一下端口寄存器,端口寄存器有低的(CRL主要控制0-7)和高的(CRH控制8-15)。然后在配置一下端口输出数据寄存器(主要用于控制输出低或者高电平)。

GPIOB->CRL&=0xFF0FFFFF;//我们这里是把第六位清零
GPIOB->CRL|=0x00300000;//赋值为3
GPIOB->ODR|=1<<5;//把第6位置为1,就是第五位输出高电平

前面我们是把第6位清0(这里有32位数据每个端口有4个配置位,所以刚好控制8个IO口),因为一个16进制数刚好是4位二进制,所以一个16进制数控制一个IO口,我们这里需要控制第PB5,所以控制第6位,前面是先清0,防止影响后面配置,我们把它置为3就是0011(通用推挽输出下50MHZ的输出)。

后面控制数据寄存器ODR

这个其实非常简单,共有16位,每位对应一个IO口,如果是1,响应位对应的IO口就输出高电平,反之。下面就是主函数控制IO口输出代码了!

GPIOB->ODR|=1<<5;//第6位置为高电平
GPIOB->ODR&=~(1<<5);//把第6位置为低电平

STM32库函数控制IO口

库函数控制IO口就不需要知道寄存器的原理,我们可以直接调用函数,我们还是控制PB5,下面直接贴出代码。

GPIO_InitTypeDef GPIO_InitStruct;//这里需要定义结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOB的时钟
//指定结构体的成员变量(GPIOB的初始化)
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//第五个管脚
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//设置输出的速度
GPIO_Init(GPIOB,& GPIO_InitStruct);//这里就是把结构体内的配置传给寄存器的配置函数
GPIO_SetBits(GPIOB,GPIO_Pin_5);//设置为高电平,默认不亮
/*主函数控制高低电平*/
GPIO_SetBits(GPIOB,GPIO_Pin_5);//设置B5为高电平,默认不亮
GPIO_ResetBits(GPIOB,GPIO_Pin_5);//设置B5为低电平
//还可以这样
PBout(5)=1;
PBout(5)=0;

STM32位操作控制IO口

位操作其实就是在库函数的基础上进行改进的,我们可以直接使用诸如LED0=1这样的类似51的操作。配置也非常简单,只需要在你写的函数的头文件里面(我的是led.h)加上下面的定义:

#define LED0 PBout(5)// PB5
#define LED1 PEout(5)// PE5	

STM32读取IO口数据

前面说的是写,这里说一下怎么读,后面我都是直接使用库函数来写程序。我们需要这样配置寄存器。我们的按键为PA3

GPIO_InitTypeDef GPIO_InitStructure;//配置的结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA时钟
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_3;//KEY0-KEY1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入(默认为高)
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA3
/*下面是宏定义部分*/
#define KEY1  GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3)//读取按键1
/*我们实际使用的时候只需要下面这样*/
if(KEY1==0){}else{}

STM32串口

串口也是stm32的基础部分,在和电脑和很多模块通信时都是用的串口通信,所以这个也非常重要。下面是串口配置的几个步骤。

  1. 串口时钟使能
  2. 串口复位
  3. GPIO端口模式配置
  4. 串口参数初始化
  5. 开启中断
  6. 使能端口
  7. 编写中断处理函数

下面是配置的函数

#include "stm32f10x.h"
void My_USART_Init(void){
    GPIO_InitTypeDef GPIO_InitStrue;//定义结构体成员变量(引脚定义)
    USART_InitTypeDef USART_InitStrue;//定义结构体成员变量(串口配置结构体)
    NVIC_InitTypeDef NVIC_InitStrue;//定义结构体变量(NVIC的配置)
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//串口时钟使能
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口时钟使能
    
    //初始化结构体
    GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;//设置为推挽输出
    GPIO_InitStrue.GPIO_Pin=GPIO_Pin_9;//设置第9管脚
    GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;//设置时钟速度
    GPIO_Init(GPIOA,&GPIO_InitStrue);//设置IO口初始化为复用
    
    //初始化结构体
    GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IN_FLOATING;//设置为浮空输出
    GPIO_InitStrue.GPIO_Pin=GPIO_Pin_10;//设置第10管脚
    GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;//设置时钟速度
    GPIO_Init(GPIOA,&GPIO_InitStrue);//设置IO口初始化为复用
    
    //初始化串口成员变量
    USART_InitStrue.USART_BaudRate=115200;//波特率
    USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流(这里我们不使用硬件流)
    USART_InitStrue.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//发送使能和接收使能(这里我们两个都需要使能)
    USART_InitStrue.USART_Parity=USART_Parity_No;//奇偶校验
    USART_InitStrue.USART_StopBits=USART_StopBits_1;//停止位(这里设置为1)
    USART_InitStrue.USART_WordLength=USART_WordLength_8b;//字节长度(这里我们设置为8个字节)
    USART_Init(USART1,&USART_InitStrue);//串口输出初始化(串口1)
    
    //使能串口(开启使能,到这里我们的配置告一段落)
    USART_Cmd(USART1,ENABLE);
    
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//接受数据执行中断函数(开启中断)
    
    //中断的配置(中断优先级设置)
    NVIC_InitStrue.NVIC_IRQChannel= USART1_IRQn;//配置通道
    NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;//是否开启中断通道
    NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1;//设置抢断优先级
    NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;//设置子优先级
    NVIC_Init(&NVIC_InitStrue);
    
}

//中断服务函数
void USART1_IRQHandler(void){
    u8 res;
    //获取中断的类型执行不同的操作
    if(USART_GetITStatus(USART1,USART_IT_RXNE))//返回1说明是接受中断
    {
          res= USART_ReceiveData(USART1); //获取接受到的数据(串口1接受)
      USART_SendData(USART1,res); //发送接受到的数据	
    }
        
}


int main(void){
    //配置串口中断
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置分组(两位抢占,两位响应)
    My_USART_Init();
    while(1);
}

这里先说一下STM32共有5个串口,其中1-3是通用同步/异步串行接口USART,4,、5是通用异步串行接口UART。下面说一下那几个IO口的具体位置:
注意事项:USART1是挂在APB2,使能时钟命令为:  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE );其他几个则挂在APB1上,如2口RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE );

配置4口和5口的时候,中断名为UART4、UART5,中断入口分别为UART4_IRQn、UART5_IRQn对应的中断服务函数为void UART4_IRQHandler(void)void UART5_IRQHandler(void)。 

第一个为A9(TX)和A10(RX)

第二个是A2(TX)和A3(RX)

第三个是B10(TX)和B11(RX)

第四个是C10(TX)和C11(RX)

第五个是C12(TX)和D2(RX)0

端口说完了,我们就开始实际配置一下,我们就直接配置第2个(因为第一个是USB转串口那边占用了)

第一步是使能IO口时钟:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

然后在使能串口时钟,我们是串口2所以:CC_APB2PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);

然后在分别配置IO口,代码和上面的是一样的,所以可以直接根据上面的代码来修改一下。

前面的代码

[highlight lanaguage="c"] GPIO_InitTypeDef GPIO_InitStrue; USART_InitTypeDef USART_InitStrue; NVIC_InitTypeDef NVIC_InitStrue; // 外设使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//初始化结构体 GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;//设置为推挽输出 GPIO_InitStrue.GPIO_Pin=GPIO_Pin_2;//设置第2管脚 GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;//设置时钟速度 GPIO_Init(GPIOA,&GPIO_InitStrue);//设置IO口初始化为复用 //初始化结构体 GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IN_FLOATING;//设置为浮空输出 GPIO_InitStrue.GPIO_Pin=GPIO_Pin_3;//设置第3管脚 GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;//设置时钟速度 GPIO_Init(GPIOA,&GPIO_InitStrue);//设置IO口初始化为复用[/highlight]

然后我们在配置一下串口的寄存器(这里我们用了库函数,所以就是直接配置结构体),这里配置都是一样的,值需要修改一下波特率和串口输出初始化那里的数字而已。

USART_InitStrue.USART_BaudRate=115200;//波特率
USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流(这里我们不使用硬件流)
USART_InitStrue.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//发送使能和接收使能(这里我们两个都需要使能)
USART_InitStrue.USART_Parity=USART_Parity_No;//奇偶校验
USART_InitStrue.USART_StopBits=USART_StopBits_1;//停止位(这里设置为1)
USART_InitStrue.USART_WordLength=USART_WordLength_8b;//字节长度(这里我们设置为8个字节)
USART_Init(USART2,&USART_InitStrue);//串口输出初始化(串口1)

初始化完毕后就可以开始使能串口了:USART_Cmd(USART2,ENABLE);

然后在开启中断:USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);

在配置一下中断(设置优先级):

//中断的配置(中断优先级设置)
NVIC_InitStrue.NVIC_IRQChannel= USART2_IRQn;//配置通道
NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;//是否开启中断通道
NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1;//设置抢断优先级
NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;//设置子优先级
NVIC_Init(&NVIC_InitStrue);

然后写一下中断服务函数:

void USART2_IRQHandler(void){
    u8 res;
    //获取中断的类型执行不同的操作
    if(USART_GetITStatus(USART2,USART_IT_RXNE))//返回1说明是接受中断
    {
          res= USART_ReceiveData(USART1); //获取接受到的数据(串口1接受)
      USART_SendData(USART2,res); //发送接受到的数据	
    }
        
}

最后再主函数里面写一下串口初始化函数就可以开始工作了。

int main(void){
    //配置串口中断
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置分组(两位抢占,两位响应)
    My_USART_Init();
    while(1);
}

我这里实验室成功了的,大家只需要在原来的代码基础上稍加修改就可以看到效果的。

下面在说一下发送字符串的函数:

void 
//这里还有一个接受数据的函数
uiuint16_t USART_ReceiveData(USART_TypeDef* USARTx); 
USART2_Send_Byte(u8 Data) //发送一个字节;

{

USART_SendData(USART2,Data);

while( USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET );

}

void USART2_Send_String(u8 *Data) //发送字符串;

{

while(*Data)

USART2_Send_Byte(*Data++);

}

实际上在SYSTEM文件夹里面有串口的函数,我们可以直接引入头文件:#include "usart.h"

然后这里贴一下演示代码

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
 int main(void)
 {		
     u16 t;  
    u16 len;	
    u16 times=0;
    delay_init();	    	 //延时函数初始化	  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
    uart_init(115200);	 //串口初始化为115200
     LED_Init();			     //LED端口初始化
    KEY_Init();          //初始化与按键连接的硬件接口
     while(1)
    {
        if(USART_RX_STA&0x8000)
        {					   
            len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
            printf("
您发送的消息为:

");
            for(t=0;t<len;t++)
            {
                USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
                while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
            }
            printf("

");//插入换行
            USART_RX_STA=0;
        }else
        {
            times++;
            if(times%5000==0)
            {
                printf("
精英STM32开发板 串口实验
");
                printf("正点原子@ALIENTEK

");
            }
            if(times%200==0)printf("请输入数据,以回车键结束
");  
            if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
            delay_ms(10);   
        }
    }	 
 }

默认这里是串口1,如果你想使用其他的串口直接在SYSTEM文件夹里面的usart.c修改即可。

STM32外部中断

中断也是stm32的重要部分,stm32非常强大,它的没一个IO口都可以作为中断(51的只有一个中断),在使用外部中断时我们需要引入固件库stm32f10x_exti.h 和 stm32f10x_exti.c

STM32F103 的中断控制器支持 19 个外部中断/
事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。

线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件

这里我们可以看到到IO口的中断实际上只有16个,但是stm32的IO口实际上是非常多的。这个是怎么实现的呢?我们可以看一下GPIO和中断线的映射关系。

这里我们引入了中断线的概念,就是说每组IO口的对应编号相同的为一个线,也就是说PA1和PB1是不能同时作为中断的,一次只能有一个作为中断。

一般的使用步骤:

1)初始化 IO 口为输入。
2)开启 AFIO 时钟
3)设置 IO 口与中断线的映射关系。
4)初始化线上中断,设置触发条件等。
5)配置中断分组(NVIC),并使能中断。
6)编写中断服务函数。

我们现在就来编写代码:

首先我们要初始化IO口,这里的配置和前面配置IO口是一样的。

GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟
GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_4|GPIO_Pin_3;//KEY0-KEY1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE4,3

然后在开启AFIO时钟(这个是复用功能时钟,打开它就可以使用IO的一些其他的功能,比如中断):RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟

然后在设置一下IO口和中断线的映射关系(比如我们要用PE3来作为中断,代码如下):

 //GPIOE.3	  中断线以及中断初始化配置 下降沿触发 //KEY1
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line=EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);	//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器

这里前面那个是把中断线和GPIO口映射起来。

第二个是中断线的标号,我们使用的是中断线3

第三个是中断模式,我们选的是中断(有中断和事件两种模式)

第四个是触发方式,我们这里选择的下降沿触发

映射好后我们在配置一下中断分组(设置抢占优先级和响应优先级)


NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;			//使能按键WK_UP所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	//抢占优先级2, 
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;					//子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能外部中断通道
NVIC_Init(&NVIC_InitStructure); 

在写一下中断的服务函数:

//外部中断3服务程序
void EXTI3_IRQHandler(void)
{
    delay_ms(10);//消抖
    if(KEY1==0)	 //按键KEY1
    {				 
        LED1=!LED1;
    }		 
    EXTI_ClearITPendingBit(EXTI_Line3);  //清除LINE3上的中断标志位  
}

最后要到主函数里面初始化一下

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "exti.h"
 int main(void)
 {		
 
    delay_init();	    	 //延时函数初始化	  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
    EXTIX_Init();         	//初始化外部中断输入 
    LED0=0;					//先点亮红灯
    while(1);	 
}


文章作者: 小游
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小游 !
  目录