STM32串口发送函数
注意:本文参考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 printf
set 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码)
具体代码讲解
- 上述代码中的u8实质表示unsigned char,在STM32标准库文件stdint.h与stm32f10x.h里面定义有
//stdint.h
typedef unsigned char uint8_t;
//stm32f10x.h
typedef uint8_t u8;
- 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);
}
- 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是指针变量,大家不要被这只“披着羊皮的狼”骗了,误导了大家,老道再次道歉,老道是新手,通过文章总结是我的一种学习方式,分享给大家是希望得到大家的监督,同时也可能对大家有点帮助,我一定会更加认真的总结将更好的文章呈现给大家)
下一篇:指针与数组间的“恩恩怨怨”