STM32学习笔记

本文详细介绍了STM32中GPIO的输入输出操作,包括GPIO输出设置、GPIO输入及其按键检测,以及EXTI外部中断、TIM定时器中断和USART串口通信的初始化、中断处理和数据传输。内容涵盖了GPIO配置、中断机制和通信协议的实际应用。
摘要由CSDN通过智能技术生成

一、GPIO输入输出

1、GPIO输出

Init初始化函数

void LED_Init(void)    {

        GPIO_InitTypeDef GPIO_InitStructure; //实例化结构体

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);//使能D端口的时钟

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//配置端口引脚13
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//配置输入输出模式
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//配置速度
        GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化

}

GPIO输出设置

GPIO_ResetBits(GPIOD,GPIO_Pin_13);//复位低电平

GPIO_SetBits( GPIOD,GPIO_Pin_13);//置位高电平

2、GPIO输入

Init初始化函数 

void key_init(void){

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启时钟

//配置端口模式

        GPIO_InitTypeDef GPIO_InitStructB; //结构体变量名GPIO_InitStructB

        GPIO_InitStructB.GPIO_Mode = GPIO_Mode_IPU; //需要上拉输入,按键未按时默认高电平

        GPIO_InitStructB.GPIO_Pin = GPIO_Pin_1 |GPIO_Pin_11;//具体引脚

        GPIO_InitStructB.GPIO_Speed = GPIO_Speed_50MHz;//在输入模式下,这个参数无影响

        GPIO_Init(GPIOB,&GPIO_InitStructB);

}

 GPIO输入设置

uint8_t key_getnum(void) 

{

uint8_t keynum = 0; //按键键码,没有按下,就返回0,局部变量

if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0){ //读取GPIO端口

        Delay_ms(20); //按键按下消抖20ms

        while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0); // 松手后动作,直到松手

        Delay_ms(20); //按键松手消抖

        keynum = 1; //键码为1.传递出去

        }

return keynum;

}

二、中断

1、EXTI外部中断 

外部中断初始化Init

void countsensor_init(void)
{
        //第一步,配置GPIO PinB14口

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启RCC时钟
        GPIO_InitTypeDef GPIO_initstruct;  //结构体名字GPIO_initstruct
        GPIO_initstruct.GPIO_Mode = GPIO_Mode_IPU;
        GPIO_initstruct.GPIO_Pin = GPIO_Pin_14;
        GPIO_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB,&GPIO_initstruct);    //传地址
        //第二步,按键中断配置
        //AFIO的库函数是和GPIO在一个文件里,可以查看Library文件中的gpio.h查看函数

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);    //开启AFIO时钟
        GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//代表连接PB14号口的第14个中断线路
        EXTI_InitTypeDef EXTI_InitStructure;//结构体类型名EXTI_InitTypeDef
        EXTI_InitStructure.EXTI_Line = EXTI_Line14;
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//因为上面GPIO_Mode_IPU设置为高电平,所以触发中断是下降
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStructure);
        //第三步,配置NVIC系统中断配置
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //分组方式
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//因为我们这个程序只有一个,所以中断优先级的配置也是非常随意的
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);    
}

(1)所有GPIO口都能触发中断,但相同的Pin不能同时触发中断(比如PA0和PB0不能同时使用,智能选一个作为中断引脚;所以如果有多个中断引脚要选择不同的pin引脚,比如PA0和PA1、PB3就可以)。

(2)GPIO_initstruct.GPIO_Mode模式为浮空输入|上拉输入|下拉输入;不知该写什么模式,可以看参考手册中的外设GPIO配置。

(3)  NVIC_PriorityGroupConfig()分组方式,整个芯片只能用一种。如放在模块中进行分组,要确保每个模块分组都选的是同一个;或者将这个代码放在主函数的最开始

(4)  NVIC_InitStructure.NVIC_IRQChannel()通道需要去stm32f10x.h里面的typedef enum IRQn中去找

外部中断执行函数


void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetFlagStatus(EXTI_Line14) == SET)
    {
       //中断代码

        //·········
        EXTI_ClearITPendingBit(EXTI_Line14);
    }

        中断函数都是无参无返回值的,名字固定在startup文件向量表中,一般都是先进行一个中断标志位的判断,确保是我们想要的中断源触发的函数,因为这个函数EXTI10到EXTI15都能进来,所以要先判断一下是不是我们想要的EXTI14进来的,每次中断函数结束后,都应该清除一下中断标志位

2、TIM定时器中断

TIM定时器Init初始化函数

void Timer_Init(void) //定时中断初始化代码

{

//配置时基单元

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启APB1时钟

TIM_InternalClockConfig(TIM2);//选择时基单元的时钟,选择内部时钟

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频

TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式

TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //ARR

TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //PSC

TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

TIM_ClearFlag(TIM2,TIM_FLAG_Update);

TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启更新中断到NVIC的通道

//NVIC中断

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组

NVIC_InitTypeDef NVIC_InitTyStructure;

NVIC_InitTyStructure.NVIC_IRQChannel = TIM2_IRQn;

NVIC_InitTyStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_InitTyStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级

NVIC_InitTyStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级

NVIC_Init(&NVIC_InitTyStructure);

//启动定时器

TIM_Cmd(TIM2,ENABLE);//当产生更新时,就会触发中断

}

/*在TIM_TimeBaseInit函数的最后,会立刻生成一个更新事件,来重新装载预分频器和重复计数器的值

预分频器有缓冲寄存器,我们写入的PSC和ARR只有在更新事件时才会起作用

为了让写入的值立刻起作用,故在函数的最后手动生成了一个更新事件

但是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,手动生成一个更新事件,就相当于在初始化时立刻进入更新函数执行一次

在开启中断之前手动清除一次更新中断标志位,就可以避免刚初始化完成就进入中断函数的问题*/

/*定时1s也就是定时频率为1Hz,定时频率=72M/ (PSC + 1) / (ARR + 1) = 1s =1Hz,

那就可以PSC给7200,ARR给10000(1MHz等于10^6Hz),然后两个参数再减1

在这里预分频是对72M进行7200分频,得到的就是10k的计数频率,

在10k的频率下,计10000个数,就是1s的时间*/

TIM定时器执行函数

void TIM2_IRQHandler(void){ 

        if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){

                //执行相应的用户代码

                TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位

        }

}

3、 TIM外部中断

TIM外部中断Init初始化函数

void Timer_Init(void){

        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);                                        

        //外部模式2需要用到gpio,进行GPIOA的时钟配置

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

        GPIO_InitTypeDef GPIO_InitStructure;

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;

        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

        GPIO_Init(GPIOA,&GPIO_InitStructure);

        //通过ETR引脚的外部时钟模式2配置

        TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);

        //配置时基单元

        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频

        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式

        TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //ARR

        TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC

        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值

        TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

        TIM_ClearFlag(TIM2,TIM_FLAG_Update);

        //使能中断

        TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启更新中断到NVIC的通道

        //NVIC中断

        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组

        NVIC_InitTypeDef NVIC_InitTyStructure;

        NVIC_InitTyStructure.NVIC_IRQChannel = TIM2_IRQn;

        NVIC_InitTyStructure.NVIC_IRQChannelCmd = ENABLE;

        NVIC_InitTyStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级

        NVIC_InitTyStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级

        NVIC_Init(&NVIC_InitTyStructure);

        //启动定时器

        TIM_Cmd(TIM2,ENABLE);//当产生更新时,就会触发中断

}

 TIM外部中断Init初执行函数

void TIM2_IRQHandler(void){

        if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)

        {

                //执行相应的用户代码

                TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位

        }

}

三、通信接口协议

1、USART串口协议

串口初始化函数

tx引脚(A9)是USART外设控制输出引脚,选择复用推挽输出。rx引脚(A10)是USART外设控制输入引脚,选择输入模式(浮空输入或者上拉输入)

void usart_init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    //TX
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
    //RX
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    USART_InitTypeDef USART_InitStruture;
    USART_InitStruture.USART_BaudRate = 9600;
    USART_InitStruture.USART_HardwareFlowControl =     USART_HardwareFlowControl_None;
    USART_InitStruture.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_InitStruture.USART_Parity = USART_Parity_No;
    USART_InitStruture.USART_StopBits  = USART_StopBits_1;
    USART_InitStruture.USART_WordLength = USART_WordLength_8b;
    //中断设置

        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        NVIC_InitTypeDef NVIC_InitTyStructure;
        NVIC_InitTyStructure.NVIC_IRQChannel = USART1_IRQn;
        NVIC_InitTyStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitTyStructure.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitTyStructure.NVIC_IRQChannelSubPriority = 1;
        NVIC_Init(&NVIC_InitTyStructure);
        USART_Cmd(USART1, ENABLE);
}

随后可以在中断函数中进行串口接收

void USART1_IRQHandler(void){}

串口发送

void Serial_SendByte(uint8_t Byte){
        USART_SendData(USART1, Byte);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

//发送字符串

void Serial_SendString(char *String){
        uint16_t i;
         for(i = 0;String[0] != '\0'; i++){
                Serial_SendByte(String[i]);
        }
}

//发送数组

void Serial_SendArray(uint8_t *Array, uint16_t Length){
    uint16_t i;
    for(i = 0;i < Length; i++){
        Serial_SendByte(Array[i]);
    }
}

使用printf()函数输出到串口助手,在Keil工程选项中,勾选Use MicroLIB,然后添加头文件#include <stdio.h>,编写代码:

int fputc(int ch, FILE *f){
    Serial_SendByte(ch);
    return ch;
}

串口接收

单字节接收,接收到的字节存在Usart_Receive变量中

u8 Usart_Receive;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)

        Usart_Receive = USART_ReceiveData(USART1);

        USART_ClearITPendingBit(USART1, USART_IT_RXNE);

}

Hex数据包接收(数组接收),规定好帧头帧尾,发送字节长度,比如一共发11字节,然后依次接收存在数组 Receive_Data.buffer(需要提前定义)。其中FRAME_HEADER为帧头字节宏定义,FRAME_TAIL为帧尾宏定义,还包含校验位使用Check_Sum()自定义函数

也可以使用文本数据包进行接收。

int USART1_IRQHandler(void)
{    
    static u8 Count=0;
    u8 Usart_Receive;

    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 
    { 
        Usart_Receive = USART_ReceiveData(USART3);
        Receive_Data.buffer[Count]=Usart_Receive;
        if(Usart_Receive == FRAME_HEADER||Count>0) 
            Count++; 
        else 
            Count=0;
        
        if (Count == 11)
        {   
                Count=0;
                if(Receive_Data.buffer[10] == FRAME_TAIL) 
                {
                    if(Receive_Data.buffer[9] ==Check_Sum(9,0))     
                  {        
                       //接收处理代码············
                  }
            }
        }
    } 
return 0;    
}

校验函数

u8 Check_Sum(unsigned char Count_Number,unsigned char Mode)
{
    unsigned char check_sum=0,k;
    
    //对发送的数据进行校验
    if(Mode==1)
    for(k=0;k<Count_Number;k++)
    {
    check_sum=check_sum^Send_Data.buffer[k];
    }
   
    //对接收的数据进行校验
    if(Mode==0)
    for(k=0;k<Count_Number;k++)
    {
    check_sum=check_sum^Receive_Data.buffer[k];
    }
    return check_sum;
}

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值