[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的基础部分,在和电脑和很多模块通信时都是用的串口通信,所以这个也非常重要。下面是串口配置的几个步骤。
- 串口时钟使能
- 串口复位
- GPIO端口模式配置
- 串口参数初始化
- 开启中断
- 使能端口
- 编写中断处理函数
下面是配置的函数
#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);
}