历史上的今天
返回首页

历史上的今天

今天是:2025年01月29日(星期三)

2019年01月29日 | STM32的UART读写及printf打印

2019-01-29 来源:eefocus

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的介绍

推荐阅读

史海拾趣

迈翔科技(COILMX)公司的发展小趣事

迈翔科技深知品质对于企业的重要性。为了确保产品质量,公司引进了ISO9001管理体系,并严格按照该体系进行生产和管理。此外,公司还建立了完善的质量检测体系,对每一批产品进行严格的质量检测和控制,确保产品出厂合格率达到了行业领先水平。

Caliber公司的发展小趣事

在电子行业中,品质是企业生存和发展的关键。Caliber公司深知这一点,从原材料采购到生产制造的每一个环节,都严格把控品质。公司建立了完善的质量管理体系,通过不断的技术改进和工艺优化,确保每一款产品的品质都达到行业最高标准。正是这种对品质的执着追求,让Caliber的产品在市场上获得了良好的口碑,也为公司的长期发展奠定了坚实的基础。

Andersen Laboratories Inc公司的发展小趣事

在不断追求技术创新和市场扩张的过程中,AnalogicTech始终坚持多元化发展战略。公司不仅在LED背光液晶电视市场取得了显著成绩,还积极拓展其他领域,如智能手机、平板电脑等消费电子产品市场。通过不断推出创新产品和解决方案,AnalogicTech成功实现了市场的多元化发展,为公司的长期稳定发展奠定了坚实基础。

这五个故事展示了AnalogicTech公司在电子行业中发展起来的关键阶段和重要举措。凭借其卓越的技术创新、全球化的运营策略、高效的物流配送体系以及市场扩张与多元化战略的推进,AnalogicTech成功地在竞争激烈的电子行业中脱颖而出,成为了一家备受瞩目的企业。

DB Lectro Inc公司的发展小趣事

作为一家有社会责任感的企业,DB Lectro Inc始终关注绿色制造和可持续发展。公司在生产过程中采用了环保材料和节能技术,降低了对环境的影响。同时,公司还积极参与公益活动和慈善事业,为社会做出了积极贡献。这些举措不仅提升了公司的品牌形象和社会声誉,还为公司赢得了更多客户和合作伙伴的信任和支持。

Alan Industries Inc公司的发展小趣事

为了进一步提升竞争力,Alan Industries Inc.积极寻求与其他企业的战略合作。公司与多家知名供应商建立了长期稳定的合作关系,确保了原材料的稳定供应和成本控制。同时,公司还通过并购和投资等方式,整合了产业链上下游资源,形成了完整的产业生态圈。这些战略举措使得公司在市场竞争中更具优势,实现了快速发展。

Emhiser Research Inc公司的发展小趣事

Emhiser Research非常注重创新管理和人才培养。公司建立了一套完善的研发流程和激励机制,鼓励员工提出创新性的想法和解决方案。同时,公司还积极与高校和研究机构合作,引进和培养了一批高素质的研发人才。这些人才不仅为公司带来了源源不断的创新动力,也为公司的长期发展奠定了坚实的基础。

问答坊 | AI 解惑

语音门禁

有哪位大侠做过这样的门禁没有:整栋楼的门禁机(每层有4户),功能:密码开锁,刷卡开锁,语音通话,主人可远程开大门?…

查看全部问答>

分布式控制系统

分布式控制系统分布式控制系统 (distributed control systems,简称DCS),又称为分散控制系统,分散型控制系统,集散控制系统.行业内业称4C技术既Control控制技术;Computer 计算机技术;Communication 通信技术;Cathode Ray Tube CRT显示技术。    ...…

查看全部问答>

ISPlever调用RAM出错

# ELAB2: Fatal Error: ELAB2_0036 Unresolved hierarchical reference to \"PUR_INST.PURNET\" from module \"mac_add_tb.UUT.u4.u1.ram1_0_0_0\" (module not found).# ELAB2: Last instance before error: /UUT/u4/u1/ram1_0_0_0# KERNEL: Error ...…

查看全部问答>

用VC编写与HID设备通信程序遇到问题!

大家好,现在情况是这样的,HID设备可以正常枚举并且Windows提示可以使用,我也基本知道了在 VC中如何访问HID设备,但是现在的问题是,CreateFile之后可以得到正确的句炳,并且HidD_GetAttributes,HidD_GetPreparsedData以及HidP_GetCaps都正确得 ...…

查看全部问答>

交叉编译qt的问题

请问我交叉编译qt的时候出现错误: cannot find -lqtopia 怎么办啊? 具体错误信息: /usr/bin/ld: cannot find -lqtopia collect2: ld returned 1 exit status cd /home/qt/x86-qt/qtopia/bin ; ln -sf quicklauncher mediarecorder make[1 ...…

查看全部问答>

现在S3C2440只要50块钱.STM32不能松懈啊

可以把2440当成单片机用的.可能2440的门槛会高一些,封装也麻烦.但它的可外接SDRAM,速度高,可扩展视频.个人觉得它的应用范围可以覆盖很多STM32的领域.如果STM32能外接SDRAM就比较完美了.片内RAM始终还是比较受限.不知后续的带网络功能的STM32会 ...…

查看全部问答>

飞思卡尔赛事说明

飞思卡尔赛事说明…

查看全部问答>

Helper2416-04——浅析S3C2416启动流程

本帖最后由 yuanlai2010 于 2014-7-6 14:31 编辑 浅析S3C2416启动流程 参与Helper2416开发板助学计划心得 由于接下来几天要给朋友做导游去江西武功山玩几天,没时间写帖子,今天就再发一帖! 对于我自己来说,每次拿到一块新的芯片都习惯先 ...…

查看全部问答>

【MSP430趣谈】MSP430第一讲

本帖最后由 qiushenghua 于 2015-11-20 13:44 编辑 MSP430教程之一现在市面上属于单片机的有很多很多了,包括基础一点的51单片机,他出现的比较早,相对而言资料也稍微丰富一点。430也作为一款优秀的单片机,相信也有很多人运用这款单片机的作为 ...…

查看全部问答>