4.APM32-USART-串口接发

效果展示

USART-串口接发

硬件原理图

串口接口
我们使用的开发板上没有USB转串口的芯片,如果要连接到电脑上还需要使用USB转串口的模块或者jlink自带的虚拟串口。开发板的PA9(TX)引脚接USB转串口模块的RX引脚,开发板的PA10(RX)引脚接USB转串口模块的TX引脚,同时双方的GND还要连起来。

源代码

串口部分

#ifndef __BSP_USARTX_H__
#define __BSP_USARTX_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "apm32f10x_usart.h"
#include "apm32f10x_gpio.h"
#include "apm32f10x_rcm.h"
#include "apm32f10x_misc.h"
/* 宏定义 --------------------------------------------------------------------*/

#define USARTX USART1

#define USARTX_BAUDRATE 115200
#define USARTX_CLOCK RCM_APB2_PERIPH_USART1

#define USARTX_TX_PIN GPIO_PIN_9
#define USARTX_TX_GPIO_PORT GPIOA
#define USARTX_TX_GPIO_CLOCK RCM_APB2_PERIPH_GPIOA

#define USARTX_RX_PIN GPIO_PIN_10
#define USARTX_RX_GPIO_PORT GPIOA
#define USARTX_RX_GPIO_CLOCK RCM_APB2_PERIPH_GPIOA

#define USARTX_IRQn USART1_IRQn

/* 函数声明 ------------------------------------------------------------------*/
void USARTx_Config(void);
void Usart_SendByte(uint8_t byte);
void Usart_SendStr_Length(uint8_t *str, uint32_t strlen);
void Usart_SendString(uint8_t *str);

#endif // __BSP_USARTX_H__

#include "bsp_usartx.h"

/**
 * @brief 配置串口,打开串口接收中断
 */
void USARTx_Config(void)
{
    // 定义IO硬件初始化结构体变量
    GPIO_Config_T GPIO_ConfigStruct;
    // 定义USART初始化结构体变量
    USART_Config_T USART_ConfigStruct;
    // 使能USART时钟
    RCM_EnableAPB2PeriphClock(USARTX_CLOCK);
    // 使能USART功能GPIO时钟
    RCM_EnableAPB2PeriphClock(USARTX_RX_GPIO_CLOCK | USARTX_TX_GPIO_CLOCK);
    // 嵌套向量中断控制器组选择
    NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4);

    // 设定USART发送对应IO模式:浮空输入
    GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;
    GPIO_ConfigStruct.pin = USARTX_RX_PIN;
    // 串口2M,I2C 10M,SPI 50M
    GPIO_ConfigStruct.speed = GPIO_SPEED_2MHz;
    GPIO_Config(USARTX_RX_GPIO_PORT, &GPIO_ConfigStruct);
    // 设定USART发送对应IO模式:复用推挽输出
    GPIO_ConfigStruct.mode = GPIO_MODE_AF_PP;
    GPIO_ConfigStruct.pin = USARTX_TX_PIN;
    GPIO_ConfigStruct.speed = GPIO_SPEED_2MHz;
    // 初始化USART发送对应IO
    GPIO_Config(USARTX_TX_GPIO_PORT, &GPIO_ConfigStruct);
    // USART波特率:115200
    USART_ConfigStruct.baudRate = USARTX_BAUDRATE;
    // USART硬件数据流控制(硬件信号控制传输停止):无
    USART_ConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
    // USART工作模式使能:允许接收和发送
    USART_ConfigStruct.mode = USART_MODE_TX_RX;
    // USART校验位:无
    USART_ConfigStruct.parity = USART_PARITY_NONE;
    // USART停止位:1位
    USART_ConfigStruct.stopBits = USART_STOP_BIT_1;
    //   USART字长(有效位):8位
    USART_ConfigStruct.wordLength = USART_WORD_LEN_8B;
    USART_Config(USARTX, &USART_ConfigStruct);
    // 使能接收中断
    USART_EnableInterrupt(USARTX, USART_INT_RXBNE);
    // 配置USART为中断源,抢占优先级为3,子优先级为0
    NVIC_EnableIRQRequest(USARTX_IRQn, 3, 0);
    // 使能接收中断
    USART_EnableInterrupt(USARTX, USART_INT_RXBNE);
    // 使能USART
    USART_Enable(USARTX);
}
/**
 * @brief 发送一个字节数据
 * @param byte 待发送的字符
 */
void Usart_SendByte(uint8_t byte)
{
    // 发送一个字节数据
    USART_TxData(USARTX, byte);
    // 等待发送完毕
    while (USART_ReadStatusFlag(USARTX, USART_FLAG_TXBE) == RESET)
        ;
}

/**
 * @brief 串口发送指定长度的字符串
 * @param str
 * @param strlen
 */
void Usart_SendStr_Length(uint8_t *str, uint32_t strlen)
{
    uint32_t k = 0;
    do
    {
        Usart_SendByte(*(str + k));
        k++;
    } while (k < strlen);
}

/**
 * @brief 串口发 送字符串,直到遇到字符串结束符
 * @param str 待发送字符串缓冲器
 */
void Usart_SendString(uint8_t *str)
{
    uint32_t k = 0;
    do
    {
        Usart_SendByte(*(str + k));
        k++;
    } while (*(str + k) != '\0');

    // 等待发送完成
    while (USART_ReadStatusFlag(USARTX, USART_FLAG_TXC) == RESET)
    {
    }
}

主程序

/**
 * @file main.c
 * @brief 本次串口使用的是USART1
 */

/* 包含头文件 ----------------------------------------------------------------*/
#include "bsp_usartx.h"
/* 私有变量 ------------------------------------------------------------------*/

uint8_t RxFlag = 0;
uint8_t Temp;
uint8_t RxBuffer[256];
int main(void)
{

	uint16_t RxCount;
	USARTx_Config();

	Usart_SendString("这是一个串口中断接收回显实验\n");
	Usart_SendString("输入数据并以回车键结束\n");
	// 简单的通信协议,遇到回车换行符认为1个命令帧
	RxCount = 0;

	while (1)
	{
		if (RxFlag)
		{
			if (RxCount < sizeof(RxBuffer))
			{
				RxBuffer[RxCount++] = Temp;
			}
			else
			{
				RxCount = 0;
			}
			if (Temp == 0x0A) // 换行字符,使用串口助手发送换行符时,推荐使用HEX模式发送
			{
				Usart_SendStr_Length(RxBuffer, RxCount);
				RxCount = 0;
			}
			RxFlag = 0;
		}
	}
}

中断程序

/*!
 * @file        apm32f10x_it.c
 *
 * @brief       Main Interrupt Service Routines
 *
 * @version     V1.0.0
 *
 * @date        2019-9-30
 *
 */

#include "apm32f10x_it.h"
#include "bsp_usartx.h"

/*!
 * @brief   This function handles NMI exception
 *
 * @param   None
 *
 * @retval  None
 *
 */
void NMI_Handler(void)
{
}

/*!
 * @brief   This function handles Hard Fault exception
 *
 * @param   None
 *
 * @retval  None
 *
 */
void HardFault_Handler(void)
{
    /* Go to infinite loop when Hard Fault exception occurs */
    while (1)
    {
    }
}

/*!
 * @brief   This function handles Memory Manage exception
 *
 * @param   None
 *
 * @retval  None
 *
 */
void MemManage_Handler(void)
{
    /* Go to infinite loop when Memory Manage exception occurs */
    while (1)
    {
    }
}

/*!
 * @brief   This function handles Bus Fault exception
 *
 * @param   None
 *
 * @retval  None
 *
 */
void BusFault_Handler(void)
{
    /* Go to infinite loop when Bus Fault exception occurs */
    while (1)
    {
    }
}
/*!
 * @brief   This function handles Usage Fault exception
 *
 * @param   None
 *
 * @retval  None
 *
 */
void UsageFault_Handler(void)
{
    /* Go to infinite loop when Usage Fault exception occurs */
    while (1)
    {
    }
}

/*!
 * @brief   This function handles SVCall exception
 *
 * @param   None
 *
 * @retval  None
 *
 */
void SVC_Handler(void)
{
}

/*!
 * @brief   This function handles Debug Monitor exception
 *
 * @param   None
 *
 * @retval  None
 *
 */
void DebugMon_Handler(void)
{
}

/*!
 * @brief   This function handles PendSV_Handler exception
 *
 * @param   None
 *
 * @retval  None
 *
 */

void PendSV_Handler(void)
{
}

/*!
 * @brief   This function handles SysTick Handler
 *
 * @param   None
 *
 * @retval  None
 *
 */
void SysTick_Handler(void)
{
}

/**
 * @brief 串口1的中断函数
 * @param
 */
void USART1_IRQHandler(void)
{
    extern uint8_t RxFlag;
    extern uint8_t Temp;
    //		uint8_t ch = 0x33;
    if (USART_ReadIntFlag(USARTX, USART_INT_RXBNE) != RESET)
    {
        USART_ClearIntFlag(USARTX, USART_INT_RXBNE);
        RxFlag = 1;
        Temp = USART_RxData(USARTX);
    }
}

代码分析

bsp_usartx.c

bsp_usartx.h文件中都是各种宏定义和函数原型声明,就不看了。我们主要来看下使用USART时相关GPIO怎么配置,和USART参数怎么选。

先看GPIO的配置,如下

    // 定义IO硬件初始化结构体变量
    GPIO_Config_T GPIO_ConfigStruct;
    // 使能USART功能GPIO时钟
    RCM_EnableAPB2PeriphClock(USARTX_RX_GPIO_CLOCK | USARTX_TX_GPIO_CLOCK);
    // 设定USART接收对应IO模式:浮空输入
    GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;
    GPIO_ConfigStruct.pin = USARTX_RX_PIN;
    // 串口2M,I2C 10M,SPI 50M
    GPIO_ConfigStruct.speed = GPIO_SPEED_2MHz;
    GPIO_Config(USARTX_RX_GPIO_PORT, &GPIO_ConfigStruct);
    // 设定USART发送对应IO模式:复用推挽输出
    GPIO_ConfigStruct.mode = GPIO_MODE_AF_PP;
    GPIO_ConfigStruct.pin = USARTX_TX_PIN;
    GPIO_ConfigStruct.speed = GPIO_SPEED_2MHz;
    // 初始化USART发送对应IO
    GPIO_Config(USARTX_TX_GPIO_PORT, &GPIO_ConfigStruct);

我们之前说过标准库使用的套路,套路如下:

  1. 声明结构体
  2. 开时钟
  3. 配置结构体
  4. 结构体填入初始化函数中
  5. 若要开启相关外设还需开启相关外设

这次我们主要来看下使用串口时的GPIO怎么配置,配置接收引脚时模式选择了GPIO_MODE_IN_FLOATING,为什么要选这个?选其他的行不行,其实选其他的也行,APM32参考手册没写那么详细,中文的STM32参考手册也没写,但是STM32的的英文手册写了,如下图

串口引脚配置

从图中我们可以知道,RX接收引脚可以设置为输入浮空或者上拉输入,最好设置为上拉输入这样可以降低干扰。从图中也不难看出TX发送引脚设置为复用推挽,如果在其他地方看到有人写代码把RX也设置成复用推挽模式,不要觉得人家错了,其实也时可以的,看下图

复用推挽输出

复用输出读取io状态图

注意 虽然输入引脚可以配置成推挽输出,但是不建议用!建议使用输入上拉模式!

GPIO的速度我们之前讲过了,这里再提一下,如下图

GPIO速度配置

看完GPIO的配置,我们再来看下USART的配置

    // 定义USART初始化结构体变量
    USART_Config_T USART_ConfigStruct;
    // 使能USART时钟
    RCM_EnableAPB2PeriphClock(USARTX_CLOCK);
   // USART波特率:115200
    USART_ConfigStruct.baudRate = USARTX_BAUDRATE;
    // USART硬件数据流控制(硬件信号控制传输停止):无
    USART_ConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
    // USART工作模式使能:允许接收和发送
    USART_ConfigStruct.mode = USART_MODE_TX_RX;
    // USART校验位:无
    USART_ConfigStruct.parity = USART_PARITY_NONE;
    // USART停止位:1位
    USART_ConfigStruct.stopBits = USART_STOP_BIT_1;
    //   USART字长(有效位):8位
    USART_ConfigStruct.wordLength = USART_WORD_LEN_8B;
    USART_Config(USARTX, &USART_ConfigStruct);
    // 配置USART为中断源,抢占优先级为3,子优先级为0
    NVIC_EnableIRQRequest(USARTX_IRQn, 3, 0);
    // 使能接收中断
    USART_EnableInterrupt(USARTX, USART_INT_RXBNE);
    // 使能USART
    USART_Enable(USARTX);

这里的串口配置还是老套路,套路如下:

  1. 声明结构体
  2. 开时钟
  3. 配置结构体
  4. 结构体填入初始化函数中
  5. 若要开启相关外设还需开启相关外设,这里我们USART_Enable(USARTX);

我们来具体看下结构体配置

/**
 * @brief   USART Configure structure definition
 */
typedef struct
{
    uint32_t                  baudRate;          /*!< Specifies the baud rate */
    USART_WORD_LEN_T          wordLength;        /*!< Specifies the word length */
    USART_STOP_BIT_T          stopBits;          /*!< Specifies the stop bits */
    USART_PARITY_T            parity;            /*!< Specifies the parity */
    USART_MODE_T              mode;              /*!< Specifies the mode */
    USART_HARDWARE_FLOW_T     hardwareFlow;      /*!< Specifies the hardware flow control */
} USART_Config_T;

  • baudRate波特率,APM32F1系列最大波特率可达4.5Mbits/s,我们一般也用不到这么大,常用的波特率2400、4800、9600、19200、38400、57600、115200。双方通信时波特率要相同,否则会乱码。
  • wordLength数据位长度,可选USART_WORD_LEN_8B数据位长度位8位,USART_WORD_LEN_9B数据位长度为9位。
  • stopBits停止位,可选USART_STOP_BIT_1USART_STOP_BIT_0_5USART_STOP_BIT_2USART_STOP_BIT_1_5,分别为1、 0.5、 2、 1.5 个停止位。
  • USART_PARITY_T 校验位,可选USART_PARITY_NONEUSART_PARITY_EVENUSART_PARITY_ODD,分别为无校验,偶校验,奇校验。
  • USART_MODE_T串口模式,可选USART_MODE_RXUSART_MODE_TXUSART_MODE_TX_RX,分别为只发送模式、只接收模式、既发送也接收模式。
  • USART_HARDWARE_FLOW_T硬件流控制,可选参数USART_HARDWARE_FLOW_NONEUSART_HARDWARE_FLOW_RTSUSART_HARDWARE_FLOW_CTSUSART_HARDWARE_FLOW_RTS_CTS,分别为不使能硬件流、使能RTS、使能CTS、同时使能RTS和CTS。

我们配置的都是比较常用的,波特率为115200,数据位长度为8位,无校验,发送和接收,无硬件控制流。我们还使能了接收寄存器不为空中断USART_EnableInterrupt(USARTX, USART_INT_RXBNE);,使能接收中断后还要配置下NVIC,因为所有中断都是由NVIC管理的,如果不使用中断接收而使用轮询接收,效率是比较低的,当然中断接收效率不如DMA接收。

下面我们逐个看下发送函数

/**
 * @brief 发送一个字节数据
 * @param byte 待发送的字符
 */
void Usart_SendByte(uint8_t byte)
{
    // 发送一个字节数据
    USART_TxData(USARTX, byte);
    // 等待发送完毕
    while (USART_ReadStatusFlag(USARTX, USART_FLAG_TXBE) == RESET)
        ;
}

发送一个字节函数为什么这样写,我们来看下参考手册219页,如下图

单字节通讯

我们没有使能发送中断,所以发送时不会产生中断,由手册可以知道为了不覆盖前面的数据我们要等待发送寄存器被清空,也就是while (USART_ReadStatusFlag(USARTX, USART_FLAG_TXBE) == RESET);等待发送寄存器位空,这样我们才能继续发送。

再来看下发送字符串函数

/**
 * @brief 串口发 送字符串,直到遇到字符串结束符
 * @param str 待发送字符串缓冲器
 */
void Usart_SendString(uint8_t *str)
{
    uint32_t k = 0;
    do
    {
        Usart_SendByte(*(str + k));
        k++;
    } while (*(str + k) != '\0');

    // 等待发送完成
    while (USART_ReadStatusFlag(USARTX, USART_FLAG_TXC) == RESET)
    {
    }
}

我们还是来看下参考手册

串口发送字符

主要看最后三条,发送字节就是重复7-8,这个7-8也就是void Usart_SendByte(uint8_t byte)函数,最后一条要等待TXCFLG置1也就是while (USART_ReadStatusFlag(USARTX, USART_FLAG_TXC) == RESET)

我们具体来看下这两个标志位的区别

USART_FLAG_TXBE:当发送缓冲器为空,可以再次写入数据时,该位被硬件置起。对USART_DATA的写操作,将清零该位。此时,数据可能还没发送完成。

USART_FLAG_TXC:当发送数据完成,该位被硬件置起,由软件将其清零。换句话说,如果最后一次发送到数据缓冲区的数据完成了从移位寄存器到信号线TX 时,才置 1,表示数据发送完成,也就是说,这个标志位真正表示数据发送完成。

main.c

uint8_t RxFlag = 0;
uint8_t Temp;
uint8_t RxBuffer[256];

声明了三个变量,RxFlag时接收标志位,置位时表示接收到数据;Temp为接收字节的临时存放区;RxBuffer接收缓冲区,接收到的数据暂时放在里面。

while (1)
	{
		if (RxFlag)
		{
			if (RxCount < sizeof(RxBuffer))
			{
				RxBuffer[RxCount++] = Temp;
			}
			else
			{
				RxCount = 0;
			}
			if (Temp == 0x0A) // 换行字符,使用串口助手发送换行符时,推荐使用HEX模式发送
			{
				Usart_SendStr_Length(RxBuffer, RxCount);
				RxCount = 0;
			}
			RxFlag = 0;
		}
	}

接收标志位置位后,如果已经接收的到数据大小小于接收缓冲区的大小就继续接收,将接收到的数据Temp放在缓冲区中,否则的话将接收到数据的大小置零,下次接收时会覆盖前面已经接收的数据,如果接收到换行符也就是回车,它的ASCII码是10对应的十六进制就是0x0A。

apm32f10x_it.c

/**
 * @brief 串口1的中断函数
 * @param
 */
void USART1_IRQHandler(void)
{
    extern uint8_t RxFlag;
    extern uint8_t Temp;
    if (USART_ReadIntFlag(USARTX, USART_INT_RXBNE) != RESET)
    {
        USART_ClearIntFlag(USARTX, USART_INT_RXBNE);
        RxFlag = 1;
        Temp = USART_RxData(USARTX);
    }
}

该段代码主要就是置位接收标志位,读取接收寄存器中的数据。

我们之前说过,一个中断函数可以给很多中断用,我们要具体判断是哪一个中断发生了,我们要去读取中断的状态,我们这次用的中断是接收寄存器不为空中断,还有很多其他中断,如下图

串口中断表

确定中断发生后,进去第一件事就是清除中断标志位以防后面忘掉造成多次进中断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值