STM32的USART串口配置——学习笔记(5)

文章内容根据野火学习教程进行整理,仅仅是学习记录。

开发板: 野火STM32F429-挑战者V2
官方固件库版本: STM32F4xx_DSP_StdPeriph_Lib_V1.8.0


一、选择USART

要配置USART要先选择要配置哪一个USART/UART。
我用的是STM32F429这个芯片,从 《STM32F4xx中文数据手册》“表 8. USART 的特性比较”“表 10. STM32F427xx 和 STM32F429xx 引脚和焊球定义 ” 可以得知:

  • 该芯片支持四个USART和四个UART。
  • USART1 和 USART6 的时钟来源于 APB2 总线时钟(最大频率为 90MHz)。
  • USART2、USART3、UART4、UART5、UART7、UART8的时钟来源于 APB1 总线时钟(最大频率为 45MHz)。
  • 具体的USART/UART引脚所接的GPIO口是哪个。
    整理如下图所示(引用自教程)。
    STM32的USART串口配置——学习笔记(5)然后具体的还是要看开发板的电路设计图,看看有哪个USART/UART通过哪个GPIO口引出(或者直接用杜邦线连接相应的GPIO引脚)。
    我这里用的是USART1,主要是要找到TX和RX接在哪个引脚。
    STM32的USART串口配置——学习笔记(5)

二、开始编码

1、声明宏定义(主要是为了方便修改)

/******************************************************************************/
/* 局部宏定义                                                                      */
/******************************************************************************/
#define USART1_HANDLE                           USART1                      /* USART1的句柄 */
#define USART1_CLK                              RCC_APB2Periph_USART1       /* USART1的外围时钟 */
#define USART1_BAUDRATE                         115200                      /* USART1的波特率 */

#define USART1_GPIO_PORT                        GPIOA                       /* USART1的端口 */

#define USART1_TX_PIN                           GPIO_Pin_9                  /* USART1的GPIO发送引脚 */
#define USART1_RX_PIN                           GPIO_Pin_10                 /* USART1的GPIO接收引脚 */
#define USART1_TX_SOURCE                        GPIO_PinSource9             /* USART1的GPIO发送引脚序号 */
#define USART1_RX_SOURCE                        GPIO_PinSource10            /* USART1的GPIO接收引脚序号 */
#define USART1_GPIO_AF                          GPIO_AF_USART1              /* USART1的GPIO引脚复用功能 */

#define DEBUG_USART_IRQ                         USART1_IRQn                 /* USART1的中断序号 */

2、配置GPIO

(1)使能GPIO时钟

我们已经知道了USART1的是接在了GPIOA端口下的引脚,所以要先使能GPIOA端口的时钟。

    /* 初始化GPIO端口时钟 */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
(2)对RX和TX引脚的GPIO进行配置
/**
  * @brief  USART的GPIO配置
  * @param  无
  * @retval 无
  */
static void gpio_cfg()
{
    /* 配置USART1的GPIO */
    bsp_gpio_config(USART1_GPIO_PORT, USART1_TX_PIN | USART1_RX_PIN, GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_PP, GPIO_PuPd_UP);
    
    /* 连接 PXx 到 USARTx_Tx*/
    GPIO_PinAFConfig(USART1_GPIO_PORT, USART1_RX_SOURCE, USART1_GPIO_AF);
    
    /*  连接 PXx 到 USARTx_Rx*/
    GPIO_PinAFConfig(USART1_GPIO_PORT, USART1_TX_SOURCE, USART1_GPIO_AF);
}

这里我对GPIO引脚的配置封装了一下(没什么更大的好处,只是习惯),bsp_gpio_config函数为:

/**
  * @brief  GPIO配置函数
  * @param  *GPIOx: 所属GPIO端口
  * @param  pin:    要配置的GPIO的PIN脚
  * @param  mode:   要配置的GPIO模式(输入/输出/复用/模拟)
  * @param  speed:  要配置的GPIO速率(2MHz/25MHz/50MHz/100MHz),输入模式不需要配置填 0
  * @param  otype:  输出类型(推挽/开漏),输入模式不需要配置填 0
  * @param  pupd:   引脚默认状态(上拉/下拉/浮空)
  * @retval 无
  */
void bsp_gpio_config(GPIO_TypeDef* GPIOx, uint32_t pin, GPIOMode_TypeDef mode, GPIOSpeed_TypeDef speed, GPIOOType_TypeDef otype, GPIOPuPd_TypeDef pupd)
{
    GPIO_InitTypeDef GPIO_def;

    GPIO_def.GPIO_Pin = pin; 
    GPIO_def.GPIO_Mode = mode;
    GPIO_def.GPIO_PuPd = pupd;
    GPIO_def.GPIO_Speed = speed;
    GPIO_def.GPIO_OType = otype;
    
    GPIO_Init(GPIOx, &GPIO_def);
}

3、配置USART

(1)使能USART时钟

由第一部分可以知道USART1的时钟是来源于APB2的时钟。
查看 stm32f4xx_rcc.c 源码也可以找到USART1通过 RCC_APB2PeriphClockCmd函数来配置时钟。

/* USART1_CLK 为 RCC_APB2Periph_USART1,在宏定义定义了 */
RCC_APB2PeriphClockCmd(USART1_CLK, ENABLE);                             /* 使能USART时钟 */
(2)初始化USART配置(使能USART时钟之后,不然没反应)
    USART_InitTypeDef USART_def;
    
    USART_def.USART_BaudRate = USART1_BAUDRATE;                             /* 配置波特率 */
    USART_def.USART_WordLength = USART_WordLength_8b;                       /* 配置字长为8(数据位+校验位) */
    USART_def.USART_StopBits = USART_StopBits_1;                            /* 配置停止位为1个停止位 */
    USART_def.USART_Parity = USART_Parity_No;                               /* 配置校验位为不使用校验 */
    USART_def.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   /* 配置硬件流控制为不使用硬件流 */
    USART_def.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                   /* 配置USART模式控制为同时使能接收和发送 */
    USART_Init(USART1_HANDLE, &USART_def);                                  /* 完成USART初始化配置 */
(3)使能串口
/* USART1_HANDLE 为 USART1, 在宏定义定义了 */
USART_Cmd(USART1_HANDLE, ENABLE);                                           /* 使能串口 */

4、启用printf、scanf、getchar等函数

这里重定向fputc、fgetc即可使用以上函数,是因为以上函数中最终对字符的读写使用的是fputc、fgetc这两个函数。所以把最终的操作改成串口的读写即可。
放哪里都行,我和USART配置相关的代码放在一个.c文件里面了。

/**
  * @brief  重定向重写C函数库中的fputc函数,重定向后可使用printf函数
  * @param  ch:要输出的字节
  * @param  *f:无用
  * @retval 无
  */
int fputc(int ch, FILE *f)
{
    USART_SendData(USART1_HANDLE, (uint8_t)ch);                             /* 发送一个字节数据到串口 */
    
    while (USART_GetFlagStatus(USART1_HANDLE, USART_FLAG_TXE) == RESET);    /* 等待发送完毕 */
    
    return (ch);
}

/**
  * @brief  重定向重写C函数库中的fgetc函数,重写向后可使用scanf、getchar等函数
  * @param  *f:无用
  * @retval 从串口读取到的一个字节
  */
int fgetc(FILE *f)
{
    while (USART_GetFlagStatus(USART1_HANDLE, USART_FLAG_RXNE) == RESET);   /* 等待串口输入数据 */

    return (int)USART_ReceiveData(USART1_HANDLE);
}

5、也可以自定义发送函数

/**
  * @brief  发送一个字节到串口
  * @param  *pUSARTx:要使用的USART句柄
  * @param  ch:要输出的字节
  * @retval 无
  */
void usart_send_byte(USART_TypeDef *pUSARTx, uint8_t ch)
{
    USART_SendData(pUSARTx, ch);                                             /* 发送一个字节数据到USART */
    
    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);          /* 等待发送数据寄存器为空 */
}

/**
  * @brief  发送两个字节到串口
  * @param  *pUSARTx:要使用的USART句柄
  * @param  ch:要输出的字节
  * @retval 无
  */
void usart_send_half_word(USART_TypeDef *pUSARTx, uint16_t ch)
{
    uint8_t temp_h, temp_l;
    
    temp_h = (ch & 0XFF00) >> 8;                                            /* 取出高八位 */
    temp_l = ch & 0XFF;                                                     /* 取出低八位 */
    
    USART_SendData(pUSARTx, temp_h);                                        /* 发送高八位 */
    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
    
    USART_SendData(pUSARTx, temp_l);                                        /* 发送低八位 */
    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

/**
  * @brief  发送一个字符串到串口
  * @param  *pUSARTx:要使用的USART句柄
  * @param  ch:要输出的字符串
  * @retval 无
  */
void usart_send_string(USART_TypeDef *pUSARTx, char *str)
{
    unsigned int k = 0;
    do 
    {
        usart_send_byte(pUSARTx, *(str + k));
        k++;
    } while(*(str + k) != '\0');
    
    while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);               /* 等待发送完成 */
}

到这位置USART的配置基本完成了,可以实现的功能有

  • 通过printf输出字符串到串口。
  • 通过getchar、scanf读取串口输入的内容。

5、配置USART的中断。

(1)注意点
  • 开启中断后一定要重写终端服务函数,否则会进入死循环。
  • 开启中断后getchar、scanf函数不太正常了,没法正常使用。亲测如此但还是不太明白什么原因。
(2)使能USART接收中断
/* USART1_HANDLE 为 USART1, 在宏定义定义了 */
USART_ITConfig(USART1_HANDLE, USART_IT_RXNE, ENABLE);                       /* 使能串口接收中断 */
(3)配置NVIC(嵌套向量中断控制器)

先选择嵌套向量中断控制器组(如果已经配置过就不要重复了)

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                             /* 嵌套向量中断控制器组选择 */

配置USART1的中断

/* USART1_IRQ 为 USART1_IRQn, 在宏定义定义了 */
/* USART1_IRQn 定义在 stm32f4xx.h */
nvic_config(USART1_IRQ, 1, 1, ENABLE);                             /* 中断配置 */

nvic_config 为自己封装的函数

/**
  * @brief  NVIC配置函数
  * @param  channel:    中断源
  * @param  preemption: 抢断优先级
  * @param  sub:        子优先级
  * @param  cmd:        使能状态
  * @retval 无
**/
void nvic_config(uint8_t channel, uint8_t preemption, uint8_t sub, FunctionalState cmd)
{
    NVIC_InitTypeDef nvic_def;
    
    nvic_def.NVIC_IRQChannel = channel;                                         /* 配置USART为中断源 */
    nvic_def.NVIC_IRQChannelPreemptionPriority = preemption;                    /* 抢断优先级为1 */
    nvic_def.NVIC_IRQChannelSubPriority = sub;                                  /* 子优先级为1 */
    nvic_def.NVIC_IRQChannelCmd = cmd;                                          /* 使能中断 */
    
    NVIC_Init(&nvic_def);                                                       /* 初始化配置NVIC */
}
(4)重写中断服务函数

打开中断后必须重写中断服务函数,否则会跳入死循环。
中断服务函数函数必须与启动文件(startup_stm32f429_439xx.s)中的中断服务函数函数名相同,否则会跳入死循环。
STM32的USART串口配置——学习笔记(5)
这里只实现了收到数据并再发出去的内容,如果不使用getchar、scanf之类的函数也可以自定义buff接收数据处理。

/**
  * @brief  重写USART1的中断函数,与startup_stm32f429_439xx.s中向量表定义的函数名必须相同
  * @param  无
  * @retval 无
  */
void USART1_IRQHandler(void)
{
    uint16_t ucTemp;
    
    if(USART_GetITStatus(USART1_HANDLE, USART_IT_RXNE) != RESET) {
        ucTemp = USART_ReceiveData(USART1_HANDLE);
        USART_SendData(USART1_HANDLE, ucTemp);    
    }
}
STM32的USART串口配置——学习笔记(5)STM32的USART串口配置——学习笔记(5) hrx-@@ 发布了64 篇原创文章 · 获赞 106 · 访问量 22万+ 私信 关注
上一篇:蓝牙4.0模块,AT指令集


下一篇:HAL库实现USART2与BC20通讯,利用usart2+dma+空闲中断