自写Usart_Printf()串口发送函数实现方法详解

2024-04-20 17:48

本文主要是介绍自写Usart_Printf()串口发送函数实现方法详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

STM32串口发送函数

  • printf()函数
    • 方法一:在你的代码前加上以下代码即可
    • 方法二:在你的代码前加上以下代码并配置编译器(对于KEIL)
  • Usart_Printf()函数
    • 数组的简单理解(实在抱歉,这里的str是指针,大家不要被这只“披着羊皮的狼”骗了
    • 具体代码讲解
    • ① 如何操作TDR与RDR
    • ② TDR与RDR是如何工作的?(详说TDR)
    • ③奇偶校验问题

注意:本文参考STM32F10XXX数据手册

printf()函数

printf函数是C自带库函数,用C都非常的熟悉,STM32编程大多也用C,那么其是否也能够用printf()函数?

答案是肯定的,C所有标准库函数都可以在STM32编程中使用

方法一:在你的代码前加上以下代码即可

//printf输出与USARTy关联,能够传参
#if 1
#pragma import(__use_no_semihosting)       /* 确保没有从 C 库链接使用半主机的函数 */
//标准库需要的支持函数                 
struct __FILE 
{ 
int handle; }; FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x) 
{ 
x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
while(USART_GetFlagStatus(USARTy,USART_FLAG_TC)==RESET); USART_SendData(USARTy,(uint8_t)ch);   
return ch;
}
#endif 

修改USARTy即可实现printf()函数与对应的串口关联,接下来就可以使用了

方法二:在你的代码前加上以下代码并配置编译器(对于KEIL)

//代码来源:\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\USART\Printf
#ifdef __GNUC__/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printfset to 'Yes') calls __io_putchar() */#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */PUTCHAR_PROTOTYPE
{/* Place your implementation of fputc here *//* e.g. write a character to the USART */USART_SendData(USART2, (uint8_t) ch);/* Loop until the end of transmission */while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET){}return ch;
}

在这里插入图片描述
图中的选项一定要勾上,不然无法正常使用printf()

注意:printf()函数只能与一个串口相关联,如果出现多个串口使用的情况下,那就只有自己写串口打印函数Usart_Printf()了

Usart_Printf()函数

Usart_Printf()并不是一个标准的库函数,所以名字是自己定义的,习惯写成这样大家都能看懂

其初始化代码如下

void Usart_Printf(u8 *str){u8 data=0;do{USART_SendData(USART2,str[data]);//发送一个字节while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);//等待单字发送完成data++;}while(str[data]!=0);  //判断数据是否发送完成                                                  }

在说代码之前先给大家小补一下C中数组的知识

数组的简单理解(实在抱歉,这里的str是指针,大家不要被这只“披着羊皮的狼”骗了

学了C的朋友都知道数组,但可能还不太明白数组是怎样储存数据的,数组是一个相同类型变量的集合,我们只需要申明一个数组变量就可以对其下的各个变量进行操作

e.g.

int a[5];
  • 在内存中关联出一块内存名字叫a
    在这里插入图片描述

  • 将a划分为5等分,这些内存连续

  • 假设在该编译环境下,int占4个字节,那么每个元素所占内存即为4个字节

  • 整个数组所占内存为5x4=20个字节

  • 假设将"TP"存在char b[ ]中,那么b[0]=0x54('T’的ascll码),b[1]=0x50('P’的ascll码)

具体代码讲解

  1. 上述代码中的u8实质表示unsigned char,在STM32标准库文件stdint.h与stm32f10x.h里面定义有
//stdint.h
typedef unsigned          char uint8_t;
//stm32f10x.h
typedef uint8_t  u8;
  1. USART_SendData()为定义在stm32f10x_usart.c下的标准库函数,其定义是这样的
/*** @brief  Transmits single data through the USARTx peripheral.* @param  USARTx: Select the USART or the UART peripheral. *   This parameter can be one of the following values:*   USART1, USART2, USART3, UART4 or UART5.* @param  Data: the data to transmit.* @retval None*/
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{/* Check the parameters */assert_param(IS_USART_ALL_PERIPH(USARTx));assert_param(IS_USART_DATA(Data)); /* Transmit Data */USARTx->DR = (Data & (uint16_t)0x01FF);
}
  1. USARTx->DR = (Data & (uint16_t)0x01FF)这句代码为串口发送的核心
  • uint16_t表示短整形,占两个字节
//stdint.h
typedef unsigned short     int uint16_t;
  • USART_DR串口数据寄存器

在这里插入图片描述
DR分为TDR发送数据寄存器和RDR接受寄存器[8:0]位可编程数据有效位
在这里插入图片描述
针对上图可以提出以下3个问题:

① 如何操作TDR与RDR

来看看标准库是如何做到的

/*** @brief  Returns the most recent received data by the USARTx peripheral.* @param  USARTx: Select the USART or the UART peripheral. *   This parameter can be one of the following values:*   USART1, USART2, USART3, UART4 or UART5.* @retval The received data.*/
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
{/* Check the parameters */assert_param(IS_USART_ALL_PERIPH(USARTx));/* Receive Data */return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
}
/*** @brief  Transmits single data through the USARTx peripheral.* @param  USARTx: Select the USART or the UART peripheral. *   This parameter can be one of the following values:*   USART1, USART2, USART3, UART4 or UART5.* @param  Data: the data to transmit.* @retval None*/
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{/* Check the parameters */assert_param(IS_USART_ALL_PERIPH(USARTx));assert_param(IS_USART_DATA(Data)); /* Transmit Data */USARTx->DR = (Data & (uint16_t)0x01FF);
}

通过分析标准库的接收和发送函数,不难得出结论:

针对DR读操作即为RDR,对DR写操作即为TDR

② TDR与RDR是如何工作的?(详说TDR)

在这里插入图片描述
假设调用函数是这样的 Usart_Printf(“ExclusiveTP is handsome.”)

 Usart_Printf("ExclusiveTP is handsome."){//str[0]=0x45('E')...str[3]=0x6c('l'),...str[11]=0x20(' '空格)...u8 data=0;do{USART_SendData(USART2,str[data]);//发送一个字节while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);//等待单字发送完成data++;}while(str[data]!=0);  //判断数据是否发送完成                                                  }USART_SendData(USART2, str[data])
{/* Check the parameters */assert_param(IS_USART_ALL_PERIPH(USART2));assert_param(IS_USART_DATA(str[data])); /* Transmit Data */USART2->DR = (str[data]& (uint16_t)0x01FF);
}

Ⅰ.将字符串“ExclusiveTP is handsome.”存储在数组str[ ]
str[0]=0x45(‘E’)…str[3]=0x6c(‘l’),…str[11]=0x20(’ '空格)…(实在抱歉,这里的str是指针,大家不要被这只“披着羊皮的狼”骗了
编译器首先会取出str说指向的地址,然后加上中括号中的偏移地址构成一个新的地址,最后取出新地址中的值,比如str指向的地址为0xffff0000,str[4]的意思就是0xffff0000+4=0xffff0004

实在抱歉!!!自己学艺不精误导了大家,我一定会注意!!

Ⅱ.data=0,USART_SendData(USART2,str[0])

Ⅲ. USART_SendData(USART2, str[0])

Ⅳ.USART2->DR = (str[0]& (uint16_t)0x01FF)

Ⅱ、Ⅲ、Ⅳ的实质是将0x45从TDR移入发送移位寄存器,特点是并行输入

Ⅴ.while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET)

/*** @brief  Checks whether the specified USART flag is set or not.* @param  USARTx: Select the USART or the UART peripheral. *   This parameter can be one of the following values:*   USART1, USART2, USART3, UART4 or UART5.* @param  USART_FLAG: specifies the flag to check.*   This parameter can be one of the following values:*     @arg USART_FLAG_CTS:  CTS Change flag (not available for UART4 and UART5)*     @arg USART_FLAG_LBD:  LIN Break detection flag*     @arg USART_FLAG_TXE:  Transmit data register empty flag*     @arg USART_FLAG_TC:   Transmission Complete flag*     @arg USART_FLAG_RXNE: Receive data register not empty flag*     @arg USART_FLAG_IDLE: Idle Line detection flag*     @arg USART_FLAG_ORE:  OverRun Error flag*     @arg USART_FLAG_NE:   Noise Error flag*     @arg USART_FLAG_FE:   Framing Error flag*     @arg USART_FLAG_PE:   Parity Error flag* @retval The new state of USART_FLAG (SET or RESET).*/
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
{FlagStatus bitstatus = RESET;/* Check the parameters */assert_param(IS_USART_ALL_PERIPH(USARTx));assert_param(IS_USART_FLAG(USART_FLAG));/* The CTS flag is not available for UART4 and UART5 */if (USART_FLAG == USART_FLAG_CTS){assert_param(IS_USART_123_PERIPH(USARTx));}  if ((USARTx->SR & USART_FLAG) != (uint16_t)RESET){bitstatus = SET;}else{bitstatus = RESET;}return bitstatus;
}

USART_GetFlagStatus()是一个带返回值的标准库函数返回RESET或SET,也就是0与非0

代码的意思就是当USART_GetFlagStatus()返回值为0的时候就一直循环,但返回值为非0的时候就跳出循环执行下一行代码

进一步剖析就是USART2->SR & USART_FLAG_TXE的值如果不为0着跳出循环执行下一行代码

 @arg USART_FLAG_TXE:  Transmit data register empty flag

上方对USART_FLAG_TXE的解释为:发送数据寄存器(TDR)为空标志

#define USART_FLAG_TXE                       ((uint16_t)0x0080)

而USART_FLAG_TXE是个宏定义值为0x0080
在这里插入图片描述
USART->SR寄存器为串口状态寄存器,这里我们只看第八位为
TXE:发送数据寄存器空
0:数据没有被移到发送移位寄存器
1:数据已经被移到发送移位寄存器

上述代码实质就是USART2->SR & 0x0080,本质上就是确定USART->SR寄存器第八位(TXE)为0还是1
如果为0,那么USART2->SR & 0x0080=0,表示数据还没有从TDR移入发送移位寄存器,循环等待
如果为1,USART2->SR & 0x0080!=0,表示数据已经从TDR移入发送移位寄存器,执行下一行代码

Ⅵ.将0x45由移位寄存器通过TX引脚发出(串行输出,低位在前

Ⅶ.data++
即data=1

Ⅷ.while(str[1]!=0)
检测下一个要发送的字节是否为NUL(空字符)
如果不为空则继续发送str[1],如果为空则发送结束

注意:这里的0不表示字符0,在ascll中字符0为0x30,这里串口通信都是以字符形式发送的

③奇偶校验问题

在这里插入图片描述

  • 奇偶校验位定义在数据位的MSB(最高位)中
  • 如果数据位为8位且进行了奇偶校验,那么实则只有[6:0]表示数据信息,第8位为奇偶校验位;如果数据位为9位且进行了奇偶校验,那么实则只有[7:0]表示数据信息,第9位为奇偶校验位;
  • 标准ascll码实则只有127个英文字符,只需要用7位二进制,为了统一在第8位写0
  • 为了区分汉字的编码与ascll码,汉字编码的第8位都为1
  • 如果串口通信设置的是8位数据字长无奇偶校验,则发送汉字与英文都不会乱码(要求:在上位机中将数据位设置为8且不进行奇偶校验)
  • 如果串口通信设置的是8位数据字长且进行奇偶校验,则英文不会出现乱码而汉字会出现乱码(要求:在上位机中将数据位设置为7且进行奇偶校验)
  • 如果串口通信设置的是9位数据字长且进行奇偶校验,则发送汉字与英文都不会乱码(要求:在上位机中将数据位设置为8且进行奇偶校验)
加油!拼命干才会百分百成功,下一篇再见!

实在抱歉,前面的str是指针变量,大家不要被这只“披着羊皮的狼”骗了,误导了大家,老道再次道歉,老道是新手,通过文章总结是我的一种学习方式,分享给大家是希望得到大家的监督,同时也可能对大家有点帮助,我一定会更加认真的总结将更好的文章呈现给大家

下一篇:指针与数组间的“恩恩怨怨”

这篇关于自写Usart_Printf()串口发送函数实现方法详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920931

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施: