历史上的今天
返回首页

历史上的今天

今天是:2024年10月21日(星期一)

正在发生

2021年10月21日 | stm32专题十一:USART(四)USART编程

2021-10-21 来源:eefocus

stm32的串口USART编程要点


先初始化串口所用到的GPIO;

初始化串口,配置pUSART_InitTypeDef结构体;

配置中断NVIC(接收中断,中断优先级);

使能串口;

编写发送和接收函数;

编写中断服务函数;

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


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位数组的函数


等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待)

之前单个字节其实也应该再加一个TC判断,之所以没加,是为了提高效率

单字节读数据到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

 

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) != '');

// 等待最后一个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);

}

}

推荐阅读

史海拾趣

得倍(DBIC)公司的发展小趣事

倍(DBIC)公司深知人才是企业发展的核心动力。因此,公司高度重视人才培养和团队建设。公司建立了完善的培训体系,为员工提供丰富的培训资源和学习机会。同时,倍(DBIC)公司还积极引进优秀人才,构建了一支高效、专业的团队。这支团队在公司的发展中发挥了重要作用,为公司创造了巨大的价值。

Guerrilla RF公司的发展小趣事
如果怀疑某个元器件损坏,可以尝试用相同型号的元器件进行替换,以验证问题是否解决。
Gore公司的发展小趣事
在需要精确控制电压和电流的工业控制系统中,三电平直流变换器能够提供稳定的电源输出。
Heimann Optoelectronics Gmbh公司的发展小趣事

为了进一步扩大市场份额,Hei Inc Optoelectronic Division积极实施国际化战略。公司首先在欧洲和北美市场设立了分支机构,并建立了完善的销售和服务网络。通过参加国际展会、与当地企业建立合作关系等方式,公司成功地将产品推向了国际市场。同时,公司还加大了对海外研发中心的投入,吸引了众多国际顶尖的光电子专家加盟。这些举措不仅提升了公司的国际竞争力,还为公司带来了更多的国际订单和合作伙伴。

乾坤(Cyntec)公司的发展小趣事

随着国内市场的逐渐饱和,乾坤公司开始将目光投向国际市场。为了打开海外市场,公司积极参加国际电子展会,与海外客户进行面对面的交流与合作。同时,乾坤也加强了与国际知名企业的战略合作,通过技术授权、联合开发等方式,不断拓展其产品的国际影响力。经过几年的努力,乾坤的电子元器件已经成功打入欧美、东南亚等多个国家和地区,公司的国际化进程取得了显著的成果。

EMC [ELAN Microelectronics Corp]公司的发展小趣事

义隆电子一直注重人才培养和团队建设。公司积极引进国内外各知名学府的优秀人才,并提供完善的在职培训和优厚的福利待遇。同时,公司还建立了高效的团队协作机制,鼓励员工之间的交流和合作。这种注重人才培养和团队建设的策略使得义隆电子能够保持持续的创新能力和市场竞争力。

问答坊 | AI 解惑

在Keil C51 uVision3 中调试w77E58如何设置才能使用片上的1K外部存贮器呀?

伟福V8仿真调试Winbond77E58不能正常使用片上1K外部存贮器的问题: 我用伟福V8/L仿真调试Winbond77E58板子, W77E58有片上1K外部存贮器,我不知是我的编译环境设置不对, 还是伟福不能仿真片上1K外部存贮器?在下面的程序中,将 int xdata i ...…

查看全部问答>

嵌入式程序运行时,硬件CPU空闲多少算正常状态?

嵌入式程序运行时,硬件CPU空闲多少算正常状态? ppc MPC8541 VxWorks…

查看全部问答>

ucosII上开发pdf查看应用程序

想在ucosII上开发一款pdf查看程序(平台君正 Xburst MIPS jz4740),现有Foxit Embedded PDF SDK可惜不免费!!!!!!!!! 总不能从头开发吧,难度太了点吧!!可有免费的库!!!推荐一个!!…

查看全部问答>

VS2005 调试问题

vs2005 编译发布成功,但弹出“unable to start debugging”这个问题怎么解决呢?先感谢各位回帖者,谢谢…

查看全部问答>

应届毕业生如何进军华为?

应届毕业生如何进军华为? 有没有在华为工作的前辈? 请指点指点,万分感谢!!!!…

查看全部问答>

【求助】74HC165的操作,只能控制一个按键,其他的控制不了,搞不明白

#define PL P2_3//SHIFT/LOAD引脚 #define CLOCK P2_4 #define SETBSC P2_5//OUTPUT QH引脚 read74hc165m() {uchar basic,c; PL=1; nop(); PL=0;//使能端为低电平时8位数据进入寄存器 nop(); PL=1;//高电平数据锁存,同时165数据自动 ...…

查看全部问答>

申请LM3S8962

三 书包小帮手  它的功能简单,却有很大的用处。我们大学生常常忘记星期几上什么课,几点上,教室在哪儿,老师的名字,电话等。它就像一个学习小管家,功能简单,外形小巧,可以放进书包上的挂饰,娃娃等,它采用防水功能。 它有个显示 ...…

查看全部问答>

万利EK-STM32F改了板子,资料怎么还是老的?

刚刚收到货,是两颗STM32芯片,ST3210-LK1 Rev.E板子和原理图根本就对不上网站上也没找到新的资料郁闷啊…

查看全部问答>

帮我看看STM32F103做RTC时的问题

我做了个RTC。。初始化时间然后查询出来都是正常的。。但是设置的时候代码如下: void RTC_WaitForLastTask(void) {   /* Loop until RTOFF flag is set */   while ((RTC->CRL & RTC_FLAG_RTOFF) == (u16)RESET) ...…

查看全部问答>