stm32专题十一:USART(四)USART编程

stm32的串口USART编程要点

  1. 先初始化串口所用到的GPIO;
  2. 初始化串口,配置pUSART_InitTypeDef结构体;
  3. 配置中断NVIC(接收中断,中断优先级);
  4. 使能串口;
  5. 编写发送和接收函数;
  6. 编写中断服务函数;

接下在看具体的代码实现过程:

USART初始化配置函数,不难但是过程挺多的,容易遗漏,代码如下:

// 串口1 USART1
#define DEBUG_USARTx    								USART1
#define DEBUG_USART_CLK    							RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd    			RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE    				115200

// USART GPIO引脚宏定义
#define DEBUG_UASRT_GPIO_CLK						RCC_APB2Periph_GPIOA
#define DEBUG_UASRT_GPIO_APBxClkCmd			RCC_APB2PeriphClockCmd

#define DEBUG_UASRT_TX_GPIO_PORT				GPIOA
#define DEBUG_UASRT_TX_GPIO_PIN					GPIO_Pin_9
#define DEBUG_UASRT_RX_GPIO_PORT				GPIOA
#define DEBUG_UASRT_RX_GPIO_PIN					GPIO_Pin_10

#define DEBUG_UASRT_IRQn								USART1_IRQn
#define DEBUG_UASRT_IRQHandler					USART1_IRQHandler

void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStruct;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	
	NVIC_InitStruct.NVIC_IRQChannel = DEBUG_UASRT_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	
	NVIC_Init(&NVIC_InitStruct);
}

void USART_config(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	
	// 开启串口的GPIO时钟
	DEBUG_UASRT_GPIO_APBxClkCmd(DEBUG_UASRT_GPIO_CLK, ENABLE);

  // USART的TX配置为复用推挽输出
	GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_TX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(DEBUG_UASRT_TX_GPIO_PORT, &GPIO_InitStruct);
	
	// USART的RX配置为浮空输入(由中文参考手册查询)
	GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_RX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;	
	GPIO_Init(DEBUG_UASRT_RX_GPIO_PORT, &GPIO_InitStruct);
	
	// 开启串口时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
	
	// 配置串口参数(波特率、8位数据、1位停止位、无校验、发送接收模式、无硬件流控)
	USART_InitStruct.USART_BaudRate = DEBUG_USART_BAUDRATE;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	
	USART_Init(DEBUG_USARTx, &USART_InitStruct);
	
	// 设置NVIC
	NVIC_Config();
	
	// 使能串口接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);
}

完成串口初始化配置后,就可以进行串口收发数据的测试

串口发送函数

void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
	USART_SendData(pUSARTx, data);
	
	// 当发送数据时,发送数据寄存器非空,TXE标志位首先为0
	// 然后程序便会等待,直至数据从DR转移到移位寄存器,此时TXE = 1,TC = 0
	// 当数据全部从移位寄存器发出后,TC = 1
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

这里值得注意的是,串口调试助手有一个特点,不管接收到什么数据,显示的都是字符。当我测试发送数据100时,显示的是字符d,如下图所示,这是为什么?关键还是在于ASCII码。

USART_SendByte(DEBUG_USARTx, 100);

这是串口助手打印出来的信息(默认不√HEX显示,这样就是输出的字符,对应ASCII),和我们预期的打印输出100完全不同,接下来分析原因。

如下是ASCII表。可以看到,串口调试助手,将接收到的数据(100)转换成字符d(ASCII值)并显示,所以,我们如果是发送数据0X64,串口助手同样会打印字符d。同理,如果是电脑的串口助手给单片机发数据,比如发1,单片机在解析时,要认为这是字符' 1 ',而不是数值1,这一点要非常注意。

如果我们勾选了hex显示,那么串口助手就会显示接收到的数据(100)对应的十六进制数(64),如下图:

既然串口调试助手默认显示字符,那我们就可以直接打印字符(使用单引号' A '),如下,字符正确显示。

USART_SendByte(DEBUG_USARTx, 'A');

发送16位数据函数

/* 发送两个字节的数据 */
void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data)
{
	uint8_t temp_h, temp_l;		// 16位数据的高8位和低8位
	
	temp_h = (data & 0xff00) >> 8;
	temp_l = data & 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);
}

发送8位数组的函数

  1. 等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待)
  2. 之前单个字节其实也应该再加一个TC判断,之所以没加,是为了提高效率
  3. 单字节读数据到DR,转移到移位寄存器,然后马上再读数据到DR(不用等到移位完毕再读取数据)
/* 发送8位数据的数组 */
void USART_SendArray(USART_TypeDef* pUSARTx, uint8_t *array, uint8_t num)
{
	uint8_t i;
	
	// 直接发送num次8位数据
	for (i = 0; i < num; i++)
	{
		USART_SendByte(pUSARTx, array[i]);
	}
	
	// 等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待)
	// 之前单个字节其实也应该再加一个TC判断,之所以没加,是为了提高效率
	// 单字节读数据到DR,转移到移位寄存器,然后马上再读数据到DR(不用等到移位完毕再读取数据)
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}

为了使用printf,我们要重定义fputc和fgetc函数,将文件流向串口,具体如下:

// fputc,是函数。函数功能: 将字符ch写到文件指针fp所指向的文件的当前写指针的位置。
// 函数格式:int fputc (int c, FILE *fp)。fp为文件指针,它的值是执行fopen()打开文件时获得的。
/* 重定向c库函数printf到串口 */
int fputc(int ch, FILE *f)
{
	/* 把ch发送到串口 */
	USART_SendData(DEBUG_USARTx, (uint8_t)ch);
	
	/* 等待发送完毕 */
	while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);

	return ch;
}


/* 重定向fgetc库函数到scanf串口 */
int fgetc(FILE *f)
{
	/* 等待串口输入数据 */
	while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
	
	return (int)USART_ReceiveData(DEBUG_USARTx);
}

附(bsp_usart.c和bsp_usart.h)

/************************************* USART.C *******************************/


#include "bsp_usart.h"
#include <stdio.h>

void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStruct;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	
	NVIC_InitStruct.NVIC_IRQChannel = DEBUG_UASRT_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	
	NVIC_Init(&NVIC_InitStruct);
}

void USART_config(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	
	// 开启串口的GPIO时钟
	DEBUG_UASRT_GPIO_APBxClkCmd(DEBUG_UASRT_GPIO_CLK, ENABLE);

  // USART的TX配置为复用推挽输出
	GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_TX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(DEBUG_UASRT_TX_GPIO_PORT, &GPIO_InitStruct);
	
	// USART的RX配置为浮空输入(由中文参考手册查询)
	GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_RX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;	
	GPIO_Init(DEBUG_UASRT_RX_GPIO_PORT, &GPIO_InitStruct);
	
	// 开启串口时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
	
	// 配置串口参数(波特率、8位数据、1位停止位、无校验、发送接收模式、无硬件流控)
	USART_InitStruct.USART_BaudRate = DEBUG_USART_BAUDRATE;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	
	USART_Init(DEBUG_USARTx, &USART_InitStruct);
	
	// 设置NVIC
	NVIC_Config();
	
	// 使能串口接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);
}

/* 发送一个字节的数据 */
void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
	USART_SendData(pUSARTx, data);
	
	// 当发送数据时,发送数据寄存器非空,TXE标志位首先为0
	// 然后程序便会等待,直至数据从DR转移到移位寄存器,此时TXE = 1,TC = 0
	// 当数据全部从移位寄存器发出后,TC = 1
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

/* 发送两个字节的数据 */
void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data)
{
	uint8_t temp_h, temp_l;		// 16位数据的高8位和低8位
	
	temp_h = (data & 0xff00) >> 8;
	temp_l = data & 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);
}

/* 发送8位数据的数组 */
void USART_SendArray(USART_TypeDef* pUSARTx, uint8_t *array, uint8_t num)
{
	uint8_t i;
	
	// 直接发送num次8位数据
	for (i = 0; i < num; i++)
	{
		USART_SendByte(pUSARTx, array[i]);
	}
	
	// 等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待)
	// 之前单个字节其实也应该再加一个TC判断,之所以没加,是为了提高效率
	// 单字节读数据到DR,转移到移位寄存器,然后马上再读数据到DR(不用等到移位完毕再读取数据)
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}

/* 发送字符串 */
void USART_SendString(USART_TypeDef* pUSARTx, uint8_t *str)
{
	uint8_t i = 0;
	do 
	{
		USART_SendByte(pUSARTx, *(str + i));
		i++;
	}	while (*(str + i) != '\0');
	
	// 等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待)
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}
	
// fputc,是函数。函数功能: 将字符ch写到文件指针fp所指向的文件的当前写指针的位置。
// 函数格式:int fputc (int c, FILE *fp)。fp为文件指针,它的值是执行fopen()打开文件时获得的。
/* 重定向c库函数printf到串口 */
int fputc(int ch, FILE *f)
{
	/* 把ch发送到串口 */
	USART_SendData(DEBUG_USARTx, (uint8_t)ch);
	
	/* 等待发送完毕 */
	while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);

	return ch;
}

/* 重定向fgetc库函数到scanf串口 */
int fgetc(FILE *f)
{
	/* 等待串口输入数据 */
	while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
	
	return (int)USART_ReceiveData(DEBUG_USARTx);
}
/********************************** USART.H ***********************************/

#ifndef __BSP_USART_H
#define __BSP_USART_H

#include "stm32f10x.h"

// 串口1 USART1
#define DEBUG_USARTx    								USART1
#define DEBUG_USART_BAUDRATE    				115200
#define DEBUG_USART_CLK    							RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd    			RCC_APB2PeriphClockCmd

// USART GPIO引脚宏定义
#define DEBUG_UASRT_GPIO_CLK						RCC_APB2Periph_GPIOA
#define DEBUG_UASRT_GPIO_APBxClkCmd			RCC_APB2PeriphClockCmd

#define DEBUG_UASRT_TX_GPIO_PORT				GPIOA
#define DEBUG_UASRT_TX_GPIO_PIN					GPIO_Pin_9
#define DEBUG_UASRT_RX_GPIO_PORT				GPIOA
#define DEBUG_UASRT_RX_GPIO_PIN					GPIO_Pin_10

#define DEBUG_UASRT_IRQn								USART1_IRQn
#define DEBUG_UASRT_IRQHandler					USART1_IRQHandler

void USART_config(void);
void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data);
void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data);
void USART_SendArray(USART_TypeDef* pUSARTx, uint8_t *array, uint8_t num);
void USART_SendString(USART_TypeDef* pUSARTx, uint8_t *str);

#endif /* __BSP_USART_H */

串口数据回显(接收到数据,然后通过串口原样发送)

void DEBUG_UASRT_IRQHandler(void)
{
	uint8_t ucTemp;
  if (USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
	{
		ucTemp = USART_ReceiveData(DEBUG_USARTx);
		USART_SendData(DEBUG_USARTx, ucTemp);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值