STM32的UART读写及printf打印

发布者:馥睿堂最新更新时间:2019-01-29 来源: eefocus关键字:STM32  UART读写  printf打印 手机看文章 扫描二维码
随时随地手机看文章

0.摘要

本文以STM32F1x系列单片机为例,主要介绍了串口的初始化、串口中断、接收/发送、串口调试等内容,也顺带讲到中断分组、半主机模式以及微库MicroLIB。


1.串口初始化

串口初始化主要包括对IO、USART和中断的初始化。根据STM32F1x手册RM0008的P166,USART在全双工模式下,发送口TX要配置成复用推挽输出,接收口RX要配置成浮空输入或上拉输入。此外,本文不使用USART的硬件流控制,所谓硬件流控制就是通过加入额外的引脚(RTS和CTS)来控制数据的收发过程,在数据传输之前确认收发双方均准备好才进行通信,用于防止接收缓冲区满而导致的数据丢失问题。


/*****************************************************

*function: 初始化串口1

*param: 串口波特率

*return:

******************************************************/

void USART1_Init(unsigned int BaudRate)

{

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟

/* TX - PA.9 */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

/* RX - PA.10 */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA.10

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入

GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitStructure.USART_BaudRate = BaudRate; //波特率

USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长8位

USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位

USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收/发模式

USART_Init(USART1, &USART_InitStructure);

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启接收中断

USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启空闲中断

USART_Cmd(USART1, ENABLE);

}

 


中断部分的配置较为简单,主要涉及中断分组的问题。在说中断分组之前,必须先了解中断优先级:STM32F1x的中断优先级分为抢占优先级(先优先级)和响应优先级(从优先级),不同抢占优先级的中断可相互打断(即相互嵌套)。抢占优先级相同的两个中断不可相互打断,先发生(中断)者先执行,如果同时发生(中断),则高响应优先级者先执行。这两个优先级在STM32F1x中通过寄存器的4个位来表示,究竟用多少个位表示抢占优先级,多少个位表示响应优先级呢?STM32F1x并没有定死,而是通过中断分组来让用户灵活分配。各中断分组定义如下图所示(截自UM0427的P228)。本文由于只使用了一个中断,因此选择任何一个分组的任何一个优先级都无所谓。

/*****************************************************

*function: 串口1中断配置

*param:

*return:

******************************************************/

void NVIC_Config(void)

{

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //中断分组1:1位抢占优先级,3位响应优先级

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //中断通道

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断

NVIC_Init(&NVIC_InitStructure);

}

 


2.串口接收

串口接收通过中断来实现,即当接收到数据时,产生中断,程序转去处理接收到的数据。接收数据用的中断包括接收中断(RXNE)和空闲中断(IDLE),如下图所示(截自RM0008的P821)。接收中断较好理解,就是每接收到一个字节的数据,就会产生中断。而空闲中断则是在接收完多个连续的字节(即一个数据帧)之后产生中断。


本文同时开启接收中断和空闲中断,串口每收到一个字节的数据,就进入接收中断,把它读取出来放好。当收完一帧数据时,就会进入空闲中断,把所接收的n个字节的数据打印出来(串口打印见下一小节)。由于同个USART的中断共用一个中断服务函数,故在函数中需要对中断源进行判断,再执行相应的操作。值得注意的是,每次进入中断都要读一下DR寄存器(Data Register),否则将不断地进入中断。


/*****************************************************

*function: 串口1中断服务函数,打印接收到的字节

*param:

*return:

******************************************************/

void USART1_IRQHandler(void)

{

static unsigned char buff[64];

static unsigned char n = 0;

unsigned char i;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断是否为接收中断

{

buff[n++] = USART1->DR; //读取接收到的字节数据

 

if(n == 64)

{

n = 0;

}

}

if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //判断是否为空闲中断

{

USART1->DR; //读DR,清标志

printf("%d characters:\r\n", n);

for(i=0; i

{

printf("buff[%d] = 0x%02hhx\r\n", i, buff[i]); //输出十六进制,保留最低两位,不够补0

}

n = 0;

}

}

 


3.串口发送与printf打印

前面说的的DR寄存器是一个可读写寄存器(实际上是由两个寄存器组成),串口的发送和接收都要围着它转,收到的数据从它里面读,而发送的数据要往它里面扔。串口的发送操作非常简单,一条语句就能搞定,就是往DR寄存器写入要发送的数据:


USART1->DR = data;

或者使用库函数:


USART_SendData(USART1, data);

串口在嵌入式领域不仅是一个通讯接口,还是一种调试工具,其好用程度不亚于硬件仿真。学过C语言的朋友应该都知道标准库函数printf()和scanf(),前者用于打印信息到控制台上,后者实现从键盘读入字符到程序。Keil、IAR等集成开发环境均支持标准库函数,如果在单片机的程序里调用printf()打印内容,最终会在哪里显示呢?答案是不可知的,因为单片机没有控制台这种东西,但我们可以利用它的外设来实现printf(),比如LCD或串口(串口再接到电脑上显示打印信息)。串口基本上大多数单片机都有,而LCD就不一定了,所以我们通常用串口来打印内容。


那么只要是有串口的单片机,调用一下printf()就可以打印信息了吗?还没那么简单,单片机并不能猜透你的意图,你需要告诉它往哪里printf,通过下面的fputc()函数来实现。fputc()是printf()的底层函数,需要把它改装一番,让它把要打印的数据发送到串口上去。


/*****************************************************

*function: 写字符文件函数

*param1: 输出的字符

*param2: 文件指针

*return: 输出字符的ASCII码

******************************************************/

int fputc(int ch, FILE *f)

{

while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); //等待上次发送结束

USART_SendData(USART1, (unsigned char)ch); //发送数据到串口

return ch;

}

除此之外,我们还要再做一点配置工作——禁用半主机模式,禁用了半主机模式才能使用标准库函数printf()打印信息到串口,在程序中加入以下代码即可。那么什么是半主机模式?为什么不用它?半主机模式是ARM单片机的一种调试机制,跟串口调试不一样的是,它需要通过仿真器来连接电脑和ARM单片机,并调用相应的指令来实现单片机向电脑显示器打印信息(或者从电脑键盘读取输入)。简而言之,这种方法比串口调试更复杂(需要进行更多的配置操作),也更不灵活(一定要用仿真器)。


/********** 禁用半主机模式 **********/

#pragma import(__use_no_semihosting)

 

struct __FILE

{

int a;

};

 

FILE __stdout;

 

void _sys_exit(int x)

{

}

 


上面的配置似乎有点麻烦,要加入这么一堆难懂的代码,难道没有更简便点的方法吗?有,但不推荐。方法是使用微库(MicroLIB),只要在Keil的“Options for Target -> Target ->Use MicroLIB”上打钩,即可使用串口打印(fputc()函数还是要改,但上述代码不用加)。微库是区别于C标准库的另一个库,当使用微库时,就默认关闭了半主机模式,也就不用添加上面的代码。这样虽然方便,但个人建议能不用就不用,原因:第一,微库是为小内存嵌入式设备而设计的,使用它可以减少代码所占空间,但对现在STM32等单片机来说,内存一般都够用,微库并非必需;第二,微库相对于C标准库而言,支持的功能更少,主要体现在对操作系统的支持上。总的来说,标准的东西总是相对更可靠,所以不必要的掉坑,还是用C标准库,不用微库。


4.最后

我们还需要一个USB转TTL模块和一台装有串口调试软件的电脑,就可以看到单片机打印到串口上的内容了。从此,如果我们想看某个变量的值,可以打印一下,想看程序跑到哪个地方,也可以打印一下,想让单片机向世界say个hello,还是可以打印一下。妈妈再也不用担心我的调试!


主函数:


int main()

{

USART1_Init(115200);

NVIC_Config();

printf("Hello, world!\r\n");

printf("Please enter any character:\r\n");

while(1);

}

运行效果:


参考:


[1] RM0008 (STM32F1x用户手册)


[2] UM0427 (STM32F101x/STM32F103x固件库手册)


[3] Keil官方对半主机模式semihosting的介绍


[4] Keil官方对微库MicroLib的介绍

关键字:STM32  UART读写  printf打印 引用地址:STM32的UART读写及printf打印

上一篇:STM32 Printf函数利用标准库实现方法
下一篇:stm32 printf函数重定向

推荐阅读最新更新时间:2024-03-16 16:23

怎么使用ST的库开发STM32
我使用的芯片是 STM32F103VET 和编译器是 IAR ARM V5.5,调试器用 JLINK V8 1,下载ST的库,现在的版本是 STM32F10x_StdPeriph_Lib_V3.3.0,解压缩,然后将 Libraries整个拷贝到你的工作目录,Project 目录是很多演示代码,可以参考怎么怎么编程,很容易的,readme里面都有说明。 2,建立一个 IAR 的project 选择 C 的空项目,为了方便管理我分了多个文件夹,分别是 建立一个 CMSIS目录 放内核启动代码添加两个系统启动文件,分别是在 STM32F10x_StdPeriph_Lib_V3.3.0\Libraries\CMSIS\CM3\Devi
[单片机]
STM32外部晶振8M更改为25M
注:修改F4去stm32f4xx.h/system_stm32f4xx.c , 修改F3去stm32f10x.h/system_stm32f10x.c 修改的地方之一:stm32f4xx.h里面的HSE_VALUE,系统默认采用外部8M晶振,所以 #define HSE_VALUE ((uint32_t)8000000) ,现把它修改为 #define HSE_VALUE ((uint32_t)25000000) 修改的地方之二:系统通过PLL倍频到168M,所以在配置PLL的时候,也需要作相应的修改。在system_stm32f4xx.c里,需要把PLL_M修改为25,不然会超频到336M的主频,使STM32不能正
[单片机]
stm32专题十四:存储器介绍
存储器通常分为易失性存储器(RAM - random access memory)和非易失性存储器(ROM - read only memory) 易失性存储器 SRAM:Static Random Access Memory(静态随机存储器),基本的存储单元由SR锁存器组成,不需要定时刷新。 DRAM:Dynamic Random Access Memory(静态随机存储器),由电容和晶体管组成,结构非常简单。动态随机存储器 DRAM 的存储单元以电容的电荷来表示数据,有电荷代表 1,无电荷代表 0。但时间一长,代表 1 的电容会放电,代表 0 的电容会吸收电荷,因此它需要定期刷新操作。刷新操
[单片机]
<font color='red'>stm32</font>专题十四:存储器介绍
STM32学习笔记-L298N驱动模块-电机
新手上路,十几天的学习感觉弯路走了不少,所以打算把学习的知识记录下来,和大家分享,不要嫌弃我,我从非常新手的角度来写。 1、STM32F103RCT6 我也是第一次学习单片机,选择了正点原子家的迷你版,学习到后面才发现什么板子都差不多,只要学会看手册就好了。推荐论坛:CSDN、正点原子官网、51黑论坛等。可以跟着正点原子提供的手把手视频教程把基本的实验做出来再去学习更深入的原理,就能得到事半功倍的效果。不要因为自己不会而畏怯,一旦遇到不懂的知识就马上查资料,还是不懂就去问,好了,感想有点多了。 2、L298N电机驱动模块 以后不要只认为淘宝是买东西的,上面可以找到的资料也很多。解释一下: 马达A输出(OUT1和OUT
[单片机]
<font color='red'>STM32</font>学习笔记-L298N驱动模块-电机
分析TCP/IP协议栈代码之UDP(STM32平台)
1. UDP介绍 UDP是一个简单的面向数据报的运输层协议:进程的每个输出操作都正好产生一个 UDP数据报,并组装成一份待发送的IP数据报。这与面向流字符的协议不同,如TCP,应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联系。 UDP数据报封装成一份 IP数据报的格式如图11 - 1所示。 RFC 768 是UDP的正式规范。 UDP不提供可靠性:它把应用程序传给IP层的数据发送出去,但是并不保证它们能到达目的地。由于缺乏可靠性,我们似乎觉得要避免使用UDP而使用一种可靠协议如TCP。在讨论完TCP后将再回到这个话题,看看什么样的应用程序可以使用UDP。 2. UDP首部 UDP首部的各字段如图1
[单片机]
分析TCP/IP协议栈代码之UDP(<font color='red'>STM32</font>平台)
STM32 usb_mem.c和usb_sil.c文件的分析
这两个c文件都还算是很简单的,先讲讲usb_men.c这个文件。从文件名就能知道跟内存有关,这个文件主要定义了两个函数,一个读双缓冲区PMA的数据PMAToUserBufferCopy(),另一个是写数据到双缓冲区PMA,UserToPMABufferCopy。如果,当你的usb设备接收到了数据,当然数据存放在PMA中了,我们要读出数据就要用到PMAToUserBufferCopy()函数了,如果我们想要发送数据给usb主机,就要将你要发送的数据拷贝到PMA缓冲区中了,这样才能发送出去,原理跟串口类似。 /*************************************************************
[单片机]
意法半导体发布远距离无线微控制器,提高智能计量、智能建筑和工业监控的连接能效
新的STM32系统芯片低功耗,支持多种无线通信协议,简化各种用途的无线系统设计 中国,2023年11月24日 - 服务多重电子应用领域、全球排名前列的半导体公司意法半导体(STMicroelectronics,简称ST;) 发布了一款新的融合无线芯片设计专长与高性能、高能效STM32系统架构的微控制器(MCU)。全新的节能功能将这款无线MCU的电池续航时间延长到15年以上。 在远距离部署的应用领域,包括能源计量、监控设备、报警系统、执行器,以及智能建筑、智能工厂和智能城市的传感器,STM32WL3无线MCU的特别有用,有助于控制功耗,并给工作划分优先级。这些高能效MCU可以改善用户体验,提供服务,减少环境足迹。通过
[嵌入式]
意法半导体发布远距离无线微控制器,提高智能计量、智能建筑和工业监控的连接能效
STM32 基础系列教程 0 - KEIL5 下载与安装
前言 学习keil/mdk-ram工具的下载与安装。 示例详解 MDK 工具下载 在浏览器中输入https://www.keil.com/download/product/ ,在弹出的网页中点 MDK-ARM, 在弹出的信息获最页面中输入信息(有些可以乱填,邮箱地址址对了,就可以!),然后点Submit提交! 提交后网页自动跳转到如下界面,直接点击要下载的文件文字即可! 接下来就是漫长的下载时间,在些时可以去喝杯茶先,国外的网,下载一般比较慢,当然也可以去直接百度一下最新版本MDK文名,如MDK5.27,网上有些同学会将下载好的文件放到网盘上分享,直接从网盘下载说不定会更快哦。
[单片机]
<font color='red'>STM32</font> 基础系列教程 0 - KEIL5 下载与安装
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved